完成UI界面设计

This commit is contained in:
FQL 2023-08-02 18:17:42 +08:00
commit 6b9437e101
435 changed files with 23063 additions and 0 deletions

15
.gitignore vendored Normal file
View File

@ -0,0 +1,15 @@
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties

1
app/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

98
app/build.gradle.kts Normal file
View File

@ -0,0 +1,98 @@
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
kotlin("plugin.serialization")
}
android {
namespace = "moe.fuqiuluo.shamrock"
compileSdk = 33
defaultConfig {
applicationId = "moe.fuqiuluo.shamrock"
minSdk = 24
targetSdk = 33
versionCode = 2
versionName = "1.0.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary = true
}
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
buildFeatures {
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.4.3"
}
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
excludes += "/META-INF/*"
excludes += "/META-INF/NOTICE.txt"
excludes += "/META-INF/DEPENDENCIES.txt"
excludes += "/META-INF/NOTICE"
excludes += "/META-INF/LICENSE"
excludes += "/META-INF/DEPENDENCIES"
excludes += "/META-INF/notice.txt"
excludes += "/META-INF/dependencies.txt"
excludes += "/META-INF/LGPL2.1"
excludes += "/META-INF/ASL2.0"
excludes += "/META-INF/INDEX.LIST"
excludes += "/META-INF/io.netty.versions.properties"
excludes += "/META-INF/INDEX.LIST"
excludes += "/META-INF/LICENSE.txt"
excludes += "/META-INF/license.txt"
excludes += "/META-INF/*.kotlin_module"
}
}
}
dependencies {
implementation("androidx.core:core-ktx:1.9.0")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.1")
implementation("androidx.activity:activity-compose:1.7.2")
implementation(platform("androidx.compose:compose-bom:2023.06.01"))
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-graphics")
implementation("androidx.compose.ui:ui-tooling-preview")
implementation("androidx.compose.material3:material3")
//noinspection GradleDynamicVersion
implementation("com.google.accompanist:accompanist-pager:0.31.5+")
//noinspection GradleDynamicVersion
implementation("com.google.accompanist:accompanist-systemuicontroller:0.31.5+")
//noinspection GradleDynamicVersion useless
// implementation("androidx.constraintlayout:constraintlayout-compose:1.1.0+")
implementation("io.coil-kt:coil:2.4.0")
implementation("io.coil-kt:coil-compose:2.4.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1")
implementation(project(":xposed"))
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
androidTestImplementation(platform("androidx.compose:compose-bom:2023.06.01"))
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
debugImplementation("androidx.compose.ui:ui-tooling")
debugImplementation("androidx.compose.ui:ui-test-manifest")
}

21
app/proguard-rules.pro vendored Normal file
View File

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

BIN
app/release/app-release.apk Normal file

Binary file not shown.

View File

@ -0,0 +1,20 @@
{
"version": 3,
"artifactType": {
"type": "APK",
"kind": "Directory"
},
"applicationId": "moe.fuqiuluo.shamrock",
"variantName": "release",
"elements": [
{
"type": "SINGLE",
"filters": [],
"attributes": [],
"versionCode": 2,
"versionName": "1.0.0",
"outputFile": "app-release.apk"
}
],
"elementType": "File"
}

View File

@ -0,0 +1,24 @@
package moe.fuqiuluo.shamrock
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("moe.fuqiuluo.shamrock", appContext.packageName)
}
}

View File

@ -0,0 +1,63 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application
android:name=".app.MyApplication"
android:zygotePreloadName="@string/app_name"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Shamrock"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.Shamrock"
tools:ignore="RedundantLabel">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<provider
android:authorities="moe.fuqiuluo.xqbot.provider"
android:name=".ui.service.MultifunctionalProvider"
android:exported="true"
android:grantUriPermissions="true"
tools:ignore="ExportedContentProvider" />
<meta-data
android:name="xposedmodule"
android:value="true" />
<meta-data
android:name="xposeddescription"
android:value="A Bot Framework" />
<meta-data
android:name="xposedminversion"
android:value="23" />
<!--适配华为huawei刘海屏-->
<meta-data
android:name="android.notch_support"
android:value="true"/>
<!--适配小米xiaomi刘海屏-->
<meta-data
android:name="notch.config"
android:value="portrait|landscape" />
</application>
</manifest>

View File

@ -0,0 +1,376 @@
@file:OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
package moe.fuqiuluo.shamrock
import android.annotation.SuppressLint
import android.os.Bundle
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.annotation.ColorRes
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.FastOutLinearInEasing
import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.MutableTransitionState
import androidx.compose.animation.core.TweenSpec
import androidx.compose.animation.scaleIn
import androidx.compose.animation.scaleOut
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.LocalIndication
import androidx.compose.foundation.background
import androidx.compose.foundation.indication
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.absolutePadding
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.PagerState
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.material3.Divider
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface
import androidx.compose.material3.TabRow
import androidx.compose.material3.TabRowDefaults
import androidx.compose.material3.TabRowDefaults.tabIndicatorOffset
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults.topAppBarColors
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.view.WindowCompat
import com.google.accompanist.systemuicontroller.rememberSystemUiController
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import moe.fuqiuluo.shamrock.ui.app.AppRuntime
import moe.fuqiuluo.shamrock.ui.app.Level
import moe.fuqiuluo.shamrock.ui.app.Logger
import moe.fuqiuluo.shamrock.ui.app.RuntimeState
import moe.fuqiuluo.shamrock.ui.fragment.DashboardFragment
import moe.fuqiuluo.shamrock.ui.fragment.HomeFragment
import moe.fuqiuluo.shamrock.ui.fragment.LabFragment
import moe.fuqiuluo.shamrock.ui.fragment.LogFragment
import moe.fuqiuluo.shamrock.ui.theme.LocalString
import moe.fuqiuluo.shamrock.ui.theme.ShamrockTheme
import moe.fuqiuluo.shamrock.ui.theme.TabDividerColor
import moe.fuqiuluo.shamrock.ui.theme.TabSelectedColor
import moe.fuqiuluo.shamrock.ui.theme.TabUnSelectedColor
import moe.fuqiuluo.shamrock.ui.theme.ToolbarColor
import moe.fuqiuluo.shamrock.ui.tools.NoIndication
import moe.fuqiuluo.shamrock.ui.tools.ShamrockTab
import java.lang.StringBuilder
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
CompositionLocalProvider(
LocalIndication provides NoIndication
) {
AppMainView()
}
WindowCompat.getInsetsController(window, LocalView.current).apply {
isAppearanceLightStatusBars = true
}
WindowCompat.setDecorFitsSystemWindows(window, true)
}
}
}
@Composable
private fun AppMainView() {
val scope = rememberCoroutineScope()
val ctx = LocalContext.current
val systemUiController = rememberSystemUiController()
val statusBarDarkIcons by remember { mutableStateOf(true) }
val navigationBarDarkIcons by remember { mutableStateOf(true) }
LaunchedEffect(systemUiController, statusBarDarkIcons, navigationBarDarkIcons) {
systemUiController.statusBarDarkContentEnabled = statusBarDarkIcons
systemUiController.navigationBarDarkContentEnabled = navigationBarDarkIcons
systemUiController.setStatusBarColor(Color.White)
}
// Home
val isFined = remember { mutableStateOf(false) }
val coreVersion = remember { mutableStateOf("1.0.0") }
val coreName = remember { mutableStateOf("Xposed") }
val coreCode = remember { mutableIntStateOf(1000) }
AppRuntime.state = remember {
RuntimeState(isFined, coreVersion, coreCode, coreName)
}
AppRuntime.logger = remember {
Logger(mutableStateOf(StringBuilder()), mutableListOf())
}
AppRuntime.AccountInfo.also {
it.uin = remember {
mutableStateOf("unknown")
}
it.nick = remember {
mutableStateOf("unknown")
}
}
@Suppress("LocalVariableName") val LocalString = LocalString
LaunchedEffect(isFined.value) {
if (isFined.value) {
AppRuntime.log("日志框架激活成功,开放操作许可。")
Toast.makeText(ctx, LocalString.frameworkYes, Toast.LENGTH_SHORT).show()
} else {
AppRuntime.log("日志框架处于未激活状态,请检查。")
Toast.makeText(ctx, LocalString.frameworkNo, Toast.LENGTH_SHORT).show()
}
}
//模拟激活事件测试
//LaunchedEffect(systemUiController) {
// delay(5000)
// isFined.value = true
// AppRuntime.AccountInfo.also {
// it.uin.value = "1372362033"
// it.nick.value = "伏秋洛啊~"
// }
//}
ShamrockTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = Color.White
) {
@Suppress("LocalVariableName") val TitlesWithIcon = LocalString.TitlesWithIcon
val state = rememberPagerState(
pageCount = { TitlesWithIcon.size },
initialPage = 0,
initialPageOffsetFraction = 0f,
)
Scaffold(
topBar = { ToolBar(
title = arrayOf(
"Clover", "CuteKitty", "Shamrock",
"Threeleaf", "CuteCat", "FuckingCat",
"XVideos", "Onlyfans", "Pornhub",
"Xposed", "LittleFox", "Springboot",
"Kotlin", "Rust & Android", "Dashabi",
"YYDS", "Amd Yes", "Gayhub",
"Yuzukkity", "HongKongDoll", "Xinrao"
).random()
) }
) {
val topPadding = it.calculateTopPadding()
Column(
modifier = Modifier
.padding(top = topPadding)
) {
TabRow(
modifier = Modifier
.width(1080.dp)
.background(Color.White)
.absolutePadding(left = 70.dp, right = 70.dp)
,
selectedTabIndex = state.currentPage,
divider = { },
containerColor = Color.White,
indicator = {
TabRowDefaults.Indicator(
Modifier.tabIndicatorOffset(it[state.currentPage]),
color = TabSelectedColor,
height = (3.3).dp
)
},
) {
TitlesWithIcon.forEachIndexed { index, titleWithIcon ->
AnimatedTab(scope, state, index, titleWithIcon)
}
}
Divider(
modifier = Modifier
.shadow(8.dp),
color = Color(0xBFDADADA),
thickness = 0.2.dp
)
HorizontalPager(
modifier = Modifier
.padding(top = 8.dp)
,
state = state,
) { page ->
when(page) {
0 -> HomeFragment(AppRuntime.state)
1 -> AppRuntime.AccountInfo.let {
DashboardFragment(it.nick.value, it.uin.value)
}
2 -> LogFragment(AppRuntime.logger)
3 -> LabFragment()
}
}
}
}
}
}
}
@Composable
private fun ToolBar(title: String) {
TopAppBar(
title = { Column {
Text(
text = title,
color = ToolbarColor,
fontWeight = FontWeight.Bold
)
Text(
text = arrayOf(
"A Framework Base On Xposed",
"今天吃什么好呢?",
"遇事不决,量子力学!",
"Just kkb?",
"いいよ,こいよ",
"伊已逝 吾亦逝",
"忆久意久 把义领",
"喵帕斯!",
"Creeper?",
"Make American Great Again!",
"TXHookPro",
"曾经有人失去了那个她",
"欲买桂花同载酒,终不似,少年游。",
"抚千窟为佑 看长安落花",
"どこにもない",
"春日和 かかってらしゃい"
).random(),
color = ToolbarColor,
fontSize = 14.sp
)
} },
modifier = Modifier
.fillMaxWidth(),
colors = topAppBarColors(
titleContentColor = Color.Black,
containerColor = Color.White,
scrolledContainerColor = Color.White,
navigationIconContentColor = Color.White
)
)
}
private val SELECTED_TABLE = arrayOf(
1, 2,
4, 8,
16, 32,
64, 128,
256, 512,
1024, 2048
)
@Composable
@SuppressLint("AutoboxingStateValueProperty")
private fun AnimatedTab(scope: CoroutineScope, state: PagerState, index: Int, titleWithIcon: Pair<String, Int>) {
val curSelected = index == state.currentPage
val lastSelectedState = remember {
mutableIntStateOf(0)
}
val enter = remember {
scaleIn(animationSpec = TweenSpec(150, easing = FastOutLinearInEasing))
}
val exit = remember {
scaleOut(animationSpec = TweenSpec(150, easing = FastOutSlowInEasing))
}
val defaultConst = SELECTED_TABLE[index * 2]
val selectedConst = SELECTED_TABLE[(index * 2) + 1]
val isFirst: Boolean = (lastSelectedState.value and defaultConst) != defaultConst
var icon: @Composable (() -> Unit)? = null
var text: @Composable (() -> Unit)? = null
if (curSelected) {
text = {
AnimatedVisibility(visibleState = MutableTransitionState(false).also {
it.targetState = isFirst || lastSelectedState.value and selectedConst == selectedConst
}, enter = enter, exit = exit, modifier = Modifier) {
Text(
text = titleWithIcon.first,
color = TabSelectedColor,
fontSize = 15.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier
.padding(bottom = 5.dp)
.indication(
remember { MutableInteractionSource() },
rememberRipple(color = Color.Transparent)
)
)
}
}
} else {
icon = {
Icon(
painter = painterResource(id = titleWithIcon.second),
contentDescription = titleWithIcon.first,
tint = Color.Unspecified,
modifier = Modifier
.height(24.dp)
.width(24.dp)
.padding(bottom = 5.dp)
.indication(
remember { MutableInteractionSource() },
rememberRipple(color = Color.Transparent)
)
)
}
}
ShamrockTab(
selected = curSelected,
onClick = { scope.launch {
state.scrollToPage(index, 0f)
} },
text = text,
icon = icon,
selectedContentColor = Color.Transparent,
unselectedContentColor = Color.Transparent,
indication = null
)
lastSelectedState.value.let {
var tmp = it
if (isFirst) {
tmp = it or defaultConst
}
lastSelectedState.value = if (curSelected) tmp or selectedConst else tmp xor selectedConst
}
}
@Preview(showBackground = true)
@Composable
private fun MainPreview() {
AppMainView()
}

View File

@ -0,0 +1,6 @@
package moe.fuqiuluo.shamrock.app
import android.app.Application
class MyApplication: Application() {
}

View File

@ -0,0 +1,69 @@
package moe.fuqiuluo.shamrock.ui.app
import android.annotation.SuppressLint
import android.icu.text.SimpleDateFormat
import android.util.Log
import androidx.compose.runtime.MutableIntState
import androidx.compose.runtime.MutableState
import androidx.compose.ui.graphics.Color
import java.util.Date
object AppRuntime {
@SuppressLint("SimpleDateFormat")
private val format = SimpleDateFormat("[HH:mm:ss] ")
lateinit var state: RuntimeState
lateinit var logger: Logger
object AccountInfo {
lateinit var uin: MutableState<String>
lateinit var nick: MutableState<String>
}
fun log(msg: String, level: Level = Level.INFO) {
if (::logger.isInitialized) {
val format = "%s%s %s".format(format.format(Date()), level.name, msg)
val builder = logger.logCache.value
val start = builder.length
val end = start + format.length
builder.append(format)
logger.logRanges.add(Logger.LogRange(start, end, level))
} else {
Log.e("AppRuntime", "logger is not initialized")
}
}
}
class RuntimeState(
val isFined: MutableState<Boolean>,
val coreVersion: MutableState<String>,
val coreCode: MutableIntState,
val coreName: MutableState<String>
) {
val attrMap = mutableMapOf<String, String>()
}
enum class Level(
val color: Color
) {
DEBUG(Color(0xFF4CAF50)),
INFO(Color(0xff6c6c6c)),
WARN(Color(0xFFFF9800)),
ERROR(Color(0xFFE91E63)),
}
class Logger(
val logCache: MutableState<StringBuilder>,
val logRanges: MutableList<LogRange>,
) {
data class LogRange(
val start: Int,
val end: Int,
val level: Level
)
}

View File

@ -0,0 +1,181 @@
package moe.fuqiuluo.shamrock.ui.fragment
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.indication
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import moe.fuqiuluo.shamrock.R
import moe.fuqiuluo.shamrock.ui.tools.IosSwitch
@Composable
fun NoticeBox(
modifier: Modifier = Modifier,
text: String,
onClick: (() -> Unit)? = null
) {
Box(
modifier = modifier
.fillMaxWidth()
.indication(remember { MutableInteractionSource() }, null)
.background(
color = Color(0xFFf4f4f4),
shape = RoundedCornerShape(12.dp)
)
.clickable(enabled = onClick != null) {
onClick?.invoke()
}
) {
Row(
modifier = Modifier
.padding(
top = 10.dp,
bottom = 10.dp
)
.fillMaxWidth(),
horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically
) {
Icon(
modifier = Modifier
.padding(10.dp)
.width(20.dp)
.height(20.dp),
painter = painterResource(id = R.drawable.round_warning_24),
contentDescription = "WarningIcon",
tint = Color(0xFF838383)
)
Text(
text = text,
fontSize = 11.sp,
color = Color(0xff6c6c6c)
)
}
}
}
@Composable
fun ActionBox(
modifier: Modifier = Modifier,
painter: Painter,
title: String,
content: @Composable (textColor: Color) -> Unit
) {
val textColor = Color(0xff6c6c6c)
Box(
modifier = modifier
.fillMaxWidth()
.indication(remember { MutableInteractionSource() }, null)
.background(
color = Color(0xFFf4f4f4),
shape = RoundedCornerShape(12.dp)
)
) {
Column(
modifier = Modifier
.padding(
top = 10.dp,
bottom = 10.dp
)
.fillMaxWidth(),
) {
Row(
horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically
) {
Icon(
modifier = Modifier
.padding(start = 10.dp)
.width(20.dp)
.height(20.dp),
painter = painter,
contentDescription = "ActionIcon",
tint = Color.Unspecified
)
Text(
modifier = Modifier.padding(start = 6.dp),
text = title,
fontSize = 12.sp,
color = textColor
)
}
Row(
modifier = Modifier.padding(8.dp)
) {
content.invoke(textColor)
}
}
}
}
@Composable
fun ActionSwitch(
text: String,
isSwitch: Boolean,
onValueChanged: (Boolean) -> Unit,
) {
Row(
modifier = Modifier
.padding(2.dp)
.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
Text(
modifier = Modifier,
text = text,
fontSize = 13.sp
)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.End
) {
IosSwitch(
switchValue = isSwitch,
buttonHeight = 22.dp,
onValueChanged = onValueChanged
)
}
}
}
@Preview(showBackground = true)
@Composable
private fun NoticeBoxPreview() {
NoticeBox(
text = "该模块仅适用于QQ版本8.9.68及以上的版本。\n" +
"同时声明本项目仅用于学习与交流请于24小时内删除。\n" +
"同时开源贡献者均享受免责条例。"
)
}
@Preview(showBackground = true)
@Composable
private fun ActionBoxPreview() {
ActionBox(
painter = painterResource(id = R.drawable.ic_help_512),
title = "使用帮助 & 教程"
) {
}
}

View File

@ -0,0 +1,318 @@
package moe.fuqiuluo.shamrock.ui.fragment
import android.content.Context
import android.content.SharedPreferences
import android.os.Build
import android.widget.Toast
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.absolutePadding
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Divider
import androidx.compose.material3.Icon
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.modifier.modifierLocalMapOf
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.content.edit
import coil.compose.AsyncImagePainter
import coil.compose.rememberAsyncImagePainter
import coil.request.ImageRequest
import coil.size.Size
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import moe.fuqiuluo.shamrock.R
import moe.fuqiuluo.shamrock.ui.app.AppRuntime
import moe.fuqiuluo.shamrock.ui.app.Level
import moe.fuqiuluo.shamrock.ui.theme.TabSelectedColor
import moe.fuqiuluo.shamrock.ui.theme.TabUnSelectedColor
@Composable
fun DashboardFragment(
nick: String,
uin: String
) {
val scope = rememberCoroutineScope()
val ctx = LocalContext.current
val preferences = ctx.getSharedPreferences("config", 0)
Surface(
modifier = Modifier
.fillMaxSize(),
) {
Column(
modifier = Modifier
.verticalScroll(rememberScrollState())
.padding(16.dp)
) {
AccountCard(nick, uin)
InformationCard()
FunctionCard(scope, ctx, preferences, "功能设置")
}
}
}
@Composable
private fun FunctionCard(
scope: CoroutineScope,
ctx: Context,
preferences: SharedPreferences,
title: String
) {
ActionBox(
modifier = Modifier.padding(top = 12.dp),
painter = painterResource(id = R.drawable.round_api_24),
title = title
) {
Column {
Divider(
modifier = Modifier,
color = TabUnSelectedColor,
thickness = 0.2.dp
)
Function(
title = "强制平板模式",
desc = "强制QQ使用平板模式实现共存登录。",
descColor = TabSelectedColor,
isSwitch = preferences.getBoolean("tablet", false)
) {
preferences.edit { putBoolean("tablet", it) }
scope.launch { Toast.makeText(ctx, "重启QQ生效", Toast.LENGTH_SHORT).show() }
}
Function(
title = "HTTP回调",
desc = "OneBot标准的HTTPAPI回调Shamrock作为Client。",
descColor = TabSelectedColor,
isSwitch = !preferences.getString("webhook", "").isNullOrBlank()
) {
//preferences.edit { putBoolean("webhook", it) }
scope.launch { Toast.makeText(ctx, "重启QQ生效", Toast.LENGTH_SHORT).show() }
}
Function(
title = "主动WebSocket",
desc = "OneBot标准WebSocketShamrock作为Server。",
descColor = TabSelectedColor,
isSwitch = preferences.getBoolean("ws", false)
) {
preferences.edit { putBoolean("ws", it) }
scope.launch { Toast.makeText(ctx, "重启QQ生效", Toast.LENGTH_SHORT).show() }
}
Function(
title = "被动WebSocket",
desc = "OneBot标准WebSocketShamrock作为Client。",
descColor = TabSelectedColor,
isSwitch = !preferences.getString("ws_api", "").isNullOrBlank()
) {
scope.launch { Toast.makeText(ctx, "重启QQ生效", Toast.LENGTH_SHORT).show() }
}
Function(
title = "专业级接口",
desc = "如果你不知道你在做什么,请不要开启本功能。",
descColor = Color.Red,
isSwitch = preferences.getBoolean("pro_api", false)
) {
preferences.edit { putBoolean("pro_api", it) }
scope.launch { Toast.makeText(ctx, "重启QQ生效", Toast.LENGTH_SHORT).show() }
AppRuntime.log("专业级API = $it", Level.WARN)
}
}
}
}
@Composable
private fun Function(
title: String,
desc: String? = null,
descColor: Color? = null,
isSwitch: Boolean,
onClick: (Boolean) -> Unit
) {
Column(
modifier = Modifier
.absolutePadding(left = 8.dp, right = 8.dp, top = 12.dp, bottom = 0.dp)
) {
if (desc != null && descColor != null) {
Text(
modifier = Modifier.padding(2.dp),
text = desc,
color = descColor,
fontSize = 11.sp
)
}
ActionSwitch(
text = title,
isSwitch = isSwitch
) {
onClick(it)
}
}
}
@Composable
private fun InformationCard() {
ActionBox(
modifier = Modifier.padding(top = 12.dp),
painter = painterResource(id = R.drawable.round_info_24),
title = "数据信息"
) {
Column {
Divider(
modifier = Modifier,
color = TabUnSelectedColor,
thickness = 0.2.dp
)
InfoItem(
title = "系统版本",
content = "${Build.VERSION.RELEASE} (API ${Build.VERSION.SDK_INT})"
)
InfoItem(title = "设备", content = "${Build.BRAND} ${Build.MODEL}")
InfoItem(title = "系统架构", content = Build.SUPPORTED_ABIS.joinToString())
InfoItem(
title = "累计调用次数",
content = "114514"
)
}
}
}
@Composable
private fun InfoItem(
modifier: Modifier = Modifier,
titleColor: Color = Color(0xFF5A5A5A),
contentColor: Color = Color(0xFF5A5A5A),
title: String,
content: String
) {
Row(
modifier = Modifier
.absolutePadding(left = 8.dp, right = 8.dp, top = 12.dp, bottom = 0.dp)
.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = title,
fontSize = 14.sp,
color = titleColor
)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.End
) {
Text(
text = content,
fontSize = 14.sp,
color = contentColor
)
}
}
}
@Composable
private fun AccountCard(
nick: String,
uin: String
) {
Row(
modifier = Modifier
.fillMaxWidth()
.background(
Brush.linearGradient(
listOf(Color(0xFF2196F3), Color(0xFF00BCD4))
), shape = RoundedCornerShape(12.dp)
),
verticalAlignment = Alignment.CenterVertically
) {
val painter = kotlin.runCatching {
rememberAsyncImagePainter(model = ImageRequest.Builder(LocalContext.current)
.data("https://q.qlogo.cn/g?b=qq&nk=$uin&s=100")
.crossfade(true)
.size(Size.ORIGINAL)
.build())
}.onSuccess {
when(it.state){
is AsyncImagePainter.State.Success ->{
}
is AsyncImagePainter.State.Loading ->{
}
is AsyncImagePainter.State.Error ->{
AppRuntime.log("头像拉取失败,请检查网络连接。", Level.ERROR)
}
else -> {}
}
}.getOrDefault(painterResource(id = R.drawable.ic_letter_q))
Icon(
modifier = Modifier
.padding(
start = 15.dp,
end = 5.dp
)
.width(45.dp)
.height(45.dp)
.clip(RoundedCornerShape(36.dp))
,
painter = painter,
contentDescription = "HeadLogo",
tint = Color.Unspecified
)
Column(
modifier = Modifier
.padding(20.dp),
verticalArrangement = Arrangement.Top,
horizontalAlignment = Alignment.Start,
) {
Text(
text = nick,
color = Color.White,
fontSize = 14.sp
)
Text(
text = "QQ号$uin",
color = Color.White,
fontSize = 14.sp
)
}
}
}
@Preview
@Composable
private fun PreViewDashBoard() {
DashboardFragment("测试昵称", "100001")
}

View File

@ -0,0 +1,163 @@
package moe.fuqiuluo.shamrock.ui.fragment
import android.widget.Toast
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Icon
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableIntState
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import moe.fuqiuluo.shamrock.R
import moe.fuqiuluo.shamrock.ui.app.RuntimeState
import moe.fuqiuluo.shamrock.ui.theme.LocalString
@Composable
fun HomeFragment(
runtime: RuntimeState
) {
val scope = rememberCoroutineScope()
val ctx = LocalContext.current
Surface(
modifier = Modifier
.fillMaxSize(),
) {
Column(
modifier = Modifier
.verticalScroll(rememberScrollState())
.padding(16.dp)
) {
StatusCardBoard(runtime.isFined, runtime.coreVersion, runtime.coreCode, runtime.coreName)
NoticeBox(
modifier = Modifier
.padding(top = 12.dp),
text = LocalString.legalWarning,
) {
Toast.makeText(ctx, arrayOf(
"请严格遵守哦!", "点我又不能下崽...", "家人们谁懂啊!", "别点啦,记得遵守规则啊!", "CRC06f77ca1"
).random(), Toast.LENGTH_SHORT).show()
}
ActionBox(
modifier = Modifier
.padding(top = 12.dp),
painter = painterResource(id = R.drawable.ic_help_512),
title = "使用教程 & 注意事项"
) { textColor ->
Text(text = """
Q如何使用呢
A在Xposed/Lsposed中激活模块选中目标应用重新后强行停止目标应用并重新启动即可
Q冻结封号是怎么回事
A也许未来的某个时刻会加强检测吧亦或许你发了什么不干净的东西呢
QShamrock有模块冲突
A目前没有发现模块冲突
QShamrock需求权限
AShamrock使用到了Net权限去实现HTTPAPI和WebSocket的一些请求请确保你所设置的端口未被占用当你使用来自一些Android机器人框架的插件请确保本APP的存活否则将无法运行当你使用来自OneBot标准的相关接口时无需保证APP的存活
QShamrock运行原理
A使用Xposed框架将代码插入到QQ运行时并在QQ内使用Ktor框架放出外部可用的API接口采用Provider/DynamicBroadcast与实现模块与目标进程数据交互无需额外的储存权限
Q兼容性框架支持有哪些
A
1支持载入一些Android机器人框架的插件(如QR词库插件)
2支持OneBot12标准但不会更新支持之后的OneBot标准(特殊性)
3原始支持来自Native/Jvm的插件
""".trimIndent(),
color = textColor,
fontSize = 12.sp
)
}
}
}
}
@Composable
private fun StatusCardBoard(
isRight: MutableState<Boolean>,
version: MutableState<String>,
code: MutableIntState,
core: MutableState<String>
) {
Row(
modifier = Modifier
.fillMaxWidth()
.background(
Brush.linearGradient(
listOf(Color(0xFF03AA9A), Color(0xFF4DB8AD))
), shape = RoundedCornerShape(12.dp)
),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
modifier = Modifier
.padding(
start = 25.dp,
end = 5.dp
)
.width(20.dp)
.height(20.dp),
painter = painterResource(id = if (isRight.value) R.drawable.round_near_me_24 else
R.drawable.round_near_me_disabled_24),
contentDescription = "StatusIcon",
tint = Color.White
)
Column(
modifier = Modifier
.padding(20.dp)
.fillMaxWidth(),
verticalArrangement = Arrangement.Top,
horizontalAlignment = Alignment.Start,
) {
Text(
text = if (isRight.value) LocalString.frameworkYesLite else LocalString.frameworkNoLite,
color = Color.White,
fontSize = 14.sp
)
Text(
text = "${version.value} (${code.intValue}) - ${core.value}",
color = Color.White,
fontSize = 14.sp
)
}
}
}
@Preview(showBackground = true)
@Composable
private fun MainPreview() {
val isFined = remember { mutableStateOf(false) }
val coreVersion = remember { mutableStateOf("1.0.0") }
val coreName = remember { mutableStateOf("Xposed") }
val coreCode = remember { mutableIntStateOf(1000) }
val runtime = remember {
RuntimeState(isFined, coreVersion, coreCode, coreName)
}
HomeFragment(runtime)
}

View File

@ -0,0 +1,139 @@
package moe.fuqiuluo.shamrock.ui.fragment
import android.widget.Toast
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.absolutePadding
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Divider
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.content.edit
import kotlinx.coroutines.launch
import moe.fuqiuluo.shamrock.R
import moe.fuqiuluo.shamrock.ui.theme.LocalString
import moe.fuqiuluo.shamrock.ui.theme.TabUnSelectedColor
import moe.fuqiuluo.shamrock.ui.tools.NoticeTextDialog
@Composable
fun LabFragment() {
val scope = rememberCoroutineScope()
val ctx = LocalContext.current
val preferences = ctx.getSharedPreferences("config", 0)
Surface(
modifier = Modifier
.fillMaxSize(),
) {
Column(
modifier = Modifier
.verticalScroll(rememberScrollState())
.padding(16.dp)
) {
val showNoticeDialog = remember { mutableStateOf(false) }
NoticeBox(text = LocalString.labWarning) {
showNoticeDialog.value = true
}
if (showNoticeDialog.value) {
NoticeTextDialog(
openDialog = showNoticeDialog,
title = "温馨提示",
text = "实验室功能会导致一些奇怪的问题,请谨慎使用!"
)
}
ActionBox(
modifier = Modifier.padding(top = 12.dp),
painter = painterResource(id = R.drawable.baseline_preview_24),
title = "显示设置"
) {
Column {
Divider(
modifier = Modifier,
color = TabUnSelectedColor,
thickness = 0.2.dp
)
Function(
title = "中二病模式",
desc = "也许会导致奇怪的问题,大抵就是你看不懂罢了。",
descColor = it,
isSwitch = preferences.getBoolean("2B", false)
) {
preferences.edit { putBoolean("2B", it) }
scope.launch { Toast.makeText(ctx, "重启生效哦~", Toast.LENGTH_SHORT).show() }
}
}
}
ActionBox(
modifier = Modifier.padding(top = 12.dp),
painter = painterResource(id = R.drawable.round_logo_dev_24),
title = "实验功能"
) {
Column {
Divider(
modifier = Modifier,
color = TabUnSelectedColor,
thickness = 0.2.dp
)
Function(
title = "自动清理QQ垃圾",
desc = "也许会导致奇怪的问题。",
descColor = it,
isSwitch = preferences.getBoolean("auto_clear", false)
) {
preferences.edit { putBoolean("auto_clear", it) }
scope.launch { Toast.makeText(ctx, "重启QQ生效", Toast.LENGTH_SHORT).show() }
}
}
}
}
}
}
@Composable
private fun Function(
title: String,
desc: String,
descColor: Color,
isSwitch: Boolean,
onClick: (Boolean) -> Unit
) {
Column(modifier = Modifier
.absolutePadding(left = 8.dp, right = 8.dp, top = 12.dp, bottom = 0.dp)
) {
Text(
modifier = Modifier.padding(2.dp),
text = desc,
color = descColor,
fontSize = 11.sp
)
ActionSwitch(text = title, isSwitch = isSwitch) {
onClick(it)
}
}
}
@Preview(showBackground = true)
@Composable
private fun LabPreView() {
LabFragment()
}

View File

@ -0,0 +1,126 @@
package moe.fuqiuluo.shamrock.ui.fragment
import android.annotation.SuppressLint
import androidx.compose.foundation.background
import androidx.compose.foundation.indication
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.ParagraphStyle
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import moe.fuqiuluo.shamrock.ui.app.Logger
import java.lang.StringBuilder
import java.util.Collections.EMPTY_LIST
@Composable
fun LogFragment(
logger: Logger
) {
val scope = rememberCoroutineScope()
Surface(
modifier = Modifier
.fillMaxSize(),
) {
Column(
modifier = Modifier
.padding(16.dp)
) {
NoticeBox(text = "日志仅保留最新的${Short.MAX_VALUE}条,超出部分会自动删除,如有需要请做好保留。")
Box(
modifier = Modifier
.padding(top = 12.dp)
.fillMaxSize()
.indication(remember { MutableInteractionSource() }, null)
.background(
color = Color(0xFFf4f4f4),
shape = RoundedCornerShape(12.dp)
)
.verticalScroll(rememberScrollState())
) {
Row(
modifier = Modifier
.padding(
start = 10.dp,
end = 10.dp,
top = 10.dp,
bottom = 10.dp
),
horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically
) {
@Suppress("UNCHECKED_CAST") val text = remember {
mutableStateOf(
AnnotatedString(
text = "",
spanStyles = EMPTY_LIST as List<AnnotatedString.Range<SpanStyle>>,
paragraphStyles = EMPTY_LIST as List<AnnotatedString.Range<ParagraphStyle>>
)
)
}
LaunchedEffect(logger.logCache.value.length) {
val spanStyles = mutableListOf<AnnotatedString.Range<SpanStyle>>()
val paragraphStyles = mutableListOf<AnnotatedString.Range<ParagraphStyle>>()
logger.logRanges.forEach {
spanStyles.add(AnnotatedString.Range(
SpanStyle(
color = it.level.color
), it.start, it.end
))
paragraphStyles.add(AnnotatedString.Range(
ParagraphStyle(
textAlign = TextAlign.Start
), it.start, it.end
))
}
text.value = AnnotatedString(
text = logger.logCache.value.toString(),
spanStyles = spanStyles,
paragraphStyles = paragraphStyles
)
}
Text(
modifier = Modifier.fillMaxWidth(),
text = text.value,
fontSize = 12.sp,
color = Color(0xff6c6c6c),
)
}
}
}
}
}
@SuppressLint("UnrememberedMutableState")
@Preview
@Composable
private fun LogPreview() {
LogFragment(
Logger(mutableStateOf(StringBuilder()), mutableListOf())
)
}

View File

@ -0,0 +1,40 @@
package moe.fuqiuluo.shamrock.ui.service
import android.content.ContentProvider
import android.content.ContentValues
import android.database.Cursor
import android.net.Uri
class MultifunctionalProvider: ContentProvider() {
override fun insert(uri: Uri, content: ContentValues?): Uri {
return uri
}
override fun onCreate(): Boolean {
return true
}
override fun query(
p0: Uri,
p1: Array<out String>?,
p2: String?,
p3: Array<out String>?,
p4: String?
): Cursor? {
return null
}
override fun getType(p0: Uri): String? {
return null
}
override fun delete(p0: Uri, p1: String?, p2: Array<out String>?): Int {
return 0
}
override fun update(p0: Uri, p1: ContentValues?, p2: String?, p3: Array<out String>?): Int {
return 0
}
}

View File

@ -0,0 +1,17 @@
package moe.fuqiuluo.shamrock.ui.theme
import androidx.compose.ui.graphics.Color
val Purple80 = Color(0xFFD0BCFF)
val PurpleGrey80 = Color(0xFFCCC2DC)
val Pink80 = Color(0xFFEFB8C8)
val Purple40 = Color(0xFF6650a4)
val PurpleGrey40 = Color(0xFF625b71)
val Pink40 = Color(0xFF7D5260)
val ToolbarColor = Color(0xFF6f6f6f)
val TabSelectedColor = Color(0xFF5A5A5A)
val TabUnSelectedColor = Color(0xFF6f6f6f)
val TabDividerColor = Color(0xFFBEBEBE)

View File

@ -0,0 +1,75 @@
@file:Suppress(
"SpellCheckingInspection", "unused", "PropertyName",
"ClassName", "NonAsciiCharacters"
)
package moe.fuqiuluo.shamrock.ui.theme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.ui.platform.LocalContext
import moe.fuqiuluo.shamrock.R
private val LocalStringDefault = Default()
private val LocalString2B = Chūnibyō()
val LocalString: VarString
@ReadOnlyComposable
@Composable
get() {
val ctx = LocalContext.current
val sharedPreferences = ctx.getSharedPreferences("config", 0)
return if (!sharedPreferences.getBoolean("2B", false)) {
LocalStringDefault
} else {
LocalString2B
}
}
private open class Chūnibyō: Default() {
init {
TitlesWithIcon = arrayOf(
"玄天" to R.drawable.round_home_24,
"天穹" to R.drawable.round_dashboard_24,
"无极" to R.drawable.round_monitor_heart_24,
"飘渺" to R.drawable.round_logo_dev_24
)
frameworkYes = "仙路已通"
frameworkNo = "鬼怪横行"
frameworkYesLite = "五行已备"
frameworkNoLite = "需待东风"
legalWarning = "白榆,北辰,曜魄,应星,云川当方位不乱,即可作于无极之域。\n" +
"执明起,至除免于灾祸。\n" +
"元冥浩浩,非凡不可动之。"
labWarning = "寒酥降矣,梅熟日久,莫不可测。"
}
}
private open class Default: VarString(
TitlesWithIcon = arrayOf(
"主页" to R.drawable.round_home_24,
"状态" to R.drawable.round_dashboard_24,
"日志" to R.drawable.round_monitor_heart_24,
"Lab" to R.drawable.round_logo_dev_24
), "框架已激活", "框架未激活",
"已激活", "未激活",
legalWarning = "该模块仅适用于目标版本8.9.68及以上的版本。\n" +
"同时声明本项目仅用于学习与交流请于24小时内删除。\n" +
"同时开源贡献者均享受免责条例。",
labWarning = "实验室功能可能会导致出乎意料的BUG!",
"日志"
)
open class VarString(
var TitlesWithIcon: Array<Pair<String, Int>>,
var frameworkYes: String,
var frameworkNo: String,
var frameworkYesLite: String,
var frameworkNoLite: String,
var legalWarning: String,
var labWarning: String,
var logTitle: String
)

View File

@ -0,0 +1,71 @@
package moe.fuqiuluo.shamrock.ui.theme
import android.app.Activity
import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.core.view.WindowCompat
private val DarkColorScheme = darkColorScheme(
primary = Color(0xFFD0BCFF),
secondary = Color(0xFFCCC2DC),
tertiary = Color(0xFFEFB8C8)
)
private val LightColorScheme = lightColorScheme(
primary = Color.White,
secondary = Color.White,
tertiary = Color(0xFF7D5260),
surface = Color(0xFFFFFBFE),
/* Other default colors to override
background = Color(0xFFFFFBFE),
onPrimary = Color.White,
onSecondary = Color.White,
onTertiary = Color.White,
onBackground = Color(0xFF1C1B1F),
onSurface = Color(0xFF1C1B1F),
*/
)
@Composable
fun ShamrockTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
// Dynamic color is available on Android 12+
dynamicColor: Boolean = true,
content: @Composable () -> Unit
) {
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
}
darkTheme -> DarkColorScheme
else -> LightColorScheme
}
val view = LocalView.current
if (!view.isInEditMode) {
SideEffect {
val window = (view.context as Activity).window
window.statusBarColor = colorScheme.primary.toArgb()
WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme
}
}
MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
content = content
)
}

View File

@ -0,0 +1,34 @@
package moe.fuqiuluo.shamrock.ui.theme
import androidx.compose.material3.Typography
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
// Set of Material typography styles to start with
val Typography = Typography(
bodyLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
lineHeight = 24.sp,
letterSpacing = 0.5.sp
)
/* Other default text styles to override
titleLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 22.sp,
lineHeight = 28.sp,
letterSpacing = 0.sp
),
labelSmall = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Medium,
fontSize = 11.sp,
lineHeight = 16.sp,
letterSpacing = 0.5.sp
)
*/
)

View File

@ -0,0 +1,73 @@
package moe.fuqiuluo.shamrock.ui.tools
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import moe.fuqiuluo.shamrock.ui.theme.ShamrockTheme
import moe.fuqiuluo.shamrock.ui.theme.TabSelectedColor
import moe.fuqiuluo.shamrock.ui.theme.TabUnSelectedColor
@Composable
fun NoticeTextDialog(
openDialog: MutableState<Boolean>,
title: String,
text: String
) {
AlertDialog(
shape = RoundedCornerShape(12.dp),
onDismissRequest = {
openDialog.value = false
},
title = {
Text(
text = title,
fontSize = 16.sp,
color = TabSelectedColor
)
},
text = {
Text(
text,
fontSize = 14.sp,
color = TabUnSelectedColor
)
},
confirmButton = {
Button(
modifier = Modifier
.fillMaxWidth(),
onClick = {
openDialog.value = false
},
colors = ButtonDefaults.buttonColors(
containerColor = Color(0xFF2196F3),
contentColor = Color.White
)
) {
Text("我知道了")
}
},
)
}
@Preview
@Composable
private fun NoticeDialogPreView() {
ShamrockTheme {
NoticeTextDialog(openDialog = remember {
mutableStateOf(true)
}, "Notice", "Text")
}
}

View File

@ -0,0 +1,8 @@
package moe.fuqiuluo.shamrock.ui.tools
operator fun <T> List<T>.get(range: IntRange): List<T> {
return this.subList(range.first, range.last)
}
val <T> List<T>.last: T
get() = this.last()

View File

@ -0,0 +1,47 @@
package moe.fuqiuluo.shamrock.ui.tools
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.lerp
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import android.annotation.SuppressLint
import androidx.compose.animation.core.tween
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.gestures.DraggableState
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.gestures.draggable
import androidx.compose.foundation.gestures.rememberDraggableState
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.composed
import androidx.compose.ui.graphics.drawscope.scale
import androidx.compose.ui.graphics.lerp
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.launch
import kotlin.math.min

View File

@ -0,0 +1,45 @@
package moe.fuqiuluo.shamrock.ui.tools
import android.annotation.SuppressLint
import androidx.compose.foundation.Indication
import androidx.compose.foundation.IndicationInstance
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.InteractionSource
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.drawscope.ContentDrawScope
import androidx.compose.ui.semantics.Role
@SuppressLint("ComposableModifierFactory")
@Composable
fun Modifier.noRippleClickable(
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
indication: Indication? = null,
enabled: Boolean = true,
onClickLabel: String? = null,
role: Role? = null,
onClick: () -> Unit,
) = clickable(
interactionSource = interactionSource,
indication = indication,
enabled = enabled,
onClickLabel = onClickLabel,
role = role,
onClick = onClick,
)
object NoIndication : Indication {
private object NoIndicationInstance : IndicationInstance {
override fun ContentDrawScope.drawIndication() {
drawContent()
}
}
@Composable
override fun rememberUpdatedInstance(interactionSource: InteractionSource): IndicationInstance {
return NoIndicationInstance
}
}

View File

@ -0,0 +1,209 @@
package moe.fuqiuluo.shamrock.ui.tools
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.CardElevation
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Paint
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
/**
* 绘制阴影范围
* [top] 顶部范围
* [start] 开始范围
* [bottom] 底部范围
* [end] 结束范围
* Create empty Shadow elevation
*/
open class ShadowElevation(
val top: Dp = 0.dp,
val start: Dp = 0.dp,
val bottom: Dp = 0.dp,
val end: Dp = 0.dp
){
companion object : ShadowElevation()
}
/**
* 绘制阴影内侧间距
* @param horizontal 横向
* @param vertical 纵向
* @return ShadowElevation
*/
@Stable
fun ShadowElevation.padding(
horizontal: Dp = 0.dp,
vertical: Dp = 0.dp
): ShadowElevation {
return ShadowElevation(
top = horizontal,
start = vertical,
bottom = horizontal,
end = vertical,
)
}
/**
* 绘制阴影所有范围
* @param elevation 圆角
* @return ShadowElevation
*/
fun ShadowElevation.all(
elevation: Dp = 0.dp,
): ShadowElevation {
return ShadowElevation(
top = elevation,
start = elevation,
bottom = elevation,
end = elevation,
)
}
class ShadowShape(
val topStart: Dp = 0.dp,
val bottomStart: Dp = 0.dp,
val topEnd: Dp = 0.dp,
val bottomEnd: Dp = 0.dp
)
fun ShadowShape.padding(
topAll: Dp = 0.dp,
bottomAll: Dp = 0.dp
): ShadowShape {
return ShadowShape(
topStart = topAll,
bottomStart = bottomAll,
topEnd = topAll,
bottomEnd = bottomAll,
)
}
fun ShadowShape.all(
elevation: Dp = 0.dp,
): ShadowShape {
return ShadowShape(
topStart = elevation,
bottomStart = elevation,
topEnd = elevation,
bottomEnd = elevation,
)
}
/**
* 阴影Layout
* 不支持圆角,在Modifier中设置圆角会导致无作用
* 以子控件的大小为测试请在子控件中设置padding及长宽
* @param shadowColor [Color] 绘制阴影颜色
* @param elevation [ShadowElevation] 绘制阴影范围
* @param shape [Dp] 绘制圆角
* @param offsetX [Dp] 偏移X轴
* @param offsetY [Dp] 偏移Y轴
* @param content (slot) 填充内容
* @receiver
*/
@RequiresApi(Build.VERSION_CODES.O)
@Composable
fun ShadowLayout(
modifier: Modifier = Modifier,
shadowColor : Color = Color(0xffD3DBF9),
elevation: ShadowElevation = ShadowElevation(),
shape: Dp = 10.dp,
offsetX: Dp = 0.dp,
offsetY: Dp = 0.dp,
alpha: Float = 0.5f,
content: @Composable ColumnScope.() -> Unit,
) {
Card(
modifier = modifier
.padding(
top = elevation.top,
bottom = elevation.bottom,
start = elevation.start,
end = elevation.end
)
.drawColoredShadow(
shadowColor,
alpha,
borderRadius = shape,
shadowRadius = shape,
offsetX = offsetX,
offsetY = offsetY,
roundedRect = true
)
.background(Color.Transparent)
,
elevation = CardDefaults.cardElevation(
0.dp, 0.dp, 0.dp, 0.dp, 0.dp, 0.dp
),
shape = RoundedCornerShape(0.dp) ,
content = content,
)
}
/**
* 绘制基础阴影
* @param color 颜色
* @param alpha 颜色透明度
* @param borderRadius 阴影便捷圆角
* @param shadowRadius 阴影圆角
* @param offsetX 偏移X轴
* @param offsetY 偏移Y轴
* @param roundedRect 是否绘制圆角就行
*/
@RequiresApi(Build.VERSION_CODES.O)
fun Modifier.drawColoredShadow(
color: Color,
alpha: Float = 0.2f,
borderRadius: Dp = 0.dp,
shadowRadius: Dp = 0.dp,
offsetX: Dp = 0.dp,
offsetY: Dp = 0.dp,
roundedRect: Boolean = true
) = this.drawBehind {
/**将颜色转换为Argb的Int类型*/
val transparentColor = android.graphics.Color.toArgb(color.copy(alpha = .0f).value.toLong())
val shadowColor = android.graphics.Color.toArgb(color.copy(alpha = alpha).value.toLong())
/**调用Canvas绘制*/
this.drawIntoCanvas {
val paint = Paint()
paint.color = Color.Transparent
/**调用底层fragment Paint绘制*/
val frameworkPaint = paint.asFrameworkPaint()
frameworkPaint.color = transparentColor
/**绘制阴影*/
frameworkPaint.setShadowLayer(
shadowRadius.toPx(),
offsetX.toPx(),
offsetY.toPx(),
shadowColor
)
/**形状绘制*/
it.drawRoundRect(
0f,
0f,
this.size.width,
this.size.height,
if (roundedRect) this.size.height / 2 else borderRadius.toPx(),
if (roundedRect) this.size.height / 2 else borderRadius.toPx(),
paint
)
}
}

View File

@ -0,0 +1,132 @@
package moe.fuqiuluo.shamrock.ui.tools
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.LinearOutSlowInEasing
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
@Composable
fun IosSwitch(
modifier: Modifier = Modifier,
buttonHeight: Dp = 40.dp,
innerPadding: Dp = 3.5.dp,
shape: RoundedCornerShape = RoundedCornerShape(45.dp),
switchValue: Boolean,
positiveColor: Color = Color(0xFF35C759),
negativeColor: Color = Color(0xFFE9E9EA),
onValueChanged: (Boolean) -> Unit,
) {
var width by remember { (mutableStateOf(0.dp)) }
val interactionSource = remember {
MutableInteractionSource()
}
var switchClicked by remember {
mutableStateOf(switchValue)
}
var padding by remember {
mutableStateOf(0.dp)
}
padding = if (!switchClicked) 0.dp else width - (width / 2)
val animateSize by animateDpAsState(
targetValue = if (!switchClicked) 0.dp else padding,
tween(
durationMillis = 300,
delayMillis = 0,
easing = LinearOutSlowInEasing
), label = "DpAnimation"
)
val animateBgColor by animateColorAsState(
targetValue = if (switchClicked) positiveColor else negativeColor,
tween(
durationMillis = 300,
delayMillis = 0,
easing = FastOutSlowInEasing
), label = "ColorAnimation"
)
val localDensity = LocalDensity.current
Box(
modifier = modifier
.defaultMinSize(
minWidth = buttonHeight * 2,
minHeight = buttonHeight
)
.onGloballyPositioned {
width = with(localDensity) {
it.size.width.toDp()
}
}
.height(buttonHeight)
.clip(shape = shape)
.background(animateBgColor)
.clickable(
interactionSource = interactionSource,
indication = null
) {
switchClicked = !switchClicked
onValueChanged(switchClicked)
}
) {
Row {
Box(
modifier = Modifier
.fillMaxHeight()
.width(animateSize)
.background(Color.Transparent)
)
Box(
modifier = Modifier
.size(buttonHeight)
.padding(innerPadding)
.shadow(elevation = 5.dp, shape)
.clip(shape = shape)
.background(Color.White)
)
}
}
}
@Preview
@Composable
private fun SwitchPreView() {
IosSwitch(
switchValue = false,
buttonHeight = 22.dp,
onValueChanged = {
}
)
}

View File

@ -0,0 +1,392 @@
package moe.fuqiuluo.shamrock.ui.tools
import androidx.compose.animation.animateColor
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.tween
import androidx.compose.animation.core.updateTransition
import androidx.compose.foundation.Indication
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.selection.selectable
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ProvideTextStyle
import androidx.compose.material3.Typography
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.FirstBaseline
import androidx.compose.ui.layout.LastBaseline
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.Placeable
import androidx.compose.ui.layout.layoutId
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import kotlin.math.max
private enum class ColorSchemeKeyTokens {
Background,
Error,
ErrorContainer,
InverseOnSurface,
InversePrimary,
InverseSurface,
OnBackground,
OnError,
OnErrorContainer,
OnPrimary,
OnPrimaryContainer,
OnSecondary,
OnSecondaryContainer,
OnSurface,
OnSurfaceVariant,
OnTertiary,
OnTertiaryContainer,
Outline,
OutlineVariant,
Primary,
PrimaryContainer,
Scrim,
Secondary,
SecondaryContainer,
Surface,
SurfaceTint,
SurfaceVariant,
Tertiary,
TertiaryContainer,
}
private enum class ShapeKeyTokens {
CornerExtraLarge,
CornerExtraLargeTop,
CornerExtraSmall,
CornerExtraSmallTop,
CornerFull,
CornerLarge,
CornerLargeEnd,
CornerLargeTop,
CornerMedium,
CornerNone,
CornerSmall,
}
private enum class TypographyKeyTokens {
BodyLarge,
BodyMedium,
BodySmall,
DisplayLarge,
DisplayMedium,
DisplaySmall,
HeadlineLarge,
HeadlineMedium,
HeadlineSmall,
LabelLarge,
LabelMedium,
LabelSmall,
TitleLarge,
TitleMedium,
TitleSmall,
}
private object ElevationTokens {
val Level0 = 0.0.dp
val Level1 = 1.0.dp
val Level2 = 3.0.dp
val Level3 = 6.0.dp
val Level4 = 8.0.dp
val Level5 = 12.0.dp
}
private object PrimaryNavigationTabTokens {
val ActiveIndicatorColor = ColorSchemeKeyTokens.Primary
val ActiveIndicatorHeight = 3.0.dp
val ActiveIndicatorShape = RoundedCornerShape(3.0.dp)
val ContainerColor = ColorSchemeKeyTokens.Surface
val ContainerElevation = ElevationTokens.Level0
val ContainerHeight = 48.0.dp
val ContainerShape = ShapeKeyTokens.CornerNone
val DividerColor = ColorSchemeKeyTokens.SurfaceVariant
val DividerHeight = 1.0.dp
val ActiveFocusIconColor = ColorSchemeKeyTokens.Primary
val ActiveHoverIconColor = ColorSchemeKeyTokens.Primary
val ActiveIconColor = ColorSchemeKeyTokens.Primary
val ActivePressedIconColor = ColorSchemeKeyTokens.Primary
val IconAndLabelTextContainerHeight = 64.0.dp
val IconSize = 24.0.dp
val InactiveFocusIconColor = ColorSchemeKeyTokens.OnSurface
val InactiveHoverIconColor = ColorSchemeKeyTokens.OnSurface
val InactiveIconColor = ColorSchemeKeyTokens.OnSurfaceVariant
val InactivePressedIconColor = ColorSchemeKeyTokens.OnSurface
val ActiveFocusLabelTextColor = ColorSchemeKeyTokens.Primary
val ActiveHoverLabelTextColor = ColorSchemeKeyTokens.Primary
val ActiveLabelTextColor = ColorSchemeKeyTokens.Primary
val ActivePressedLabelTextColor = ColorSchemeKeyTokens.Primary
val InactiveFocusLabelTextColor = ColorSchemeKeyTokens.OnSurface
val InactiveHoverLabelTextColor = ColorSchemeKeyTokens.OnSurface
val InactiveLabelTextColor = ColorSchemeKeyTokens.OnSurfaceVariant
val InactivePressedLabelTextColor = ColorSchemeKeyTokens.OnSurface
val LabelTextFont = TypographyKeyTokens.TitleSmall
}
private val HorizontalTextPadding = 16.dp
private val LargeTabHeight = 72.dp
private val SmallTabHeight = PrimaryNavigationTabTokens.ContainerHeight
private val IconDistanceFromBaseline = 20.sp
// Distance from the top of the indicator to the text baseline when there is one line of text and an
// icon
private val SingleLineTextBaselineWithIcon = 14.dp
// Distance from the top of the indicator to the last text baseline when there are two lines of text
// and an icon
private val DoubleLineTextBaselineWithIcon = 6.dp
@Composable
private fun TabBaselineLayout(
text: @Composable (() -> Unit)?,
icon: @Composable (() -> Unit)?
) {
Layout(
{
if (text != null) {
Box(
Modifier
.layoutId("text")
.padding(horizontal = HorizontalTextPadding)
) { text() }
}
if (icon != null) {
Box(Modifier.layoutId("icon")) { icon() }
}
}
) { measurables, constraints ->
val textPlaceable = text?.let {
measurables.first { it.layoutId == "text" }.measure(
// Measure with loose constraints for height as we don't want the text to take up more
// space than it needs
constraints.copy(minHeight = 0)
)
}
val iconPlaceable = icon?.let {
measurables.first { it.layoutId == "icon" }.measure(constraints)
}
val tabWidth = max(textPlaceable?.width ?: 0, iconPlaceable?.width ?: 0)
val specHeight = if (textPlaceable != null && iconPlaceable != null) {
LargeTabHeight
} else {
SmallTabHeight
}.roundToPx()
val tabHeight = max(
specHeight,
(iconPlaceable?.height ?: 0) + (textPlaceable?.height ?: 0) +
IconDistanceFromBaseline.roundToPx()
)
val firstBaseline = textPlaceable?.get(FirstBaseline)
val lastBaseline = textPlaceable?.get(LastBaseline)
layout(tabWidth, tabHeight) {
when {
textPlaceable != null && iconPlaceable != null -> placeTextAndIcon(
density = this@Layout,
textPlaceable = textPlaceable,
iconPlaceable = iconPlaceable,
tabWidth = tabWidth,
tabHeight = tabHeight,
firstBaseline = firstBaseline!!,
lastBaseline = lastBaseline!!
)
textPlaceable != null -> placeTextOrIcon(textPlaceable, tabHeight)
iconPlaceable != null -> placeTextOrIcon(iconPlaceable, tabHeight)
else -> {
}
}
}
}
}
private fun Placeable.PlacementScope.placeTextOrIcon(
textOrIconPlaceable: Placeable,
tabHeight: Int
) {
val contentY = (tabHeight - textOrIconPlaceable.height) / 2
textOrIconPlaceable.placeRelative(0, contentY)
}
private fun Placeable.PlacementScope.placeTextAndIcon(
density: Density,
textPlaceable: Placeable,
iconPlaceable: Placeable,
tabWidth: Int,
tabHeight: Int,
firstBaseline: Int,
lastBaseline: Int
) {
val baselineOffset = if (firstBaseline == lastBaseline) {
SingleLineTextBaselineWithIcon
} else {
DoubleLineTextBaselineWithIcon
}
// Total offset between the last text baseline and the bottom of the Tab layout
val textOffset = with(density) {
baselineOffset.roundToPx() + PrimaryNavigationTabTokens.ActiveIndicatorHeight.roundToPx()
}
// How much space there is between the top of the icon (essentially the top of this layout)
// and the top of the text layout's bounding box (not baseline)
val iconOffset = with(density) {
iconPlaceable.height + IconDistanceFromBaseline.roundToPx() - firstBaseline
}
val textPlaceableX = (tabWidth - textPlaceable.width) / 2
val textPlaceableY = tabHeight - lastBaseline - textOffset
textPlaceable.placeRelative(textPlaceableX, textPlaceableY)
val iconPlaceableX = (tabWidth - iconPlaceable.width) / 2
val iconPlaceableY = textPlaceableY - iconOffset
iconPlaceable.placeRelative(iconPlaceableX, iconPlaceableY)
}
@Composable
fun ShamrockTab(
selected: Boolean,
onClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
text: @Composable (() -> Unit)? = null,
icon: @Composable (() -> Unit)? = null,
selectedContentColor: Color = LocalContentColor.current,
unselectedContentColor: Color = selectedContentColor,
indication: Indication? = rememberRipple(bounded = true, color = selectedContentColor),
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }
) {
val styledText: @Composable (() -> Unit)? = text?.let {
@Composable {
val style =
MaterialTheme.typography.fromToken(PrimaryNavigationTabTokens.LabelTextFont)
.copy(textAlign = TextAlign.Center)
ProvideTextStyle(style, content = text)
}
}
ShamrockTab(
selected,
onClick,
modifier,
enabled,
selectedContentColor,
unselectedContentColor,
interactionSource,
indication
) {
TabBaselineLayout(icon = icon, text = styledText)
}
}
@Composable
fun ShamrockTab(
selected: Boolean,
onClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
selectedContentColor: Color = LocalContentColor.current,
unselectedContentColor: Color = selectedContentColor,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
indication: Indication? = rememberRipple(bounded = true, color = selectedContentColor),
content: @Composable ColumnScope.() -> Unit
) {
// The color of the Ripple should always the selected color, as we want to show the color
// before the item is considered selected, and hence before the new contentColor is
// provided by TabTransition.
TabTransition(selectedContentColor, unselectedContentColor, selected) {
Column(
modifier = modifier
.selectable(
selected = selected,
onClick = onClick,
enabled = enabled,
role = Role.Tab,
interactionSource = interactionSource,
indication = indication
)
.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
content = content
)
}
}
private const val TabFadeInAnimationDuration = 150
private const val TabFadeInAnimationDelay = 100
private const val TabFadeOutAnimationDuration = 100
@Composable
private fun TabTransition(
activeColor: Color,
inactiveColor: Color,
selected: Boolean,
content: @Composable () -> Unit
) {
val transition = updateTransition(selected, label = "")
val color by transition.animateColor(
transitionSpec = {
if (false isTransitioningTo true) {
tween(
durationMillis = TabFadeInAnimationDuration,
delayMillis = TabFadeInAnimationDelay,
easing = LinearEasing
)
} else {
tween(
durationMillis = TabFadeOutAnimationDuration,
easing = LinearEasing
)
}
}, label = ""
) {
if (it) activeColor else inactiveColor
}
CompositionLocalProvider(
LocalContentColor provides color,
content = content
)
}
private fun Typography.fromToken(value: TypographyKeyTokens): TextStyle {
return when (value) {
TypographyKeyTokens.DisplayLarge -> displayLarge
TypographyKeyTokens.DisplayMedium -> displayMedium
TypographyKeyTokens.DisplaySmall -> displaySmall
TypographyKeyTokens.HeadlineLarge -> headlineLarge
TypographyKeyTokens.HeadlineMedium -> headlineMedium
TypographyKeyTokens.HeadlineSmall -> headlineSmall
TypographyKeyTokens.TitleLarge -> titleLarge
TypographyKeyTokens.TitleMedium -> titleMedium
TypographyKeyTokens.TitleSmall -> titleSmall
TypographyKeyTokens.BodyLarge -> bodyLarge
TypographyKeyTokens.BodyMedium -> bodyMedium
TypographyKeyTokens.BodySmall -> bodySmall
TypographyKeyTokens.LabelLarge -> labelLarge
TypographyKeyTokens.LabelMedium -> labelMedium
TypographyKeyTokens.LabelSmall -> labelSmall
}
}

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#6C6C6C"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M19,3H5C3.89,3 3,3.9 3,5v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2V5C21,3.9 20.11,3 19,3zM19,19H5V7h14V19zM13.5,13c0,0.83 -0.67,1.5 -1.5,1.5s-1.5,-0.67 -1.5,-1.5c0,-0.83 0.67,-1.5 1.5,-1.5S13.5,12.17 13.5,13zM12,9c-2.73,0 -5.06,1.66 -6,4c0.94,2.34 3.27,4 6,4s5.06,-1.66 6,-4C17.06,10.66 14.73,9 12,9zM12,15.5c-1.38,0 -2.5,-1.12 -2.5,-2.5c0,-1.38 1.12,-2.5 2.5,-2.5c1.38,0 2.5,1.12 2.5,2.5C14.5,14.38 13.38,15.5 12,15.5z"/>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>

View File

@ -0,0 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#6F6F6F"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M13,13L13,13c-0.56,0.56 -1.45,0.56 -2,0.01L11,13c-0.55,-0.55 -0.55,-1.44 0,-1.99L11,11c0.55,-0.55 1.44,-0.55 1.99,0L13,11C13.55,11.55 13.55,12.45 13,13zM12,6l2.12,2.12l2.5,-2.5l-3.2,-3.2c-0.78,-0.78 -2.05,-0.78 -2.83,0l-3.2,3.2l2.5,2.5L12,6zM6,12l2.12,-2.12l-2.5,-2.5l-3.2,3.2c-0.78,0.78 -0.78,2.05 0,2.83l3.2,3.2l2.5,-2.5L6,12zM18,12l-2.12,2.12l2.5,2.5l3.2,-3.2c0.78,-0.78 0.78,-2.05 0,-2.83l-3.2,-3.2l-2.5,2.5L18,12zM12,18l-2.12,-2.12l-2.5,2.5l3.2,3.2c0.78,0.78 2.05,0.78 2.83,0l3.2,-3.2l-2.5,-2.5L12,18z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#6F6F6F"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M4,13h6c0.55,0 1,-0.45 1,-1L11,4c0,-0.55 -0.45,-1 -1,-1L4,3c-0.55,0 -1,0.45 -1,1v8c0,0.55 0.45,1 1,1zM4,21h6c0.55,0 1,-0.45 1,-1v-4c0,-0.55 -0.45,-1 -1,-1L4,15c-0.55,0 -1,0.45 -1,1v4c0,0.55 0.45,1 1,1zM14,21h6c0.55,0 1,-0.45 1,-1v-8c0,-0.55 -0.45,-1 -1,-1h-6c-0.55,0 -1,0.45 -1,1v8c0,0.55 0.45,1 1,1zM13,4v4c0,0.55 0.45,1 1,1h6c0.55,0 1,-0.45 1,-1L21,4c0,-0.55 -0.45,-1 -1,-1h-6c-0.55,0 -1,0.45 -1,1z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M9,16.2l-3.5,-3.5c-0.39,-0.39 -1.01,-0.39 -1.4,0 -0.39,0.39 -0.39,1.01 0,1.4l4.19,4.19c0.39,0.39 1.02,0.39 1.41,0L20.3,7.7c0.39,-0.39 0.39,-1.01 0,-1.4 -0.39,-0.39 -1.01,-0.39 -1.4,0L9,16.2z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#6F6F6F"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M10,19v-5h4v5c0,0.55 0.45,1 1,1h3c0.55,0 1,-0.45 1,-1v-7h1.7c0.46,0 0.68,-0.57 0.33,-0.87L12.67,3.6c-0.38,-0.34 -0.96,-0.34 -1.34,0l-8.36,7.53c-0.34,0.3 -0.13,0.87 0.33,0.87H5v7c0,0.55 0.45,1 1,1h3c0.55,0 1,-0.45 1,-1z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#6C6C6C"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,17c-0.55,0 -1,-0.45 -1,-1v-4c0,-0.55 0.45,-1 1,-1s1,0.45 1,1v4c0,0.55 -0.45,1 -1,1zM13,9h-2L11,7h2v2z"/>
</vector>

View File

@ -0,0 +1,6 @@
<vector android:height="24dp" android:tint="#6F6F6F"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M19,3H5C3.9,3 3,3.9 3,5v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V5C21,3.9 20.1,3 19,3zM7.68,14.98H6V9h1.71c1.28,0 1.71,1.03 1.71,1.71l0,2.56C9.42,13.95 9,14.98 7.68,14.98zM12.38,11.46v1.07h-1.18v1.39h1.93v1.07h-2.25c-0.4,0.01 -0.74,-0.31 -0.75,-0.71V9.75c-0.01,-0.4 0.31,-0.74 0.71,-0.75h2.28l0,1.07h-1.92v1.39H12.38zM16.88,14.23c-0.48,1.11 -1.33,0.89 -1.71,0L13.77,9h1.18l1.07,4.11L17.09,9h1.18L16.88,14.23z"/>
<path android:fillColor="@android:color/white" android:pathData="M7.77,10.12H7.14v3.77h0.63c0.14,0 0.28,-0.05 0.42,-0.16c0.14,-0.1 0.21,-0.26 0.21,-0.47v-2.52c0,-0.21 -0.07,-0.37 -0.21,-0.47C8.05,10.17 7.91,10.12 7.77,10.12z"/>
</vector>

View File

@ -0,0 +1,6 @@
<vector android:height="24dp" android:tint="#6F6F6F"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M15.11,12.45L14,10.24l-3.11,6.21C10.73,16.79 10.38,17 10,17s-0.73,-0.21 -0.89,-0.55L7.38,13H2v5c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2v-5h-6C15.62,13 15.27,12.79 15.11,12.45z"/>
<path android:fillColor="@android:color/white" android:pathData="M20,4H4C2.9,4 2,4.9 2,6v5h6c0.38,0 0.73,0.21 0.89,0.55L10,13.76l3.11,-6.21c0.34,-0.68 1.45,-0.68 1.79,0L16.62,11H22V6C22,4.9 21.1,4 20,4z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M18.75,3.94L4.07,10.08c-0.83,0.35 -0.81,1.53 0.02,1.85L9.43,14c0.26,0.1 0.47,0.31 0.57,0.57l2.06,5.33c0.32,0.84 1.51,0.86 1.86,0.03l6.15,-14.67c0.33,-0.83 -0.5,-1.66 -1.32,-1.32z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M12,6.34l6.95,-2.58c0.8,-0.3 1.58,0.48 1.29,1.29L17.66,12L12,6.34zM21.9,19.07L4.93,2.1c-0.39,-0.39 -1.02,-0.39 -1.41,0c-0.39,0.39 -0.39,1.02 0,1.41l4.36,4.36l-4.2,1.56C3.27,9.59 3,9.97 3,10.4c0,0.42 0.26,0.8 0.65,0.96l6.42,2.57l2.57,6.42C12.8,20.74 13.18,21 13.6,21c0.43,0 0.82,-0.27 0.97,-0.67l1.56,-4.2l4.36,4.36c0.39,0.39 1.02,0.39 1.41,0C22.29,20.09 22.29,19.46 21.9,19.07z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#6C6C6C"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M4.47,21h15.06c1.54,0 2.5,-1.67 1.73,-3L13.73,4.99c-0.77,-1.33 -2.69,-1.33 -3.46,0L2.74,18c-0.77,1.33 0.19,3 1.73,3zM12,14c-0.55,0 -1,-0.45 -1,-1v-2c0,-0.55 0.45,-1 1,-1s1,0.45 1,1v2c0,0.55 -0.45,1 -1,1zM13,18h-2v-2h2v2z"/>
</vector>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 982 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
</resources>

View File

@ -0,0 +1,3 @@
<resources>
<string name="app_name">Shamrock</string>
</resources>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.Shamrock" parent="android:Theme.Material.Light.NoActionBar" />
</resources>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?><!--
Sample backup rules file; uncomment and customize as necessary.
See https://developer.android.com/guide/topics/data/autobackup
for details.
Note: This file is ignored for devices older that API 31
See https://developer.android.com/about/versions/12/backup-restore
-->
<full-backup-content>
<!--
<include domain="sharedpref" path="."/>
<exclude domain="sharedpref" path="device.xml"/>
-->
</full-backup-content>

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?><!--
Sample data extraction rules file; uncomment and customize as necessary.
See https://developer.android.com/about/versions/12/backup-restore#xml-changes
for details.
-->
<data-extraction-rules>
<cloud-backup>
<!-- TODO: Use <include> and <exclude> to control what is backed up.
<include .../>
<exclude .../>
-->
</cloud-backup>
<!--
<device-transfer>
<include .../>
<exclude .../>
</device-transfer>
-->
</data-extraction-rules>

View File

@ -0,0 +1,17 @@
package moe.fuqiuluo.shamrock
import org.junit.Test
import org.junit.Assert.*
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}

7
build.gradle.kts Normal file
View File

@ -0,0 +1,7 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id("com.android.application") version "8.1.0" apply false
id("org.jetbrains.kotlin.android") version "1.8.10" apply false
id("org.jetbrains.kotlin.plugin.serialization") version "1.8.10" apply false
id("com.android.library") version "8.1.0" apply false
}

23
gradle.properties Normal file
View File

@ -0,0 +1,23 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app's APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
# Enables namespacing of each library's R class so that its R class includes only the
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,6 @@
#Thu Jul 27 20:53:51 CST 2023
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

185
gradlew vendored Normal file
View File

@ -0,0 +1,185 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"

89
gradlew.bat vendored Normal file
View File

@ -0,0 +1,89 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

1
qqinterface/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

View File

@ -0,0 +1,42 @@
plugins {
id("com.android.library")
id("org.jetbrains.kotlin.android")
}
android {
namespace = "moe.fuqiuluo.qqinterface"
compileSdk = 33
defaultConfig {
minSdk = 24
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
}
dependencies {
implementation("androidx.core:core-ktx:1.9.0")
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("com.google.android.material:material:1.9.0")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
}

View File

21
qqinterface/proguard-rules.pro vendored Normal file
View File

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>

View File

@ -0,0 +1,9 @@
package com.tencent.common.app;
import mqq.app.AppRuntime;
public abstract class AppInterface extends AppRuntime {
public String getCurrentNickname() {
return "";
}
}

View File

@ -0,0 +1,6 @@
package com.tencent.common.app.business;
import com.tencent.common.app.AppInterface;
public abstract class BaseQQAppInterface extends AppInterface {
}

View File

@ -0,0 +1,7 @@
package com.tencent.common.config.pad;
public enum DeviceType {
PHONE,
TABLET,
FOLD
}

View File

@ -0,0 +1,4 @@
package com.tencent.image;
public class URLDrawableHandler {
}

View File

@ -0,0 +1,76 @@
package com.tencent.mobileqq.activity.photo;
import android.os.Parcel;
import android.os.Parcelable;
import java.io.Serializable;
public class PhotoSendParams implements Parcelable, Serializable {
public static final Creator<PhotoSendParams> CREATOR = new Creator<PhotoSendParams>() {
@Override
public PhotoSendParams createFromParcel(Parcel parcel) {
return new PhotoSendParams(parcel);
}
@Override
public PhotoSendParams[] newArray(int i) {
return new PhotoSendParams[i];
}
};
public static final int SEND_PIC_NORMAL = 0;
public static final int SEND_PIC_QZONE = 1;
public long fileSize;
public int picType;
public String rawDownloadUrl;
public int rawHeight;
public String rawMd5;
public String rawPicPath;
public int rawWidth;
public String thumbPath;
public PhotoSendParams(String str, String str2, String str3, long j2, int i2, int i3, String str4, int i4) {
this.picType = 0;
this.thumbPath = str;
this.rawMd5 = str2;
this.rawPicPath = str3;
this.fileSize = j2;
this.rawHeight = i2;
this.rawWidth = i3;
this.rawDownloadUrl = str4;
this.picType = i4;
}
@Override // android.os.Parcelable
public int describeContents() {
return 0;
}
public String toString() {
return "PhotoSendParams:&thumbPath:" + this.thumbPath + " &rawMd5:" + this.rawMd5 + " &rawPicPath:" + this.rawPicPath + " &rawHeight:" + this.rawHeight + " &rawWidth:" + this.rawWidth + " &rawDownloadUrl:" + this.rawDownloadUrl + " &picType:" + this.picType;
}
@Override // android.os.Parcelable
public void writeToParcel(Parcel parcel, int i2) {
parcel.writeString(this.thumbPath);
parcel.writeString(this.rawMd5);
parcel.writeString(this.rawPicPath);
parcel.writeLong(this.fileSize);
parcel.writeInt(this.rawHeight);
parcel.writeInt(this.rawWidth);
parcel.writeString(this.rawDownloadUrl);
parcel.writeInt(this.picType);
}
public PhotoSendParams(Parcel parcel) {
this.picType = 0;
this.thumbPath = parcel.readString();
this.rawMd5 = parcel.readString();
this.rawPicPath = parcel.readString();
this.fileSize = parcel.readLong();
this.rawHeight = parcel.readInt();
this.rawWidth = parcel.readInt();
this.rawDownloadUrl = parcel.readString();
this.picType = parcel.readInt();
}
}

View File

@ -0,0 +1,24 @@
package com.tencent.mobileqq.app;
import com.tencent.common.app.business.BaseQQAppInterface;
public class QQAppInterface extends BaseQQAppInterface {
@Override
public String getCurrentAccountUin() {
return null;
}
public void execute(Runnable runnable) {
}
@Override
public String getCurrentNickname() {
return null;
}
public void syncOnlineFriend() {
}
}

View File

@ -0,0 +1,4 @@
package com.tencent.mobileqq.data;
public class MessageRecord {
}

View File

@ -0,0 +1,38 @@
package com.tencent.mobileqq.openapi;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
public class OpenApiProvider extends ContentProvider {
@Override
public boolean onCreate() {
return false;
}
@Override
public Cursor query(Uri uri, String[] strings, String s, String[] strings1, String s1) {
return null;
}
@Override
public String getType(Uri uri) {
return null;
}
@Override
public Uri insert(Uri uri, ContentValues contentValues) {
return null;
}
@Override
public int delete(Uri uri, String s, String[] strings) {
return 0;
}
@Override
public int update(Uri uri, ContentValues contentValues, String s, String[] strings) {
return 0;
}
}

View File

@ -0,0 +1,6 @@
package com.tencent.mobileqq.qfix;
import android.app.Application;
public class ApplicationDelegate extends Application {
}

View File

@ -0,0 +1,29 @@
package com.tencent.mobileqq.transfile;
import com.tencent.mobileqq.transfile.api.ITransFileController;
import java.util.concurrent.atomic.AtomicBoolean;
import mqq.app.AppRuntime;
public class BaseTransFileController implements ITransFileController {
@Override
public AtomicBoolean isWorking() {
return null;
}
@Override
public synchronized boolean transferAsync(TransferRequest transferRequest) {
return false;
}
@Override
public void onCreate(AppRuntime appRuntime) {
}
@Override
public void onDestroy() {
}
}

View File

@ -0,0 +1,4 @@
package com.tencent.mobileqq.transfile;
public class GroupPicUploadProcessor {
}

View File

@ -0,0 +1,208 @@
package com.tencent.mobileqq.transfile;
import com.tencent.image.URLDrawableHandler;
import com.tencent.mobileqq.activity.photo.PhotoSendParams;
import com.tencent.mobileqq.data.MessageRecord;
import java.io.OutputStream;
public class TransferRequest {
public int chatType;
public int delayShowProgressTimeInMs;
public Object extraObject;
public boolean isShareImageByServer;
public int mBusiType;
public boolean mCanSendMsg;
public int mCommandId;
public String mDisplayOutFilePath;
//public c mDownCallBack;
public int mDownMode;
public byte[] mExtentionInfo;
public Object mExtraObj;
public long mFastForwardFileSize;
public int mFastForwardHeight;
public int mFastForwardWidth;
public int mFileType;
public long mGroupFileID;
public String mGroupFileKeyStr;
public boolean mIsPresend;
public boolean mIsPttPreSend;
public boolean mIsSecSnapChatPic;
public boolean mIsUp;
public String mLocalPath;
public String mMd5;
public long mMsgTime;
public OutputStream mOut;
public String mOutFilePath;
public String mPeerUin;
public int mPicSendSource;
public int mPttUploadPanel;
public boolean mReqVideoSubtitle;
public int mRequestDisplayLength;
public int mRequestLength;
public int mRequestOffset;
public TransferResult mResult;
public long mSecMsgId;
public String mSecondId;
public String mSelfUin;
public String mServerPath;
public int mSourceVideoCodecFormat;
public long mSubMsgId;
public int mTargetVideoCodecFormat;
public String mThumbMd5;
public String mThumbPath;
public int mUinType;
public long mUniseq;
public int multiMsgType;
public int pcmForVadNum;
public String pcmForVadPath;
public int pcmForVadPos;
public PhotoSendParams photoSendParams;
public String resIdStr;
public byte[] toSendData;
public int upMsgBusiType;
//public WXVadSeg vadSeg;
public String mRichTag = "";
public boolean isJubaoMsgType = false;
public boolean mIsSelfSend = false;
public boolean useOutputstream = true;
public boolean mSupportRangeBreakDown = false;
public int mDbRecVersion = 5;
public boolean needSendMsg = true;
public int mPrioty = 1;
public MessageRecord mRec = null;
public boolean mNeedReport = true;
public boolean mIsOnlyGetUrl = false;
public boolean mIsFastForward = false;
public boolean myPresendInvalid = false;
public boolean mPttCompressFinish = false;
public boolean bEnableEnc = false;
public boolean isQzonePic = false;
private String mKey = null;
/* compiled from: P */
/* loaded from: classes18.dex */
public static class AppInfo {
public String packName;
public String sourceIconBig;
public String sourceIconSmall;
public String sourceName;
public String sourceUrl;
public int status;
}
/* compiled from: P */
/* loaded from: classes18.dex */
public static class AppShare {
public long mAppShareID;
public String mShareUrl;
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("mAppShareID:" + this.mAppShareID);
sb.append(",mShareUrl:" + this.mShareUrl);
return sb.toString();
}
}
/* compiled from: P */
/* loaded from: classes18.dex */
public static class PicDownExtraInfo {
public URLDrawableHandler mHandler;
public int mStartDownOffset;
public String mUrlFromMsg;
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("mUrlFromMsg:" + this.mUrlFromMsg);
sb.append(",mStartDownOffset:" + this.mStartDownOffset);
return sb.toString();
}
}
/* compiled from: P */
/* loaded from: classes18.dex */
public static class PicUpExtraInfo {
public boolean mIsRaw;
public boolean mIsShareAppPic;
public AppShare mShareAppInfo;
public int mUinType;
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("mUinType:" + this.mUinType);
sb.append(",mIsRaw:" + this.mIsRaw);
sb.append(",mIsShareAppPic:" + this.mIsShareAppPic);
sb.append(",mShareAppInfo:{" + this.mShareAppInfo + "}");
return sb.toString();
}
}
/* compiled from: P */
/* loaded from: classes18.dex */
public static class PttDownExtraInfo {
public int mFromType;
public int mLayer;
public PttDownExtraInfo(int i2, int i3) {
this.mFromType = i2;
this.mLayer = i3;
}
}
/* compiled from: P */
/* loaded from: classes18.dex */
public static class ShareExtraInfo {
public long appId;
public AppInfo appInfo;
public String audioUrl;
public boolean enableServerSendMsg;
public int forwardType;
public String imageUrl;
public int imageUrlStatus;
public String pkgName;
public int serviceType;
public int shortUrlStatus;
public String summary;
public String targetUrl;
public String title;
}
public String getKey() {
String str = this.mKey;
if (str == null) {
return this.mPeerUin + "_" + this.mFileType + "_" + this.mUniseq + "_" + this.mSubMsgId;
}
return str;
}
public String getKeyForTransfer() {
return this.mPeerUin + this.mUniseq;
}
public String toString() {
StringBuilder sb = new StringBuilder("TransferRequest\n");
sb.append("mUniseq=" + this.mUniseq);
sb.append(",mMd5=" + this.mMd5);
sb.append(",mIsIp=" + this.mIsUp);
sb.append(",mUinType=" + this.mUinType);
sb.append(",mFileType=" + this.mFileType);
sb.append(",mSelfUin=" + this.mSelfUin);
sb.append(",mPeerUin=" + this.mPeerUin);
sb.append(",mSecondId=" + this.mSecondId);
sb.append(",mServerPath=" + this.mServerPath);
sb.append(",mLocalPath=" + this.mLocalPath);
sb.append(",mBusiType=" + this.mBusiType);
sb.append(",mGroupFileID=" + this.mGroupFileID);
sb.append(",mExtraObj={" + this.mExtraObj + "}");
StringBuilder sb2 = new StringBuilder();
sb2.append(",mPrioty=");
sb2.append(this.mPrioty);
sb.append(sb2.toString());
//sb.append(",mLogicCallBack=" + this.mUpCallBack);
sb.append(",bEnableEnc=" + this.bEnableEnc);
sb.append(",isQzonePic=" + this.isQzonePic);
sb.append(",pcmForVadPath=" + this.pcmForVadPath);
return sb.toString();
}
}

View File

@ -0,0 +1,11 @@
package com.tencent.mobileqq.transfile;
public class TransferResult {
public static final int RESULT_FAIL = -1;
public static final int RESULT_NOT_SET = -2;
public static final int RESULT_OK = 0;
public long mErrCode;
public String mErrDesc;
public TransferRequest mOrigReq;
public int mResult = -2;
}

View File

@ -0,0 +1,37 @@
package com.tencent.mobileqq.transfile.api;
import com.tencent.mobileqq.transfile.TransferRequest;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import mqq.app.api.IRuntimeService;
public interface ITransFileController extends IRuntimeService {
// void addHandle(TransProcessorHandler transProcessorHandler);
//@Deprecated
//void addProcessor(String str, IHttpCommunicatorListener iHttpCommunicatorListener);
//boolean containsProcessor(String str, long j2);
//IHttpCommunicatorListener findProcessor(String str);
//IHttpCommunicatorListener findProcessor(String str, long j2);
//TransFileControllerBusHelper getBusHelper();
//ConcurrentHashMap<String, IHttpCommunicatorListener> getProcessMap();
AtomicBoolean isWorking();
// void removeHandle(TransProcessorHandler transProcessorHandler);
// boolean removeProcessor(String str);
// void removeProcessorByPeerUin(String str, int i2);
// void stop(TransferRequest transferRequest);
boolean transferAsync(TransferRequest transferRequest);
}

View File

@ -0,0 +1,8 @@
package com.tencent.mobileqq.transfile.api.impl;
import com.tencent.mobileqq.transfile.BaseTransFileController;
import com.tencent.mobileqq.transfile.api.ITransFileController;
public class TransFileControllerImpl extends BaseTransFileController implements ITransFileController {
}

View File

@ -0,0 +1,4 @@
package com.tencent.mobileqq.transfile.protohandler;
public class GroupPicUpHandler {
}

View File

@ -0,0 +1,60 @@
package com.tencent.qphone.base.remote;
import java.util.HashMap;
public class SimpleAccount {
public static final String _ISLOGINED = "_isLogined";
public static final String _LOGINPROCESS = "_loginedProcess";
public static final String _LOGINTIME = "_loginTime";
public static final String _UIN = "_uin";
public boolean containsKey(String str) {
return false;
}
public boolean equals(Object obj) {
return false;
}
public String getAttribute(String str, String str2) {
return str2;
}
public HashMap<String, String> getAttributes() {
return null;
}
public String getLoginProcess() {
return "";
}
public String getUin() {
return "";
}
public boolean isLogined() {
return false;
}
public String removeAttribute(String str) {
return "";
}
public void setAttribute(String str, String str2) {
throw new RuntimeException("key found space ");
}
public void setLoginProcess(String str) {
}
public void setUin(String str) {
}
public String toStoreString() {
return "";
}
public String toString() {
return "";
}
}

View File

@ -0,0 +1,15 @@
package com.tencent.qphone.base.util;
import com.tencent.mobileqq.qfix.ApplicationDelegate;
public abstract class BaseApplication extends ApplicationDelegate {
public static BaseApplication getContext() {
throw new UnsupportedOperationException();
}
public abstract int getAppId();
public abstract int getNTCoreVersion();
public abstract String getQua();
}

View File

@ -0,0 +1,7 @@
package com.tencent.qqnt.kernel.api;
import mqq.app.api.IRuntimeService;
public interface IKernelService extends IRuntimeService {
}

View File

@ -0,0 +1,8 @@
package com.tencent.qqnt.kernel.api.impl;
import com.tencent.qqnt.kernel.nativeinterface.IKernelMsgListener;
public class MsgService {
public void addMsgListener(IKernelMsgListener listener) {
}
}

View File

@ -0,0 +1,9 @@
package com.tencent.qqnt.kernel.nativeinterface;
/* loaded from: classes2.dex */
public enum ATTYPE {
UNKNOWN_AT_TYPE,
AT_EXPLICIT_USER,
AT_ROLE_GROUP,
AT_GUILD
}

View File

@ -0,0 +1,52 @@
package com.tencent.qqnt.kernel.nativeinterface;
public final class AVRecordElement {
Integer extraType;
boolean hasRead;
int mainType;
String text;
long time;
int type;
public AVRecordElement() {
this.text = "";
}
public Integer getExtraType() {
return this.extraType;
}
public boolean getHasRead() {
return this.hasRead;
}
public int getMainType() {
return this.mainType;
}
public String getText() {
return this.text;
}
public long getTime() {
return this.time;
}
public int getType() {
return this.type;
}
public String toString() {
return "AVRecordElement{type=" + this.type + ",time=" + this.time + ",text=" + this.text + ",mainType=" + this.mainType + ",hasRead=" + this.hasRead + ",extraType=" + this.extraType + ",}";
}
public AVRecordElement(int i2, long j2, String str, int i3, boolean z, Integer num) {
this.text = "";
this.type = i2;
this.time = j2;
this.text = str;
this.mainType = i3;
this.hasRead = z;
this.extraType = num;
}
}

View File

@ -0,0 +1,41 @@
package com.tencent.qqnt.kernel.nativeinterface;
import java.io.Serializable;
public final class AioOperateGrayTipElement implements Serializable {
String fromGrpCodeOfTmpChat;
int operateType;
String peerUid;
long serialVersionUID;
public AioOperateGrayTipElement() {
this.serialVersionUID = 1L;
this.peerUid = "";
this.fromGrpCodeOfTmpChat = "";
}
public String getFromGrpCodeOfTmpChat() {
return this.fromGrpCodeOfTmpChat;
}
public int getOperateType() {
return this.operateType;
}
public String getPeerUid() {
return this.peerUid;
}
public String toString() {
return "AioOperateGrayTipElement{operateType=" + this.operateType + ",peerUid=" + this.peerUid + ",fromGrpCodeOfTmpChat=" + this.fromGrpCodeOfTmpChat + ",}";
}
public AioOperateGrayTipElement(int i2, String str, String str2) {
this.serialVersionUID = 1L;
this.peerUid = "";
this.fromGrpCodeOfTmpChat = "";
this.operateType = i2;
this.peerUid = str;
this.fromGrpCodeOfTmpChat = str2;
}
}

View File

@ -0,0 +1,62 @@
package com.tencent.qqnt.kernel.nativeinterface;
public final class AnonymousExtInfo {
int anonymousFlag;
byte[] anonymousId;
String anonymousNick;
long bubbleId;
long expireTime;
long headPicIndex;
String rankColor;
public AnonymousExtInfo() {
this.anonymousId = new byte[0];
this.anonymousNick = "";
this.rankColor = "";
}
public int getAnonymousFlag() {
return this.anonymousFlag;
}
public byte[] getAnonymousId() {
return this.anonymousId;
}
public String getAnonymousNick() {
return this.anonymousNick;
}
public long getBubbleId() {
return this.bubbleId;
}
public long getExpireTime() {
return this.expireTime;
}
public long getHeadPicIndex() {
return this.headPicIndex;
}
public String getRankColor() {
return this.rankColor;
}
public String toString() {
return "AnonymousExtInfo{anonymousFlag=" + this.anonymousFlag + ",anonymousId=" + this.anonymousId + ",anonymousNick=" + this.anonymousNick + ",headPicIndex=" + this.headPicIndex + ",expireTime=" + this.expireTime + ",bubbleId=" + this.bubbleId + ",rankColor=" + this.rankColor + ",}";
}
public AnonymousExtInfo(int i2, byte[] bArr, String str, long j2, long j3, long j4, String str2) {
this.anonymousId = new byte[0];
this.anonymousNick = "";
this.rankColor = "";
this.anonymousFlag = i2;
this.anonymousId = bArr;
this.anonymousNick = str;
this.headPicIndex = j2;
this.expireTime = j3;
this.bubbleId = j4;
this.rankColor = str2;
}
}

View File

@ -0,0 +1,34 @@
package com.tencent.qqnt.kernel.nativeinterface;
public final class ArkElement {
String bytesData;
LinkInfo linkInfo;
Integer subElementType;
public ArkElement() {
this.bytesData = "";
}
public String getBytesData() {
return this.bytesData;
}
public LinkInfo getLinkInfo() {
return this.linkInfo;
}
public Integer getSubElementType() {
return this.subElementType;
}
public String toString() {
return "ArkElement{bytesData=" + this.bytesData + ",linkInfo=" + this.linkInfo + ",subElementType=" + this.subElementType + ",}";
}
public ArkElement(String str, LinkInfo linkInfo, Integer num) {
this.bytesData = "";
this.bytesData = str;
this.linkInfo = linkInfo;
this.subElementType = num;
}
}

View File

@ -0,0 +1,35 @@
package com.tencent.qqnt.kernel.nativeinterface;
/* loaded from: classes2.dex */
public final class BasicPointInfo {
byte[] headUrl;
long readSeqNo;
long seqNo;
public BasicPointInfo() {
this.headUrl = new byte[0];
}
public byte[] getHeadUrl() {
return this.headUrl;
}
public long getReadSeqNo() {
return this.readSeqNo;
}
public long getSeqNo() {
return this.seqNo;
}
public String toString() {
return "BasicPointInfo{headUrl=" + this.headUrl + ",seqNo=" + this.seqNo + ",readSeqNo=" + this.readSeqNo + ",}";
}
public BasicPointInfo(byte[] bArr, long j2, long j3) {
this.headUrl = new byte[0];
this.headUrl = bArr;
this.seqNo = j2;
this.readSeqNo = j3;
}
}

View File

@ -0,0 +1,9 @@
package com.tencent.qqnt.kernel.nativeinterface;
/* compiled from: P */
/* loaded from: classes2.dex */
public enum BeatTypeEnum {
KRECOVERY,
KFOLDNOTSPREAD,
KFOLDCANSPREAD
}

View File

@ -0,0 +1,39 @@
package com.tencent.qqnt.kernel.nativeinterface;
import java.io.Serializable;
public final class BlockGrayTipElement implements Serializable {
boolean block;
boolean isBuddy;
String peerUid;
long serialVersionUID;
public BlockGrayTipElement() {
this.serialVersionUID = 1L;
this.peerUid = "";
}
public boolean getBlock() {
return this.block;
}
public boolean getIsBuddy() {
return this.isBuddy;
}
public String getPeerUid() {
return this.peerUid;
}
public String toString() {
return "BlockGrayTipElement{peerUid=" + this.peerUid + ",block=" + this.block + ",isBuddy=" + this.isBuddy + ",}";
}
public BlockGrayTipElement(String str, boolean z, boolean z2) {
this.serialVersionUID = 1L;
this.peerUid = "";
this.peerUid = str;
this.block = z;
this.isBuddy = z2;
}
}

Some files were not shown because too many files have changed in this diff Show More