Compare commits
110 Commits
master
...
de28107287
| Author | SHA1 | Date | |
|---|---|---|---|
|
de28107287
|
|||
|
3f6fcc0106
|
|||
|
71119ac4c4
|
|||
|
c91eaaf1a5
|
|||
|
77802d3dc9
|
|||
|
8669a2c9ef
|
|||
|
d589df860e
|
|||
|
db25ab4e5f
|
|||
|
e855a414a4
|
|||
|
2823788765
|
|||
|
fc4baca21d
|
|||
|
e188d743f0
|
|||
|
f7dd806885
|
|||
|
326e777b7f
|
|||
|
0eb0667d2a
|
|||
|
cd97f6156f
|
|||
|
3979027471
|
|||
|
d3f3fba413
|
|||
|
7b77069744
|
|||
|
585a261bb8
|
|||
|
2631c22e52
|
|||
|
af3edb1d30
|
|||
|
ba72ef2759
|
|||
|
2639d88492
|
|||
|
04812cf56b
|
|||
|
112e30804e
|
|||
|
7e5554cfcd
|
|||
|
7e91f267e3
|
|||
|
21bb9a729f
|
|||
|
3b4b6b4e8e
|
|||
|
253d186fdf
|
|||
|
cead8fe91e
|
|||
|
c602ce0726
|
|||
|
4dfb500370
|
|||
|
93b22ea14b
|
|||
|
c9363ee34b
|
|||
|
e9232631de
|
|||
|
402965503f
|
|||
|
7653028241
|
|||
|
f0ef28bd19
|
|||
|
e0485ecc32
|
|||
|
893131fe02
|
|||
|
0e5b6d7f98
|
|||
|
310214fc20
|
|||
|
18c03c194b
|
|||
|
b115d3d598
|
|||
|
da4c58b631
|
|||
|
086b588445
|
|||
|
3f325126f1
|
|||
|
1b350eb22a
|
|||
|
167df010a9
|
|||
|
47647217f1
|
|||
|
5839f1d394
|
|||
|
21264d8ff7
|
|||
|
0e592c1600
|
|||
|
2f0c4b6a97
|
|||
|
4ba02420ed
|
|||
|
3a6fb2a6f0
|
|||
|
0c6baba099
|
|||
|
35a421472d
|
|||
|
6732eb6a22
|
|||
|
f5cfbd7296
|
|||
|
627a32c7a3
|
|||
|
5246715d78
|
|||
|
60ffc569a5
|
|||
|
a3800bfad6
|
|||
|
454108d871
|
|||
|
cb6fe19033
|
|||
|
3d69514a39
|
|||
|
96159e5b80
|
|||
|
9b3b391fef
|
|||
|
3286ed2934
|
|||
|
c9c0debb2b
|
|||
|
c1879dfdc8
|
|||
|
640686296e
|
|||
|
a3b1241fca
|
|||
|
596ad2ccbe
|
|||
|
1607897fc9
|
|||
|
9d6094173d
|
|||
|
5efbf660c6
|
|||
|
8fedafd261
|
|||
|
1d4f317bb5
|
|||
|
a85e561863
|
|||
|
d84427b039
|
|||
|
3338522d40
|
|||
|
3a91e834b7
|
|||
|
3d8bc944e3
|
|||
|
c596767c37
|
|||
|
b2cbea5383
|
|||
|
2e0efd1cb9
|
|||
|
61d229b100
|
|||
|
32d19ae291
|
|||
|
c8f072c930
|
|||
|
4d047247f1
|
|||
|
b11ae055c3
|
|||
|
b2e7ecc92c
|
|||
|
23893a4ac1
|
|||
|
54a7625c1b
|
|||
|
8ed9d6942a
|
|||
|
92cd20f36f
|
|||
|
8b200d14c6
|
|||
|
58117cc6f6
|
|||
|
e777f832e6
|
|||
|
0e24b46525
|
|||
|
cba2e83074
|
|||
|
cda1f455b9
|
|||
|
96cc7c221f
|
|||
|
f81f26a5cb
|
|||
|
4cc1c0f68b
|
|||
|
1bd81cdf6c
|
6
.gitignore
vendored
6
.gitignore
vendored
@@ -17,6 +17,10 @@ build/
|
||||
# Local configuration file (sdk path, etc)
|
||||
local.properties
|
||||
|
||||
# Key Store
|
||||
keystore.properties
|
||||
*.jks
|
||||
|
||||
# Eclipse project files
|
||||
.classpath
|
||||
.project
|
||||
@@ -43,3 +47,5 @@ _sandbox
|
||||
|
||||
# Android Studio captures folder
|
||||
captures/
|
||||
|
||||
.kotlin/
|
||||
|
||||
123
.idea/codeStyles/Project.xml
generated
Normal file
123
.idea/codeStyles/Project.xml
generated
Normal file
@@ -0,0 +1,123 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<code_scheme name="Project" version="173">
|
||||
<JetCodeStyleSettings>
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
</JetCodeStyleSettings>
|
||||
<codeStyleSettings language="XML">
|
||||
<option name="FORCE_REARRANGE_MODE" value="1" />
|
||||
<indentOptions>
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
</indentOptions>
|
||||
<arrangement>
|
||||
<rules>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>xmlns:android</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>xmlns:.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:id</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:name</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>name</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>style</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>ANDROID_ATTRIBUTE_ORDER</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>.*</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
</rules>
|
||||
</arrangement>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="kotlin">
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
</codeStyleSettings>
|
||||
</code_scheme>
|
||||
</component>
|
||||
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
@@ -0,0 +1,5 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||
</state>
|
||||
</component>
|
||||
3
app/.gitignore
vendored
3
app/.gitignore
vendored
@@ -1,2 +1,3 @@
|
||||
/build
|
||||
/src/main/res/raw/dependencies.json
|
||||
/src/main/res/raw/dependencies.json
|
||||
/release/
|
||||
|
||||
@@ -1,33 +1,72 @@
|
||||
import com.android.build.gradle.internal.api.BaseVariantOutputImpl
|
||||
import com.mikepenz.aboutlibraries.plugin.AboutLibrariesTask
|
||||
import java.io.FileInputStream
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneOffset
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.util.Properties
|
||||
|
||||
val localDateTime: LocalDateTime = LocalDateTime.now(ZoneOffset.UTC)
|
||||
val baseVersionCode = 1
|
||||
val baseVersionName = "0.0.0"
|
||||
|
||||
val keystoreProperties = rootProject.file("keystore.properties").run {
|
||||
if (!exists()) {
|
||||
null
|
||||
} else {
|
||||
Properties().apply {
|
||||
load(FileInputStream(this@run))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.androidApplication)
|
||||
alias(libs.plugins.jetbrainsKotlinAndroid)
|
||||
alias(libs.plugins.compose.compiler)
|
||||
alias(libs.plugins.ksp)
|
||||
alias(libs.plugins.aboutlibraries)
|
||||
alias(libs.plugins.hilt)
|
||||
alias(libs.plugins.protobuf)
|
||||
alias(libs.plugins.kotlinxSerialization)
|
||||
alias(libs.plugins.secrets)
|
||||
alias(libs.plugins.room)
|
||||
alias(libs.plugins.parcelize)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "top.fatweb.oxygen.toolbox"
|
||||
compileSdk = 34
|
||||
compileSdk = 35
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "top.fatweb.oxygen.toolbox"
|
||||
minSdk = 21
|
||||
targetSdk = 34
|
||||
versionCode = 1
|
||||
versionName = "1.0.0-SNAPSHOT"
|
||||
minSdk = 24
|
||||
targetSdk = 35
|
||||
versionCode = baseVersionCode
|
||||
versionName = "$baseVersionName${
|
||||
if (baseVersionCode % 100 != 0) ".${
|
||||
localDateTime.format(
|
||||
DateTimeFormatter.ofPattern("yyMMdd")
|
||||
)
|
||||
}" else ""
|
||||
}"
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables {
|
||||
useSupportLibrary = true
|
||||
}
|
||||
|
||||
// Required when setting minSdkVersion to 20 or lower
|
||||
multiDexEnabled = true
|
||||
setProperty("archivesBaseName", "$applicationId-v$versionName($versionCode)")
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
keystoreProperties?.let {
|
||||
create("release") {
|
||||
storeFile = rootProject.file(it["storeFile"] as String)
|
||||
storePassword = it["storePassword"] as String
|
||||
keyAlias = it["keyAlias"] as String
|
||||
keyPassword = it["keyPassword"] as String
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
@@ -38,13 +77,7 @@ android {
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
android.applicationVariants.all {
|
||||
outputs.all {
|
||||
(this as BaseVariantOutputImpl).outputFileName =
|
||||
"${project.name}_${defaultConfig.versionName}-${defaultConfig.versionCode}_${buildType.name}.apk"
|
||||
signingConfig = signingConfigs.findByName("release")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,9 +93,7 @@ android {
|
||||
}
|
||||
buildFeatures {
|
||||
compose = true
|
||||
}
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion = "1.5.10"
|
||||
buildConfig = true
|
||||
}
|
||||
packaging {
|
||||
resources {
|
||||
@@ -101,11 +132,7 @@ aboutLibraries {
|
||||
registerAndroidTasks = false
|
||||
configPath = "libConfig"
|
||||
outputFileName = "dependencies.json"
|
||||
exclusionPatterns = listOf(
|
||||
Regex("androidx.*"),
|
||||
Regex("org.jetbrains.*"),
|
||||
Regex("com.google.guava:listenablefuture")
|
||||
).map { it.toPattern() }
|
||||
exclusionPatterns = listOf<Regex>().map { it.toPattern() }
|
||||
}
|
||||
|
||||
task("exportLibrariesToJson", AboutLibrariesTask::class) {
|
||||
@@ -116,12 +143,26 @@ task("exportLibrariesToJson", AboutLibrariesTask::class) {
|
||||
afterEvaluate {
|
||||
tasks.findByName("preBuild")?.dependsOn(tasks.findByName("exportLibrariesToJson"))
|
||||
tasks.findByName("kspDebugKotlin")?.dependsOn(tasks.findByName("generateDebugProto"))
|
||||
tasks.findByName("kspReleaseKotlin")?.dependsOn(tasks.findByName("generateReleaseProto"))
|
||||
}
|
||||
|
||||
secrets {
|
||||
defaultPropertiesFileName = "secrets.defaults.properties"
|
||||
}
|
||||
|
||||
ksp {
|
||||
arg("room.generateKotlin", "true")
|
||||
}
|
||||
|
||||
room {
|
||||
schemaDirectory("$projectDir/schemas")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
coreLibraryDesugaring(libs.desugar.jdk.libs)
|
||||
|
||||
testImplementation(libs.junit)
|
||||
testImplementation(libs.paging.common)
|
||||
|
||||
androidTestImplementation(platform(libs.androidx.compose.bom))
|
||||
androidTestImplementation(libs.androidx.ui.test.junit4)
|
||||
@@ -142,6 +183,7 @@ dependencies {
|
||||
implementation(libs.material.icons.core)
|
||||
implementation(libs.material.icons.extended)
|
||||
implementation(libs.material3.window.size)
|
||||
implementation(libs.animation.graphics)
|
||||
implementation(libs.androidx.core.ktx)
|
||||
implementation(libs.androidx.activity.compose)
|
||||
implementation(libs.androidx.appcompat)
|
||||
@@ -150,6 +192,7 @@ dependencies {
|
||||
implementation(libs.lifecycle.viewmodel.compose)
|
||||
implementation(libs.androidx.core.splashscreen)
|
||||
implementation(libs.hilt.android)
|
||||
implementation(libs.androidx.web.kit)
|
||||
ksp(libs.hilt.android)
|
||||
ksp(libs.hilt.compiler)
|
||||
implementation(libs.coil.kt)
|
||||
@@ -160,4 +203,17 @@ dependencies {
|
||||
implementation(libs.protobuf.kotlin.lite)
|
||||
implementation(libs.androidx.navigation.compose)
|
||||
implementation(libs.androidx.hilt.navigation.compose)
|
||||
implementation(libs.kotlinx.serialization.json)
|
||||
implementation(libs.retrofit.core)
|
||||
implementation(libs.retrofit.kotlin.serialization)
|
||||
implementation(libs.okhttp.logging)
|
||||
implementation(libs.paging.runtime)
|
||||
implementation(libs.paging.compose)
|
||||
implementation(libs.androidsvg.aar)
|
||||
implementation(libs.compose.webview)
|
||||
ksp(libs.room.compiler)
|
||||
implementation(libs.room.runtime)
|
||||
implementation(libs.room.ktx)
|
||||
implementation(libs.timber)
|
||||
implementation(libs.compose.shimmer)
|
||||
}
|
||||
15
app/proguard-rules.pro
vendored
15
app/proguard-rules.pro
vendored
@@ -12,10 +12,23 @@
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Keep DataStore fields
|
||||
-keepclassmembers class * extends com.google.protobuf.GeneratedMessageLite* {
|
||||
<fields>;
|
||||
}
|
||||
|
||||
# Keep SerialName annotation
|
||||
-keepclassmembers class * {
|
||||
@kotlinx.serialization.SerialName <fields>;
|
||||
}
|
||||
|
||||
# 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
|
||||
#-renamesourcefileattribute SourceFile
|
||||
|
||||
-dontwarn kotlinx.serialization.KSerializer
|
||||
-dontwarn kotlinx.serialization.Serializable
|
||||
@@ -0,0 +1,155 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 1,
|
||||
"identityHash": "34c5a37d790e5542a93e0dc27bb3f4f1",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "tools",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `toolId` TEXT NOT NULL, `icon` TEXT NOT NULL, `platform` TEXT NOT NULL, `description` TEXT, `base` TEXT, `authorUsername` TEXT NOT NULL, `authorNickname` TEXT NOT NULL, `authorAvatar` TEXT NOT NULL, `ver` TEXT NOT NULL, `keywords` TEXT NOT NULL, `categories` TEXT NOT NULL, `source` TEXT, `dist` TEXT, `entryPoint` TEXT NOT NULL, `createTime` TEXT NOT NULL, `updateTime` TEXT NOT NULL, `isStar` INTEGER NOT NULL, `isInstalled` INTEGER NOT NULL, `upgrade` TEXT DEFAULT NULL, PRIMARY KEY(`id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "toolId",
|
||||
"columnName": "toolId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "icon",
|
||||
"columnName": "icon",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "platform",
|
||||
"columnName": "platform",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "description",
|
||||
"columnName": "description",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "base",
|
||||
"columnName": "base",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "authorUsername",
|
||||
"columnName": "authorUsername",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "authorNickname",
|
||||
"columnName": "authorNickname",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "authorAvatar",
|
||||
"columnName": "authorAvatar",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "ver",
|
||||
"columnName": "ver",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "keywords",
|
||||
"columnName": "keywords",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "categories",
|
||||
"columnName": "categories",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "source",
|
||||
"columnName": "source",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "dist",
|
||||
"columnName": "dist",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "entryPoint",
|
||||
"columnName": "entryPoint",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "createTime",
|
||||
"columnName": "createTime",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "updateTime",
|
||||
"columnName": "updateTime",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isStar",
|
||||
"columnName": "isStar",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isInstalled",
|
||||
"columnName": "isInstalled",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "upgrade",
|
||||
"columnName": "upgrade",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "NULL"
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
}
|
||||
],
|
||||
"views": [],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '34c5a37d790e5542a93e0dc27bb3f4f1')"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,10 @@
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="32"
|
||||
tools:ignore="ScopedStorage" />
|
||||
|
||||
<application
|
||||
android:name=".OxygenApplication"
|
||||
@@ -27,6 +31,13 @@
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:scheme="oxygen" android:host="opentool"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
|
||||
365
app/src/main/assets/template/global-variables-dark.css
Normal file
365
app/src/main/assets/template/global-variables-dark.css
Normal file
@@ -0,0 +1,365 @@
|
||||
:root {
|
||||
--blue: #1677FF;
|
||||
--blue1: #111a2c;
|
||||
--blue2: #112545;
|
||||
--blue3: #15325b;
|
||||
--blue4: #15417e;
|
||||
--blue5: #1554ad;
|
||||
--blue6: #1668dc;
|
||||
--blue7: #3c89e8;
|
||||
--blue8: #65a9f3;
|
||||
--blue9: #8dc5f8;
|
||||
--blue10: #b7dcfa;
|
||||
--purple: #722ED1;
|
||||
--purple1: #1a1325;
|
||||
--purple2: #24163a;
|
||||
--purple3: #301c4d;
|
||||
--purple4: #3e2069;
|
||||
--purple5: #51258f;
|
||||
--purple6: #642ab5;
|
||||
--purple7: #854eca;
|
||||
--purple8: #ab7ae0;
|
||||
--purple9: #cda8f0;
|
||||
--purple10: #ebd7fa;
|
||||
--cyan: #13C2C2;
|
||||
--cyan1: #112123;
|
||||
--cyan2: #113536;
|
||||
--cyan3: #144848;
|
||||
--cyan4: #146262;
|
||||
--cyan5: #138585;
|
||||
--cyan6: #13a8a8;
|
||||
--cyan7: #33bcb7;
|
||||
--cyan8: #58d1c9;
|
||||
--cyan9: #84e2d8;
|
||||
--cyan10: #b2f1e8;
|
||||
--green: #52C41A;
|
||||
--green1: #162312;
|
||||
--green2: #1d3712;
|
||||
--green3: #274916;
|
||||
--green4: #306317;
|
||||
--green5: #3c8618;
|
||||
--green6: #49aa19;
|
||||
--green7: #6abe39;
|
||||
--green8: #8fd460;
|
||||
--green9: #b2e58b;
|
||||
--green10: #d5f2bb;
|
||||
--magenta: #EB2F96;
|
||||
--magenta1: #291321;
|
||||
--magenta2: #40162f;
|
||||
--magenta3: #551c3b;
|
||||
--magenta4: #75204f;
|
||||
--magenta5: #a02669;
|
||||
--magenta6: #cb2b83;
|
||||
--magenta7: #e0529c;
|
||||
--magenta8: #f37fb7;
|
||||
--magenta9: #f8a8cc;
|
||||
--magenta10: #fad2e3;
|
||||
--pink: #EB2F96;
|
||||
--pink1: #291321;
|
||||
--pink2: #40162f;
|
||||
--pink3: #551c3b;
|
||||
--pink4: #75204f;
|
||||
--pink5: #a02669;
|
||||
--pink6: #cb2b83;
|
||||
--pink7: #e0529c;
|
||||
--pink8: #f37fb7;
|
||||
--pink9: #f8a8cc;
|
||||
--pink10: #fad2e3;
|
||||
--red: #F5222D;
|
||||
--red1: #2a1215;
|
||||
--red2: #431418;
|
||||
--red3: #58181c;
|
||||
--red4: #791a1f;
|
||||
--red5: #a61d24;
|
||||
--red6: #d32029;
|
||||
--red7: #e84749;
|
||||
--red8: #f37370;
|
||||
--red9: #f89f9a;
|
||||
--red10: #fac8c3;
|
||||
--orange: #FA8C16;
|
||||
--orange1: #2b1d11;
|
||||
--orange2: #442a11;
|
||||
--orange3: #593815;
|
||||
--orange4: #7c4a15;
|
||||
--orange5: #aa6215;
|
||||
--orange6: #d87a16;
|
||||
--orange7: #e89a3c;
|
||||
--orange8: #f3b765;
|
||||
--orange9: #f8cf8d;
|
||||
--orange10: #fae3b7;
|
||||
--yellow: #FADB14;
|
||||
--yellow1: #2b2611;
|
||||
--yellow2: #443b11;
|
||||
--yellow3: #595014;
|
||||
--yellow4: #7c6e14;
|
||||
--yellow5: #aa9514;
|
||||
--yellow6: #d8bd14;
|
||||
--yellow7: #e8d639;
|
||||
--yellow8: #f3ea62;
|
||||
--yellow9: #f8f48b;
|
||||
--yellow10: #fafab5;
|
||||
--volcano: #FA541C;
|
||||
--volcano1: #2b1611;
|
||||
--volcano2: #441d12;
|
||||
--volcano3: #592716;
|
||||
--volcano4: #7c3118;
|
||||
--volcano5: #aa3e19;
|
||||
--volcano6: #d84a1b;
|
||||
--volcano7: #e87040;
|
||||
--volcano8: #f3956a;
|
||||
--volcano9: #f8b692;
|
||||
--volcano10: #fad4bc;
|
||||
--geekblue: #2F54EB;
|
||||
--geekblue1: #131629;
|
||||
--geekblue2: #161d40;
|
||||
--geekblue3: #1c2755;
|
||||
--geekblue4: #203175;
|
||||
--geekblue5: #263ea0;
|
||||
--geekblue6: #2b4acb;
|
||||
--geekblue7: #5273e0;
|
||||
--geekblue8: #7f9ef3;
|
||||
--geekblue9: #a8c1f8;
|
||||
--geekblue10: #d2e0fa;
|
||||
--gold: #FAAD14;
|
||||
--gold1: #2b2111;
|
||||
--gold2: #443111;
|
||||
--gold3: #594214;
|
||||
--gold4: #7c5914;
|
||||
--gold5: #aa7714;
|
||||
--gold6: #d89614;
|
||||
--gold7: #e8b339;
|
||||
--gold8: #f3cc62;
|
||||
--gold9: #f8df8b;
|
||||
--gold10: #faedb5;
|
||||
--lime: #A0D911;
|
||||
--lime1: #1f2611;
|
||||
--lime2: #2e3c10;
|
||||
--lime3: #3e4f13;
|
||||
--lime4: #536d13;
|
||||
--lime5: #6f9412;
|
||||
--lime6: #8bbb11;
|
||||
--lime7: #a9d134;
|
||||
--lime8: #c9e75d;
|
||||
--lime9: #e4f88b;
|
||||
--lime10: #f0fab5;
|
||||
--colorPrimary: #453fa2;
|
||||
--colorSuccess: #49aa19;
|
||||
--colorWarning: #d89614;
|
||||
--colorError: #dc4446;
|
||||
--colorInfo: #1668dc;
|
||||
--colorLink: #1668dc;
|
||||
--colorTextBase: #fff;
|
||||
--colorBgBase: #000;
|
||||
--fontFamily: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial,
|
||||
'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
|
||||
'Noto Color Emoji';
|
||||
--fontFamilyCode: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace;
|
||||
--fontSize: 14;
|
||||
--lineWidth: 1;
|
||||
--lineType: solid;
|
||||
--motionUnit: 0.1;
|
||||
--motionBase: 0;
|
||||
--motionEaseOutCirc: cubic-bezier(0.08, 0.82, 0.17, 1);
|
||||
--motionEaseInOutCirc: cubic-bezier(0.78, 0.14, 0.15, 0.86);
|
||||
--motionEaseOut: cubic-bezier(0.215, 0.61, 0.355, 1);
|
||||
--motionEaseInOut: cubic-bezier(0.645, 0.045, 0.355, 1);
|
||||
--motionEaseOutBack: cubic-bezier(0.12, 0.4, 0.29, 1.46);
|
||||
--motionEaseInBack: cubic-bezier(0.71, -0.46, 0.88, 0.6);
|
||||
--motionEaseInQuint: cubic-bezier(0.755, 0.05, 0.855, 0.06);
|
||||
--motionEaseOutQuint: cubic-bezier(0.23, 1, 0.32, 1);
|
||||
--borderRadius: 6;
|
||||
--sizeUnit: 4;
|
||||
--sizeStep: 4;
|
||||
--sizePopupArrow: 16;
|
||||
--controlHeight: 32;
|
||||
--zIndexBase: 0;
|
||||
--zIndexPopupBase: 1000;
|
||||
--opacityImage: 1;
|
||||
--colorLinkHover: #4E47BB;
|
||||
--colorText: rgba(255, 255, 255, 0.85);
|
||||
--colorTextSecondary: rgba(255, 255, 255, 0.65);
|
||||
--colorTextTertiary: rgba(255, 255, 255, 0.45);
|
||||
--colorTextQuaternary: rgba(255, 255, 255, 0.25);
|
||||
--colorFill: rgba(255, 255, 255, 0.18);
|
||||
--colorFillSecondary: rgba(255, 255, 255, 0.12);
|
||||
--colorFillTertiary: rgba(255, 255, 255, 0.08);
|
||||
--colorFillQuaternary: rgba(255, 255, 255, 0.04);
|
||||
--colorBgSolid: rgba(255, 255, 255, 0.95);
|
||||
--colorBgSolidHover: rgb(255, 255, 255);
|
||||
--colorBgSolidActive: rgba(255, 255, 255, 0.9);
|
||||
--colorBgLayout: #000000;
|
||||
--colorBgContainer: #141414;
|
||||
--colorBgElevated: #1f1f1f;
|
||||
--colorBgSpotlight: #424242;
|
||||
--colorBgBlur: rgba(255, 255, 255, 0.04);
|
||||
--colorBorder: #424242;
|
||||
--colorBorderSecondary: #303030;
|
||||
--colorPrimaryBg: #161622;
|
||||
--colorPrimaryBgHover: #1c1b34;
|
||||
--colorPrimaryBorder: #252346;
|
||||
--colorPrimaryBorderHover: #2e2b5f;
|
||||
--colorPrimaryHover: #6b62b5;
|
||||
--colorPrimaryActive: #3a3581;
|
||||
--colorPrimaryTextHover: #6b62b5;
|
||||
--colorPrimaryText: #453fa2;
|
||||
--colorPrimaryTextActive: #3a3581;
|
||||
--colorSuccessBg: #162312;
|
||||
--colorSuccessBgHover: #1d3712;
|
||||
--colorSuccessBorder: #274916;
|
||||
--colorSuccessBorderHover: #306317;
|
||||
--colorSuccessHover: #306317;
|
||||
--colorSuccessActive: #3c8618;
|
||||
--colorSuccessTextHover: #6abe39;
|
||||
--colorSuccessText: #49aa19;
|
||||
--colorSuccessTextActive: #3c8618;
|
||||
--colorErrorBg: #2c1618;
|
||||
--colorErrorBgHover: #451d1f;
|
||||
--colorErrorBgFilledHover: #441e1f;
|
||||
--colorErrorBgActive: #5b2526;
|
||||
--colorErrorBorder: #5b2526;
|
||||
--colorErrorBorderHover: #7e2e2f;
|
||||
--colorErrorHover: #e86e6b;
|
||||
--colorErrorActive: #ad393a;
|
||||
--colorErrorTextHover: #e86e6b;
|
||||
--colorErrorText: #dc4446;
|
||||
--colorErrorTextActive: #ad393a;
|
||||
--colorWarningBg: #2b2111;
|
||||
--colorWarningBgHover: #443111;
|
||||
--colorWarningBorder: #594214;
|
||||
--colorWarningBorderHover: #7c5914;
|
||||
--colorWarningHover: #7c5914;
|
||||
--colorWarningActive: #aa7714;
|
||||
--colorWarningTextHover: #e8b339;
|
||||
--colorWarningText: #d89614;
|
||||
--colorWarningTextActive: #aa7714;
|
||||
--colorInfoBg: #111a2c;
|
||||
--colorInfoBgHover: #112545;
|
||||
--colorInfoBorder: #15325b;
|
||||
--colorInfoBorderHover: #15417e;
|
||||
--colorInfoHover: #15417e;
|
||||
--colorInfoActive: #1554ad;
|
||||
--colorInfoTextHover: #3c89e8;
|
||||
--colorInfoText: #1668dc;
|
||||
--colorInfoTextActive: #1554ad;
|
||||
--colorLinkActive: #1554ad;
|
||||
--colorBgMask: rgba(0, 0, 0, 0.45);
|
||||
--colorWhite: #fff;
|
||||
--fontSizeSM: 12;
|
||||
--fontSizeLG: 16;
|
||||
--fontSizeXL: 20;
|
||||
--fontSizeHeading1: 38;
|
||||
--fontSizeHeading2: 30;
|
||||
--fontSizeHeading3: 24;
|
||||
--fontSizeHeading4: 20;
|
||||
--fontSizeHeading5: 16;
|
||||
--lineHeight: 1.5714285714285714;
|
||||
--lineHeightLG: 1.5;
|
||||
--lineHeightSM: 1.6666666666666667;
|
||||
--lineHeightHeading1: 1.2105263157894737;
|
||||
--lineHeightHeading2: 1.2666666666666666;
|
||||
--lineHeightHeading3: 1.3333333333333333;
|
||||
--lineHeightHeading4: 1.4;
|
||||
--lineHeightHeading5: 1.5;
|
||||
--sizeXXL: 48;
|
||||
--sizeXL: 32;
|
||||
--sizeLG: 24;
|
||||
--sizeMD: 20;
|
||||
--sizeMS: 16;
|
||||
--size: 16;
|
||||
--sizeSM: 12;
|
||||
--sizeXS: 8;
|
||||
--sizeXXS: 4;
|
||||
--controlHeightSM: 24;
|
||||
--controlHeightXS: 16;
|
||||
--controlHeightLG: 40;
|
||||
--motionDurationFast: 0.1s;
|
||||
--motionDurationMid: 0.2s;
|
||||
--motionDurationSlow: 0.3s;
|
||||
--lineWidthBold: 2;
|
||||
--borderRadiusXS: 2;
|
||||
--borderRadiusSM: 4;
|
||||
--borderRadiusLG: 8;
|
||||
--borderRadiusOuter: 4;
|
||||
--colorFillContent: rgba(255, 255, 255, 0.12);
|
||||
--colorFillContentHover: rgba(255, 255, 255, 0.18);
|
||||
--colorFillAlter: rgba(255, 255, 255, 0.04);
|
||||
--colorBgContainerDisabled: rgba(255, 255, 255, 0.08);
|
||||
--colorBorderBg: #141414;
|
||||
--colorSplit: rgba(253, 253, 253, 0.12);
|
||||
--colorTextPlaceholder: rgba(255, 255, 255, 0.25);
|
||||
--colorTextDisabled: rgba(255, 255, 255, 0.25);
|
||||
--colorTextHeading: rgba(255, 255, 255, 0.85);
|
||||
--colorTextLabel: rgba(255, 255, 255, 0.65);
|
||||
--colorTextDescription: rgba(255, 255, 255, 0.45);
|
||||
--colorTextLightSolid: #fff;
|
||||
--colorHighlight: #dc4446;
|
||||
--colorBgTextHover: rgba(255, 255, 255, 0.12);
|
||||
--colorBgTextActive: rgba(255, 255, 255, 0.18);
|
||||
--colorIcon: rgba(255, 255, 255, 0.45);
|
||||
--colorIconHover: rgba(255, 255, 255, 0.85);
|
||||
--colorErrorOutline: rgba(238, 38, 56, 0.11);
|
||||
--colorWarningOutline: rgba(173, 107, 0, 0.15);
|
||||
--fontSizeIcon: 12;
|
||||
--lineWidthFocus: 3;
|
||||
--controlOutlineWidth: 2;
|
||||
--controlInteractiveSize: 16;
|
||||
--controlItemBgHover: rgba(255, 255, 255, 0.08);
|
||||
--controlItemBgActive: #161622;
|
||||
--controlItemBgActiveHover: #1c1b34;
|
||||
--controlItemBgActiveDisabled: rgba(255, 255, 255, 0.18);
|
||||
--controlOutline: rgba(53, 53, 253, 0.06);
|
||||
--fontWeightStrong: 600;
|
||||
--opacityLoading: 0.65;
|
||||
--linkDecoration: none;
|
||||
--linkHoverDecoration: none;
|
||||
--linkFocusDecoration: none;
|
||||
--controlPaddingHorizontal: 12;
|
||||
--controlPaddingHorizontalSM: 8;
|
||||
--paddingXXS: 4;
|
||||
--paddingXS: 8;
|
||||
--paddingSM: 12;
|
||||
--padding: 16;
|
||||
--paddingMD: 20;
|
||||
--paddingLG: 24;
|
||||
--paddingXL: 32;
|
||||
--paddingContentHorizontalLG: 24;
|
||||
--paddingContentVerticalLG: 16;
|
||||
--paddingContentHorizontal: 16;
|
||||
--paddingContentVertical: 12;
|
||||
--paddingContentHorizontalSM: 16;
|
||||
--paddingContentVerticalSM: 8;
|
||||
--marginXXS: 4;
|
||||
--marginXS: 8;
|
||||
--marginSM: 12;
|
||||
--margin: 16;
|
||||
--marginMD: 20;
|
||||
--marginLG: 24;
|
||||
--marginXL: 32;
|
||||
--marginXXL: 48;
|
||||
--boxShadow: 0 6px 16px 0 rgba(0, 0, 0, 0.08),
|
||||
0 3px 6px -4px rgba(0, 0, 0, 0.12),
|
||||
0 9px 28px 8px rgba(0, 0, 0, 0.05);
|
||||
--boxShadowSecondary: 0 6px 16px 0 rgba(0, 0, 0, 0.08),
|
||||
0 3px 6px -4px rgba(0, 0, 0, 0.12),
|
||||
0 9px 28px 8px rgba(0, 0, 0, 0.05);
|
||||
--boxShadowTertiary: 0 1px 2px 0 rgba(0, 0, 0, 0.03),
|
||||
0 1px 6px -1px rgba(0, 0, 0, 0.02),
|
||||
0 2px 4px 0 rgba(0, 0, 0, 0.02);
|
||||
--screenXS: 480;
|
||||
--screenXSMin: 480;
|
||||
--screenXSMax: 575;
|
||||
--screenSM: 576;
|
||||
--screenSMMin: 576;
|
||||
--screenSMMax: 767;
|
||||
--screenMD: 768;
|
||||
--screenMDMin: 768;
|
||||
--screenMDMax: 991;
|
||||
--screenLG: 992;
|
||||
--screenLGMin: 992;
|
||||
--screenLGMax: 1199;
|
||||
--screenXL: 1200;
|
||||
--screenXLMin: 1200;
|
||||
--screenXLMax: 1599;
|
||||
--screenXXL: 1600;
|
||||
--screenXXLMin: 1600;
|
||||
}
|
||||
518
app/src/main/assets/template/global-variables-dark.js
Normal file
518
app/src/main/assets/template/global-variables-dark.js
Normal file
@@ -0,0 +1,518 @@
|
||||
const globalVariables = {
|
||||
OxygenTheme: {
|
||||
isDarkMode: true,
|
||||
blue: '#1677FF',
|
||||
purple: '#722ED1',
|
||||
cyan: '#13C2C2',
|
||||
green: '#52C41A',
|
||||
magenta: '#EB2F96',
|
||||
pink: '#EB2F96',
|
||||
red: '#F5222D',
|
||||
orange: '#FA8C16',
|
||||
yellow: '#FADB14',
|
||||
volcano: '#FA541C',
|
||||
geekblue: '#2F54EB',
|
||||
gold: '#FAAD14',
|
||||
lime: '#A0D911',
|
||||
colorPrimary: '#453fa2',
|
||||
colorSuccess: '#49aa19',
|
||||
colorWarning: '#d89614',
|
||||
colorError: '#dc4446',
|
||||
colorInfo: '#1668dc',
|
||||
colorLink: '#1668dc',
|
||||
colorTextBase: '#fff',
|
||||
colorBgBase: '#000',
|
||||
fontFamily:
|
||||
"-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial,\n'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',\n'Noto Color Emoji'",
|
||||
fontFamilyCode: "'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace",
|
||||
fontSize: 14,
|
||||
lineWidth: 1,
|
||||
lineType: 'solid',
|
||||
motionUnit: 0.1,
|
||||
motionBase: 0,
|
||||
motionEaseOutCirc: 'cubic-bezier(0.08, 0.82, 0.17, 1)',
|
||||
motionEaseInOutCirc: 'cubic-bezier(0.78, 0.14, 0.15, 0.86)',
|
||||
motionEaseOut: 'cubic-bezier(0.215, 0.61, 0.355, 1)',
|
||||
motionEaseInOut: 'cubic-bezier(0.645, 0.045, 0.355, 1)',
|
||||
motionEaseOutBack: 'cubic-bezier(0.12, 0.4, 0.29, 1.46)',
|
||||
motionEaseInBack: 'cubic-bezier(0.71, -0.46, 0.88, 0.6)',
|
||||
motionEaseInQuint: 'cubic-bezier(0.755, 0.05, 0.855, 0.06)',
|
||||
motionEaseOutQuint: 'cubic-bezier(0.23, 1, 0.32, 1)',
|
||||
borderRadius: 6,
|
||||
sizeUnit: 4,
|
||||
sizeStep: 4,
|
||||
sizePopupArrow: 16,
|
||||
controlHeight: 32,
|
||||
zIndexBase: 0,
|
||||
zIndexPopupBase: 1000,
|
||||
opacityImage: 1,
|
||||
motion: true,
|
||||
colorLinkHover: '#4E47BB',
|
||||
'blue-1': '#111a2c',
|
||||
blue1: '#111a2c',
|
||||
'blue-2': '#112545',
|
||||
blue2: '#112545',
|
||||
'blue-3': '#15325b',
|
||||
blue3: '#15325b',
|
||||
'blue-4': '#15417e',
|
||||
blue4: '#15417e',
|
||||
'blue-5': '#1554ad',
|
||||
blue5: '#1554ad',
|
||||
'blue-6': '#1668dc',
|
||||
blue6: '#1668dc',
|
||||
'blue-7': '#3c89e8',
|
||||
blue7: '#3c89e8',
|
||||
'blue-8': '#65a9f3',
|
||||
blue8: '#65a9f3',
|
||||
'blue-9': '#8dc5f8',
|
||||
blue9: '#8dc5f8',
|
||||
'blue-10': '#b7dcfa',
|
||||
blue10: '#b7dcfa',
|
||||
'purple-1': '#1a1325',
|
||||
purple1: '#1a1325',
|
||||
'purple-2': '#24163a',
|
||||
purple2: '#24163a',
|
||||
'purple-3': '#301c4d',
|
||||
purple3: '#301c4d',
|
||||
'purple-4': '#3e2069',
|
||||
purple4: '#3e2069',
|
||||
'purple-5': '#51258f',
|
||||
purple5: '#51258f',
|
||||
'purple-6': '#642ab5',
|
||||
purple6: '#642ab5',
|
||||
'purple-7': '#854eca',
|
||||
purple7: '#854eca',
|
||||
'purple-8': '#ab7ae0',
|
||||
purple8: '#ab7ae0',
|
||||
'purple-9': '#cda8f0',
|
||||
purple9: '#cda8f0',
|
||||
'purple-10': '#ebd7fa',
|
||||
purple10: '#ebd7fa',
|
||||
'cyan-1': '#112123',
|
||||
cyan1: '#112123',
|
||||
'cyan-2': '#113536',
|
||||
cyan2: '#113536',
|
||||
'cyan-3': '#144848',
|
||||
cyan3: '#144848',
|
||||
'cyan-4': '#146262',
|
||||
cyan4: '#146262',
|
||||
'cyan-5': '#138585',
|
||||
cyan5: '#138585',
|
||||
'cyan-6': '#13a8a8',
|
||||
cyan6: '#13a8a8',
|
||||
'cyan-7': '#33bcb7',
|
||||
cyan7: '#33bcb7',
|
||||
'cyan-8': '#58d1c9',
|
||||
cyan8: '#58d1c9',
|
||||
'cyan-9': '#84e2d8',
|
||||
cyan9: '#84e2d8',
|
||||
'cyan-10': '#b2f1e8',
|
||||
cyan10: '#b2f1e8',
|
||||
'green-1': '#162312',
|
||||
green1: '#162312',
|
||||
'green-2': '#1d3712',
|
||||
green2: '#1d3712',
|
||||
'green-3': '#274916',
|
||||
green3: '#274916',
|
||||
'green-4': '#306317',
|
||||
green4: '#306317',
|
||||
'green-5': '#3c8618',
|
||||
green5: '#3c8618',
|
||||
'green-6': '#49aa19',
|
||||
green6: '#49aa19',
|
||||
'green-7': '#6abe39',
|
||||
green7: '#6abe39',
|
||||
'green-8': '#8fd460',
|
||||
green8: '#8fd460',
|
||||
'green-9': '#b2e58b',
|
||||
green9: '#b2e58b',
|
||||
'green-10': '#d5f2bb',
|
||||
green10: '#d5f2bb',
|
||||
'magenta-1': '#291321',
|
||||
magenta1: '#291321',
|
||||
'magenta-2': '#40162f',
|
||||
magenta2: '#40162f',
|
||||
'magenta-3': '#551c3b',
|
||||
magenta3: '#551c3b',
|
||||
'magenta-4': '#75204f',
|
||||
magenta4: '#75204f',
|
||||
'magenta-5': '#a02669',
|
||||
magenta5: '#a02669',
|
||||
'magenta-6': '#cb2b83',
|
||||
magenta6: '#cb2b83',
|
||||
'magenta-7': '#e0529c',
|
||||
magenta7: '#e0529c',
|
||||
'magenta-8': '#f37fb7',
|
||||
magenta8: '#f37fb7',
|
||||
'magenta-9': '#f8a8cc',
|
||||
magenta9: '#f8a8cc',
|
||||
'magenta-10': '#fad2e3',
|
||||
magenta10: '#fad2e3',
|
||||
'pink-1': '#291321',
|
||||
pink1: '#291321',
|
||||
'pink-2': '#40162f',
|
||||
pink2: '#40162f',
|
||||
'pink-3': '#551c3b',
|
||||
pink3: '#551c3b',
|
||||
'pink-4': '#75204f',
|
||||
pink4: '#75204f',
|
||||
'pink-5': '#a02669',
|
||||
pink5: '#a02669',
|
||||
'pink-6': '#cb2b83',
|
||||
pink6: '#cb2b83',
|
||||
'pink-7': '#e0529c',
|
||||
pink7: '#e0529c',
|
||||
'pink-8': '#f37fb7',
|
||||
pink8: '#f37fb7',
|
||||
'pink-9': '#f8a8cc',
|
||||
pink9: '#f8a8cc',
|
||||
'pink-10': '#fad2e3',
|
||||
pink10: '#fad2e3',
|
||||
'red-1': '#2a1215',
|
||||
red1: '#2a1215',
|
||||
'red-2': '#431418',
|
||||
red2: '#431418',
|
||||
'red-3': '#58181c',
|
||||
red3: '#58181c',
|
||||
'red-4': '#791a1f',
|
||||
red4: '#791a1f',
|
||||
'red-5': '#a61d24',
|
||||
red5: '#a61d24',
|
||||
'red-6': '#d32029',
|
||||
red6: '#d32029',
|
||||
'red-7': '#e84749',
|
||||
red7: '#e84749',
|
||||
'red-8': '#f37370',
|
||||
red8: '#f37370',
|
||||
'red-9': '#f89f9a',
|
||||
red9: '#f89f9a',
|
||||
'red-10': '#fac8c3',
|
||||
red10: '#fac8c3',
|
||||
'orange-1': '#2b1d11',
|
||||
orange1: '#2b1d11',
|
||||
'orange-2': '#442a11',
|
||||
orange2: '#442a11',
|
||||
'orange-3': '#593815',
|
||||
orange3: '#593815',
|
||||
'orange-4': '#7c4a15',
|
||||
orange4: '#7c4a15',
|
||||
'orange-5': '#aa6215',
|
||||
orange5: '#aa6215',
|
||||
'orange-6': '#d87a16',
|
||||
orange6: '#d87a16',
|
||||
'orange-7': '#e89a3c',
|
||||
orange7: '#e89a3c',
|
||||
'orange-8': '#f3b765',
|
||||
orange8: '#f3b765',
|
||||
'orange-9': '#f8cf8d',
|
||||
orange9: '#f8cf8d',
|
||||
'orange-10': '#fae3b7',
|
||||
orange10: '#fae3b7',
|
||||
'yellow-1': '#2b2611',
|
||||
yellow1: '#2b2611',
|
||||
'yellow-2': '#443b11',
|
||||
yellow2: '#443b11',
|
||||
'yellow-3': '#595014',
|
||||
yellow3: '#595014',
|
||||
'yellow-4': '#7c6e14',
|
||||
yellow4: '#7c6e14',
|
||||
'yellow-5': '#aa9514',
|
||||
yellow5: '#aa9514',
|
||||
'yellow-6': '#d8bd14',
|
||||
yellow6: '#d8bd14',
|
||||
'yellow-7': '#e8d639',
|
||||
yellow7: '#e8d639',
|
||||
'yellow-8': '#f3ea62',
|
||||
yellow8: '#f3ea62',
|
||||
'yellow-9': '#f8f48b',
|
||||
yellow9: '#f8f48b',
|
||||
'yellow-10': '#fafab5',
|
||||
yellow10: '#fafab5',
|
||||
'volcano-1': '#2b1611',
|
||||
volcano1: '#2b1611',
|
||||
'volcano-2': '#441d12',
|
||||
volcano2: '#441d12',
|
||||
'volcano-3': '#592716',
|
||||
volcano3: '#592716',
|
||||
'volcano-4': '#7c3118',
|
||||
volcano4: '#7c3118',
|
||||
'volcano-5': '#aa3e19',
|
||||
volcano5: '#aa3e19',
|
||||
'volcano-6': '#d84a1b',
|
||||
volcano6: '#d84a1b',
|
||||
'volcano-7': '#e87040',
|
||||
volcano7: '#e87040',
|
||||
'volcano-8': '#f3956a',
|
||||
volcano8: '#f3956a',
|
||||
'volcano-9': '#f8b692',
|
||||
volcano9: '#f8b692',
|
||||
'volcano-10': '#fad4bc',
|
||||
volcano10: '#fad4bc',
|
||||
'geekblue-1': '#131629',
|
||||
geekblue1: '#131629',
|
||||
'geekblue-2': '#161d40',
|
||||
geekblue2: '#161d40',
|
||||
'geekblue-3': '#1c2755',
|
||||
geekblue3: '#1c2755',
|
||||
'geekblue-4': '#203175',
|
||||
geekblue4: '#203175',
|
||||
'geekblue-5': '#263ea0',
|
||||
geekblue5: '#263ea0',
|
||||
'geekblue-6': '#2b4acb',
|
||||
geekblue6: '#2b4acb',
|
||||
'geekblue-7': '#5273e0',
|
||||
geekblue7: '#5273e0',
|
||||
'geekblue-8': '#7f9ef3',
|
||||
geekblue8: '#7f9ef3',
|
||||
'geekblue-9': '#a8c1f8',
|
||||
geekblue9: '#a8c1f8',
|
||||
'geekblue-10': '#d2e0fa',
|
||||
geekblue10: '#d2e0fa',
|
||||
'gold-1': '#2b2111',
|
||||
gold1: '#2b2111',
|
||||
'gold-2': '#443111',
|
||||
gold2: '#443111',
|
||||
'gold-3': '#594214',
|
||||
gold3: '#594214',
|
||||
'gold-4': '#7c5914',
|
||||
gold4: '#7c5914',
|
||||
'gold-5': '#aa7714',
|
||||
gold5: '#aa7714',
|
||||
'gold-6': '#d89614',
|
||||
gold6: '#d89614',
|
||||
'gold-7': '#e8b339',
|
||||
gold7: '#e8b339',
|
||||
'gold-8': '#f3cc62',
|
||||
gold8: '#f3cc62',
|
||||
'gold-9': '#f8df8b',
|
||||
gold9: '#f8df8b',
|
||||
'gold-10': '#faedb5',
|
||||
gold10: '#faedb5',
|
||||
'lime-1': '#1f2611',
|
||||
lime1: '#1f2611',
|
||||
'lime-2': '#2e3c10',
|
||||
lime2: '#2e3c10',
|
||||
'lime-3': '#3e4f13',
|
||||
lime3: '#3e4f13',
|
||||
'lime-4': '#536d13',
|
||||
lime4: '#536d13',
|
||||
'lime-5': '#6f9412',
|
||||
lime5: '#6f9412',
|
||||
'lime-6': '#8bbb11',
|
||||
lime6: '#8bbb11',
|
||||
'lime-7': '#a9d134',
|
||||
lime7: '#a9d134',
|
||||
'lime-8': '#c9e75d',
|
||||
lime8: '#c9e75d',
|
||||
'lime-9': '#e4f88b',
|
||||
lime9: '#e4f88b',
|
||||
'lime-10': '#f0fab5',
|
||||
lime10: '#f0fab5',
|
||||
colorText: 'rgba(255, 255, 255, 0.85)',
|
||||
colorTextSecondary: 'rgba(255, 255, 255, 0.65)',
|
||||
colorTextTertiary: 'rgba(255, 255, 255, 0.45)',
|
||||
colorTextQuaternary: 'rgba(255, 255, 255, 0.25)',
|
||||
colorFill: 'rgba(255, 255, 255, 0.18)',
|
||||
colorFillSecondary: 'rgba(255, 255, 255, 0.12)',
|
||||
colorFillTertiary: 'rgba(255, 255, 255, 0.08)',
|
||||
colorFillQuaternary: 'rgba(255, 255, 255, 0.04)',
|
||||
colorBgSolid: 'rgba(255, 255, 255, 0.95)',
|
||||
colorBgSolidHover: 'rgb(255, 255, 255)',
|
||||
colorBgSolidActive: 'rgba(255, 255, 255, 0.9)',
|
||||
colorBgLayout: '#000000',
|
||||
colorBgContainer: '#141414',
|
||||
colorBgElevated: '#1f1f1f',
|
||||
colorBgSpotlight: '#424242',
|
||||
colorBgBlur: 'rgba(255, 255, 255, 0.04)',
|
||||
colorBorder: '#424242',
|
||||
colorBorderSecondary: '#303030',
|
||||
colorPrimaryBg: '#161622',
|
||||
colorPrimaryBgHover: '#1c1b34',
|
||||
colorPrimaryBorder: '#252346',
|
||||
colorPrimaryBorderHover: '#2e2b5f',
|
||||
colorPrimaryHover: '#6b62b5',
|
||||
colorPrimaryActive: '#3a3581',
|
||||
colorPrimaryTextHover: '#6b62b5',
|
||||
colorPrimaryText: '#453fa2',
|
||||
colorPrimaryTextActive: '#3a3581',
|
||||
colorSuccessBg: '#162312',
|
||||
colorSuccessBgHover: '#1d3712',
|
||||
colorSuccessBorder: '#274916',
|
||||
colorSuccessBorderHover: '#306317',
|
||||
colorSuccessHover: '#306317',
|
||||
colorSuccessActive: '#3c8618',
|
||||
colorSuccessTextHover: '#6abe39',
|
||||
colorSuccessText: '#49aa19',
|
||||
colorSuccessTextActive: '#3c8618',
|
||||
colorErrorBg: '#2c1618',
|
||||
colorErrorBgHover: '#451d1f',
|
||||
colorErrorBgFilledHover: '#441e1f',
|
||||
colorErrorBgActive: '#5b2526',
|
||||
colorErrorBorder: '#5b2526',
|
||||
colorErrorBorderHover: '#7e2e2f',
|
||||
colorErrorHover: '#e86e6b',
|
||||
colorErrorActive: '#ad393a',
|
||||
colorErrorTextHover: '#e86e6b',
|
||||
colorErrorText: '#dc4446',
|
||||
colorErrorTextActive: '#ad393a',
|
||||
colorWarningBg: '#2b2111',
|
||||
colorWarningBgHover: '#443111',
|
||||
colorWarningBorder: '#594214',
|
||||
colorWarningBorderHover: '#7c5914',
|
||||
colorWarningHover: '#7c5914',
|
||||
colorWarningActive: '#aa7714',
|
||||
colorWarningTextHover: '#e8b339',
|
||||
colorWarningText: '#d89614',
|
||||
colorWarningTextActive: '#aa7714',
|
||||
colorInfoBg: '#111a2c',
|
||||
colorInfoBgHover: '#112545',
|
||||
colorInfoBorder: '#15325b',
|
||||
colorInfoBorderHover: '#15417e',
|
||||
colorInfoHover: '#15417e',
|
||||
colorInfoActive: '#1554ad',
|
||||
colorInfoTextHover: '#3c89e8',
|
||||
colorInfoText: '#1668dc',
|
||||
colorInfoTextActive: '#1554ad',
|
||||
colorLinkActive: '#1554ad',
|
||||
colorBgMask: 'rgba(0, 0, 0, 0.45)',
|
||||
colorWhite: '#fff',
|
||||
fontSizeSM: 12,
|
||||
fontSizeLG: 16,
|
||||
fontSizeXL: 20,
|
||||
fontSizeHeading1: 38,
|
||||
fontSizeHeading2: 30,
|
||||
fontSizeHeading3: 24,
|
||||
fontSizeHeading4: 20,
|
||||
fontSizeHeading5: 16,
|
||||
lineHeight: 1.5714285714285714,
|
||||
lineHeightLG: 1.5,
|
||||
lineHeightSM: 1.6666666666666667,
|
||||
fontHeight: 22,
|
||||
fontHeightLG: 24,
|
||||
fontHeightSM: 20,
|
||||
lineHeightHeading1: 1.2105263157894737,
|
||||
lineHeightHeading2: 1.2666666666666666,
|
||||
lineHeightHeading3: 1.3333333333333333,
|
||||
lineHeightHeading4: 1.4,
|
||||
lineHeightHeading5: 1.5,
|
||||
sizeXXL: 48,
|
||||
sizeXL: 32,
|
||||
sizeLG: 24,
|
||||
sizeMD: 20,
|
||||
sizeMS: 16,
|
||||
size: 16,
|
||||
sizeSM: 12,
|
||||
sizeXS: 8,
|
||||
sizeXXS: 4,
|
||||
controlHeightSM: 24,
|
||||
controlHeightXS: 16,
|
||||
controlHeightLG: 40,
|
||||
motionDurationFast: '0.1s',
|
||||
motionDurationMid: '0.2s',
|
||||
motionDurationSlow: '0.3s',
|
||||
lineWidthBold: 2,
|
||||
borderRadiusXS: 2,
|
||||
borderRadiusSM: 4,
|
||||
borderRadiusLG: 8,
|
||||
borderRadiusOuter: 4,
|
||||
colorFillContent: 'rgba(255, 255, 255, 0.12)',
|
||||
colorFillContentHover: 'rgba(255, 255, 255, 0.18)',
|
||||
colorFillAlter: 'rgba(255, 255, 255, 0.04)',
|
||||
colorBgContainerDisabled: 'rgba(255, 255, 255, 0.08)',
|
||||
colorBorderBg: '#141414',
|
||||
colorSplit: 'rgba(253, 253, 253, 0.12)',
|
||||
colorTextPlaceholder: 'rgba(255, 255, 255, 0.25)',
|
||||
colorTextDisabled: 'rgba(255, 255, 255, 0.25)',
|
||||
colorTextHeading: 'rgba(255, 255, 255, 0.85)',
|
||||
colorTextLabel: 'rgba(255, 255, 255, 0.65)',
|
||||
colorTextDescription: 'rgba(255, 255, 255, 0.45)',
|
||||
colorTextLightSolid: '#fff',
|
||||
colorHighlight: '#dc4446',
|
||||
colorBgTextHover: 'rgba(255, 255, 255, 0.12)',
|
||||
colorBgTextActive: 'rgba(255, 255, 255, 0.18)',
|
||||
colorIcon: 'rgba(255, 255, 255, 0.45)',
|
||||
colorIconHover: 'rgba(255, 255, 255, 0.85)',
|
||||
colorErrorOutline: 'rgba(238, 38, 56, 0.11)',
|
||||
colorWarningOutline: 'rgba(173, 107, 0, 0.15)',
|
||||
fontSizeIcon: 12,
|
||||
lineWidthFocus: 3,
|
||||
controlOutlineWidth: 2,
|
||||
controlInteractiveSize: 16,
|
||||
controlItemBgHover: 'rgba(255, 255, 255, 0.08)',
|
||||
controlItemBgActive: '#161622',
|
||||
controlItemBgActiveHover: '#1c1b34',
|
||||
controlItemBgActiveDisabled: 'rgba(255, 255, 255, 0.18)',
|
||||
controlTmpOutline: 'rgba(255, 255, 255, 0.04)',
|
||||
controlOutline: 'rgba(53, 53, 253, 0.06)',
|
||||
fontWeightStrong: 600,
|
||||
opacityLoading: 0.65,
|
||||
linkDecoration: 'none',
|
||||
linkHoverDecoration: 'none',
|
||||
linkFocusDecoration: 'none',
|
||||
controlPaddingHorizontal: 12,
|
||||
controlPaddingHorizontalSM: 8,
|
||||
paddingXXS: 4,
|
||||
paddingXS: 8,
|
||||
paddingSM: 12,
|
||||
padding: 16,
|
||||
paddingMD: 20,
|
||||
paddingLG: 24,
|
||||
paddingXL: 32,
|
||||
paddingContentHorizontalLG: 24,
|
||||
paddingContentVerticalLG: 16,
|
||||
paddingContentHorizontal: 16,
|
||||
paddingContentVertical: 12,
|
||||
paddingContentHorizontalSM: 16,
|
||||
paddingContentVerticalSM: 8,
|
||||
marginXXS: 4,
|
||||
marginXS: 8,
|
||||
marginSM: 12,
|
||||
margin: 16,
|
||||
marginMD: 20,
|
||||
marginLG: 24,
|
||||
marginXL: 32,
|
||||
marginXXL: 48,
|
||||
boxShadow:
|
||||
'\n 0 6px 16px 0 rgba(0, 0, 0, 0.08),\n 0 3px 6px -4px rgba(0, 0, 0, 0.12),\n 0 9px 28px 8px rgba(0, 0, 0, 0.05)\n ',
|
||||
boxShadowSecondary:
|
||||
'\n 0 6px 16px 0 rgba(0, 0, 0, 0.08),\n 0 3px 6px -4px rgba(0, 0, 0, 0.12),\n 0 9px 28px 8px rgba(0, 0, 0, 0.05)\n ',
|
||||
boxShadowTertiary:
|
||||
'\n 0 1px 2px 0 rgba(0, 0, 0, 0.03),\n 0 1px 6px -1px rgba(0, 0, 0, 0.02),\n 0 2px 4px 0 rgba(0, 0, 0, 0.02)\n ',
|
||||
screenXS: 480,
|
||||
screenXSMin: 480,
|
||||
screenXSMax: 575,
|
||||
screenSM: 576,
|
||||
screenSMMin: 576,
|
||||
screenSMMax: 767,
|
||||
screenMD: 768,
|
||||
screenMDMin: 768,
|
||||
screenMDMax: 991,
|
||||
screenLG: 992,
|
||||
screenLGMin: 992,
|
||||
screenLGMax: 1199,
|
||||
screenXL: 1200,
|
||||
screenXLMin: 1200,
|
||||
screenXLMax: 1599,
|
||||
screenXXL: 1600,
|
||||
screenXXLMin: 1600,
|
||||
boxShadowPopoverArrow: '2px 2px 5px rgba(0, 0, 0, 0.05)',
|
||||
boxShadowCard:
|
||||
'\n 0 1px 2px -2px rgba(0, 0, 0, 0.16),\n 0 3px 6px 0 rgba(0, 0, 0, 0.12),\n 0 5px 12px 4px rgba(0, 0, 0, 0.09)\n ',
|
||||
boxShadowDrawerRight:
|
||||
'\n -6px 0 16px 0 rgba(0, 0, 0, 0.08),\n -3px 0 6px -4px rgba(0, 0, 0, 0.12),\n -9px 0 28px 8px rgba(0, 0, 0, 0.05)\n ',
|
||||
boxShadowDrawerLeft:
|
||||
'\n 6px 0 16px 0 rgba(0, 0, 0, 0.08),\n 3px 0 6px -4px rgba(0, 0, 0, 0.12),\n 9px 0 28px 8px rgba(0, 0, 0, 0.05)\n ',
|
||||
boxShadowDrawerUp:
|
||||
'\n 0 6px 16px 0 rgba(0, 0, 0, 0.08),\n 0 3px 6px -4px rgba(0, 0, 0, 0.12),\n 0 9px 28px 8px rgba(0, 0, 0, 0.05)\n ',
|
||||
boxShadowDrawerDown:
|
||||
'\n 0 -6px 16px 0 rgba(0, 0, 0, 0.08),\n 0 -3px 6px -4px rgba(0, 0, 0, 0.12),\n 0 -9px 28px 8px rgba(0, 0, 0, 0.05)\n ',
|
||||
boxShadowTabsOverflowLeft: 'inset 10px 0 8px -8px rgba(0, 0, 0, 0.08)',
|
||||
boxShadowTabsOverflowRight: 'inset -10px 0 8px -8px rgba(0, 0, 0, 0.08)',
|
||||
boxShadowTabsOverflowTop: 'inset 0 10px 8px -8px rgba(0, 0, 0, 0.08)',
|
||||
boxShadowTabsOverflowBottom: 'inset 0 -10px 8px -8px rgba(0, 0, 0, 0.08)'
|
||||
}
|
||||
}
|
||||
|
||||
for (const key in globalVariables) {
|
||||
globalThis[key] = globalVariables[key]
|
||||
}
|
||||
365
app/src/main/assets/template/global-variables-light.css
Normal file
365
app/src/main/assets/template/global-variables-light.css
Normal file
@@ -0,0 +1,365 @@
|
||||
:root {
|
||||
--blue: #1677FF;
|
||||
--blue1: #e6f4ff;
|
||||
--blue2: #bae0ff;
|
||||
--blue3: #91caff;
|
||||
--blue4: #69b1ff;
|
||||
--blue5: #4096ff;
|
||||
--blue6: #1677ff;
|
||||
--blue7: #0958d9;
|
||||
--blue8: #003eb3;
|
||||
--blue9: #002c8c;
|
||||
--blue10: #001d66;
|
||||
--purple: #722ED1;
|
||||
--purple1: #f9f0ff;
|
||||
--purple2: #efdbff;
|
||||
--purple3: #d3adf7;
|
||||
--purple4: #b37feb;
|
||||
--purple5: #9254de;
|
||||
--purple6: #722ed1;
|
||||
--purple7: #531dab;
|
||||
--purple8: #391085;
|
||||
--purple9: #22075e;
|
||||
--purple10: #120338;
|
||||
--cyan: #13C2C2;
|
||||
--cyan1: #e6fffb;
|
||||
--cyan2: #b5f5ec;
|
||||
--cyan3: #87e8de;
|
||||
--cyan4: #5cdbd3;
|
||||
--cyan5: #36cfc9;
|
||||
--cyan6: #13c2c2;
|
||||
--cyan7: #08979c;
|
||||
--cyan8: #006d75;
|
||||
--cyan9: #00474f;
|
||||
--cyan10: #002329;
|
||||
--green: #52C41A;
|
||||
--green1: #f6ffed;
|
||||
--green2: #d9f7be;
|
||||
--green3: #b7eb8f;
|
||||
--green4: #95de64;
|
||||
--green5: #73d13d;
|
||||
--green6: #52c41a;
|
||||
--green7: #389e0d;
|
||||
--green8: #237804;
|
||||
--green9: #135200;
|
||||
--green10: #092b00;
|
||||
--magenta: #EB2F96;
|
||||
--magenta1: #fff0f6;
|
||||
--magenta2: #ffd6e7;
|
||||
--magenta3: #ffadd2;
|
||||
--magenta4: #ff85c0;
|
||||
--magenta5: #f759ab;
|
||||
--magenta6: #eb2f96;
|
||||
--magenta7: #c41d7f;
|
||||
--magenta8: #9e1068;
|
||||
--magenta9: #780650;
|
||||
--magenta10: #520339;
|
||||
--pink: #EB2F96;
|
||||
--pink1: #fff0f6;
|
||||
--pink2: #ffd6e7;
|
||||
--pink3: #ffadd2;
|
||||
--pink4: #ff85c0;
|
||||
--pink5: #f759ab;
|
||||
--pink6: #eb2f96;
|
||||
--pink7: #c41d7f;
|
||||
--pink8: #9e1068;
|
||||
--pink9: #780650;
|
||||
--pink10: #520339;
|
||||
--red: #F5222D;
|
||||
--red1: #fff1f0;
|
||||
--red2: #ffccc7;
|
||||
--red3: #ffa39e;
|
||||
--red4: #ff7875;
|
||||
--red5: #ff4d4f;
|
||||
--red6: #f5222d;
|
||||
--red7: #cf1322;
|
||||
--red8: #a8071a;
|
||||
--red9: #820014;
|
||||
--red10: #5c0011;
|
||||
--orange: #FA8C16;
|
||||
--orange1: #fff7e6;
|
||||
--orange2: #ffe7ba;
|
||||
--orange3: #ffd591;
|
||||
--orange4: #ffc069;
|
||||
--orange5: #ffa940;
|
||||
--orange6: #fa8c16;
|
||||
--orange7: #d46b08;
|
||||
--orange8: #ad4e00;
|
||||
--orange9: #873800;
|
||||
--orange10: #612500;
|
||||
--yellow: #FADB14;
|
||||
--yellow1: #feffe6;
|
||||
--yellow2: #ffffb8;
|
||||
--yellow3: #fffb8f;
|
||||
--yellow4: #fff566;
|
||||
--yellow5: #ffec3d;
|
||||
--yellow6: #fadb14;
|
||||
--yellow7: #d4b106;
|
||||
--yellow8: #ad8b00;
|
||||
--yellow9: #876800;
|
||||
--yellow10: #614700;
|
||||
--volcano: #FA541C;
|
||||
--volcano1: #fff2e8;
|
||||
--volcano2: #ffd8bf;
|
||||
--volcano3: #ffbb96;
|
||||
--volcano4: #ff9c6e;
|
||||
--volcano5: #ff7a45;
|
||||
--volcano6: #fa541c;
|
||||
--volcano7: #d4380d;
|
||||
--volcano8: #ad2102;
|
||||
--volcano9: #871400;
|
||||
--volcano10: #610b00;
|
||||
--geekblue: #2F54EB;
|
||||
--geekblue1: #f0f5ff;
|
||||
--geekblue2: #d6e4ff;
|
||||
--geekblue3: #adc6ff;
|
||||
--geekblue4: #85a5ff;
|
||||
--geekblue5: #597ef7;
|
||||
--geekblue6: #2f54eb;
|
||||
--geekblue7: #1d39c4;
|
||||
--geekblue8: #10239e;
|
||||
--geekblue9: #061178;
|
||||
--geekblue10: #030852;
|
||||
--gold: #FAAD14;
|
||||
--gold1: #fffbe6;
|
||||
--gold2: #fff1b8;
|
||||
--gold3: #ffe58f;
|
||||
--gold4: #ffd666;
|
||||
--gold5: #ffc53d;
|
||||
--gold6: #faad14;
|
||||
--gold7: #d48806;
|
||||
--gold8: #ad6800;
|
||||
--gold9: #874d00;
|
||||
--gold10: #613400;
|
||||
--lime: #A0D911;
|
||||
--lime1: #fcffe6;
|
||||
--lime2: #f4ffb8;
|
||||
--lime3: #eaff8f;
|
||||
--lime4: #d3f261;
|
||||
--lime5: #bae637;
|
||||
--lime6: #a0d911;
|
||||
--lime7: #7cb305;
|
||||
--lime8: #5b8c00;
|
||||
--lime9: #3f6600;
|
||||
--lime10: #254000;
|
||||
--colorPrimary: #4e47bb;
|
||||
--colorSuccess: #52c41a;
|
||||
--colorWarning: #faad14;
|
||||
--colorError: #ff4d4f;
|
||||
--colorInfo: #1677ff;
|
||||
--colorLink: #1677ff;
|
||||
--colorTextBase: #000;
|
||||
--colorBgBase: #fff;
|
||||
--fontFamily: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial,
|
||||
'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
|
||||
'Noto Color Emoji';
|
||||
--fontFamilyCode: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace;
|
||||
--fontSize: 14;
|
||||
--lineWidth: 1;
|
||||
--lineType: solid;
|
||||
--motionUnit: 0.1;
|
||||
--motionBase: 0;
|
||||
--motionEaseOutCirc: cubic-bezier(0.08, 0.82, 0.17, 1);
|
||||
--motionEaseInOutCirc: cubic-bezier(0.78, 0.14, 0.15, 0.86);
|
||||
--motionEaseOut: cubic-bezier(0.215, 0.61, 0.355, 1);
|
||||
--motionEaseInOut: cubic-bezier(0.645, 0.045, 0.355, 1);
|
||||
--motionEaseOutBack: cubic-bezier(0.12, 0.4, 0.29, 1.46);
|
||||
--motionEaseInBack: cubic-bezier(0.71, -0.46, 0.88, 0.6);
|
||||
--motionEaseInQuint: cubic-bezier(0.755, 0.05, 0.855, 0.06);
|
||||
--motionEaseOutQuint: cubic-bezier(0.23, 1, 0.32, 1);
|
||||
--borderRadius: 6;
|
||||
--sizeUnit: 4;
|
||||
--sizeStep: 4;
|
||||
--sizePopupArrow: 16;
|
||||
--controlHeight: 32;
|
||||
--zIndexBase: 0;
|
||||
--zIndexPopupBase: 1000;
|
||||
--opacityImage: 1;
|
||||
--colorLinkHover: #4E47BB;
|
||||
--colorText: rgba(0, 0, 0, 0.88);
|
||||
--colorTextSecondary: rgba(0, 0, 0, 0.65);
|
||||
--colorTextTertiary: rgba(0, 0, 0, 0.45);
|
||||
--colorTextQuaternary: rgba(0, 0, 0, 0.25);
|
||||
--colorFill: rgba(0, 0, 0, 0.15);
|
||||
--colorFillSecondary: rgba(0, 0, 0, 0.06);
|
||||
--colorFillTertiary: rgba(0, 0, 0, 0.04);
|
||||
--colorFillQuaternary: rgba(0, 0, 0, 0.02);
|
||||
--colorBgSolid: rgb(0, 0, 0);
|
||||
--colorBgSolidHover: rgba(0, 0, 0, 0.75);
|
||||
--colorBgSolidActive: rgba(0, 0, 0, 0.95);
|
||||
--colorBgLayout: #f5f5f5;
|
||||
--colorBgContainer: #ffffff;
|
||||
--colorBgElevated: #ffffff;
|
||||
--colorBgSpotlight: rgba(0, 0, 0, 0.85);
|
||||
--colorBgBlur: transparent;
|
||||
--colorBorder: #d9d9d9;
|
||||
--colorBorderSecondary: #f0f0f0;
|
||||
--colorPrimaryBg: #eeebfa;
|
||||
--colorPrimaryBgHover: #e2dfed;
|
||||
--colorPrimaryBorder: #c6c1e0;
|
||||
--colorPrimaryBorderHover: #9d94d4;
|
||||
--colorPrimaryHover: #756bc7;
|
||||
--colorPrimaryActive: #343194;
|
||||
--colorPrimaryTextHover: #756bc7;
|
||||
--colorPrimaryText: #4e47bb;
|
||||
--colorPrimaryTextActive: #343194;
|
||||
--colorSuccessBg: #f6ffed;
|
||||
--colorSuccessBgHover: #d9f7be;
|
||||
--colorSuccessBorder: #b7eb8f;
|
||||
--colorSuccessBorderHover: #95de64;
|
||||
--colorSuccessHover: #95de64;
|
||||
--colorSuccessActive: #389e0d;
|
||||
--colorSuccessTextHover: #73d13d;
|
||||
--colorSuccessText: #52c41a;
|
||||
--colorSuccessTextActive: #389e0d;
|
||||
--colorErrorBg: #fff2f0;
|
||||
--colorErrorBgHover: #fff1f0;
|
||||
--colorErrorBgFilledHover: #ffdfdc;
|
||||
--colorErrorBgActive: #ffccc7;
|
||||
--colorErrorBorder: #ffccc7;
|
||||
--colorErrorBorderHover: #ffa39e;
|
||||
--colorErrorHover: #ff7875;
|
||||
--colorErrorActive: #d9363e;
|
||||
--colorErrorTextHover: #ff7875;
|
||||
--colorErrorText: #ff4d4f;
|
||||
--colorErrorTextActive: #d9363e;
|
||||
--colorWarningBg: #fffbe6;
|
||||
--colorWarningBgHover: #fff1b8;
|
||||
--colorWarningBorder: #ffe58f;
|
||||
--colorWarningBorderHover: #ffd666;
|
||||
--colorWarningHover: #ffd666;
|
||||
--colorWarningActive: #d48806;
|
||||
--colorWarningTextHover: #ffc53d;
|
||||
--colorWarningText: #faad14;
|
||||
--colorWarningTextActive: #d48806;
|
||||
--colorInfoBg: #e6f4ff;
|
||||
--colorInfoBgHover: #bae0ff;
|
||||
--colorInfoBorder: #91caff;
|
||||
--colorInfoBorderHover: #69b1ff;
|
||||
--colorInfoHover: #69b1ff;
|
||||
--colorInfoActive: #0958d9;
|
||||
--colorInfoTextHover: #4096ff;
|
||||
--colorInfoText: #1677ff;
|
||||
--colorInfoTextActive: #0958d9;
|
||||
--colorLinkActive: #0958d9;
|
||||
--colorBgMask: rgba(0, 0, 0, 0.45);
|
||||
--colorWhite: #fff;
|
||||
--fontSizeSM: 12;
|
||||
--fontSizeLG: 16;
|
||||
--fontSizeXL: 20;
|
||||
--fontSizeHeading1: 38;
|
||||
--fontSizeHeading2: 30;
|
||||
--fontSizeHeading3: 24;
|
||||
--fontSizeHeading4: 20;
|
||||
--fontSizeHeading5: 16;
|
||||
--lineHeight: 1.5714285714285714;
|
||||
--lineHeightLG: 1.5;
|
||||
--lineHeightSM: 1.6666666666666667;
|
||||
--lineHeightHeading1: 1.2105263157894737;
|
||||
--lineHeightHeading2: 1.2666666666666666;
|
||||
--lineHeightHeading3: 1.3333333333333333;
|
||||
--lineHeightHeading4: 1.4;
|
||||
--lineHeightHeading5: 1.5;
|
||||
--sizeXXL: 48;
|
||||
--sizeXL: 32;
|
||||
--sizeLG: 24;
|
||||
--sizeMD: 20;
|
||||
--sizeMS: 16;
|
||||
--size: 16;
|
||||
--sizeSM: 12;
|
||||
--sizeXS: 8;
|
||||
--sizeXXS: 4;
|
||||
--controlHeightSM: 24;
|
||||
--controlHeightXS: 16;
|
||||
--controlHeightLG: 40;
|
||||
--motionDurationFast: 0.1s;
|
||||
--motionDurationMid: 0.2s;
|
||||
--motionDurationSlow: 0.3s;
|
||||
--lineWidthBold: 2;
|
||||
--borderRadiusXS: 2;
|
||||
--borderRadiusSM: 4;
|
||||
--borderRadiusLG: 8;
|
||||
--borderRadiusOuter: 4;
|
||||
--colorFillContent: rgba(0, 0, 0, 0.06);
|
||||
--colorFillContentHover: rgba(0, 0, 0, 0.15);
|
||||
--colorFillAlter: rgba(0, 0, 0, 0.02);
|
||||
--colorBgContainerDisabled: rgba(0, 0, 0, 0.04);
|
||||
--colorBorderBg: #ffffff;
|
||||
--colorSplit: rgba(5, 5, 5, 0.06);
|
||||
--colorTextPlaceholder: rgba(0, 0, 0, 0.25);
|
||||
--colorTextDisabled: rgba(0, 0, 0, 0.25);
|
||||
--colorTextHeading: rgba(0, 0, 0, 0.88);
|
||||
--colorTextLabel: rgba(0, 0, 0, 0.65);
|
||||
--colorTextDescription: rgba(0, 0, 0, 0.45);
|
||||
--colorTextLightSolid: #fff;
|
||||
--colorHighlight: #ff4d4f;
|
||||
--colorBgTextHover: rgba(0, 0, 0, 0.06);
|
||||
--colorBgTextActive: rgba(0, 0, 0, 0.15);
|
||||
--colorIcon: rgba(0, 0, 0, 0.45);
|
||||
--colorIconHover: rgba(0, 0, 0, 0.88);
|
||||
--colorErrorOutline: rgba(255, 38, 5, 0.06);
|
||||
--colorWarningOutline: rgba(255, 215, 5, 0.1);
|
||||
--fontSizeIcon: 12;
|
||||
--lineWidthFocus: 3;
|
||||
--controlOutlineWidth: 2;
|
||||
--controlInteractiveSize: 16;
|
||||
--controlItemBgHover: rgba(0, 0, 0, 0.04);
|
||||
--controlItemBgActive: #eeebfa;
|
||||
--controlItemBgActiveHover: #e2dfed;
|
||||
--controlItemBgActiveDisabled: rgba(0, 0, 0, 0.15);
|
||||
--controlOutline: rgba(42, 5, 192, 0.08);
|
||||
--fontWeightStrong: 600;
|
||||
--opacityLoading: 0.65;
|
||||
--linkDecoration: none;
|
||||
--linkHoverDecoration: none;
|
||||
--linkFocusDecoration: none;
|
||||
--controlPaddingHorizontal: 12;
|
||||
--controlPaddingHorizontalSM: 8;
|
||||
--paddingXXS: 4;
|
||||
--paddingXS: 8;
|
||||
--paddingSM: 12;
|
||||
--padding: 16;
|
||||
--paddingMD: 20;
|
||||
--paddingLG: 24;
|
||||
--paddingXL: 32;
|
||||
--paddingContentHorizontalLG: 24;
|
||||
--paddingContentVerticalLG: 16;
|
||||
--paddingContentHorizontal: 16;
|
||||
--paddingContentVertical: 12;
|
||||
--paddingContentHorizontalSM: 16;
|
||||
--paddingContentVerticalSM: 8;
|
||||
--marginXXS: 4;
|
||||
--marginXS: 8;
|
||||
--marginSM: 12;
|
||||
--margin: 16;
|
||||
--marginMD: 20;
|
||||
--marginLG: 24;
|
||||
--marginXL: 32;
|
||||
--marginXXL: 48;
|
||||
--boxShadow: 0 6px 16px 0 rgba(0, 0, 0, 0.08),
|
||||
0 3px 6px -4px rgba(0, 0, 0, 0.12),
|
||||
0 9px 28px 8px rgba(0, 0, 0, 0.05);
|
||||
--boxShadowSecondary: 0 6px 16px 0 rgba(0, 0, 0, 0.08),
|
||||
0 3px 6px -4px rgba(0, 0, 0, 0.12),
|
||||
0 9px 28px 8px rgba(0, 0, 0, 0.05);
|
||||
--boxShadowTertiary: 0 1px 2px 0 rgba(0, 0, 0, 0.03),
|
||||
0 1px 6px -1px rgba(0, 0, 0, 0.02),
|
||||
0 2px 4px 0 rgba(0, 0, 0, 0.02);
|
||||
--screenXS: 480;
|
||||
--screenXSMin: 480;
|
||||
--screenXSMax: 575;
|
||||
--screenSM: 576;
|
||||
--screenSMMin: 576;
|
||||
--screenSMMax: 767;
|
||||
--screenMD: 768;
|
||||
--screenMDMin: 768;
|
||||
--screenMDMax: 991;
|
||||
--screenLG: 992;
|
||||
--screenLGMin: 992;
|
||||
--screenLGMax: 1199;
|
||||
--screenXL: 1200;
|
||||
--screenXLMin: 1200;
|
||||
--screenXLMax: 1599;
|
||||
--screenXXL: 1600;
|
||||
--screenXXLMin: 1600;
|
||||
}
|
||||
519
app/src/main/assets/template/global-variables-light.js
Normal file
519
app/src/main/assets/template/global-variables-light.js
Normal file
@@ -0,0 +1,519 @@
|
||||
const globalVariables = {
|
||||
OxygenTheme: {
|
||||
isDarkMode: false,
|
||||
blue: '#1677FF',
|
||||
purple: '#722ED1',
|
||||
cyan: '#13C2C2',
|
||||
green: '#52C41A',
|
||||
magenta: '#EB2F96',
|
||||
pink: '#EB2F96',
|
||||
red: '#F5222D',
|
||||
orange: '#FA8C16',
|
||||
yellow: '#FADB14',
|
||||
volcano: '#FA541C',
|
||||
geekblue: '#2F54EB',
|
||||
gold: '#FAAD14',
|
||||
lime: '#A0D911',
|
||||
colorPrimary: '#4e47bb',
|
||||
colorSuccess: '#52c41a',
|
||||
colorWarning: '#faad14',
|
||||
colorError: '#ff4d4f',
|
||||
colorInfo: '#1677ff',
|
||||
colorLink: '#1677ff',
|
||||
colorTextBase: '#000',
|
||||
colorBgBase: '#fff',
|
||||
fontFamily:
|
||||
"-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial,\n'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',\n'Noto Color Emoji'",
|
||||
fontFamilyCode: "'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace",
|
||||
fontSize: 14,
|
||||
lineWidth: 1,
|
||||
lineType: 'solid',
|
||||
motionUnit: 0.1,
|
||||
motionBase: 0,
|
||||
motionEaseOutCirc: 'cubic-bezier(0.08, 0.82, 0.17, 1)',
|
||||
motionEaseInOutCirc: 'cubic-bezier(0.78, 0.14, 0.15, 0.86)',
|
||||
motionEaseOut: 'cubic-bezier(0.215, 0.61, 0.355, 1)',
|
||||
motionEaseInOut: 'cubic-bezier(0.645, 0.045, 0.355, 1)',
|
||||
motionEaseOutBack: 'cubic-bezier(0.12, 0.4, 0.29, 1.46)',
|
||||
motionEaseInBack: 'cubic-bezier(0.71, -0.46, 0.88, 0.6)',
|
||||
motionEaseInQuint: 'cubic-bezier(0.755, 0.05, 0.855, 0.06)',
|
||||
motionEaseOutQuint: 'cubic-bezier(0.23, 1, 0.32, 1)',
|
||||
borderRadius: 6,
|
||||
sizeUnit: 4,
|
||||
sizeStep: 4,
|
||||
sizePopupArrow: 16,
|
||||
controlHeight: 32,
|
||||
zIndexBase: 0,
|
||||
zIndexPopupBase: 1000,
|
||||
opacityImage: 1,
|
||||
motion: true,
|
||||
colorLinkHover: '#4E47BB',
|
||||
'blue-1': '#e6f4ff',
|
||||
blue1: '#e6f4ff',
|
||||
'blue-2': '#bae0ff',
|
||||
blue2: '#bae0ff',
|
||||
'blue-3': '#91caff',
|
||||
blue3: '#91caff',
|
||||
'blue-4': '#69b1ff',
|
||||
blue4: '#69b1ff',
|
||||
'blue-5': '#4096ff',
|
||||
blue5: '#4096ff',
|
||||
'blue-6': '#1677ff',
|
||||
blue6: '#1677ff',
|
||||
'blue-7': '#0958d9',
|
||||
blue7: '#0958d9',
|
||||
'blue-8': '#003eb3',
|
||||
blue8: '#003eb3',
|
||||
'blue-9': '#002c8c',
|
||||
blue9: '#002c8c',
|
||||
'blue-10': '#001d66',
|
||||
blue10: '#001d66',
|
||||
'purple-1': '#f9f0ff',
|
||||
purple1: '#f9f0ff',
|
||||
'purple-2': '#efdbff',
|
||||
purple2: '#efdbff',
|
||||
'purple-3': '#d3adf7',
|
||||
purple3: '#d3adf7',
|
||||
'purple-4': '#b37feb',
|
||||
purple4: '#b37feb',
|
||||
'purple-5': '#9254de',
|
||||
purple5: '#9254de',
|
||||
'purple-6': '#722ed1',
|
||||
purple6: '#722ed1',
|
||||
'purple-7': '#531dab',
|
||||
purple7: '#531dab',
|
||||
'purple-8': '#391085',
|
||||
purple8: '#391085',
|
||||
'purple-9': '#22075e',
|
||||
purple9: '#22075e',
|
||||
'purple-10': '#120338',
|
||||
purple10: '#120338',
|
||||
'cyan-1': '#e6fffb',
|
||||
cyan1: '#e6fffb',
|
||||
'cyan-2': '#b5f5ec',
|
||||
cyan2: '#b5f5ec',
|
||||
'cyan-3': '#87e8de',
|
||||
cyan3: '#87e8de',
|
||||
'cyan-4': '#5cdbd3',
|
||||
cyan4: '#5cdbd3',
|
||||
'cyan-5': '#36cfc9',
|
||||
cyan5: '#36cfc9',
|
||||
'cyan-6': '#13c2c2',
|
||||
cyan6: '#13c2c2',
|
||||
'cyan-7': '#08979c',
|
||||
cyan7: '#08979c',
|
||||
'cyan-8': '#006d75',
|
||||
cyan8: '#006d75',
|
||||
'cyan-9': '#00474f',
|
||||
cyan9: '#00474f',
|
||||
'cyan-10': '#002329',
|
||||
cyan10: '#002329',
|
||||
'green-1': '#f6ffed',
|
||||
green1: '#f6ffed',
|
||||
'green-2': '#d9f7be',
|
||||
green2: '#d9f7be',
|
||||
'green-3': '#b7eb8f',
|
||||
green3: '#b7eb8f',
|
||||
'green-4': '#95de64',
|
||||
green4: '#95de64',
|
||||
'green-5': '#73d13d',
|
||||
green5: '#73d13d',
|
||||
'green-6': '#52c41a',
|
||||
green6: '#52c41a',
|
||||
'green-7': '#389e0d',
|
||||
green7: '#389e0d',
|
||||
'green-8': '#237804',
|
||||
green8: '#237804',
|
||||
'green-9': '#135200',
|
||||
green9: '#135200',
|
||||
'green-10': '#092b00',
|
||||
green10: '#092b00',
|
||||
'magenta-1': '#fff0f6',
|
||||
magenta1: '#fff0f6',
|
||||
'magenta-2': '#ffd6e7',
|
||||
magenta2: '#ffd6e7',
|
||||
'magenta-3': '#ffadd2',
|
||||
magenta3: '#ffadd2',
|
||||
'magenta-4': '#ff85c0',
|
||||
magenta4: '#ff85c0',
|
||||
'magenta-5': '#f759ab',
|
||||
magenta5: '#f759ab',
|
||||
'magenta-6': '#eb2f96',
|
||||
magenta6: '#eb2f96',
|
||||
'magenta-7': '#c41d7f',
|
||||
magenta7: '#c41d7f',
|
||||
'magenta-8': '#9e1068',
|
||||
magenta8: '#9e1068',
|
||||
'magenta-9': '#780650',
|
||||
magenta9: '#780650',
|
||||
'magenta-10': '#520339',
|
||||
magenta10: '#520339',
|
||||
'pink-1': '#fff0f6',
|
||||
pink1: '#fff0f6',
|
||||
'pink-2': '#ffd6e7',
|
||||
pink2: '#ffd6e7',
|
||||
'pink-3': '#ffadd2',
|
||||
pink3: '#ffadd2',
|
||||
'pink-4': '#ff85c0',
|
||||
pink4: '#ff85c0',
|
||||
'pink-5': '#f759ab',
|
||||
pink5: '#f759ab',
|
||||
'pink-6': '#eb2f96',
|
||||
pink6: '#eb2f96',
|
||||
'pink-7': '#c41d7f',
|
||||
pink7: '#c41d7f',
|
||||
'pink-8': '#9e1068',
|
||||
pink8: '#9e1068',
|
||||
'pink-9': '#780650',
|
||||
pink9: '#780650',
|
||||
'pink-10': '#520339',
|
||||
pink10: '#520339',
|
||||
'red-1': '#fff1f0',
|
||||
red1: '#fff1f0',
|
||||
'red-2': '#ffccc7',
|
||||
red2: '#ffccc7',
|
||||
'red-3': '#ffa39e',
|
||||
red3: '#ffa39e',
|
||||
'red-4': '#ff7875',
|
||||
red4: '#ff7875',
|
||||
'red-5': '#ff4d4f',
|
||||
red5: '#ff4d4f',
|
||||
'red-6': '#f5222d',
|
||||
red6: '#f5222d',
|
||||
'red-7': '#cf1322',
|
||||
red7: '#cf1322',
|
||||
'red-8': '#a8071a',
|
||||
red8: '#a8071a',
|
||||
'red-9': '#820014',
|
||||
red9: '#820014',
|
||||
'red-10': '#5c0011',
|
||||
red10: '#5c0011',
|
||||
'orange-1': '#fff7e6',
|
||||
orange1: '#fff7e6',
|
||||
'orange-2': '#ffe7ba',
|
||||
orange2: '#ffe7ba',
|
||||
'orange-3': '#ffd591',
|
||||
orange3: '#ffd591',
|
||||
'orange-4': '#ffc069',
|
||||
orange4: '#ffc069',
|
||||
'orange-5': '#ffa940',
|
||||
orange5: '#ffa940',
|
||||
'orange-6': '#fa8c16',
|
||||
orange6: '#fa8c16',
|
||||
'orange-7': '#d46b08',
|
||||
orange7: '#d46b08',
|
||||
'orange-8': '#ad4e00',
|
||||
orange8: '#ad4e00',
|
||||
'orange-9': '#873800',
|
||||
orange9: '#873800',
|
||||
'orange-10': '#612500',
|
||||
orange10: '#612500',
|
||||
'yellow-1': '#feffe6',
|
||||
yellow1: '#feffe6',
|
||||
'yellow-2': '#ffffb8',
|
||||
yellow2: '#ffffb8',
|
||||
'yellow-3': '#fffb8f',
|
||||
yellow3: '#fffb8f',
|
||||
'yellow-4': '#fff566',
|
||||
yellow4: '#fff566',
|
||||
'yellow-5': '#ffec3d',
|
||||
yellow5: '#ffec3d',
|
||||
'yellow-6': '#fadb14',
|
||||
yellow6: '#fadb14',
|
||||
'yellow-7': '#d4b106',
|
||||
yellow7: '#d4b106',
|
||||
'yellow-8': '#ad8b00',
|
||||
yellow8: '#ad8b00',
|
||||
'yellow-9': '#876800',
|
||||
yellow9: '#876800',
|
||||
'yellow-10': '#614700',
|
||||
yellow10: '#614700',
|
||||
'volcano-1': '#fff2e8',
|
||||
volcano1: '#fff2e8',
|
||||
'volcano-2': '#ffd8bf',
|
||||
volcano2: '#ffd8bf',
|
||||
'volcano-3': '#ffbb96',
|
||||
volcano3: '#ffbb96',
|
||||
'volcano-4': '#ff9c6e',
|
||||
volcano4: '#ff9c6e',
|
||||
'volcano-5': '#ff7a45',
|
||||
volcano5: '#ff7a45',
|
||||
'volcano-6': '#fa541c',
|
||||
volcano6: '#fa541c',
|
||||
'volcano-7': '#d4380d',
|
||||
volcano7: '#d4380d',
|
||||
'volcano-8': '#ad2102',
|
||||
volcano8: '#ad2102',
|
||||
'volcano-9': '#871400',
|
||||
volcano9: '#871400',
|
||||
'volcano-10': '#610b00',
|
||||
volcano10: '#610b00',
|
||||
'geekblue-1': '#f0f5ff',
|
||||
geekblue1: '#f0f5ff',
|
||||
'geekblue-2': '#d6e4ff',
|
||||
geekblue2: '#d6e4ff',
|
||||
'geekblue-3': '#adc6ff',
|
||||
geekblue3: '#adc6ff',
|
||||
'geekblue-4': '#85a5ff',
|
||||
geekblue4: '#85a5ff',
|
||||
'geekblue-5': '#597ef7',
|
||||
geekblue5: '#597ef7',
|
||||
'geekblue-6': '#2f54eb',
|
||||
geekblue6: '#2f54eb',
|
||||
'geekblue-7': '#1d39c4',
|
||||
geekblue7: '#1d39c4',
|
||||
'geekblue-8': '#10239e',
|
||||
geekblue8: '#10239e',
|
||||
'geekblue-9': '#061178',
|
||||
geekblue9: '#061178',
|
||||
'geekblue-10': '#030852',
|
||||
geekblue10: '#030852',
|
||||
'gold-1': '#fffbe6',
|
||||
gold1: '#fffbe6',
|
||||
'gold-2': '#fff1b8',
|
||||
gold2: '#fff1b8',
|
||||
'gold-3': '#ffe58f',
|
||||
gold3: '#ffe58f',
|
||||
'gold-4': '#ffd666',
|
||||
gold4: '#ffd666',
|
||||
'gold-5': '#ffc53d',
|
||||
gold5: '#ffc53d',
|
||||
'gold-6': '#faad14',
|
||||
gold6: '#faad14',
|
||||
'gold-7': '#d48806',
|
||||
gold7: '#d48806',
|
||||
'gold-8': '#ad6800',
|
||||
gold8: '#ad6800',
|
||||
'gold-9': '#874d00',
|
||||
gold9: '#874d00',
|
||||
'gold-10': '#613400',
|
||||
gold10: '#613400',
|
||||
'lime-1': '#fcffe6',
|
||||
lime1: '#fcffe6',
|
||||
'lime-2': '#f4ffb8',
|
||||
lime2: '#f4ffb8',
|
||||
'lime-3': '#eaff8f',
|
||||
lime3: '#eaff8f',
|
||||
'lime-4': '#d3f261',
|
||||
lime4: '#d3f261',
|
||||
'lime-5': '#bae637',
|
||||
lime5: '#bae637',
|
||||
'lime-6': '#a0d911',
|
||||
lime6: '#a0d911',
|
||||
'lime-7': '#7cb305',
|
||||
lime7: '#7cb305',
|
||||
'lime-8': '#5b8c00',
|
||||
lime8: '#5b8c00',
|
||||
'lime-9': '#3f6600',
|
||||
lime9: '#3f6600',
|
||||
'lime-10': '#254000',
|
||||
lime10: '#254000',
|
||||
colorText: 'rgba(0, 0, 0, 0.88)',
|
||||
colorTextSecondary: 'rgba(0, 0, 0, 0.65)',
|
||||
colorTextTertiary: 'rgba(0, 0, 0, 0.45)',
|
||||
colorTextQuaternary: 'rgba(0, 0, 0, 0.25)',
|
||||
colorFill: 'rgba(0, 0, 0, 0.15)',
|
||||
colorFillSecondary: 'rgba(0, 0, 0, 0.06)',
|
||||
colorFillTertiary: 'rgba(0, 0, 0, 0.04)',
|
||||
colorFillQuaternary: 'rgba(0, 0, 0, 0.02)',
|
||||
colorBgSolid: 'rgb(0, 0, 0)',
|
||||
colorBgSolidHover: 'rgba(0, 0, 0, 0.75)',
|
||||
colorBgSolidActive: 'rgba(0, 0, 0, 0.95)',
|
||||
colorBgLayout: '#f5f5f5',
|
||||
colorBgContainer: '#ffffff',
|
||||
colorBgElevated: '#ffffff',
|
||||
colorBgSpotlight: 'rgba(0, 0, 0, 0.85)',
|
||||
colorBgBlur: 'transparent',
|
||||
colorBorder: '#d9d9d9',
|
||||
colorBorderSecondary: '#f0f0f0',
|
||||
colorPrimaryBg: '#eeebfa',
|
||||
colorPrimaryBgHover: '#e2dfed',
|
||||
colorPrimaryBorder: '#c6c1e0',
|
||||
colorPrimaryBorderHover: '#9d94d4',
|
||||
colorPrimaryHover: '#756bc7',
|
||||
colorPrimaryActive: '#343194',
|
||||
colorPrimaryTextHover: '#756bc7',
|
||||
colorPrimaryText: '#4e47bb',
|
||||
colorPrimaryTextActive: '#343194',
|
||||
colorSuccessBg: '#f6ffed',
|
||||
colorSuccessBgHover: '#d9f7be',
|
||||
colorSuccessBorder: '#b7eb8f',
|
||||
colorSuccessBorderHover: '#95de64',
|
||||
colorSuccessHover: '#95de64',
|
||||
colorSuccessActive: '#389e0d',
|
||||
colorSuccessTextHover: '#73d13d',
|
||||
colorSuccessText: '#52c41a',
|
||||
colorSuccessTextActive: '#389e0d',
|
||||
colorErrorBg: '#fff2f0',
|
||||
colorErrorBgHover: '#fff1f0',
|
||||
colorErrorBgFilledHover: '#ffdfdc',
|
||||
colorErrorBgActive: '#ffccc7',
|
||||
colorErrorBorder: '#ffccc7',
|
||||
colorErrorBorderHover: '#ffa39e',
|
||||
colorErrorHover: '#ff7875',
|
||||
colorErrorActive: '#d9363e',
|
||||
colorErrorTextHover: '#ff7875',
|
||||
colorErrorText: '#ff4d4f',
|
||||
colorErrorTextActive: '#d9363e',
|
||||
colorWarningBg: '#fffbe6',
|
||||
colorWarningBgHover: '#fff1b8',
|
||||
colorWarningBorder: '#ffe58f',
|
||||
colorWarningBorderHover: '#ffd666',
|
||||
colorWarningHover: '#ffd666',
|
||||
colorWarningActive: '#d48806',
|
||||
colorWarningTextHover: '#ffc53d',
|
||||
colorWarningText: '#faad14',
|
||||
colorWarningTextActive: '#d48806',
|
||||
colorInfoBg: '#e6f4ff',
|
||||
colorInfoBgHover: '#bae0ff',
|
||||
colorInfoBorder: '#91caff',
|
||||
colorInfoBorderHover: '#69b1ff',
|
||||
colorInfoHover: '#69b1ff',
|
||||
colorInfoActive: '#0958d9',
|
||||
colorInfoTextHover: '#4096ff',
|
||||
colorInfoText: '#1677ff',
|
||||
colorInfoTextActive: '#0958d9',
|
||||
colorLinkActive: '#0958d9',
|
||||
colorBgMask: 'rgba(0, 0, 0, 0.45)',
|
||||
colorWhite: '#fff',
|
||||
fontSizeSM: 12,
|
||||
fontSizeLG: 16,
|
||||
fontSizeXL: 20,
|
||||
fontSizeHeading1: 38,
|
||||
fontSizeHeading2: 30,
|
||||
fontSizeHeading3: 24,
|
||||
fontSizeHeading4: 20,
|
||||
fontSizeHeading5: 16,
|
||||
lineHeight: 1.5714285714285714,
|
||||
lineHeightLG: 1.5,
|
||||
lineHeightSM: 1.6666666666666667,
|
||||
fontHeight: 22,
|
||||
fontHeightLG: 24,
|
||||
fontHeightSM: 20,
|
||||
lineHeightHeading1: 1.2105263157894737,
|
||||
lineHeightHeading2: 1.2666666666666666,
|
||||
lineHeightHeading3: 1.3333333333333333,
|
||||
lineHeightHeading4: 1.4,
|
||||
lineHeightHeading5: 1.5,
|
||||
sizeXXL: 48,
|
||||
sizeXL: 32,
|
||||
sizeLG: 24,
|
||||
sizeMD: 20,
|
||||
sizeMS: 16,
|
||||
size: 16,
|
||||
sizeSM: 12,
|
||||
sizeXS: 8,
|
||||
sizeXXS: 4,
|
||||
controlHeightSM: 24,
|
||||
controlHeightXS: 16,
|
||||
controlHeightLG: 40,
|
||||
motionDurationFast: '0.1s',
|
||||
motionDurationMid: '0.2s',
|
||||
motionDurationSlow: '0.3s',
|
||||
lineWidthBold: 2,
|
||||
borderRadiusXS: 2,
|
||||
borderRadiusSM: 4,
|
||||
borderRadiusLG: 8,
|
||||
borderRadiusOuter: 4,
|
||||
colorFillContent: 'rgba(0, 0, 0, 0.06)',
|
||||
colorFillContentHover: 'rgba(0, 0, 0, 0.15)',
|
||||
colorFillAlter: 'rgba(0, 0, 0, 0.02)',
|
||||
colorBgContainerDisabled: 'rgba(0, 0, 0, 0.04)',
|
||||
colorBorderBg: '#ffffff',
|
||||
colorSplit: 'rgba(5, 5, 5, 0.06)',
|
||||
colorTextPlaceholder: 'rgba(0, 0, 0, 0.25)',
|
||||
colorTextDisabled: 'rgba(0, 0, 0, 0.25)',
|
||||
colorTextHeading: 'rgba(0, 0, 0, 0.88)',
|
||||
colorTextLabel: 'rgba(0, 0, 0, 0.65)',
|
||||
colorTextDescription: 'rgba(0, 0, 0, 0.45)',
|
||||
colorTextLightSolid: '#fff',
|
||||
colorHighlight: '#ff4d4f',
|
||||
colorBgTextHover: 'rgba(0, 0, 0, 0.06)',
|
||||
colorBgTextActive: 'rgba(0, 0, 0, 0.15)',
|
||||
colorIcon: 'rgba(0, 0, 0, 0.45)',
|
||||
colorIconHover: 'rgba(0, 0, 0, 0.88)',
|
||||
colorErrorOutline: 'rgba(255, 38, 5, 0.06)',
|
||||
colorWarningOutline: 'rgba(255, 215, 5, 0.1)',
|
||||
fontSizeIcon: 12,
|
||||
lineWidthFocus: 3,
|
||||
controlOutlineWidth: 2,
|
||||
controlInteractiveSize: 16,
|
||||
controlItemBgHover: 'rgba(0, 0, 0, 0.04)',
|
||||
controlItemBgActive: '#eeebfa',
|
||||
controlItemBgActiveHover: '#e2dfed',
|
||||
controlItemBgActiveDisabled: 'rgba(0, 0, 0, 0.15)',
|
||||
controlTmpOutline: 'rgba(0, 0, 0, 0.02)',
|
||||
controlOutline: 'rgba(42, 5, 192, 0.08)',
|
||||
fontWeightStrong: 600,
|
||||
opacityLoading: 0.65,
|
||||
linkDecoration: 'none',
|
||||
linkHoverDecoration: 'none',
|
||||
linkFocusDecoration: 'none',
|
||||
controlPaddingHorizontal: 12,
|
||||
controlPaddingHorizontalSM: 8,
|
||||
paddingXXS: 4,
|
||||
paddingXS: 8,
|
||||
paddingSM: 12,
|
||||
padding: 16,
|
||||
paddingMD: 20,
|
||||
paddingLG: 24,
|
||||
paddingXL: 32,
|
||||
paddingContentHorizontalLG: 24,
|
||||
paddingContentVerticalLG: 16,
|
||||
paddingContentHorizontal: 16,
|
||||
paddingContentVertical: 12,
|
||||
paddingContentHorizontalSM: 16,
|
||||
paddingContentVerticalSM: 8,
|
||||
marginXXS: 4,
|
||||
marginXS: 8,
|
||||
marginSM: 12,
|
||||
margin: 16,
|
||||
marginMD: 20,
|
||||
marginLG: 24,
|
||||
marginXL: 32,
|
||||
marginXXL: 48,
|
||||
boxShadow:
|
||||
'\n 0 6px 16px 0 rgba(0, 0, 0, 0.08),\n 0 3px 6px -4px rgba(0, 0, 0, 0.12),\n 0 9px 28px 8px rgba(0, 0, 0, 0.05)\n ',
|
||||
boxShadowSecondary:
|
||||
'\n 0 6px 16px 0 rgba(0, 0, 0, 0.08),\n 0 3px 6px -4px rgba(0, 0, 0, 0.12),\n 0 9px 28px 8px rgba(0, 0, 0, 0.05)\n ',
|
||||
boxShadowTertiary:
|
||||
'\n 0 1px 2px 0 rgba(0, 0, 0, 0.03),\n 0 1px 6px -1px rgba(0, 0, 0, 0.02),\n 0 2px 4px 0 rgba(0, 0, 0, 0.02)\n ',
|
||||
screenXS: 480,
|
||||
screenXSMin: 480,
|
||||
screenXSMax: 575,
|
||||
screenSM: 576,
|
||||
screenSMMin: 576,
|
||||
screenSMMax: 767,
|
||||
screenMD: 768,
|
||||
screenMDMin: 768,
|
||||
screenMDMax: 991,
|
||||
screenLG: 992,
|
||||
screenLGMin: 992,
|
||||
screenLGMax: 1199,
|
||||
screenXL: 1200,
|
||||
screenXLMin: 1200,
|
||||
screenXLMax: 1599,
|
||||
screenXXL: 1600,
|
||||
screenXXLMin: 1600,
|
||||
boxShadowPopoverArrow: '2px 2px 5px rgba(0, 0, 0, 0.05)',
|
||||
boxShadowCard:
|
||||
'\n 0 1px 2px -2px rgba(0, 0, 0, 0.16),\n 0 3px 6px 0 rgba(0, 0, 0, 0.12),\n 0 5px 12px 4px rgba(0, 0, 0, 0.09)\n ',
|
||||
boxShadowDrawerRight:
|
||||
'\n -6px 0 16px 0 rgba(0, 0, 0, 0.08),\n -3px 0 6px -4px rgba(0, 0, 0, 0.12),\n -9px 0 28px 8px rgba(0, 0, 0, 0.05)\n ',
|
||||
boxShadowDrawerLeft:
|
||||
'\n 6px 0 16px 0 rgba(0, 0, 0, 0.08),\n 3px 0 6px -4px rgba(0, 0, 0, 0.12),\n 9px 0 28px 8px rgba(0, 0, 0, 0.05)\n ',
|
||||
boxShadowDrawerUp:
|
||||
'\n 0 6px 16px 0 rgba(0, 0, 0, 0.08),\n 0 3px 6px -4px rgba(0, 0, 0, 0.12),\n 0 9px 28px 8px rgba(0, 0, 0, 0.05)\n ',
|
||||
boxShadowDrawerDown:
|
||||
'\n 0 -6px 16px 0 rgba(0, 0, 0, 0.08),\n 0 -3px 6px -4px rgba(0, 0, 0, 0.12),\n 0 -9px 28px 8px rgba(0, 0, 0, 0.05)\n ',
|
||||
boxShadowTabsOverflowLeft: 'inset 10px 0 8px -8px rgba(0, 0, 0, 0.08)',
|
||||
boxShadowTabsOverflowRight: 'inset -10px 0 8px -8px rgba(0, 0, 0, 0.08)',
|
||||
boxShadowTabsOverflowTop: 'inset 0 10px 8px -8px rgba(0, 0, 0, 0.08)',
|
||||
boxShadowTabsOverflowBottom: 'inset 0 -10px 8px -8px rgba(0, 0, 0, 0.08)',
|
||||
isDarkMode: false
|
||||
}
|
||||
}
|
||||
|
||||
for (const key in globalVariables) {
|
||||
globalThis[key] = globalVariables[key]
|
||||
}
|
||||
23
app/src/main/assets/template/tool-view.html
Normal file
23
app/src/main/assets/template/tool-view.html
Normal file
@@ -0,0 +1,23 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<title>Preview</title>
|
||||
<script id="global-js-variables">
|
||||
{{replace_global_js_variables}}
|
||||
</script>
|
||||
<style id="global-css-variables">
|
||||
{{replace_global_css_variables}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<script type="module" id="appDictSrc">{{replace_dict_code}}</script>
|
||||
<script type="module" id="appBaseSrc">{{replace_base_code}}</script>
|
||||
<div id="root">
|
||||
<div style="position:absolute;top: 0;left:0;width:100%;height:100%;display: flex;justify-content: center;align-items: center;color: #777;">
|
||||
Loading...
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -2,13 +2,15 @@ package top.fatweb.oxygen.toolbox
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.SystemBarStyle
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.activity.viewModels
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi
|
||||
import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass
|
||||
import androidx.compose.runtime.Composable
|
||||
@@ -17,11 +19,14 @@ import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.webkit.WebViewCompat
|
||||
import dagger.hilt.EntryPoint
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
@@ -32,13 +37,15 @@ import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import top.fatweb.oxygen.toolbox.model.DarkThemeConfig
|
||||
import top.fatweb.oxygen.toolbox.model.LanguageConfig
|
||||
import top.fatweb.oxygen.toolbox.model.LaunchPageConfig
|
||||
import top.fatweb.oxygen.toolbox.model.ThemeBrandConfig
|
||||
import top.fatweb.oxygen.toolbox.model.userdata.DarkThemeConfig
|
||||
import top.fatweb.oxygen.toolbox.model.userdata.LanguageConfig
|
||||
import top.fatweb.oxygen.toolbox.model.userdata.LaunchPageConfig
|
||||
import top.fatweb.oxygen.toolbox.model.userdata.ThemeBrandConfig
|
||||
import top.fatweb.oxygen.toolbox.monitor.NetworkMonitor
|
||||
import top.fatweb.oxygen.toolbox.monitor.TimeZoneMonitor
|
||||
import top.fatweb.oxygen.toolbox.repository.UserDataRepository
|
||||
import top.fatweb.oxygen.toolbox.navigation.PREVIEW_ARG
|
||||
import top.fatweb.oxygen.toolbox.navigation.navigateToToolView
|
||||
import top.fatweb.oxygen.toolbox.repository.userdata.UserDataRepository
|
||||
import top.fatweb.oxygen.toolbox.ui.OxygenApp
|
||||
import top.fatweb.oxygen.toolbox.ui.rememberOxygenAppState
|
||||
import top.fatweb.oxygen.toolbox.ui.theme.OxygenTheme
|
||||
@@ -46,8 +53,6 @@ import top.fatweb.oxygen.toolbox.ui.util.LocalTimeZone
|
||||
import top.fatweb.oxygen.toolbox.ui.util.LocaleUtils
|
||||
import javax.inject.Inject
|
||||
|
||||
const val TAG = "MainActivity"
|
||||
|
||||
@OptIn(ExperimentalMaterial3WindowSizeClassApi::class)
|
||||
@AndroidEntryPoint
|
||||
class MainActivity : ComponentActivity() {
|
||||
@@ -87,18 +92,19 @@ class MainActivity : ComponentActivity() {
|
||||
LaunchedEffect(locale) {
|
||||
LocaleUtils.switchLocale(this@MainActivity, locale)
|
||||
}
|
||||
UseIsFirstLaunch(viewModel) {
|
||||
CheckWebView(it)
|
||||
}
|
||||
}
|
||||
|
||||
val darkTheme = shouldUseDarkTheme(uiState)
|
||||
LaunchedEffect(darkTheme) {
|
||||
enableEdgeToEdge(
|
||||
statusBarStyle = SystemBarStyle.auto(
|
||||
android.graphics.Color.TRANSPARENT,
|
||||
android.graphics.Color.TRANSPARENT
|
||||
lightScrim = android.graphics.Color.TRANSPARENT,
|
||||
darkScrim = android.graphics.Color.TRANSPARENT
|
||||
) { darkTheme },
|
||||
navigationBarStyle = SystemBarStyle.auto(
|
||||
lightScrim, darkScrim
|
||||
) { darkTheme }
|
||||
navigationBarStyle = SystemBarStyle.dark(android.graphics.Color.TRANSPARENT)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -111,9 +117,7 @@ class MainActivity : ComponentActivity() {
|
||||
|
||||
val currentTimeZone by appState.currentTimeZone.collectAsStateWithLifecycle()
|
||||
|
||||
CompositionLocalProvider(
|
||||
LocalTimeZone provides currentTimeZone
|
||||
) {
|
||||
CompositionLocalProvider(LocalTimeZone provides currentTimeZone) {
|
||||
OxygenTheme(
|
||||
darkTheme = darkTheme,
|
||||
androidTheme = shouldUseAndroidTheme(uiState),
|
||||
@@ -122,10 +126,21 @@ class MainActivity : ComponentActivity() {
|
||||
OxygenApp(appState)
|
||||
}
|
||||
}
|
||||
Log.d(TAG, "onCreate: C")
|
||||
}
|
||||
|
||||
Log.d(TAG, "onCreate: D")
|
||||
LaunchedEffect(intent.data) {
|
||||
intent.data?.run {
|
||||
val pathSegments = pathSegments
|
||||
val preview = getBooleanQueryParameter(PREVIEW_ARG, false)
|
||||
if (pathSegments.size == 2) {
|
||||
appState.navController.navigateToToolView(
|
||||
username = pathSegments[0],
|
||||
toolId = pathSegments[1],
|
||||
preview = preview
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EntryPoint
|
||||
@@ -137,67 +152,108 @@ class MainActivity : ComponentActivity() {
|
||||
override fun attachBaseContext(newBase: Context) {
|
||||
val userDataRepository =
|
||||
EntryPointAccessors.fromApplication<UserDataRepositoryEntryPoint>(newBase).userDataRepository
|
||||
super.attachBaseContext(LocaleUtils.attachBaseContext(newBase, runBlocking {
|
||||
userDataRepository.userData.first().languageConfig
|
||||
}))
|
||||
super.attachBaseContext(
|
||||
LocaleUtils.attachBaseContext(
|
||||
context = newBase,
|
||||
languageConfig = runBlocking {
|
||||
userDataRepository.userData.first().languageConfig
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun shouldUseDarkTheme(
|
||||
uiState: MainActivityUiState
|
||||
): Boolean = when (uiState) {
|
||||
MainActivityUiState.Loading -> isSystemInDarkTheme()
|
||||
is MainActivityUiState.Success -> when (uiState.userData.darkThemeConfig) {
|
||||
DarkThemeConfig.FOLLOW_SYSTEM -> isSystemInDarkTheme()
|
||||
DarkThemeConfig.LIGHT -> false
|
||||
DarkThemeConfig.DARK -> true
|
||||
private fun UseIsFirstLaunch(viewModel: MainActivityViewModel, callback: @Composable (ondDismiss: () -> Unit) -> Unit) {
|
||||
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
|
||||
if (!whatIsFirstLaunch(uiState)) {
|
||||
return
|
||||
}
|
||||
|
||||
callback {
|
||||
viewModel.updateIsNotFirstLaunch()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun shouldUseAndroidTheme(
|
||||
uiState: MainActivityUiState
|
||||
): Boolean = when (uiState) {
|
||||
MainActivityUiState.Loading -> false
|
||||
is MainActivityUiState.Success -> when (uiState.userData.themeBrandConfig) {
|
||||
ThemeBrandConfig.DEFAULT -> false
|
||||
ThemeBrandConfig.ANDROID -> true
|
||||
private fun whatIsFirstLaunch(uiState: MainActivityUiState): Boolean =
|
||||
when (uiState) {
|
||||
MainActivityUiState.Loading -> false
|
||||
is MainActivityUiState.Success ->
|
||||
!uiState.userData.isNotFirstLaunch
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CheckWebView(onDismiss: () -> Unit) {
|
||||
val versionName = WebViewCompat.getCurrentWebViewPackage(LocalContext.current)?.versionName
|
||||
if (versionName == null) {
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = {Text(text = stringResource(R.string.core_web_view_warning))},
|
||||
text = { Text(text = stringResource(R.string.core_cannot_load_web_view_version)) },
|
||||
confirmButton = {
|
||||
TextButton(onClick = onDismiss) {
|
||||
Text(text = stringResource(R.string.core_dismiss))
|
||||
}
|
||||
}
|
||||
)
|
||||
return
|
||||
}
|
||||
if (versionName.split(".").first().toInt() < 80) {
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = {Text(text = stringResource(R.string.core_web_view_warning))},
|
||||
text = { Text(text = stringResource(R.string.core_web_view_version_too_low)) },
|
||||
confirmButton = {
|
||||
TextButton(onClick = onDismiss) {
|
||||
Text(text = stringResource(R.string.core_dismiss))
|
||||
}
|
||||
}
|
||||
)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun shouldUseDynamicColor(
|
||||
uiState: MainActivityUiState
|
||||
): Boolean = when (uiState) {
|
||||
MainActivityUiState.Loading -> true
|
||||
is MainActivityUiState.Success -> uiState.userData.useDynamicColor
|
||||
}
|
||||
private fun shouldUseDarkTheme(uiState: MainActivityUiState): Boolean =
|
||||
when (uiState) {
|
||||
MainActivityUiState.Loading -> isSystemInDarkTheme()
|
||||
is MainActivityUiState.Success ->
|
||||
when (uiState.userData.darkThemeConfig) {
|
||||
DarkThemeConfig.FollowSystem -> isSystemInDarkTheme()
|
||||
DarkThemeConfig.Light -> false
|
||||
DarkThemeConfig.Dark -> true
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun whatLocale(
|
||||
uiState: MainActivityUiState
|
||||
): LanguageConfig = when (uiState) {
|
||||
MainActivityUiState.Loading -> LanguageConfig.FOLLOW_SYSTEM
|
||||
is MainActivityUiState.Success -> uiState.userData.languageConfig
|
||||
}
|
||||
private fun shouldUseAndroidTheme(uiState: MainActivityUiState): Boolean =
|
||||
when (uiState) {
|
||||
MainActivityUiState.Loading -> false
|
||||
is MainActivityUiState.Success ->
|
||||
when (uiState.userData.themeBrandConfig) {
|
||||
ThemeBrandConfig.Default -> false
|
||||
ThemeBrandConfig.Android -> true
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun whatLaunchPage(
|
||||
uiState: MainActivityUiState
|
||||
): LaunchPageConfig = when (uiState) {
|
||||
MainActivityUiState.Loading -> LaunchPageConfig.TOOLS
|
||||
is MainActivityUiState.Success -> uiState.userData.launchPageConfig
|
||||
}
|
||||
private fun shouldUseDynamicColor(uiState: MainActivityUiState): Boolean =
|
||||
when (uiState) {
|
||||
MainActivityUiState.Loading -> true
|
||||
is MainActivityUiState.Success -> uiState.userData.useDynamicColor
|
||||
}
|
||||
|
||||
/**
|
||||
* The default light scrim, as defined by androidx and the platform:
|
||||
* https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:activity/activity/src/main/java/androidx/activity/EdgeToEdge.kt;l=35-38;drc=27e7d52e8604a080133e8b842db10c89b4482598
|
||||
*/
|
||||
private val lightScrim = android.graphics.Color.argb(0xe6, 0xFF, 0xFF, 0xFF)
|
||||
@Composable
|
||||
private fun whatLocale(uiState: MainActivityUiState): LanguageConfig =
|
||||
when (uiState) {
|
||||
MainActivityUiState.Loading -> LanguageConfig.FollowSystem
|
||||
is MainActivityUiState.Success -> uiState.userData.languageConfig
|
||||
}
|
||||
|
||||
/**
|
||||
* The default dark scrim, as defined by androidx and the platform:
|
||||
* https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:activity/activity/src/main/java/androidx/activity/EdgeToEdge.kt;l=40-44;drc=27e7d52e8604a080133e8b842db10c89b4482598
|
||||
*/
|
||||
private val darkScrim = android.graphics.Color.argb(0x80, 0x1b, 0x1b, 0x1b)
|
||||
@Composable
|
||||
private fun whatLaunchPage(uiState: MainActivityUiState): LaunchPageConfig =
|
||||
when (uiState) {
|
||||
MainActivityUiState.Loading -> LaunchPageConfig.Tools
|
||||
is MainActivityUiState.Success -> uiState.userData.launchPageConfig
|
||||
}
|
||||
|
||||
@@ -7,25 +7,32 @@ import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import top.fatweb.oxygen.toolbox.model.UserData
|
||||
import top.fatweb.oxygen.toolbox.repository.UserDataRepository
|
||||
import kotlinx.coroutines.launch
|
||||
import top.fatweb.oxygen.toolbox.model.userdata.UserData
|
||||
import top.fatweb.oxygen.toolbox.repository.userdata.UserDataRepository
|
||||
import javax.inject.Inject
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
@HiltViewModel
|
||||
class MainActivityViewModel @Inject constructor(
|
||||
userDataRepository: UserDataRepository
|
||||
private val userDataRepository: UserDataRepository
|
||||
) : ViewModel() {
|
||||
val uiState: StateFlow<MainActivityUiState> = userDataRepository.userData.map {
|
||||
MainActivityUiState.Success(it)
|
||||
}.stateIn(
|
||||
scope = viewModelScope,
|
||||
initialValue = MainActivityUiState.Loading,
|
||||
started = SharingStarted.WhileSubscribed(5.seconds.inWholeMilliseconds)
|
||||
started = SharingStarted.WhileSubscribed(stopTimeoutMillis = 5.seconds.inWholeMilliseconds)
|
||||
)
|
||||
|
||||
fun updateIsNotFirstLaunch() {
|
||||
viewModelScope.launch {
|
||||
userDataRepository.updateIsNotFirstLaunch()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed interface MainActivityUiState {
|
||||
data object Loading : MainActivityUiState
|
||||
data class Success(val userData: UserData) : MainActivityUiState
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,19 @@ package top.fatweb.oxygen.toolbox
|
||||
|
||||
import android.app.Application
|
||||
import dagger.hilt.android.HiltAndroidApp
|
||||
import top.fatweb.oxygen.toolbox.repository.UserDataRepository
|
||||
import timber.log.Timber
|
||||
import top.fatweb.oxygen.toolbox.repository.userdata.UserDataRepository
|
||||
import top.fatweb.oxygen.toolbox.util.OxygenLogTree
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltAndroidApp
|
||||
class OxygenApplication : Application() {
|
||||
@Inject
|
||||
lateinit var userDataRepository: UserDataRepository
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
Timber.plant(if (BuildConfig.DEBUG) Timber.DebugTree() else OxygenLogTree(this))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package top.fatweb.oxygen.toolbox.data.lib
|
||||
|
||||
import android.content.Context
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.serialization.json.Json
|
||||
import top.fatweb.oxygen.toolbox.R
|
||||
import top.fatweb.oxygen.toolbox.model.lib.Dependencies
|
||||
import top.fatweb.oxygen.toolbox.network.Dispatcher
|
||||
import top.fatweb.oxygen.toolbox.network.OxygenDispatchers
|
||||
import javax.inject.Inject
|
||||
|
||||
class DepDataSource @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
@Dispatcher(OxygenDispatchers.IO) private val ioDispatcher: CoroutineDispatcher
|
||||
) {
|
||||
private val json = Json { ignoreUnknownKeys = true }
|
||||
|
||||
val dependencies = flow {
|
||||
val inputStream = context.resources.openRawResource(R.raw.dependencies)
|
||||
val jsonString = inputStream.bufferedReader().use { it.readText() }
|
||||
val dependencies = json.decodeFromString<Dependencies>(jsonString)
|
||||
emit(dependencies)
|
||||
}.flowOn(ioDispatcher)
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package top.fatweb.oxygen.toolbox.data.network
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import top.fatweb.oxygen.toolbox.model.Result
|
||||
import top.fatweb.oxygen.toolbox.network.model.PageVo
|
||||
import top.fatweb.oxygen.toolbox.network.model.ResponseResult
|
||||
import top.fatweb.oxygen.toolbox.network.model.ToolBaseVo
|
||||
import top.fatweb.oxygen.toolbox.network.model.ToolVo
|
||||
|
||||
interface OxygenNetworkDataSource {
|
||||
suspend fun getStore(
|
||||
searchValue: String = "",
|
||||
currentPage: Int = 1
|
||||
): ResponseResult<PageVo<ToolVo>>
|
||||
|
||||
fun detail(
|
||||
username: String,
|
||||
toolId: String,
|
||||
ver: String = "latest",
|
||||
platform: ToolBaseVo.Platform = ToolBaseVo.Platform.Android
|
||||
): Flow<Result<ToolVo>>
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package top.fatweb.oxygen.toolbox.data.tool
|
||||
|
||||
import android.content.Context
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import top.fatweb.oxygen.toolbox.network.Dispatcher
|
||||
import top.fatweb.oxygen.toolbox.network.OxygenDispatchers
|
||||
import javax.inject.Inject
|
||||
|
||||
class ToolDataSource @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
@Dispatcher(OxygenDispatchers.IO) private val ioDispatcher: CoroutineDispatcher
|
||||
) {
|
||||
val toolViewTemplate = flow {
|
||||
emit(
|
||||
context.assets.open("template/tool-view.html")
|
||||
.bufferedReader()
|
||||
.use {
|
||||
it.readText()
|
||||
}
|
||||
)
|
||||
}.flowOn(ioDispatcher)
|
||||
|
||||
fun getGlobalJsVariables(isDarkMode: Boolean) = flow {
|
||||
emit(
|
||||
context.assets.open(
|
||||
if (isDarkMode) "template/global-variables-dark.js"
|
||||
else "template/global-variables-light.js"
|
||||
)
|
||||
.bufferedReader()
|
||||
.use {
|
||||
it.readText()
|
||||
}
|
||||
)
|
||||
}.flowOn(ioDispatcher)
|
||||
|
||||
fun getGlobalCssVariables(isDarkMode: Boolean) = flow {
|
||||
emit(
|
||||
context.assets.open(
|
||||
if (isDarkMode) "template/global-variables-dark.css"
|
||||
else "template/global-variables-light.css"
|
||||
)
|
||||
.bufferedReader()
|
||||
.use {
|
||||
it.readText()
|
||||
}
|
||||
)
|
||||
}.flowOn(ioDispatcher)
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package top.fatweb.oxygen.toolbox.data.tool
|
||||
|
||||
import android.content.Context
|
||||
import androidx.room.Database
|
||||
import androidx.room.Room
|
||||
import androidx.room.RoomDatabase
|
||||
import top.fatweb.oxygen.toolbox.data.tool.dao.ToolDao
|
||||
import top.fatweb.oxygen.toolbox.model.tool.ToolEntity
|
||||
|
||||
@Database(
|
||||
entities = [ToolEntity::class],
|
||||
version = 1,
|
||||
autoMigrations = [],
|
||||
exportSchema = true
|
||||
)
|
||||
abstract class ToolDatabase : RoomDatabase() {
|
||||
abstract fun toolDao(): ToolDao
|
||||
|
||||
companion object {
|
||||
@Volatile
|
||||
private var INSTANCE: ToolDatabase? = null
|
||||
|
||||
fun getInstance(context: Context): ToolDatabase =
|
||||
INSTANCE ?: synchronized(this) {
|
||||
Room.databaseBuilder(
|
||||
context = context,
|
||||
klass = ToolDatabase::class.java,
|
||||
name = "tools.db"
|
||||
)
|
||||
.build()
|
||||
.also { INSTANCE = it }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package top.fatweb.oxygen.toolbox.data.tool.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Delete
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import androidx.room.Update
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import top.fatweb.oxygen.toolbox.model.tool.ToolEntity
|
||||
|
||||
@Dao
|
||||
interface ToolDao {
|
||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||
suspend fun insertTool(tool: ToolEntity)
|
||||
|
||||
@Update
|
||||
suspend fun updateTool(tool: ToolEntity)
|
||||
|
||||
@Delete
|
||||
suspend fun deleteTool(tool: ToolEntity)
|
||||
|
||||
@Query("SELECT * FROM tools " +
|
||||
"WHERE id = :id")
|
||||
fun selectToolById(id: Long): Flow<ToolEntity?>
|
||||
|
||||
@Query("SELECT * FROM tools " +
|
||||
"WHERE :searchValue = '' " +
|
||||
"OR name LIKE '%' || :searchValue || '%' COLLATE NOCASE " +
|
||||
"OR keywords LIKE '%\"%' || :searchValue || '%\"%' COLLATE NOCASE " +
|
||||
"ORDER BY updateTime DESC")
|
||||
fun selectAllTools(searchValue: String): Flow<List<ToolEntity>>
|
||||
|
||||
@Query("SELECT * FROM tools " +
|
||||
"WHERE isStar = 1 " +
|
||||
"AND (:searchValue = '' " +
|
||||
"OR name LIKE '%' || :searchValue || '%' COLLATE NOCASE " +
|
||||
"OR keywords LIKE '%\"%' || :searchValue || '%\"%' COLLATE NOCASE" +
|
||||
") " +
|
||||
"ORDER BY updateTime DESC")
|
||||
fun selectStarTools(searchValue: String): Flow<List<ToolEntity>>
|
||||
|
||||
@Query("SELECT * FROM tools " +
|
||||
"WHERE authorUsername = :username " +
|
||||
"and toolId = :toolId LIMIT 1")
|
||||
fun selectToolByUsernameAndToolId(username: String, toolId: String): Flow<ToolEntity?>
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package top.fatweb.oxygen.toolbox.datastore
|
||||
package top.fatweb.oxygen.toolbox.data.userdata
|
||||
|
||||
import androidx.datastore.core.DataMigration
|
||||
import top.fatweb.oxygen.toolbox.data.UserPreferences
|
||||
import top.fatweb.oxygen.toolbox.data.copy
|
||||
|
||||
internal object IntToStringIdsMigration : DataMigration<UserPreferences> {
|
||||
override suspend fun cleanUp() = Unit
|
||||
@@ -9,4 +11,4 @@ internal object IntToStringIdsMigration : DataMigration<UserPreferences> {
|
||||
currentData.copy { }
|
||||
|
||||
override suspend fun shouldMigrate(currentData: UserPreferences): Boolean = false
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,18 @@
|
||||
package top.fatweb.oxygen.toolbox.datastore
|
||||
package top.fatweb.oxygen.toolbox.data.userdata
|
||||
|
||||
import androidx.datastore.core.DataStore
|
||||
import kotlinx.coroutines.flow.map
|
||||
import top.fatweb.oxygen.toolbox.model.DarkThemeConfig
|
||||
import top.fatweb.oxygen.toolbox.model.LanguageConfig
|
||||
import top.fatweb.oxygen.toolbox.model.LaunchPageConfig
|
||||
import top.fatweb.oxygen.toolbox.model.ThemeBrandConfig
|
||||
import top.fatweb.oxygen.toolbox.model.UserData
|
||||
import top.fatweb.oxygen.toolbox.data.DarkThemeConfigProto
|
||||
import top.fatweb.oxygen.toolbox.data.LanguageConfigProto
|
||||
import top.fatweb.oxygen.toolbox.data.LaunchPageConfigProto
|
||||
import top.fatweb.oxygen.toolbox.data.ThemeBrandConfigProto
|
||||
import top.fatweb.oxygen.toolbox.data.UserPreferences
|
||||
import top.fatweb.oxygen.toolbox.data.copy
|
||||
import top.fatweb.oxygen.toolbox.model.userdata.DarkThemeConfig
|
||||
import top.fatweb.oxygen.toolbox.model.userdata.LanguageConfig
|
||||
import top.fatweb.oxygen.toolbox.model.userdata.LaunchPageConfig
|
||||
import top.fatweb.oxygen.toolbox.model.userdata.ThemeBrandConfig
|
||||
import top.fatweb.oxygen.toolbox.model.userdata.UserData
|
||||
import javax.inject.Inject
|
||||
|
||||
class OxygenPreferencesDataSource @Inject constructor(
|
||||
@@ -20,23 +26,23 @@ class OxygenPreferencesDataSource @Inject constructor(
|
||||
LanguageConfigProto.UNRECOGNIZED,
|
||||
LanguageConfigProto.LANGUAGE_CONFIG_UNSPECIFIED,
|
||||
LanguageConfigProto.LANGUAGE_CONFIG_FOLLOW_SYSTEM
|
||||
-> LanguageConfig.FOLLOW_SYSTEM
|
||||
-> LanguageConfig.FollowSystem
|
||||
|
||||
LanguageConfigProto.LANGUAGE_CONFIG_CHINESE
|
||||
-> LanguageConfig.CHINESE
|
||||
-> LanguageConfig.Chinese
|
||||
|
||||
LanguageConfigProto.LANGUAGE_CONFIG_ENGLISH
|
||||
-> LanguageConfig.ENGLISH
|
||||
-> LanguageConfig.English
|
||||
},
|
||||
launchPageConfig = when (it.launchPageConfig) {
|
||||
null,
|
||||
LaunchPageConfigProto.UNRECOGNIZED,
|
||||
LaunchPageConfigProto.LAUNCH_PAGE_CONFIG_UNSPECIFIED,
|
||||
LaunchPageConfigProto.LAUNCH_PAGE_CONFIG_TOOLS
|
||||
-> LaunchPageConfig.TOOLS
|
||||
-> LaunchPageConfig.Tools
|
||||
|
||||
LaunchPageConfigProto.LAUNCH_PAGE_CONFIG_STAR
|
||||
-> LaunchPageConfig.STAR
|
||||
-> LaunchPageConfig.Star
|
||||
},
|
||||
themeBrandConfig = when (it.themeBrandConfig) {
|
||||
null,
|
||||
@@ -44,10 +50,10 @@ class OxygenPreferencesDataSource @Inject constructor(
|
||||
ThemeBrandConfigProto.THEME_BRAND_CONFIG_UNSPECIFIED,
|
||||
ThemeBrandConfigProto.THEME_BRAND_CONFIG_DEFAULT
|
||||
->
|
||||
ThemeBrandConfig.DEFAULT
|
||||
ThemeBrandConfig.Default
|
||||
|
||||
ThemeBrandConfigProto.THEME_BRAND_CONFIG_ANDROID
|
||||
-> ThemeBrandConfig.ANDROID
|
||||
-> ThemeBrandConfig.Android
|
||||
},
|
||||
darkThemeConfig = when (it.darkThemeConfig) {
|
||||
null,
|
||||
@@ -55,15 +61,16 @@ class OxygenPreferencesDataSource @Inject constructor(
|
||||
DarkThemeConfigProto.DARK_THEME_CONFIG_UNSPECIFIED,
|
||||
DarkThemeConfigProto.DARK_THEME_CONFIG_FOLLOW_SYSTEM
|
||||
->
|
||||
DarkThemeConfig.FOLLOW_SYSTEM
|
||||
DarkThemeConfig.FollowSystem
|
||||
|
||||
DarkThemeConfigProto.DARK_THEME_CONFIG_LIGHT
|
||||
-> DarkThemeConfig.LIGHT
|
||||
-> DarkThemeConfig.Light
|
||||
|
||||
DarkThemeConfigProto.DARK_THEME_CONFIG_DARK
|
||||
-> DarkThemeConfig.DARK
|
||||
-> DarkThemeConfig.Dark
|
||||
},
|
||||
useDynamicColor = it.useDynamicColor
|
||||
useDynamicColor = it.useDynamicColor,
|
||||
isNotFirstLaunch = it.isNotFirstLaunch
|
||||
)
|
||||
}
|
||||
|
||||
@@ -71,9 +78,9 @@ class OxygenPreferencesDataSource @Inject constructor(
|
||||
userPreferences.updateData {
|
||||
it.copy {
|
||||
this.languageConfig = when (languageConfig) {
|
||||
LanguageConfig.FOLLOW_SYSTEM -> LanguageConfigProto.LANGUAGE_CONFIG_FOLLOW_SYSTEM
|
||||
LanguageConfig.CHINESE -> LanguageConfigProto.LANGUAGE_CONFIG_CHINESE
|
||||
LanguageConfig.ENGLISH -> LanguageConfigProto.LANGUAGE_CONFIG_ENGLISH
|
||||
LanguageConfig.FollowSystem -> LanguageConfigProto.LANGUAGE_CONFIG_FOLLOW_SYSTEM
|
||||
LanguageConfig.Chinese -> LanguageConfigProto.LANGUAGE_CONFIG_CHINESE
|
||||
LanguageConfig.English -> LanguageConfigProto.LANGUAGE_CONFIG_ENGLISH
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -83,8 +90,8 @@ class OxygenPreferencesDataSource @Inject constructor(
|
||||
userPreferences.updateData {
|
||||
it.copy {
|
||||
this.launchPageConfig = when (launchPageConfig) {
|
||||
LaunchPageConfig.TOOLS -> LaunchPageConfigProto.LAUNCH_PAGE_CONFIG_TOOLS
|
||||
LaunchPageConfig.STAR -> LaunchPageConfigProto.LAUNCH_PAGE_CONFIG_STAR
|
||||
LaunchPageConfig.Tools -> LaunchPageConfigProto.LAUNCH_PAGE_CONFIG_TOOLS
|
||||
LaunchPageConfig.Star -> LaunchPageConfigProto.LAUNCH_PAGE_CONFIG_STAR
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -94,8 +101,8 @@ class OxygenPreferencesDataSource @Inject constructor(
|
||||
userPreferences.updateData {
|
||||
it.copy {
|
||||
this.themeBrandConfig = when (themeBrandConfig) {
|
||||
ThemeBrandConfig.DEFAULT -> ThemeBrandConfigProto.THEME_BRAND_CONFIG_DEFAULT
|
||||
ThemeBrandConfig.ANDROID -> ThemeBrandConfigProto.THEME_BRAND_CONFIG_ANDROID
|
||||
ThemeBrandConfig.Default -> ThemeBrandConfigProto.THEME_BRAND_CONFIG_DEFAULT
|
||||
ThemeBrandConfig.Android -> ThemeBrandConfigProto.THEME_BRAND_CONFIG_ANDROID
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -105,9 +112,9 @@ class OxygenPreferencesDataSource @Inject constructor(
|
||||
userPreferences.updateData {
|
||||
it.copy {
|
||||
this.darkThemeConfig = when (darkThemeConfig) {
|
||||
DarkThemeConfig.FOLLOW_SYSTEM -> DarkThemeConfigProto.DARK_THEME_CONFIG_FOLLOW_SYSTEM
|
||||
DarkThemeConfig.LIGHT -> DarkThemeConfigProto.DARK_THEME_CONFIG_LIGHT
|
||||
DarkThemeConfig.DARK -> DarkThemeConfigProto.DARK_THEME_CONFIG_DARK
|
||||
DarkThemeConfig.FollowSystem -> DarkThemeConfigProto.DARK_THEME_CONFIG_FOLLOW_SYSTEM
|
||||
DarkThemeConfig.Light -> DarkThemeConfigProto.DARK_THEME_CONFIG_LIGHT
|
||||
DarkThemeConfig.Dark -> DarkThemeConfigProto.DARK_THEME_CONFIG_DARK
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -120,4 +127,12 @@ class OxygenPreferencesDataSource @Inject constructor(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun updateIsNotFirstLaunch() {
|
||||
userPreferences.updateData {
|
||||
it.copy {
|
||||
this.isNotFirstLaunch = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
package top.fatweb.oxygen.toolbox.datastore
|
||||
package top.fatweb.oxygen.toolbox.data.userdata
|
||||
|
||||
import androidx.datastore.core.CorruptionException
|
||||
import androidx.datastore.core.Serializer
|
||||
import com.google.protobuf.InvalidProtocolBufferException
|
||||
import top.fatweb.oxygen.toolbox.data.UserPreferences
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import javax.inject.Inject
|
||||
@@ -14,10 +15,13 @@ class UserPreferencesSerializer @Inject constructor() : Serializer<UserPreferenc
|
||||
try {
|
||||
UserPreferences.parseFrom(input)
|
||||
} catch (exception: InvalidProtocolBufferException) {
|
||||
throw CorruptionException("Cannot read proto.", exception)
|
||||
throw CorruptionException(
|
||||
message = "Cannot read proto.",
|
||||
cause = exception
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun writeTo(t: UserPreferences, output: OutputStream) {
|
||||
t.writeTo(output)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,8 @@ internal object CoroutineScopesModule {
|
||||
@Singleton
|
||||
@ApplicationScope
|
||||
fun providesCoroutineScope(
|
||||
@Dispatcher(OxygenDispatchers.Default) dispatcher: CoroutineDispatcher
|
||||
): CoroutineScope = CoroutineScope(SupervisorJob() + dispatcher)
|
||||
}
|
||||
@Dispatcher(OxygenDispatchers.Default)
|
||||
dispatcher: CoroutineDispatcher
|
||||
): CoroutineScope =
|
||||
CoroutineScope(SupervisorJob() + dispatcher)
|
||||
}
|
||||
|
||||
@@ -8,18 +8,33 @@ import top.fatweb.oxygen.toolbox.monitor.ConnectivityManagerNetworkMonitor
|
||||
import top.fatweb.oxygen.toolbox.monitor.NetworkMonitor
|
||||
import top.fatweb.oxygen.toolbox.monitor.TimeZoneBroadcastMonitor
|
||||
import top.fatweb.oxygen.toolbox.monitor.TimeZoneMonitor
|
||||
import top.fatweb.oxygen.toolbox.repository.OfflineFirstUserDataRepository
|
||||
import top.fatweb.oxygen.toolbox.repository.UserDataRepository
|
||||
import top.fatweb.oxygen.toolbox.repository.lib.DepRepository
|
||||
import top.fatweb.oxygen.toolbox.repository.lib.impl.LocalDepRepository
|
||||
import top.fatweb.oxygen.toolbox.repository.tool.StoreRepository
|
||||
import top.fatweb.oxygen.toolbox.repository.tool.ToolRepository
|
||||
import top.fatweb.oxygen.toolbox.repository.tool.impl.NetworkStoreRepository
|
||||
import top.fatweb.oxygen.toolbox.repository.tool.impl.OfflineToolRepository
|
||||
import top.fatweb.oxygen.toolbox.repository.userdata.UserDataRepository
|
||||
import top.fatweb.oxygen.toolbox.repository.userdata.impl.LocalUserDataRepository
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
abstract class DataModule {
|
||||
@Binds
|
||||
internal abstract fun bindsUserDataRepository(userDataRepository: OfflineFirstUserDataRepository): UserDataRepository
|
||||
|
||||
@Binds
|
||||
internal abstract fun bindsNetworkMonitor(networkMonitor: ConnectivityManagerNetworkMonitor): NetworkMonitor
|
||||
|
||||
@Binds
|
||||
internal abstract fun bindsTimeZoneMonitor(timeZoneMonitor: TimeZoneBroadcastMonitor): TimeZoneMonitor
|
||||
}
|
||||
|
||||
@Binds
|
||||
internal abstract fun bindsUserDataRepository(userDataRepository: LocalUserDataRepository): UserDataRepository
|
||||
|
||||
@Binds
|
||||
internal abstract fun bindsDepRepository(depRepository: LocalDepRepository): DepRepository
|
||||
|
||||
@Binds
|
||||
internal abstract fun bindsStoreRepository(storeRepository: NetworkStoreRepository): StoreRepository
|
||||
|
||||
@Binds
|
||||
internal abstract fun bindsToolRepository(toolRepository: OfflineToolRepository): ToolRepository
|
||||
}
|
||||
|
||||
@@ -11,9 +11,9 @@ import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import top.fatweb.oxygen.toolbox.datastore.IntToStringIdsMigration
|
||||
import top.fatweb.oxygen.toolbox.datastore.UserPreferences
|
||||
import top.fatweb.oxygen.toolbox.datastore.UserPreferencesSerializer
|
||||
import top.fatweb.oxygen.toolbox.data.UserPreferences
|
||||
import top.fatweb.oxygen.toolbox.data.userdata.IntToStringIdsMigration
|
||||
import top.fatweb.oxygen.toolbox.data.userdata.UserPreferencesSerializer
|
||||
import top.fatweb.oxygen.toolbox.network.Dispatcher
|
||||
import top.fatweb.oxygen.toolbox.network.OxygenDispatchers
|
||||
import javax.inject.Singleton
|
||||
@@ -38,4 +38,4 @@ object DataStoreModule {
|
||||
) {
|
||||
context.dataStoreFile("user_preferences.pb")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package top.fatweb.oxygen.toolbox.di
|
||||
|
||||
import android.content.Context
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import top.fatweb.oxygen.toolbox.data.tool.ToolDatabase
|
||||
import top.fatweb.oxygen.toolbox.data.tool.dao.ToolDao
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object DatabaseModule {
|
||||
@Provides
|
||||
fun provideToolDao(@ApplicationContext context: Context): ToolDao =
|
||||
ToolDatabase.getInstance(context).toolDao()
|
||||
}
|
||||
@@ -19,4 +19,4 @@ object DispatchersModule {
|
||||
@Provides
|
||||
@Dispatcher(OxygenDispatchers.Default)
|
||||
fun providesDefaultDispatcher(): CoroutineDispatcher = Dispatchers.Default
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package top.fatweb.oxygen.toolbox.di
|
||||
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.Call
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import top.fatweb.oxygen.toolbox.BuildConfig
|
||||
import top.fatweb.oxygen.toolbox.data.network.OxygenNetworkDataSource
|
||||
import top.fatweb.oxygen.toolbox.network.retrofit.RetrofitOxygenNetwork
|
||||
import top.fatweb.oxygen.toolbox.util.HttpLogger
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object NetworkModule {
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesNetworkJson(): Json = Json {
|
||||
ignoreUnknownKeys = true
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun okHttpCallFactory(): Call.Factory =
|
||||
OkHttpClient.Builder()
|
||||
.addInterceptor(
|
||||
HttpLoggingInterceptor(HttpLogger())
|
||||
.apply {
|
||||
level =
|
||||
if (BuildConfig.DEBUG) HttpLoggingInterceptor.Level.BODY else HttpLoggingInterceptor.Level.BASIC
|
||||
}
|
||||
)
|
||||
.build()
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesOxygenNetworkDataSource(
|
||||
networkJson: Json,
|
||||
okhttpCallFactory: dagger.Lazy<Call.Factory>
|
||||
): OxygenNetworkDataSource =
|
||||
RetrofitOxygenNetwork(networkJson, okhttpCallFactory)
|
||||
}
|
||||
@@ -1,20 +1,77 @@
|
||||
package top.fatweb.oxygen.toolbox.icon
|
||||
|
||||
import android.graphics.BitmapFactory
|
||||
import android.graphics.drawable.PictureDrawable
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.AccessTime
|
||||
import androidx.compose.material.icons.filled.Build
|
||||
import androidx.compose.material.icons.filled.Cancel
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material.icons.filled.Code
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material.icons.filled.Download
|
||||
import androidx.compose.material.icons.filled.Fullscreen
|
||||
import androidx.compose.material.icons.filled.FullscreenExit
|
||||
import androidx.compose.material.icons.filled.Inbox
|
||||
import androidx.compose.material.icons.filled.MoreVert
|
||||
import androidx.compose.material.icons.filled.Reorder
|
||||
import androidx.compose.material.icons.filled.Upgrade
|
||||
import androidx.compose.material.icons.outlined.Home
|
||||
import androidx.compose.material.icons.outlined.Info
|
||||
import androidx.compose.material.icons.outlined.StarBorder
|
||||
import androidx.compose.material.icons.outlined.Store
|
||||
import androidx.compose.material.icons.rounded.ArrowBackIosNew
|
||||
import androidx.compose.material.icons.rounded.CheckCircle
|
||||
import androidx.compose.material.icons.rounded.Home
|
||||
import androidx.compose.material.icons.rounded.KeyboardArrowDown
|
||||
import androidx.compose.material.icons.rounded.Search
|
||||
import androidx.compose.material.icons.rounded.Star
|
||||
import androidx.compose.material.icons.rounded.Store
|
||||
import androidx.compose.ui.graphics.ImageBitmap
|
||||
import androidx.compose.ui.graphics.asImageBitmap
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import com.caverock.androidsvg.SVG
|
||||
import top.fatweb.oxygen.toolbox.util.decodeToByteArray
|
||||
import top.fatweb.oxygen.toolbox.util.decodeToString
|
||||
import kotlin.io.encoding.Base64
|
||||
import kotlin.io.encoding.ExperimentalEncodingApi
|
||||
|
||||
object OxygenIcons {
|
||||
val ArrowDown = Icons.Rounded.KeyboardArrowDown
|
||||
val Back = Icons.Rounded.ArrowBackIosNew
|
||||
val Box = Icons.Default.Inbox
|
||||
val Close = Icons.Default.Close
|
||||
val Code = Icons.Default.Code
|
||||
val Delete = Icons.Default.Delete
|
||||
val Download = Icons.Default.Download
|
||||
val Error = Icons.Default.Cancel
|
||||
val FullScreen = Icons.Default.Fullscreen
|
||||
val FullScreenExit = Icons.Default.FullscreenExit
|
||||
val Home = Icons.Rounded.Home
|
||||
val HomeBorder = Icons.Outlined.Home
|
||||
val Info = Icons.Outlined.Info
|
||||
val MoreVert = Icons.Default.MoreVert
|
||||
val Reorder = Icons.Default.Reorder
|
||||
val Search = Icons.Rounded.Search
|
||||
val Star = Icons.Rounded.Star
|
||||
val StarBorder = Icons.Outlined.StarBorder
|
||||
val Search = Icons.Rounded.Search
|
||||
val MoreVert = Icons.Default.MoreVert
|
||||
val Back = Icons.Rounded.ArrowBackIosNew
|
||||
}
|
||||
val Store = Icons.Rounded.Store
|
||||
val StoreBorder = Icons.Outlined.Store
|
||||
val Success = Icons.Rounded.CheckCircle
|
||||
val Time = Icons.Default.AccessTime
|
||||
val Tool = Icons.Default.Build
|
||||
val Upgrade = Icons.Default.Upgrade
|
||||
|
||||
@OptIn(ExperimentalEncodingApi::class)
|
||||
fun fromSvgBase64(base64String: String): ImageBitmap {
|
||||
val svg = SVG.getFromString(Base64.decodeToString(base64String))
|
||||
val drawable = PictureDrawable(svg.renderToPicture())
|
||||
return drawable.toBitmap().asImageBitmap()
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalEncodingApi::class)
|
||||
fun fromPngBase64(base64String: String): ImageBitmap {
|
||||
val byteArray = Base64.decodeToByteArray(base64String)
|
||||
return BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size).asImageBitmap()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package top.fatweb.oxygen.toolbox.model
|
||||
|
||||
import androidx.room.TypeConverter
|
||||
import kotlinx.datetime.LocalDateTime
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import top.fatweb.oxygen.toolbox.model.tool.ToolEntity.Platform
|
||||
|
||||
class Converters {
|
||||
private val json = Json { ignoreUnknownKeys = true }
|
||||
|
||||
@TypeConverter
|
||||
fun fromPlatform(platform: Platform): String = platform.name
|
||||
|
||||
@TypeConverter
|
||||
fun toPlatform(name: String): Platform = Platform.valueOf(name)
|
||||
|
||||
@TypeConverter
|
||||
fun fromStringList(stringList: List<String>): String = json.encodeToString(stringList)
|
||||
|
||||
@TypeConverter
|
||||
fun toStringList(stringList: String): List<String> = json.decodeFromString(stringList)
|
||||
|
||||
@TypeConverter
|
||||
fun fromLocalDateTime(localDateTime: LocalDateTime): String = localDateTime.toString()
|
||||
|
||||
@TypeConverter
|
||||
fun toLocalDateTime(string: String): LocalDateTime = LocalDateTime.parse(string)
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package top.fatweb.oxygen.toolbox.model
|
||||
|
||||
enum class DarkThemeConfig {
|
||||
FOLLOW_SYSTEM,
|
||||
LIGHT,
|
||||
DARK,
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package top.fatweb.oxygen.toolbox.model
|
||||
|
||||
enum class LanguageConfig(val code: String? = null) {
|
||||
FOLLOW_SYSTEM,
|
||||
CHINESE("cn"),
|
||||
ENGLISH("en")
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
package top.fatweb.oxygen.toolbox.model
|
||||
|
||||
enum class LaunchPageConfig {
|
||||
TOOLS,
|
||||
STAR
|
||||
}
|
||||
13
app/src/main/kotlin/top/fatweb/oxygen/toolbox/model/Page.kt
Normal file
13
app/src/main/kotlin/top/fatweb/oxygen/toolbox/model/Page.kt
Normal file
@@ -0,0 +1,13 @@
|
||||
package top.fatweb.oxygen.toolbox.model
|
||||
|
||||
data class Page<T>(
|
||||
val total: Long,
|
||||
|
||||
val pages: Long,
|
||||
|
||||
val size: Long,
|
||||
|
||||
val current: Long,
|
||||
|
||||
val records: List<T>
|
||||
)
|
||||
@@ -0,0 +1,41 @@
|
||||
package top.fatweb.oxygen.toolbox.model
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onStart
|
||||
import top.fatweb.oxygen.toolbox.network.model.ResponseResult
|
||||
|
||||
sealed interface Result<out T> {
|
||||
data class Success<T>(val data: T) : Result<T>
|
||||
data class Fail(val message: String): Result<Nothing>
|
||||
data class Error(val exception: Throwable) : Result<Nothing>
|
||||
data object Loading : Result<Nothing>
|
||||
}
|
||||
|
||||
fun <T> Flow<ResponseResult<T>>.asResult(): Flow<Result<T>> = map<ResponseResult<T>, Result<T>> {
|
||||
if (it.success) {
|
||||
Result.Success(it.data!!)
|
||||
} else {
|
||||
Result.Fail(it.msg)
|
||||
}
|
||||
}
|
||||
.onStart { emit(Result.Loading) }
|
||||
.catch { emit(Result.Error(it)) }
|
||||
|
||||
fun <T, R> Result<T>.asExternalModel(block: (T) -> R): Result<R> =
|
||||
when (this) {
|
||||
is Result.Success -> {
|
||||
Result.Success(block(data))
|
||||
}
|
||||
|
||||
is Result.Fail -> {
|
||||
Result.Fail(message)
|
||||
}
|
||||
|
||||
is Result.Error -> {
|
||||
Result.Error(exception)
|
||||
}
|
||||
|
||||
Result.Loading -> Result.Loading
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
package top.fatweb.oxygen.toolbox.model
|
||||
|
||||
enum class ThemeBrandConfig {
|
||||
DEFAULT,
|
||||
ANDROID
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package top.fatweb.oxygen.toolbox.model.lib
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class Dependencies(
|
||||
val metadata: Metadata,
|
||||
|
||||
val libraries: List<Library>,
|
||||
|
||||
val licenses: Map<String, License>
|
||||
)
|
||||
@@ -0,0 +1,10 @@
|
||||
package top.fatweb.oxygen.toolbox.model.lib
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class Developer(
|
||||
val name: String? = null,
|
||||
|
||||
val organisationUrl: String? = null
|
||||
)
|
||||
@@ -0,0 +1,10 @@
|
||||
package top.fatweb.oxygen.toolbox.model.lib
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class Funding(
|
||||
val platform: String,
|
||||
|
||||
val url: String
|
||||
)
|
||||
@@ -0,0 +1,28 @@
|
||||
package top.fatweb.oxygen.toolbox.model.lib
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class Library(
|
||||
val uniqueId: String,
|
||||
|
||||
val artifactVersion: String? = null,
|
||||
|
||||
val name: String? = null,
|
||||
|
||||
val description: String? = null,
|
||||
|
||||
val website: String? = null,
|
||||
|
||||
val developers: List<Developer>,
|
||||
|
||||
val organization: Organization? = null,
|
||||
|
||||
val scm: Scm? = null,
|
||||
|
||||
val licenses: List<String>,
|
||||
|
||||
val funding: List<Funding>,
|
||||
|
||||
val tag: String? = null
|
||||
)
|
||||
@@ -0,0 +1,20 @@
|
||||
package top.fatweb.oxygen.toolbox.model.lib
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class License(
|
||||
val name: String,
|
||||
|
||||
val url: String? = null,
|
||||
|
||||
val year: String? = null,
|
||||
|
||||
val content: String? = null,
|
||||
|
||||
val internalHash: String? = null,
|
||||
|
||||
val hash: String,
|
||||
|
||||
val spdxId: String? = null
|
||||
)
|
||||
@@ -0,0 +1,8 @@
|
||||
package top.fatweb.oxygen.toolbox.model.lib
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class Metadata(
|
||||
val generated: String
|
||||
)
|
||||
@@ -0,0 +1,10 @@
|
||||
package top.fatweb.oxygen.toolbox.model.lib
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class Organization(
|
||||
val name: String,
|
||||
|
||||
val url: String? = null
|
||||
)
|
||||
@@ -0,0 +1,12 @@
|
||||
package top.fatweb.oxygen.toolbox.model.lib
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class Scm(
|
||||
val connection: String? = null,
|
||||
|
||||
val developerConnection: String? = null,
|
||||
|
||||
val url: String? = null
|
||||
)
|
||||
@@ -0,0 +1,95 @@
|
||||
package top.fatweb.oxygen.toolbox.model.tool
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import androidx.room.TypeConverters
|
||||
import kotlinx.datetime.LocalDateTime
|
||||
import top.fatweb.oxygen.toolbox.model.Converters
|
||||
|
||||
@Entity(tableName = "tools")
|
||||
@TypeConverters(Converters::class)
|
||||
data class ToolEntity(
|
||||
@PrimaryKey
|
||||
val id: Long,
|
||||
|
||||
val name: String,
|
||||
|
||||
val toolId: String,
|
||||
|
||||
val icon: String,
|
||||
|
||||
val platform: Platform,
|
||||
|
||||
val description: String? = null,
|
||||
|
||||
val base: String? = null,
|
||||
|
||||
val authorUsername: String,
|
||||
|
||||
val authorNickname: String,
|
||||
|
||||
val authorAvatar: String,
|
||||
|
||||
val ver: String,
|
||||
|
||||
val keywords: List<String>,
|
||||
|
||||
val categories: List<String>,
|
||||
|
||||
val source: String? = null,
|
||||
|
||||
val dist: String? = null,
|
||||
|
||||
val entryPoint: String,
|
||||
|
||||
val createTime: LocalDateTime,
|
||||
|
||||
val updateTime: LocalDateTime,
|
||||
|
||||
val isStar: Boolean = false,
|
||||
|
||||
var isInstalled: Boolean = false,
|
||||
|
||||
@ColumnInfo(defaultValue = "NULL")
|
||||
var upgrade: String? = null
|
||||
) {
|
||||
constructor(toolId: String, authorUsername: String, ver: String, upgrade: String? = null) :
|
||||
this(
|
||||
id = -1,
|
||||
name = "Unknown",
|
||||
toolId = toolId,
|
||||
icon = "",
|
||||
platform = Platform.Android,
|
||||
authorUsername = authorUsername,
|
||||
authorNickname = "Unknown",
|
||||
authorAvatar = "",
|
||||
ver = ver,
|
||||
keywords = emptyList(),
|
||||
categories = emptyList(),
|
||||
entryPoint = "",
|
||||
createTime = LocalDateTime(
|
||||
year = 1970,
|
||||
monthNumber = 1,
|
||||
dayOfMonth = 1,
|
||||
hour = 0,
|
||||
minute = 0
|
||||
),
|
||||
updateTime = LocalDateTime(
|
||||
year = 1970,
|
||||
monthNumber = 1,
|
||||
dayOfMonth = 1,
|
||||
hour = 0,
|
||||
minute = 0
|
||||
),
|
||||
upgrade = upgrade
|
||||
)
|
||||
|
||||
enum class Platform {
|
||||
Web,
|
||||
|
||||
Desktop,
|
||||
|
||||
Android
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package top.fatweb.oxygen.toolbox.model.tool
|
||||
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import top.fatweb.oxygen.toolbox.icon.OxygenIcons
|
||||
|
||||
data class ToolGroup(
|
||||
val id: String,
|
||||
|
||||
val icon: ImageVector = OxygenIcons.Box,
|
||||
|
||||
val title: String,
|
||||
|
||||
val tools: List<ToolEntity> = emptyList()
|
||||
)
|
||||
@@ -0,0 +1,7 @@
|
||||
package top.fatweb.oxygen.toolbox.model.userdata
|
||||
|
||||
enum class DarkThemeConfig {
|
||||
FollowSystem,
|
||||
Light,
|
||||
Dark,
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package top.fatweb.oxygen.toolbox.model.userdata
|
||||
|
||||
enum class LanguageConfig(val code: String? = null) {
|
||||
FollowSystem,
|
||||
Chinese("cn"),
|
||||
English("en")
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package top.fatweb.oxygen.toolbox.model.userdata
|
||||
|
||||
enum class LaunchPageConfig {
|
||||
Tools,
|
||||
Star
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package top.fatweb.oxygen.toolbox.model.userdata
|
||||
|
||||
enum class ThemeBrandConfig {
|
||||
Default,
|
||||
Android
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
package top.fatweb.oxygen.toolbox.model
|
||||
package top.fatweb.oxygen.toolbox.model.userdata
|
||||
|
||||
data class UserData(
|
||||
val languageConfig: LanguageConfig,
|
||||
val launchPageConfig: LaunchPageConfig,
|
||||
val themeBrandConfig: ThemeBrandConfig,
|
||||
val darkThemeConfig: DarkThemeConfig,
|
||||
val useDynamicColor: Boolean
|
||||
val useDynamicColor: Boolean,
|
||||
val isNotFirstLaunch: Boolean
|
||||
)
|
||||
@@ -57,13 +57,8 @@ internal class ConnectivityManagerNetworkMonitor @Inject constructor(
|
||||
}
|
||||
.conflate()
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
private fun ConnectivityManager.isCurrentlyConnected() = when {
|
||||
VERSION.SDK_INT >= VERSION_CODES.M ->
|
||||
activeNetwork
|
||||
?.let(::getNetworkCapabilities)
|
||||
?.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
|
||||
|
||||
else -> activeNetworkInfo?.isConnected
|
||||
} ?: false
|
||||
}
|
||||
private fun ConnectivityManager.isCurrentlyConnected() =
|
||||
activeNetwork
|
||||
?.let(::getNetworkCapabilities)
|
||||
?.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) == true
|
||||
}
|
||||
|
||||
@@ -4,4 +4,4 @@ import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface NetworkMonitor {
|
||||
val isOnline: Flow<Boolean>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,8 +43,8 @@ class TimeZoneBroadcastMonitor @Inject constructor(
|
||||
val zonIdFromIntent = if (Build.VERSION.SDK_INT < VERSION_CODES.R) {
|
||||
null
|
||||
} else {
|
||||
intent.getStringExtra(Intent.EXTRA_TIMEZONE)?.let { timeZoneId ->
|
||||
val zoneId = ZoneId.of(timeZoneId, ZoneId.SHORT_IDS)
|
||||
intent.getStringExtra(Intent.EXTRA_TIMEZONE)?.run {
|
||||
val zoneId = ZoneId.of(this, ZoneId.SHORT_IDS)
|
||||
zoneId.toKotlinTimeZone()
|
||||
}
|
||||
}
|
||||
@@ -64,5 +64,9 @@ class TimeZoneBroadcastMonitor @Inject constructor(
|
||||
.distinctUntilChanged()
|
||||
.conflate()
|
||||
.flowOn(ioDispatcher)
|
||||
.shareIn(appScope, SharingStarted.WhileSubscribed(5.seconds.inWholeMilliseconds), 1)
|
||||
}
|
||||
.shareIn(
|
||||
scope = appScope,
|
||||
started = SharingStarted.WhileSubscribed(stopTimeoutMillis = 5.seconds.inWholeMilliseconds),
|
||||
replay = 1
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package top.fatweb.oxygen.toolbox.navigation
|
||||
|
||||
import androidx.compose.animation.slideInHorizontally
|
||||
import androidx.compose.animation.slideOutHorizontally
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavOptions
|
||||
import androidx.navigation.compose.composable
|
||||
import top.fatweb.oxygen.toolbox.ui.about.AboutRoute
|
||||
|
||||
const val ABOUT_ROUTE = "about_route"
|
||||
|
||||
fun NavController.navigateToAbout(navOptions: NavOptions? = null) =
|
||||
navigate(route = ABOUT_ROUTE, navOptions = navOptions)
|
||||
|
||||
fun NavGraphBuilder.aboutScreen(
|
||||
onNavigateToLibraries: () -> Unit,
|
||||
onBackClick: () -> Unit
|
||||
) {
|
||||
composable(
|
||||
route = ABOUT_ROUTE,
|
||||
enterTransition = {
|
||||
slideInHorizontally { it }
|
||||
},
|
||||
popEnterTransition = null,
|
||||
popExitTransition = {
|
||||
slideOutHorizontally { it }
|
||||
}
|
||||
) {
|
||||
AboutRoute(
|
||||
onNavigateToLibraries = onNavigateToLibraries,
|
||||
onBackClick = onBackClick
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package top.fatweb.oxygen.toolbox.navigation
|
||||
|
||||
import androidx.compose.animation.slideInHorizontally
|
||||
import androidx.compose.animation.slideOutHorizontally
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavOptions
|
||||
import androidx.navigation.compose.composable
|
||||
import top.fatweb.oxygen.toolbox.ui.about.LibrariesRoute
|
||||
|
||||
const val LIBRARIES_ROUTE = "libraries_route"
|
||||
|
||||
fun NavController.navigateToLibraries(navOptions: NavOptions? = null) =
|
||||
navigate(route = LIBRARIES_ROUTE, navOptions = navOptions)
|
||||
|
||||
fun NavGraphBuilder.librariesScreen(
|
||||
onBackClick: () -> Unit
|
||||
) {
|
||||
composable(
|
||||
route = LIBRARIES_ROUTE,
|
||||
enterTransition = {
|
||||
slideInHorizontally { it }
|
||||
},
|
||||
popEnterTransition = null,
|
||||
popExitTransition = {
|
||||
slideOutHorizontally { it }
|
||||
}
|
||||
) {
|
||||
LibrariesRoute(onBackClick = onBackClick)
|
||||
}
|
||||
}
|
||||
@@ -1,31 +1,73 @@
|
||||
package top.fatweb.oxygen.toolbox.navigation
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavDestination
|
||||
import androidx.navigation.compose.NavHost
|
||||
import top.fatweb.oxygen.toolbox.ui.OxygenAppState
|
||||
import top.fatweb.oxygen.toolbox.ui.util.LocalFullScreen
|
||||
|
||||
@Composable
|
||||
fun OxygenNavHost(
|
||||
modifier: Modifier = Modifier,
|
||||
appState: OxygenAppState,
|
||||
onShowSnackbar: suspend (String, String?) -> Boolean,
|
||||
startDestination: String
|
||||
startDestination: String,
|
||||
isVertical: Boolean,
|
||||
searchValue: String,
|
||||
searchCount: Int,
|
||||
onShowSnackbar: suspend (message: String, action: String?) -> Boolean
|
||||
) {
|
||||
val navController = appState.navController
|
||||
val fullScreen = LocalFullScreen.current
|
||||
|
||||
LaunchedEffect(navController) {
|
||||
navController.addOnDestinationChangedListener(object :
|
||||
NavController.OnDestinationChangedListener {
|
||||
override fun onDestinationChanged(
|
||||
controller: NavController,
|
||||
destination: NavDestination,
|
||||
arguments: Bundle?
|
||||
) {
|
||||
fullScreen.onStateChange.invoke(false)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
NavHost(
|
||||
modifier = modifier,
|
||||
navController = navController,
|
||||
startDestination = startDestination
|
||||
) {
|
||||
searchScreen(
|
||||
aboutScreen(
|
||||
onBackClick = navController::popBackStack,
|
||||
onNavigateToLibraries = navController::navigateToLibraries
|
||||
)
|
||||
librariesScreen(
|
||||
onBackClick = navController::popBackStack
|
||||
)
|
||||
toolStoreScreen(
|
||||
isVertical = isVertical,
|
||||
searchValue = searchValue,
|
||||
searchCount = searchCount,
|
||||
onNavigateToToolView = navController::navigateToToolView
|
||||
)
|
||||
toolsScreen(
|
||||
|
||||
isVertical = isVertical,
|
||||
searchValue = searchValue,
|
||||
onShowSnackbar = onShowSnackbar,
|
||||
onNavigateToToolView = navController::navigateToToolView,
|
||||
onNavigateToToolStore = { appState.navigateToTopLevelDestination(TopLevelDestination.ToolStore) }
|
||||
)
|
||||
starScreen(
|
||||
|
||||
isVertical = isVertical,
|
||||
searchValue = searchValue,
|
||||
onNavigateToToolView = navController::navigateToToolView
|
||||
)
|
||||
toolViewScreen(
|
||||
onBackClick = navController::popBackStack
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
package top.fatweb.oxygen.toolbox.navigation
|
||||
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavOptions
|
||||
import androidx.navigation.compose.composable
|
||||
import top.fatweb.oxygen.toolbox.ui.search.SearchRoute
|
||||
|
||||
const val SEARCH_ROUTE = "search_route"
|
||||
|
||||
fun NavController.navigateToSearch(navOptions: NavOptions? = null) =
|
||||
navigate(SEARCH_ROUTE, navOptions)
|
||||
|
||||
fun NavGraphBuilder.searchScreen(
|
||||
onBackClick: () -> Unit
|
||||
) {
|
||||
composable(
|
||||
route = SEARCH_ROUTE
|
||||
) {
|
||||
SearchRoute(onBackClick = onBackClick)
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,49 @@
|
||||
package top.fatweb.oxygen.toolbox.navigation
|
||||
|
||||
import androidx.compose.animation.slideInHorizontally
|
||||
import androidx.compose.animation.slideInVertically
|
||||
import androidx.compose.animation.slideOutHorizontally
|
||||
import androidx.compose.animation.slideOutVertically
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavOptions
|
||||
import androidx.navigation.compose.composable
|
||||
import top.fatweb.oxygen.toolbox.ui.star.StarRoute
|
||||
|
||||
const val STAR_ROUTE = "star_route"
|
||||
|
||||
fun NavController.navigateToStar(navOptions: NavOptions) = navigate(STAR_ROUTE, navOptions)
|
||||
fun NavController.navigateToStar(navOptions: NavOptions) =
|
||||
navigate(route = STAR_ROUTE, navOptions = navOptions)
|
||||
|
||||
fun NavGraphBuilder.starScreen() {
|
||||
fun NavGraphBuilder.starScreen(
|
||||
isVertical: Boolean,
|
||||
searchValue: String,
|
||||
onNavigateToToolView: (username: String, toolId: String, preview: Boolean) -> Unit
|
||||
) {
|
||||
composable(
|
||||
route = STAR_ROUTE
|
||||
) {
|
||||
route = STAR_ROUTE,
|
||||
enterTransition = {
|
||||
when (initialState.destination.route) {
|
||||
TOOL_STORE_ROUTE, TOOLS_ROUTE ->
|
||||
if (isVertical) slideInHorizontally { it }
|
||||
else slideInVertically { it }
|
||||
|
||||
else -> null
|
||||
}
|
||||
},
|
||||
exitTransition = {
|
||||
when (targetState.destination.route) {
|
||||
TOOL_STORE_ROUTE, TOOLS_ROUTE ->
|
||||
if (isVertical) slideOutHorizontally { it }
|
||||
else slideOutVertically { it }
|
||||
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
) {
|
||||
StarRoute(
|
||||
searchValue = searchValue,
|
||||
onNavigateToToolView = onNavigateToToolView
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package top.fatweb.oxygen.toolbox.navigation
|
||||
|
||||
import androidx.compose.animation.slideInHorizontally
|
||||
import androidx.compose.animation.slideInVertically
|
||||
import androidx.compose.animation.slideOutHorizontally
|
||||
import androidx.compose.animation.slideOutVertically
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavOptions
|
||||
import androidx.navigation.compose.composable
|
||||
import top.fatweb.oxygen.toolbox.ui.store.ToolStoreRoute
|
||||
|
||||
const val TOOL_STORE_ROUTE = "tool_store_route"
|
||||
|
||||
fun NavController.navigateToToolStore(navOptions: NavOptions? = null) =
|
||||
navigate(route = TOOL_STORE_ROUTE, navOptions = navOptions)
|
||||
|
||||
fun NavGraphBuilder.toolStoreScreen(
|
||||
isVertical: Boolean,
|
||||
searchValue: String,
|
||||
searchCount: Int,
|
||||
onNavigateToToolView: (username: String, toolId: String, preview: Boolean) -> Unit
|
||||
) {
|
||||
composable(
|
||||
route = TOOL_STORE_ROUTE,
|
||||
enterTransition = {
|
||||
when (initialState.destination.route) {
|
||||
TOOLS_ROUTE, STAR_ROUTE ->
|
||||
if (isVertical) slideInHorizontally { -it }
|
||||
else slideInVertically { -it }
|
||||
|
||||
else -> null
|
||||
}
|
||||
},
|
||||
exitTransition = {
|
||||
when (targetState.destination.route) {
|
||||
TOOLS_ROUTE, STAR_ROUTE ->
|
||||
if (isVertical) slideOutHorizontally { -it }
|
||||
else slideOutVertically { -it }
|
||||
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
) {
|
||||
ToolStoreRoute(
|
||||
searchValue = searchValue,
|
||||
searchCount = searchCount,
|
||||
onNavigateToToolView = onNavigateToToolView
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package top.fatweb.oxygen.toolbox.navigation
|
||||
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavOptionsBuilder
|
||||
import androidx.navigation.NavType
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.navArgument
|
||||
import top.fatweb.oxygen.toolbox.ui.view.ToolViewRoute
|
||||
import java.net.URLDecoder
|
||||
import java.net.URLEncoder
|
||||
import kotlin.text.Charsets.UTF_8
|
||||
|
||||
private val URL_CHARACTER_ENCODING = UTF_8.name()
|
||||
|
||||
internal const val USER_NAME_ARG = "username"
|
||||
internal const val TOOL_ID_ARG = "toolId"
|
||||
internal const val PREVIEW_ARG = "preview"
|
||||
const val TOOL_VIEW_ROUTE = "tool_view_route"
|
||||
|
||||
internal class ToolViewArgs(
|
||||
val username: String,
|
||||
val toolId: String,
|
||||
val preview: Boolean
|
||||
) {
|
||||
constructor(savedStateHandle: SavedStateHandle) :
|
||||
this(
|
||||
URLDecoder.decode(
|
||||
checkNotNull(savedStateHandle[USER_NAME_ARG]),
|
||||
URL_CHARACTER_ENCODING
|
||||
),
|
||||
URLDecoder.decode(
|
||||
checkNotNull(savedStateHandle[TOOL_ID_ARG]),
|
||||
URL_CHARACTER_ENCODING
|
||||
),
|
||||
checkNotNull(savedStateHandle[PREVIEW_ARG])
|
||||
)
|
||||
}
|
||||
|
||||
fun NavController.navigateToToolView(
|
||||
username: String,
|
||||
toolId: String,
|
||||
preview: Boolean,
|
||||
navOptions: NavOptionsBuilder.() -> Unit = {}
|
||||
) {
|
||||
val encodedUsername = URLEncoder.encode(username, URL_CHARACTER_ENCODING)
|
||||
val encodedToolId = URLEncoder.encode(toolId, URL_CHARACTER_ENCODING)
|
||||
val newRoute = "$TOOL_VIEW_ROUTE/$encodedUsername/$encodedToolId?$PREVIEW_ARG=$preview"
|
||||
navigate(newRoute) {
|
||||
navOptions()
|
||||
}
|
||||
}
|
||||
|
||||
fun NavGraphBuilder.toolViewScreen(
|
||||
onBackClick: () -> Unit
|
||||
) {
|
||||
composable(
|
||||
route = "${TOOL_VIEW_ROUTE}/{$USER_NAME_ARG}/{$TOOL_ID_ARG}?$PREVIEW_ARG={$PREVIEW_ARG}",
|
||||
arguments = listOf(
|
||||
navArgument(USER_NAME_ARG) { type = NavType.StringType },
|
||||
navArgument(TOOL_ID_ARG) { type = NavType.StringType },
|
||||
navArgument(PREVIEW_ARG) { type = NavType.BoolType; defaultValue = false }
|
||||
)
|
||||
) {
|
||||
ToolViewRoute(
|
||||
onBackClick = onBackClick
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,61 @@
|
||||
package top.fatweb.oxygen.toolbox.navigation
|
||||
|
||||
import androidx.compose.animation.slideInHorizontally
|
||||
import androidx.compose.animation.slideInVertically
|
||||
import androidx.compose.animation.slideOutHorizontally
|
||||
import androidx.compose.animation.slideOutVertically
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavOptions
|
||||
import androidx.navigation.compose.composable
|
||||
import top.fatweb.oxygen.toolbox.ui.tools.ToolsRoute
|
||||
|
||||
const val TOOLS_ROUTE = "tools_route"
|
||||
|
||||
fun NavController.navigateToTools(navOptions: NavOptions) = navigate(TOOLS_ROUTE, navOptions)
|
||||
fun NavController.navigateToTools(navOptions: NavOptions) =
|
||||
navigate(route = TOOLS_ROUTE, navOptions = navOptions)
|
||||
|
||||
fun NavGraphBuilder.toolsScreen() {
|
||||
fun NavGraphBuilder.toolsScreen(
|
||||
isVertical: Boolean,
|
||||
searchValue: String,
|
||||
onNavigateToToolView: (username: String, toolId: String, preview: Boolean) -> Unit,
|
||||
onNavigateToToolStore: () -> Unit,
|
||||
onShowSnackbar: suspend (message: String, action: String?) -> Boolean
|
||||
) {
|
||||
composable(
|
||||
route = TOOLS_ROUTE
|
||||
) { }
|
||||
}
|
||||
route = TOOLS_ROUTE,
|
||||
enterTransition = {
|
||||
when (initialState.destination.route) {
|
||||
TOOL_STORE_ROUTE ->
|
||||
if (isVertical) slideInHorizontally { it }
|
||||
else slideInVertically { it }
|
||||
|
||||
STAR_ROUTE ->
|
||||
if (isVertical) slideInHorizontally { -it }
|
||||
else slideInVertically { -it }
|
||||
|
||||
else -> null
|
||||
}
|
||||
},
|
||||
exitTransition = {
|
||||
when (targetState.destination.route) {
|
||||
TOOL_STORE_ROUTE ->
|
||||
if (isVertical) slideOutHorizontally { it }
|
||||
else slideOutVertically { it }
|
||||
|
||||
STAR_ROUTE ->
|
||||
if (isVertical) slideOutHorizontally { -it }
|
||||
else slideOutVertically { -it }
|
||||
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
) {
|
||||
ToolsRoute(
|
||||
searchValue = searchValue,
|
||||
onNavigateToToolView = onNavigateToToolView,
|
||||
onNavigateToToolStore = onNavigateToToolStore,
|
||||
onShowSnackbar = onShowSnackbar
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +1,38 @@
|
||||
package top.fatweb.oxygen.toolbox.navigation
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import top.fatweb.oxygen.toolbox.R
|
||||
import top.fatweb.oxygen.toolbox.icon.OxygenIcons
|
||||
|
||||
enum class TopLevelDestination(
|
||||
val route: String,
|
||||
val selectedIcon: ImageVector,
|
||||
val unselectedIcon: ImageVector,
|
||||
val iconTextId: Int,
|
||||
val titleTextId: Int
|
||||
@StringRes val iconTextId: Int,
|
||||
@StringRes val titleTextId: Int
|
||||
) {
|
||||
TOOLS(
|
||||
ToolStore(
|
||||
route = "tool_store_route",
|
||||
selectedIcon = OxygenIcons.Store,
|
||||
unselectedIcon = OxygenIcons.StoreBorder,
|
||||
iconTextId = R.string.feature_store_title,
|
||||
titleTextId = R.string.feature_store_title
|
||||
),
|
||||
|
||||
Tools(
|
||||
route = "tools_route",
|
||||
selectedIcon = OxygenIcons.Home,
|
||||
unselectedIcon = OxygenIcons.HomeBorder,
|
||||
iconTextId = R.string.feature_tools_title,
|
||||
titleTextId = R.string.feature_tools_title
|
||||
),
|
||||
|
||||
STAR(
|
||||
Star(
|
||||
route = "star_route",
|
||||
selectedIcon = OxygenIcons.Star,
|
||||
unselectedIcon = OxygenIcons.StarBorder,
|
||||
iconTextId = R.string.feature_star_title,
|
||||
titleTextId = R.string.feature_star_title
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,4 +9,4 @@ annotation class Dispatcher(val oxygenDispatcher: OxygenDispatchers)
|
||||
enum class OxygenDispatchers {
|
||||
Default,
|
||||
IO
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package top.fatweb.oxygen.toolbox.network.model
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import top.fatweb.oxygen.toolbox.model.Page
|
||||
|
||||
@Serializable
|
||||
data class PageVo<T>(
|
||||
val total: Long,
|
||||
|
||||
val pages: Long,
|
||||
|
||||
val size: Long,
|
||||
|
||||
val current: Long,
|
||||
|
||||
val records: List<T>
|
||||
)
|
||||
|
||||
fun <T, R> PageVo<T>.asExternalModel(block: (T) -> R): Page<R> =
|
||||
Page(
|
||||
total = total,
|
||||
pages = pages,
|
||||
size = size,
|
||||
current = current,
|
||||
records = records.map(block)
|
||||
)
|
||||
@@ -0,0 +1,14 @@
|
||||
package top.fatweb.oxygen.toolbox.network.model
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class ResponseResult<T>(
|
||||
val code: Long,
|
||||
|
||||
val success: Boolean,
|
||||
|
||||
val msg: String,
|
||||
|
||||
val data: T? = null
|
||||
)
|
||||
@@ -0,0 +1,45 @@
|
||||
package top.fatweb.oxygen.toolbox.network.model
|
||||
|
||||
import kotlinx.datetime.LocalDateTime
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import top.fatweb.oxygen.toolbox.model.tool.ToolEntity
|
||||
import top.fatweb.oxygen.toolbox.network.serializer.LocalDateTimeSerializer
|
||||
|
||||
@Serializable
|
||||
data class ToolBaseVo(
|
||||
val id: Long,
|
||||
|
||||
val name: String,
|
||||
|
||||
val source: ToolDataVo? = null,
|
||||
|
||||
val dist: ToolDataVo,
|
||||
|
||||
val platform: Platform? = null,
|
||||
|
||||
val compiled: Boolean? = null,
|
||||
|
||||
@Serializable(LocalDateTimeSerializer::class)
|
||||
val createTime: LocalDateTime? = null,
|
||||
|
||||
@Serializable(LocalDateTimeSerializer::class)
|
||||
val updateTime: LocalDateTime? = null
|
||||
) {
|
||||
@Serializable
|
||||
enum class Platform {
|
||||
@SerialName("WEB")
|
||||
Web,
|
||||
|
||||
@SerialName("DESKTOP")
|
||||
Desktop,
|
||||
|
||||
@SerialName("ANDROID")
|
||||
Android;
|
||||
|
||||
override fun toString(): String =
|
||||
javaClass.getField(name).getAnnotation(SerialName::class.java)!!.value
|
||||
}
|
||||
}
|
||||
|
||||
fun ToolBaseVo.Platform.asExternalModel() = ToolEntity.Platform.valueOf(this.name)
|
||||
@@ -0,0 +1,20 @@
|
||||
package top.fatweb.oxygen.toolbox.network.model
|
||||
|
||||
import kotlinx.datetime.LocalDateTime
|
||||
import kotlinx.serialization.Serializable
|
||||
import top.fatweb.oxygen.toolbox.network.serializer.LocalDateTimeSerializer
|
||||
|
||||
@Serializable
|
||||
data class ToolCategoryVo(
|
||||
val id: Long,
|
||||
|
||||
val name: String,
|
||||
|
||||
val enable: Boolean,
|
||||
|
||||
@Serializable(LocalDateTimeSerializer::class)
|
||||
val createTime: LocalDateTime,
|
||||
|
||||
@Serializable(LocalDateTimeSerializer::class)
|
||||
val updateTime: LocalDateTime
|
||||
)
|
||||
@@ -0,0 +1,18 @@
|
||||
package top.fatweb.oxygen.toolbox.network.model
|
||||
|
||||
import kotlinx.datetime.LocalDateTime
|
||||
import kotlinx.serialization.Serializable
|
||||
import top.fatweb.oxygen.toolbox.network.serializer.LocalDateTimeSerializer
|
||||
|
||||
@Serializable
|
||||
data class ToolDataVo(
|
||||
val id: Long,
|
||||
|
||||
val data: String,
|
||||
|
||||
@Serializable(LocalDateTimeSerializer::class)
|
||||
val createTime: LocalDateTime? = null,
|
||||
|
||||
@Serializable(LocalDateTimeSerializer::class)
|
||||
val updateTime: LocalDateTime? = null
|
||||
)
|
||||
@@ -0,0 +1,84 @@
|
||||
package top.fatweb.oxygen.toolbox.network.model
|
||||
|
||||
import kotlinx.datetime.LocalDateTime
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import top.fatweb.oxygen.toolbox.model.tool.ToolEntity
|
||||
import top.fatweb.oxygen.toolbox.network.serializer.LocalDateTimeSerializer
|
||||
|
||||
@Serializable
|
||||
data class ToolVo(
|
||||
val id: Long,
|
||||
|
||||
val name: String,
|
||||
|
||||
val toolId: String,
|
||||
|
||||
val icon: String,
|
||||
|
||||
val platform: ToolBaseVo.Platform,
|
||||
|
||||
val description: String? = null,
|
||||
|
||||
val base: ToolBaseVo? = null,
|
||||
|
||||
val author: UserWithInfoVo,
|
||||
|
||||
val ver: String,
|
||||
|
||||
val keywords: List<String>,
|
||||
|
||||
val categories: List<ToolCategoryVo>,
|
||||
|
||||
val source: ToolDataVo? = null,
|
||||
|
||||
val dist: ToolDataVo? = null,
|
||||
|
||||
val entryPoint: String,
|
||||
|
||||
val publish: Long,
|
||||
|
||||
val review: ReviewType,
|
||||
|
||||
@Serializable(LocalDateTimeSerializer::class)
|
||||
val createTime: LocalDateTime,
|
||||
|
||||
@Serializable(LocalDateTimeSerializer::class)
|
||||
val updateTime: LocalDateTime
|
||||
) {
|
||||
@Serializable
|
||||
enum class ReviewType {
|
||||
@SerialName("NONE")
|
||||
None,
|
||||
|
||||
@SerialName("PROCESSING")
|
||||
Processing,
|
||||
|
||||
@SerialName("PASS")
|
||||
Pass,
|
||||
|
||||
@SerialName("REJECT")
|
||||
Reject
|
||||
}
|
||||
}
|
||||
|
||||
fun ToolVo.asExternalModel() = ToolEntity(
|
||||
id = id,
|
||||
name = name,
|
||||
toolId = toolId,
|
||||
icon = icon,
|
||||
platform = platform.asExternalModel(),
|
||||
description = description,
|
||||
base = base?.dist?.data,
|
||||
authorUsername = author.username,
|
||||
authorNickname = author.userInfo.nickname,
|
||||
authorAvatar = author.userInfo.avatar,
|
||||
ver = ver,
|
||||
keywords = keywords,
|
||||
categories = categories.map { it.name },
|
||||
source = source?.data,
|
||||
dist = dist?.data,
|
||||
entryPoint = entryPoint,
|
||||
createTime = createTime,
|
||||
updateTime = updateTime
|
||||
)
|
||||
@@ -0,0 +1,21 @@
|
||||
package top.fatweb.oxygen.toolbox.network.model
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class UserWithInfoVo(
|
||||
val id: Long,
|
||||
|
||||
val username: String,
|
||||
|
||||
val userInfo: UserInfoVo
|
||||
) {
|
||||
@Serializable
|
||||
data class UserInfoVo(
|
||||
val id: Long,
|
||||
|
||||
val nickname: String,
|
||||
|
||||
val avatar: String
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package top.fatweb.oxygen.toolbox.network.paging
|
||||
|
||||
import androidx.paging.PagingSource
|
||||
import androidx.paging.PagingState
|
||||
import kotlinx.coroutines.flow.first
|
||||
import top.fatweb.oxygen.toolbox.data.network.OxygenNetworkDataSource
|
||||
import top.fatweb.oxygen.toolbox.data.tool.dao.ToolDao
|
||||
import top.fatweb.oxygen.toolbox.model.tool.ToolEntity
|
||||
import top.fatweb.oxygen.toolbox.network.model.ToolVo
|
||||
import top.fatweb.oxygen.toolbox.network.model.asExternalModel
|
||||
|
||||
internal class ToolStorePagingSource(
|
||||
private val oxygenNetworkDataSource: OxygenNetworkDataSource,
|
||||
private val toolDao: ToolDao,
|
||||
private val searchValue: String
|
||||
) : PagingSource<Int, ToolEntity>() {
|
||||
override fun getRefreshKey(state: PagingState<Int, ToolEntity>): Int? =
|
||||
state.anchorPosition?.let {
|
||||
val anchorPage = state.closestPageToPosition(it)
|
||||
anchorPage?.prevKey?.plus(1) ?: anchorPage?.nextKey?.minus(1)
|
||||
}
|
||||
|
||||
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, ToolEntity> {
|
||||
return try {
|
||||
val currentPage = params.key ?: 1
|
||||
val (_, success, msg, data) = oxygenNetworkDataSource.getStore(
|
||||
searchValue = searchValue,
|
||||
currentPage = currentPage
|
||||
)
|
||||
|
||||
if (!success) {
|
||||
return LoadResult.Error(RuntimeException(msg))
|
||||
}
|
||||
val (_, pages, _, _, records) = data!!
|
||||
|
||||
LoadResult.Page(
|
||||
data = records.map(ToolVo::asExternalModel).map { toolEntity ->
|
||||
toolDao.selectToolByUsernameAndToolId(
|
||||
username = toolEntity.authorUsername,
|
||||
toolId = toolEntity.toolId
|
||||
).first()?.let {
|
||||
if (it.id == toolEntity.id) {
|
||||
it
|
||||
} else {
|
||||
it.copy(upgrade = toolEntity.ver).also { copy ->
|
||||
toolDao.updateTool(copy)
|
||||
}
|
||||
}
|
||||
} ?: toolEntity
|
||||
},
|
||||
prevKey = if (currentPage == 1) null else currentPage - 1,
|
||||
nextKey = if (currentPage < pages) currentPage + 1 else null
|
||||
)
|
||||
} catch (e: Throwable) {
|
||||
LoadResult.Error(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package top.fatweb.oxygen.toolbox.network.retrofit
|
||||
|
||||
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.Call
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Path
|
||||
import retrofit2.http.Query
|
||||
import top.fatweb.oxygen.toolbox.BuildConfig
|
||||
import top.fatweb.oxygen.toolbox.data.network.OxygenNetworkDataSource
|
||||
import top.fatweb.oxygen.toolbox.model.Result
|
||||
import top.fatweb.oxygen.toolbox.model.asResult
|
||||
import top.fatweb.oxygen.toolbox.network.model.PageVo
|
||||
import top.fatweb.oxygen.toolbox.network.model.ResponseResult
|
||||
import top.fatweb.oxygen.toolbox.network.model.ToolBaseVo
|
||||
import top.fatweb.oxygen.toolbox.network.model.ToolVo
|
||||
import javax.inject.Inject
|
||||
|
||||
private interface RetrofitOxygenNetworkApi {
|
||||
@GET(value = "/tool/store")
|
||||
suspend fun getStore(
|
||||
@Query("currentPage") currentPage: Int,
|
||||
@Query("searchValue") searchValue: String,
|
||||
@Query("platform") platform: ToolBaseVo.Platform? = ToolBaseVo.Platform.Android
|
||||
): ResponseResult<PageVo<ToolVo>>
|
||||
|
||||
@GET(value = "/tool/detail/{username}/{toolId}/{ver}")
|
||||
suspend fun detail(
|
||||
@Path("username") username: String,
|
||||
@Path("toolId") toolId: String,
|
||||
@Path("ver") ver: String,
|
||||
@Query("platform") platform: ToolBaseVo.Platform? = ToolBaseVo.Platform.Android
|
||||
): ResponseResult<ToolVo>
|
||||
}
|
||||
|
||||
private const val API_BASE_URL = BuildConfig.API_URL
|
||||
|
||||
internal class RetrofitOxygenNetwork @Inject constructor(
|
||||
networkJson: Json,
|
||||
okhttpCallFactory: dagger.Lazy<Call.Factory>
|
||||
) : OxygenNetworkDataSource {
|
||||
private val networkApi = Retrofit.Builder()
|
||||
.baseUrl(API_BASE_URL)
|
||||
.callFactory { okhttpCallFactory.get().newCall(it) }
|
||||
.addConverterFactory(
|
||||
networkJson.asConverterFactory("application/json".toMediaType())
|
||||
)
|
||||
.build()
|
||||
.create(RetrofitOxygenNetworkApi::class.java)
|
||||
|
||||
override suspend fun getStore(
|
||||
searchValue: String,
|
||||
currentPage: Int
|
||||
): ResponseResult<PageVo<ToolVo>> =
|
||||
networkApi.getStore(searchValue = searchValue, currentPage = currentPage)
|
||||
|
||||
override fun detail(
|
||||
username: String,
|
||||
toolId: String,
|
||||
ver: String,
|
||||
platform: ToolBaseVo.Platform
|
||||
): Flow<Result<ToolVo>> =
|
||||
flow {
|
||||
emit(
|
||||
networkApi.detail(
|
||||
username = username,
|
||||
toolId = toolId,
|
||||
ver = ver,
|
||||
platform = platform
|
||||
)
|
||||
)
|
||||
}.asResult()
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package top.fatweb.oxygen.toolbox.network.serializer
|
||||
|
||||
import kotlinx.datetime.LocalDateTime
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.descriptors.PrimitiveKind
|
||||
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
|
||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
|
||||
object LocalDateTimeSerializer : KSerializer<LocalDateTime> {
|
||||
override val descriptor: SerialDescriptor =
|
||||
PrimitiveSerialDescriptor(serialName = "LocalDateTime", kind = PrimitiveKind.STRING)
|
||||
|
||||
override fun deserialize(decoder: Decoder): LocalDateTime =
|
||||
LocalDateTime.parse(input = decoder.decodeString().removeSuffix("Z"))
|
||||
|
||||
override fun serialize(encoder: Encoder, value: LocalDateTime) {
|
||||
encoder.encodeString(value.toString().padEnd(length = 24, padChar = 'Z'))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package top.fatweb.oxygen.toolbox.repository.lib
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import top.fatweb.oxygen.toolbox.model.lib.Dependencies
|
||||
|
||||
interface DepRepository {
|
||||
fun searchName(name: String): Flow<Dependencies>
|
||||
|
||||
fun getSearchNameCount(): Flow<Int>
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package top.fatweb.oxygen.toolbox.repository.lib.impl
|
||||
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import top.fatweb.oxygen.toolbox.data.lib.DepDataSource
|
||||
import top.fatweb.oxygen.toolbox.model.lib.Dependencies
|
||||
import top.fatweb.oxygen.toolbox.repository.lib.DepRepository
|
||||
import javax.inject.Inject
|
||||
|
||||
class LocalDepRepository @Inject constructor(
|
||||
private val depDataSource: DepDataSource
|
||||
) : DepRepository {
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
override fun searchName(name: String): Flow<Dependencies> =
|
||||
depDataSource.dependencies.flatMapLatest { dependencies ->
|
||||
flowOf(dependencies.copy(
|
||||
libraries = dependencies.libraries.filter {
|
||||
it.name?.lowercase()?.contains(Regex("^.*${name.lowercase()}.*$")) ?: false
|
||||
}
|
||||
))
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
override fun getSearchNameCount(): Flow<Int> =
|
||||
depDataSource.dependencies.flatMapLatest { flowOf(it.libraries.size) }
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package top.fatweb.oxygen.toolbox.repository.tool
|
||||
|
||||
import androidx.paging.PagingData
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import top.fatweb.oxygen.toolbox.model.Result
|
||||
import top.fatweb.oxygen.toolbox.model.tool.ToolEntity
|
||||
|
||||
interface StoreRepository {
|
||||
suspend fun getStore(
|
||||
searchValue: String
|
||||
): Flow<PagingData<ToolEntity>>
|
||||
|
||||
fun detail(
|
||||
username: String,
|
||||
toolId: String,
|
||||
ver: String = "latest"
|
||||
): Flow<Result<ToolEntity>>
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package top.fatweb.oxygen.toolbox.repository.tool
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import top.fatweb.oxygen.toolbox.model.tool.ToolEntity
|
||||
|
||||
interface ToolRepository {
|
||||
val toolViewTemplate: Flow<String>
|
||||
|
||||
fun getGlobalJsVariables(isDarkMode: Boolean): Flow<String>
|
||||
|
||||
fun getGlobalCssVariables(isDarkMode: Boolean): Flow<String>
|
||||
|
||||
fun getAllToolsStream(searchValue: String): Flow<List<ToolEntity>>
|
||||
|
||||
fun getStarToolsStream(searchValue: String): Flow<List<ToolEntity>>
|
||||
|
||||
fun getToolById(id: Long): Flow<ToolEntity?>
|
||||
|
||||
fun getToolByUsernameAndToolId(username: String, toolId: String): Flow<ToolEntity?>
|
||||
|
||||
suspend fun saveTool(toolEntity: ToolEntity)
|
||||
|
||||
suspend fun updateTool(toolEntity: ToolEntity)
|
||||
|
||||
suspend fun removeTool(toolEntity: ToolEntity)
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package top.fatweb.oxygen.toolbox.repository.tool.impl
|
||||
|
||||
import androidx.paging.Pager
|
||||
import androidx.paging.PagingConfig
|
||||
import androidx.paging.PagingData
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import top.fatweb.oxygen.toolbox.data.network.OxygenNetworkDataSource
|
||||
import top.fatweb.oxygen.toolbox.data.tool.dao.ToolDao
|
||||
import top.fatweb.oxygen.toolbox.model.Result
|
||||
import top.fatweb.oxygen.toolbox.model.asExternalModel
|
||||
import top.fatweb.oxygen.toolbox.model.tool.ToolEntity
|
||||
import top.fatweb.oxygen.toolbox.network.model.ToolVo
|
||||
import top.fatweb.oxygen.toolbox.network.model.asExternalModel
|
||||
import top.fatweb.oxygen.toolbox.network.paging.ToolStorePagingSource
|
||||
import top.fatweb.oxygen.toolbox.repository.tool.StoreRepository
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val PAGE_SIZE = 20
|
||||
|
||||
internal class NetworkStoreRepository @Inject constructor(
|
||||
private val oxygenNetworkDataSource: OxygenNetworkDataSource,
|
||||
private val toolDao: ToolDao
|
||||
) : StoreRepository {
|
||||
override suspend fun getStore(
|
||||
searchValue: String
|
||||
): Flow<PagingData<ToolEntity>> =
|
||||
Pager(
|
||||
config = PagingConfig(pageSize = PAGE_SIZE),
|
||||
pagingSourceFactory = {
|
||||
ToolStorePagingSource(
|
||||
oxygenNetworkDataSource = oxygenNetworkDataSource,
|
||||
toolDao = toolDao,
|
||||
searchValue = searchValue
|
||||
)
|
||||
}
|
||||
).flow
|
||||
|
||||
override fun detail(
|
||||
username: String,
|
||||
toolId: String,
|
||||
ver: String
|
||||
): Flow<Result<ToolEntity>> =
|
||||
oxygenNetworkDataSource.detail(
|
||||
username = username,
|
||||
toolId = toolId,
|
||||
ver = ver
|
||||
).map {
|
||||
it.asExternalModel(ToolVo::asExternalModel)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package top.fatweb.oxygen.toolbox.repository.tool.impl
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import top.fatweb.oxygen.toolbox.data.tool.ToolDataSource
|
||||
import top.fatweb.oxygen.toolbox.data.tool.dao.ToolDao
|
||||
import top.fatweb.oxygen.toolbox.model.tool.ToolEntity
|
||||
import top.fatweb.oxygen.toolbox.repository.tool.ToolRepository
|
||||
import javax.inject.Inject
|
||||
|
||||
class OfflineToolRepository @Inject constructor(
|
||||
private val toolDataSource: ToolDataSource,
|
||||
private val toolDao: ToolDao
|
||||
) : ToolRepository {
|
||||
override val toolViewTemplate: Flow<String>
|
||||
get() = toolDataSource.toolViewTemplate
|
||||
|
||||
override fun getGlobalJsVariables(isDarkMode: Boolean): Flow<String> =
|
||||
toolDataSource.getGlobalJsVariables(isDarkMode)
|
||||
|
||||
override fun getGlobalCssVariables(isDarkMode: Boolean): Flow<String> =
|
||||
toolDataSource.getGlobalCssVariables(isDarkMode)
|
||||
|
||||
override fun getAllToolsStream(searchValue: String): Flow<List<ToolEntity>> =
|
||||
toolDao.selectAllTools(searchValue)
|
||||
|
||||
override fun getStarToolsStream(searchValue: String): Flow<List<ToolEntity>> =
|
||||
toolDao.selectStarTools(searchValue)
|
||||
|
||||
override fun getToolById(id: Long): Flow<ToolEntity?> =
|
||||
toolDao.selectToolById(id)
|
||||
|
||||
override fun getToolByUsernameAndToolId(username: String, toolId: String): Flow<ToolEntity?> =
|
||||
toolDao.selectToolByUsernameAndToolId(username, toolId)
|
||||
|
||||
override suspend fun saveTool(toolEntity: ToolEntity) =
|
||||
toolDao.insertTool(toolEntity.copy(isInstalled = true))
|
||||
|
||||
override suspend fun updateTool(toolEntity: ToolEntity) =
|
||||
toolDao.updateTool(toolEntity)
|
||||
|
||||
override suspend fun removeTool(toolEntity: ToolEntity) =
|
||||
toolDao.deleteTool(toolEntity)
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
package top.fatweb.oxygen.toolbox.repository
|
||||
package top.fatweb.oxygen.toolbox.repository.userdata
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import top.fatweb.oxygen.toolbox.model.DarkThemeConfig
|
||||
import top.fatweb.oxygen.toolbox.model.LanguageConfig
|
||||
import top.fatweb.oxygen.toolbox.model.LaunchPageConfig
|
||||
import top.fatweb.oxygen.toolbox.model.ThemeBrandConfig
|
||||
import top.fatweb.oxygen.toolbox.model.UserData
|
||||
import top.fatweb.oxygen.toolbox.model.userdata.DarkThemeConfig
|
||||
import top.fatweb.oxygen.toolbox.model.userdata.LanguageConfig
|
||||
import top.fatweb.oxygen.toolbox.model.userdata.LaunchPageConfig
|
||||
import top.fatweb.oxygen.toolbox.model.userdata.ThemeBrandConfig
|
||||
import top.fatweb.oxygen.toolbox.model.userdata.UserData
|
||||
|
||||
interface UserDataRepository {
|
||||
val userData: Flow<UserData>
|
||||
@@ -19,4 +19,6 @@ interface UserDataRepository {
|
||||
suspend fun setDarkThemeConfig(darkThemeConfig: DarkThemeConfig)
|
||||
|
||||
suspend fun setUseDynamicColor(useDynamicColor: Boolean)
|
||||
}
|
||||
|
||||
suspend fun updateIsNotFirstLaunch()
|
||||
}
|
||||
@@ -1,15 +1,16 @@
|
||||
package top.fatweb.oxygen.toolbox.repository
|
||||
package top.fatweb.oxygen.toolbox.repository.userdata.impl
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import top.fatweb.oxygen.toolbox.datastore.OxygenPreferencesDataSource
|
||||
import top.fatweb.oxygen.toolbox.model.DarkThemeConfig
|
||||
import top.fatweb.oxygen.toolbox.model.LanguageConfig
|
||||
import top.fatweb.oxygen.toolbox.model.LaunchPageConfig
|
||||
import top.fatweb.oxygen.toolbox.model.ThemeBrandConfig
|
||||
import top.fatweb.oxygen.toolbox.model.UserData
|
||||
import top.fatweb.oxygen.toolbox.data.userdata.OxygenPreferencesDataSource
|
||||
import top.fatweb.oxygen.toolbox.model.userdata.DarkThemeConfig
|
||||
import top.fatweb.oxygen.toolbox.model.userdata.LanguageConfig
|
||||
import top.fatweb.oxygen.toolbox.model.userdata.LaunchPageConfig
|
||||
import top.fatweb.oxygen.toolbox.model.userdata.ThemeBrandConfig
|
||||
import top.fatweb.oxygen.toolbox.model.userdata.UserData
|
||||
import top.fatweb.oxygen.toolbox.repository.userdata.UserDataRepository
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class OfflineFirstUserDataRepository @Inject constructor(
|
||||
internal class LocalUserDataRepository @Inject constructor(
|
||||
private val oxygenPreferencesDataSource: OxygenPreferencesDataSource
|
||||
) : UserDataRepository {
|
||||
override val userData: Flow<UserData> =
|
||||
@@ -34,4 +35,8 @@ internal class OfflineFirstUserDataRepository @Inject constructor(
|
||||
override suspend fun setUseDynamicColor(useDynamicColor: Boolean) {
|
||||
oxygenPreferencesDataSource.setUseDynamicColor(useDynamicColor)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun updateIsNotFirstLaunch() {
|
||||
oxygenPreferencesDataSource.updateIsNotFirstLaunch()
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,14 @@
|
||||
package top.fatweb.oxygen.toolbox.ui
|
||||
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.WindowInsetsSides
|
||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.only
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.safeDrawing
|
||||
import androidx.compose.foundation.layout.safeDrawingPadding
|
||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
@@ -22,21 +20,30 @@ import androidx.compose.material3.SnackbarResult
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
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.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.WindowInsetsControllerCompat
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.navigation.NavDestination
|
||||
import androidx.navigation.NavDestination.Companion.hierarchy
|
||||
import top.fatweb.oxygen.toolbox.R
|
||||
import top.fatweb.oxygen.toolbox.icon.OxygenIcons
|
||||
import top.fatweb.oxygen.toolbox.model.LaunchPageConfig
|
||||
import top.fatweb.oxygen.toolbox.model.userdata.LaunchPageConfig
|
||||
import top.fatweb.oxygen.toolbox.navigation.ABOUT_ROUTE
|
||||
import top.fatweb.oxygen.toolbox.navigation.OxygenNavHost
|
||||
import top.fatweb.oxygen.toolbox.navigation.STAR_ROUTE
|
||||
import top.fatweb.oxygen.toolbox.navigation.TOOLS_ROUTE
|
||||
@@ -48,113 +55,205 @@ import top.fatweb.oxygen.toolbox.ui.component.OxygenNavigationBarItem
|
||||
import top.fatweb.oxygen.toolbox.ui.component.OxygenNavigationRail
|
||||
import top.fatweb.oxygen.toolbox.ui.component.OxygenNavigationRailItem
|
||||
import top.fatweb.oxygen.toolbox.ui.component.OxygenTopAppBar
|
||||
import top.fatweb.oxygen.toolbox.ui.component.SearchButtonPosition
|
||||
import top.fatweb.oxygen.toolbox.ui.settings.SettingsDialog
|
||||
import top.fatweb.oxygen.toolbox.ui.theme.GradientColors
|
||||
import top.fatweb.oxygen.toolbox.ui.theme.LocalGradientColors
|
||||
import top.fatweb.oxygen.toolbox.ui.util.FullScreen
|
||||
import top.fatweb.oxygen.toolbox.ui.util.LocalFullScreen
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun OxygenApp(appState: OxygenAppState) {
|
||||
val shouldShowGradientBackground =
|
||||
appState.currentTopLevelDestination == TopLevelDestination.TOOLS
|
||||
appState.currentDestination?.route == ABOUT_ROUTE
|
||||
var showSettingsDialog by rememberSaveable {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
|
||||
OxygenBackground {
|
||||
OxygenGradientBackground(
|
||||
gradientColors = if (shouldShowGradientBackground) LocalGradientColors.current else GradientColors()
|
||||
) {
|
||||
val destination = appState.currentTopLevelDestination
|
||||
val context = LocalContext.current
|
||||
val window = (context as ComponentActivity).window
|
||||
val windowInsetsController = WindowInsetsControllerCompat(window, window.decorView)
|
||||
var isFullScreen by remember { mutableStateOf(false) }
|
||||
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
val fullScreen = FullScreen(
|
||||
enable = isFullScreen,
|
||||
onStateChange = {
|
||||
isFullScreen = it
|
||||
}
|
||||
)
|
||||
|
||||
val isOffline by appState.isOffline.collectAsStateWithLifecycle()
|
||||
DisposableEffect(Unit) {
|
||||
windowInsetsController.systemBarsBehavior =
|
||||
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
||||
val listener = WindowInsetsControllerCompat.OnControllableInsetsChangedListener { _, _ ->
|
||||
isFullScreen = false
|
||||
}
|
||||
windowInsetsController.addOnControllableInsetsChangedListener(listener)
|
||||
onDispose {
|
||||
windowInsetsController.removeOnControllableInsetsChangedListener(listener)
|
||||
}
|
||||
}
|
||||
|
||||
val noConnectMessage = stringResource(R.string.no_connect)
|
||||
LaunchedEffect(isFullScreen) {
|
||||
if (isFullScreen) {
|
||||
windowInsetsController.hide(WindowInsetsCompat.Type.systemBars())
|
||||
} else {
|
||||
windowInsetsController.show(WindowInsetsCompat.Type.systemBars())
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(isOffline) {
|
||||
if (isOffline) {
|
||||
snackbarHostState.showSnackbar(
|
||||
message = noConnectMessage,
|
||||
duration = SnackbarDuration.Indefinite
|
||||
CompositionLocalProvider(LocalFullScreen provides fullScreen) {
|
||||
OxygenBackground {
|
||||
OxygenGradientBackground(
|
||||
gradientColors = if (shouldShowGradientBackground) LocalGradientColors.current else GradientColors()
|
||||
) {
|
||||
val destination = appState.currentTopLevelDestination
|
||||
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
|
||||
val isOffline by appState.isOffline.collectAsStateWithLifecycle()
|
||||
|
||||
val noConnectMessage = stringResource(R.string.core_no_connect)
|
||||
|
||||
var canScroll by remember { mutableStateOf(true) }
|
||||
val topAppBarScrollBehavior =
|
||||
if (canScroll) TopAppBarDefaults.enterAlwaysScrollBehavior() else TopAppBarDefaults.pinnedScrollBehavior()
|
||||
|
||||
var activeSearch by remember { mutableStateOf(false) }
|
||||
var searchValue by remember { mutableStateOf("") }
|
||||
var searchCount by remember { mutableIntStateOf(0) }
|
||||
|
||||
LaunchedEffect(activeSearch) {
|
||||
canScroll = !activeSearch
|
||||
}
|
||||
|
||||
LaunchedEffect(destination) {
|
||||
activeSearch = false
|
||||
searchValue = ""
|
||||
if (searchCount == 0) {
|
||||
searchCount++
|
||||
} else {
|
||||
searchCount = 0
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(isOffline) {
|
||||
if (isOffline) {
|
||||
snackbarHostState.showSnackbar(
|
||||
message = noConnectMessage,
|
||||
duration = SnackbarDuration.Indefinite
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (showSettingsDialog) {
|
||||
SettingsDialog(
|
||||
onDismiss = { showSettingsDialog = false },
|
||||
onNavigateToLibraries = appState::navigateToLibraries,
|
||||
onNavigateToAbout = appState::navigateToAbout
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (showSettingsDialog) {
|
||||
SettingsDialog(
|
||||
onDismiss = { showSettingsDialog = false }
|
||||
)
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
containerColor = Color.Transparent,
|
||||
contentColor = MaterialTheme.colorScheme.onBackground,
|
||||
contentWindowInsets = WindowInsets(0, 0, 0, 0),
|
||||
snackbarHost = { SnackbarHost(snackbarHostState) },
|
||||
bottomBar = {
|
||||
if (appState.shouldShowBottomBar && destination != null) {
|
||||
OxygenBottomBar(
|
||||
destinations = appState.topLevelDestinations,
|
||||
onNavigateToDestination = appState::navigateToTopLevelDestination,
|
||||
currentDestination = appState.currentDestination
|
||||
)
|
||||
}
|
||||
}
|
||||
) { padding ->
|
||||
Row(
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.padding(padding)
|
||||
.consumeWindowInsets(padding)
|
||||
.windowInsetsPadding(
|
||||
WindowInsets.safeDrawing.only(
|
||||
WindowInsetsSides.Horizontal
|
||||
Scaffold(
|
||||
modifier = Modifier
|
||||
.nestedScroll(connection = topAppBarScrollBehavior.nestedScrollConnection),
|
||||
containerColor = Color.Transparent,
|
||||
contentColor = MaterialTheme.colorScheme.onBackground,
|
||||
contentWindowInsets = WindowInsets(left = 0, top = 0, right = 0, bottom = 0),
|
||||
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
||||
bottomBar = {
|
||||
AnimatedVisibility(
|
||||
visible = appState.shouldShowBottomBar && destination != null
|
||||
) {
|
||||
OxygenBottomBar(
|
||||
destinations = appState.topLevelDestinations,
|
||||
currentDestination = appState.currentDestination,
|
||||
onNavigateToDestination = appState::navigateToTopLevelDestination
|
||||
)
|
||||
)
|
||||
) {
|
||||
if (appState.shouldShowNavRail && destination != null) {
|
||||
OxygenNavRail(
|
||||
modifier = Modifier.safeDrawingPadding(),
|
||||
destinations = appState.topLevelDestinations,
|
||||
onNavigateToDestination = appState::navigateToTopLevelDestination,
|
||||
currentDestination = appState.currentDestination
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Column(
|
||||
Modifier.fillMaxSize()
|
||||
) { padding ->
|
||||
Row(
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
) {
|
||||
if (destination != null) {
|
||||
OxygenTopAppBar(
|
||||
titleRes = destination.titleTextId,
|
||||
navigationIcon = OxygenIcons.Search,
|
||||
navigationIconContentDescription = stringResource(R.string.feature_settings_top_app_bar_navigation_icon_description),
|
||||
actionIcon = OxygenIcons.MoreVert,
|
||||
actionIconContentDescription = stringResource(R.string.feature_settings_top_app_bar_action_icon_description),
|
||||
colors = TopAppBarDefaults.centerAlignedTopAppBarColors(
|
||||
containerColor = Color.Transparent
|
||||
),
|
||||
onNavigationClick = { appState.navigateToSearch() },
|
||||
onActionClick = { showSettingsDialog = true }
|
||||
AnimatedVisibility(
|
||||
visible = appState.shouldShowNavRail && destination != null
|
||||
) {
|
||||
OxygenNavRail(
|
||||
modifier = Modifier
|
||||
.padding(padding)
|
||||
.consumeWindowInsets(padding)
|
||||
.safeDrawingPadding(),
|
||||
destinations = appState.topLevelDestinations,
|
||||
currentDestination = appState.currentDestination,
|
||||
onNavigateToDestination = appState::navigateToTopLevelDestination
|
||||
)
|
||||
}
|
||||
|
||||
OxygenNavHost(
|
||||
appState = appState,
|
||||
onShowSnackbar = { message, action ->
|
||||
snackbarHostState.showSnackbar(
|
||||
message = message,
|
||||
actionLabel = action,
|
||||
duration = SnackbarDuration.Short
|
||||
) == SnackbarResult.ActionPerformed
|
||||
},
|
||||
startDestination = when (appState.launchPageConfig) {
|
||||
LaunchPageConfig.TOOLS -> TOOLS_ROUTE
|
||||
LaunchPageConfig.STAR -> STAR_ROUTE
|
||||
Column(
|
||||
Modifier.fillMaxSize()
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
visible = destination != null
|
||||
) {
|
||||
OxygenTopAppBar(
|
||||
scrollBehavior = topAppBarScrollBehavior,
|
||||
title = {
|
||||
destination?.let {
|
||||
Text(
|
||||
text = stringResource(destination.titleTextId),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
},
|
||||
navigationIcon = OxygenIcons.Search,
|
||||
navigationIconContentDescription = stringResource(R.string.feature_settings_top_app_bar_navigation_icon_description),
|
||||
actionIcon = OxygenIcons.MoreVert,
|
||||
actionIconContentDescription = stringResource(R.string.feature_settings_top_app_bar_action_icon_description),
|
||||
activeSearch = activeSearch,
|
||||
searchButtonPosition = SearchButtonPosition.Navigation,
|
||||
query = searchValue,
|
||||
colors = TopAppBarDefaults.centerAlignedTopAppBarColors(
|
||||
containerColor = Color.Transparent,
|
||||
scrolledContainerColor = Color.Transparent
|
||||
),
|
||||
onNavigationClick = { activeSearch = true },
|
||||
onActionClick = { showSettingsDialog = true },
|
||||
onQueryChange = {
|
||||
searchValue = it
|
||||
},
|
||||
onSearch = {
|
||||
searchCount++
|
||||
},
|
||||
onCancelSearch = {
|
||||
searchValue = ""
|
||||
activeSearch = false
|
||||
searchCount = 0
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
OxygenNavHost(
|
||||
appState = appState,
|
||||
startDestination = when (appState.launchPageConfig) {
|
||||
LaunchPageConfig.Tools -> TOOLS_ROUTE
|
||||
LaunchPageConfig.Star -> STAR_ROUTE
|
||||
},
|
||||
isVertical = appState.shouldShowBottomBar,
|
||||
searchValue = searchValue,
|
||||
searchCount = searchCount,
|
||||
onShowSnackbar = { message, action ->
|
||||
snackbarHostState.showSnackbar(
|
||||
message = message,
|
||||
actionLabel = action,
|
||||
duration = SnackbarDuration.Short
|
||||
) == SnackbarResult.ActionPerformed
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -166,8 +265,8 @@ fun OxygenApp(appState: OxygenAppState) {
|
||||
private fun OxygenBottomBar(
|
||||
modifier: Modifier = Modifier,
|
||||
destinations: List<TopLevelDestination>,
|
||||
onNavigateToDestination: (TopLevelDestination) -> Unit,
|
||||
currentDestination: NavDestination?
|
||||
currentDestination: NavDestination?,
|
||||
onNavigateToDestination: (TopLevelDestination) -> Unit
|
||||
) {
|
||||
OxygenNavigationBar(
|
||||
modifier = modifier
|
||||
@@ -181,13 +280,13 @@ private fun OxygenBottomBar(
|
||||
icon = {
|
||||
Icon(
|
||||
imageVector = destination.unselectedIcon,
|
||||
contentDescription = null
|
||||
contentDescription = stringResource(destination.iconTextId)
|
||||
)
|
||||
},
|
||||
selectedIcon = {
|
||||
Icon(
|
||||
imageVector = destination.selectedIcon,
|
||||
contentDescription = null
|
||||
contentDescription = stringResource(destination.iconTextId)
|
||||
)
|
||||
},
|
||||
onClick = { onNavigateToDestination(destination) }
|
||||
@@ -200,8 +299,8 @@ private fun OxygenBottomBar(
|
||||
private fun OxygenNavRail(
|
||||
modifier: Modifier = Modifier,
|
||||
destinations: List<TopLevelDestination>,
|
||||
onNavigateToDestination: (TopLevelDestination) -> Unit,
|
||||
currentDestination: NavDestination?
|
||||
currentDestination: NavDestination?,
|
||||
onNavigateToDestination: (TopLevelDestination) -> Unit
|
||||
) {
|
||||
OxygenNavigationRail(
|
||||
modifier = modifier
|
||||
@@ -215,13 +314,13 @@ private fun OxygenNavRail(
|
||||
icon = {
|
||||
Icon(
|
||||
imageVector = destination.unselectedIcon,
|
||||
contentDescription = null
|
||||
contentDescription = stringResource(destination.iconTextId)
|
||||
)
|
||||
},
|
||||
selectedIcon = {
|
||||
Icon(
|
||||
imageVector = destination.selectedIcon,
|
||||
contentDescription = null
|
||||
contentDescription = stringResource(destination.iconTextId)
|
||||
)
|
||||
},
|
||||
onClick = { onNavigateToDestination(destination) }
|
||||
@@ -232,5 +331,5 @@ private fun OxygenNavRail(
|
||||
|
||||
private fun NavDestination?.isTopLevelDestinationInHierarchy(destination: TopLevelDestination) =
|
||||
this?.hierarchy?.any {
|
||||
it.route?.contains(destination.name, true) ?: false
|
||||
} ?: false
|
||||
it.route?.equals(destination.route) == true
|
||||
} == true
|
||||
|
||||
@@ -17,14 +17,17 @@ import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.datetime.TimeZone
|
||||
import top.fatweb.oxygen.toolbox.model.LaunchPageConfig
|
||||
import top.fatweb.oxygen.toolbox.model.userdata.LaunchPageConfig
|
||||
import top.fatweb.oxygen.toolbox.monitor.NetworkMonitor
|
||||
import top.fatweb.oxygen.toolbox.monitor.TimeZoneMonitor
|
||||
import top.fatweb.oxygen.toolbox.navigation.STAR_ROUTE
|
||||
import top.fatweb.oxygen.toolbox.navigation.TOOLS_ROUTE
|
||||
import top.fatweb.oxygen.toolbox.navigation.TOOL_STORE_ROUTE
|
||||
import top.fatweb.oxygen.toolbox.navigation.TopLevelDestination
|
||||
import top.fatweb.oxygen.toolbox.navigation.navigateToSearch
|
||||
import top.fatweb.oxygen.toolbox.navigation.navigateToAbout
|
||||
import top.fatweb.oxygen.toolbox.navigation.navigateToLibraries
|
||||
import top.fatweb.oxygen.toolbox.navigation.navigateToStar
|
||||
import top.fatweb.oxygen.toolbox.navigation.navigateToToolStore
|
||||
import top.fatweb.oxygen.toolbox.navigation.navigateToTools
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
@@ -56,21 +59,24 @@ fun rememberOxygenAppState(
|
||||
|
||||
@Stable
|
||||
class OxygenAppState(
|
||||
val windowSizeClass: WindowSizeClass,
|
||||
private val windowSizeClass: WindowSizeClass,
|
||||
networkMonitor: NetworkMonitor,
|
||||
timeZoneMonitor: TimeZoneMonitor,
|
||||
coroutineScope: CoroutineScope,
|
||||
val navController: NavHostController,
|
||||
val launchPageConfig: LaunchPageConfig
|
||||
) {
|
||||
val topLevelDestinations: List<TopLevelDestination> = TopLevelDestination.entries
|
||||
|
||||
val currentDestination: NavDestination?
|
||||
@Composable get() = navController
|
||||
.currentBackStackEntryAsState().value?.destination
|
||||
|
||||
val currentTopLevelDestination: TopLevelDestination?
|
||||
@Composable get() = when (currentDestination?.route) {
|
||||
TOOLS_ROUTE -> TopLevelDestination.TOOLS
|
||||
STAR_ROUTE -> TopLevelDestination.STAR
|
||||
TOOL_STORE_ROUTE -> TopLevelDestination.ToolStore
|
||||
TOOLS_ROUTE -> TopLevelDestination.Tools
|
||||
STAR_ROUTE -> TopLevelDestination.Star
|
||||
else -> null
|
||||
}
|
||||
|
||||
@@ -85,16 +91,14 @@ class OxygenAppState(
|
||||
.stateIn(
|
||||
scope = coroutineScope,
|
||||
initialValue = false,
|
||||
started = SharingStarted.WhileSubscribed(5.seconds.inWholeMilliseconds)
|
||||
started = SharingStarted.WhileSubscribed(stopTimeoutMillis = 5.seconds.inWholeMilliseconds)
|
||||
)
|
||||
|
||||
val topLevelDestinations: List<TopLevelDestination> = TopLevelDestination.entries
|
||||
|
||||
val currentTimeZone = timeZoneMonitor.currentTimeZone
|
||||
.stateIn(
|
||||
scope = coroutineScope,
|
||||
initialValue = TimeZone.currentSystemDefault(),
|
||||
started = SharingStarted.WhileSubscribed(5.seconds.inWholeMilliseconds)
|
||||
started = SharingStarted.WhileSubscribed(stopTimeoutMillis = 5.seconds.inWholeMilliseconds)
|
||||
)
|
||||
|
||||
fun navigateToTopLevelDestination(topLevelDestination: TopLevelDestination) {
|
||||
@@ -108,10 +112,13 @@ class OxygenAppState(
|
||||
}
|
||||
|
||||
when (topLevelDestination) {
|
||||
TopLevelDestination.TOOLS -> navController.navigateToTools(topLevelNavOptions)
|
||||
TopLevelDestination.STAR -> navController.navigateToStar(topLevelNavOptions)
|
||||
TopLevelDestination.ToolStore -> navController.navigateToToolStore(topLevelNavOptions)
|
||||
TopLevelDestination.Tools -> navController.navigateToTools(topLevelNavOptions)
|
||||
TopLevelDestination.Star -> navController.navigateToStar(topLevelNavOptions)
|
||||
}
|
||||
}
|
||||
|
||||
fun navigateToSearch() = navController.navigateToSearch()
|
||||
}
|
||||
fun navigateToLibraries() = navController.navigateToLibraries()
|
||||
|
||||
fun navigateToAbout() = navController.navigateToAbout()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,218 @@
|
||||
package top.fatweb.oxygen.toolbox.ui.about
|
||||
|
||||
import androidx.compose.animation.core.AnimationConstants
|
||||
import androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi
|
||||
import androidx.compose.animation.graphics.res.animatedVectorResource
|
||||
import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
|
||||
import androidx.compose.animation.graphics.vector.AnimatedImageVector
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.WindowInsetsSides
|
||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.only
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.safeDrawing
|
||||
import androidx.compose.foundation.layout.safeDrawingPadding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.webkit.WebViewCompat
|
||||
import kotlinx.coroutines.delay
|
||||
import top.fatweb.oxygen.toolbox.R
|
||||
import top.fatweb.oxygen.toolbox.icon.OxygenIcons
|
||||
import top.fatweb.oxygen.toolbox.ui.component.OxygenTopAppBar
|
||||
import top.fatweb.oxygen.toolbox.ui.theme.OxygenPreviews
|
||||
import top.fatweb.oxygen.toolbox.ui.theme.OxygenTheme
|
||||
import top.fatweb.oxygen.toolbox.ui.util.ResourcesUtils
|
||||
|
||||
@Composable
|
||||
internal fun AboutRoute(
|
||||
modifier: Modifier = Modifier,
|
||||
onBackClick: () -> Unit,
|
||||
onNavigateToLibraries: () -> Unit
|
||||
) {
|
||||
AboutScreen(
|
||||
modifier = modifier.safeDrawingPadding(),
|
||||
onBackClick = onBackClick,
|
||||
onNavigateToLibraries = onNavigateToLibraries
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
internal fun AboutScreen(
|
||||
modifier: Modifier = Modifier,
|
||||
onBackClick: () -> Unit,
|
||||
onNavigateToLibraries: () -> Unit
|
||||
) {
|
||||
val scrollState = rememberScrollState()
|
||||
val topAppBarScrollBehavior =
|
||||
TopAppBarDefaults.pinnedScrollBehavior(canScroll = { scrollState.maxValue > 0 })
|
||||
|
||||
Scaffold(
|
||||
modifier = Modifier
|
||||
.nestedScroll(connection = topAppBarScrollBehavior.nestedScrollConnection),
|
||||
containerColor = Color.Transparent,
|
||||
contentWindowInsets = WindowInsets(left = 0, top = 0, right = 0, bottom = 0),
|
||||
) { padding ->
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.padding(padding)
|
||||
.consumeWindowInsets(padding)
|
||||
.windowInsetsPadding(
|
||||
WindowInsets.safeDrawing.only(
|
||||
WindowInsetsSides.Horizontal
|
||||
)
|
||||
)
|
||||
.verticalScroll(state = scrollState),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
OxygenTopAppBar(
|
||||
scrollBehavior = topAppBarScrollBehavior,
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(R.string.feature_settings_more_about),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
},
|
||||
navigationIcon = OxygenIcons.Back,
|
||||
navigationIconContentDescription = stringResource(R.string.core_back),
|
||||
colors = TopAppBarDefaults.centerAlignedTopAppBarColors(
|
||||
containerColor = Color.Transparent,
|
||||
scrolledContainerColor = Color.Transparent
|
||||
),
|
||||
onNavigationClick = onBackClick
|
||||
)
|
||||
Spacer(Modifier.height(64.dp))
|
||||
AboutAppInfo()
|
||||
Spacer(Modifier.weight(1f))
|
||||
AboutFooter(
|
||||
onNavigateToLibraries = onNavigateToLibraries
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalAnimationGraphicsApi::class)
|
||||
@Composable
|
||||
private fun AboutAppInfo(
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val logo = AnimatedImageVector.animatedVectorResource(R.drawable.ic_launcher)
|
||||
var atEnd by remember { mutableStateOf(false) }
|
||||
LaunchedEffect(Unit) {
|
||||
delay(AnimationConstants.DefaultDurationMillis.toLong())
|
||||
atEnd = true
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = modifier,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Image(
|
||||
modifier = Modifier
|
||||
.width(160.dp),
|
||||
painter = rememberAnimatedVectorPainter(animatedImageVector = logo, atEnd = atEnd),
|
||||
contentDescription = stringResource(R.string.app_full_name)
|
||||
)
|
||||
Spacer(Modifier.height(16.dp))
|
||||
Text(
|
||||
style = MaterialTheme.typography.headlineMedium,
|
||||
fontWeight = FontWeight.Bold,
|
||||
text = stringResource(R.string.app_name)
|
||||
)
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Text(
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = MaterialTheme.colorScheme.outline,
|
||||
text = stringResource(R.string.app_description)
|
||||
)
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Text(
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.outline,
|
||||
text = "${ResourcesUtils.getAppVersionName(LocalContext.current)} (${
|
||||
stringResource(
|
||||
if (ResourcesUtils.getAppVersionCode(LocalContext.current) % 100 == 0L)
|
||||
R.string.core_ga_version
|
||||
else
|
||||
R.string.core_beta_version
|
||||
)
|
||||
})"
|
||||
)
|
||||
Text(
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.outline,
|
||||
text = "WebView: ${
|
||||
WebViewCompat.getCurrentWebViewPackage(context)?.versionName ?: stringResource(
|
||||
R.string.core_unknown
|
||||
)
|
||||
}"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AboutFooter(
|
||||
modifier: Modifier = Modifier, onNavigateToLibraries: () -> Unit
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier.padding(32.dp)
|
||||
) {
|
||||
TextButton(
|
||||
onClick = onNavigateToLibraries
|
||||
) {
|
||||
Text(
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
text = stringResource(R.string.feature_settings_open_source_license)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OxygenPreviews
|
||||
@Composable
|
||||
private fun AboutAppInfoPreview() {
|
||||
OxygenTheme {
|
||||
AboutAppInfo()
|
||||
}
|
||||
}
|
||||
|
||||
@OxygenPreviews
|
||||
@Composable
|
||||
private fun AboutScreenPreview() {
|
||||
OxygenTheme {
|
||||
AboutScreen(onBackClick = {}, onNavigateToLibraries = {})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package top.fatweb.oxygen.toolbox.ui.about
|
||||
|
||||
import androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridScope
|
||||
import androidx.compose.foundation.lazy.staggeredgrid.items
|
||||
import top.fatweb.oxygen.toolbox.ui.component.LibraryCard
|
||||
|
||||
fun LazyStaggeredGridScope.librariesPanel(
|
||||
librariesScreenUiState: LibrariesScreenUiState,
|
||||
onClickLicense: (key: String) -> Unit
|
||||
) {
|
||||
when (librariesScreenUiState) {
|
||||
LibrariesScreenUiState.Loading, LibrariesScreenUiState.Nothing, LibrariesScreenUiState.NotFound -> Unit
|
||||
|
||||
is LibrariesScreenUiState.Success -> {
|
||||
items(
|
||||
items = librariesScreenUiState.dependencies.libraries,
|
||||
key = { it.uniqueId }
|
||||
) {
|
||||
LibraryCard(
|
||||
library = it,
|
||||
licenses = librariesScreenUiState.dependencies.licenses.filter { entry ->
|
||||
it.licenses.contains(entry.key)
|
||||
},
|
||||
onClickLicense = onClickLicense
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,292 @@
|
||||
package top.fatweb.oxygen.toolbox.ui.about
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import androidx.activity.compose.ReportDrawnWhen
|
||||
import androidx.compose.foundation.gestures.Orientation
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.WindowInsetsSides
|
||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.only
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.safeDrawing
|
||||
import androidx.compose.foundation.layout.systemBars
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||
import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid
|
||||
import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells
|
||||
import androidx.compose.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridState
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import top.fatweb.oxygen.toolbox.R
|
||||
import top.fatweb.oxygen.toolbox.icon.OxygenIcons
|
||||
import top.fatweb.oxygen.toolbox.ui.component.Indicator
|
||||
import top.fatweb.oxygen.toolbox.ui.component.OxygenTopAppBar
|
||||
import top.fatweb.oxygen.toolbox.ui.component.scrollbar.DraggableScrollbar
|
||||
import top.fatweb.oxygen.toolbox.ui.component.scrollbar.rememberDraggableScroller
|
||||
import top.fatweb.oxygen.toolbox.ui.component.scrollbar.scrollbarState
|
||||
import top.fatweb.oxygen.toolbox.ui.theme.OxygenPreviews
|
||||
import top.fatweb.oxygen.toolbox.ui.theme.OxygenTheme
|
||||
|
||||
@Composable
|
||||
internal fun LibrariesRoute(
|
||||
modifier: Modifier = Modifier,
|
||||
viewModel: LibrariesScreenViewModel = hiltViewModel(),
|
||||
onBackClick: () -> Unit
|
||||
) {
|
||||
val librariesScreenUiState by viewModel.librariesScreenUiState.collectAsStateWithLifecycle()
|
||||
|
||||
LibrariesScreen(
|
||||
modifier = modifier,
|
||||
librariesScreenUiState = librariesScreenUiState,
|
||||
onBackClick = onBackClick,
|
||||
onSearch = { viewModel.onSearchValueChange(it) }
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
internal fun LibrariesScreen(
|
||||
modifier: Modifier = Modifier,
|
||||
librariesScreenUiState: LibrariesScreenUiState,
|
||||
onBackClick: () -> Unit,
|
||||
onSearch: (String) -> Unit
|
||||
) {
|
||||
val configuration = LocalConfiguration.current
|
||||
val context = LocalContext.current
|
||||
|
||||
val isLibrariesLoading = librariesScreenUiState is LibrariesScreenUiState.Loading
|
||||
|
||||
ReportDrawnWhen { !isLibrariesLoading }
|
||||
|
||||
val itemsAvailable = howManyItems(librariesScreenUiState)
|
||||
|
||||
val state = rememberLazyStaggeredGridState()
|
||||
val scrollbarState = state.scrollbarState(itemsAvailable = itemsAvailable)
|
||||
|
||||
var showDialog by remember { mutableStateOf(false) }
|
||||
var dialogTitle by remember { mutableStateOf("") }
|
||||
var dialogContent by remember { mutableStateOf("") }
|
||||
var dialogUrl by remember { mutableStateOf("") }
|
||||
|
||||
var canScroll by remember { mutableStateOf(true) }
|
||||
val topAppBarScrollBehavior =
|
||||
if (canScroll) TopAppBarDefaults.enterAlwaysScrollBehavior() else TopAppBarDefaults.pinnedScrollBehavior()
|
||||
|
||||
var activeSearch by remember { mutableStateOf(false) }
|
||||
var searchValue by remember { mutableStateOf("") }
|
||||
|
||||
LaunchedEffect(activeSearch) {
|
||||
canScroll = !activeSearch
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
modifier = Modifier
|
||||
.nestedScroll(connection = topAppBarScrollBehavior.nestedScrollConnection),
|
||||
containerColor = Color.Transparent,
|
||||
contentWindowInsets = WindowInsets(left = 0, top = 0, right = 0, bottom = 0),
|
||||
) { padding ->
|
||||
Column(
|
||||
modifier
|
||||
.fillMaxWidth()
|
||||
.padding(padding)
|
||||
.consumeWindowInsets(padding)
|
||||
.windowInsetsPadding(
|
||||
WindowInsets.safeDrawing.only(
|
||||
WindowInsetsSides.Horizontal
|
||||
)
|
||||
)
|
||||
) {
|
||||
OxygenTopAppBar(
|
||||
scrollBehavior = topAppBarScrollBehavior,
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(R.string.feature_settings_open_source_license),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
},
|
||||
navigationIcon = OxygenIcons.Back,
|
||||
navigationIconContentDescription = stringResource(R.string.core_back),
|
||||
actionIcon = OxygenIcons.Search,
|
||||
actionIconContentDescription = stringResource(R.string.core_search),
|
||||
activeSearch = activeSearch,
|
||||
query = searchValue,
|
||||
colors = TopAppBarDefaults.centerAlignedTopAppBarColors(
|
||||
containerColor = Color.Transparent,
|
||||
scrolledContainerColor = Color.Transparent
|
||||
),
|
||||
onNavigationClick = onBackClick,
|
||||
onActionClick = {
|
||||
activeSearch = true
|
||||
},
|
||||
onQueryChange = {
|
||||
searchValue = it
|
||||
onSearch(it)
|
||||
},
|
||||
onSearch = onSearch,
|
||||
onCancelSearch = {
|
||||
searchValue = ""
|
||||
activeSearch = false
|
||||
onSearch("")
|
||||
}
|
||||
)
|
||||
Box {
|
||||
when (librariesScreenUiState) {
|
||||
LibrariesScreenUiState.Loading -> {
|
||||
Indicator()
|
||||
}
|
||||
|
||||
LibrariesScreenUiState.Nothing -> {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.verticalScroll(state = rememberScrollState()),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
Text(text = stringResource(R.string.core_nothing))
|
||||
}
|
||||
}
|
||||
|
||||
LibrariesScreenUiState.NotFound -> {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.verticalScroll(state = rememberScrollState()),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
Text(text = stringResource(R.string.core_nothing_found))
|
||||
}
|
||||
}
|
||||
|
||||
is LibrariesScreenUiState.Success -> {
|
||||
val handleOnClickLicense = { key: String ->
|
||||
val license = librariesScreenUiState.dependencies.licenses[key]
|
||||
if (license != null) {
|
||||
showDialog = true
|
||||
dialogTitle = license.name
|
||||
dialogContent = license.content ?: ""
|
||||
dialogUrl = license.url ?: ""
|
||||
}
|
||||
}
|
||||
|
||||
LazyVerticalStaggeredGrid(
|
||||
modifier = Modifier
|
||||
.fillMaxSize(),
|
||||
columns = StaggeredGridCells.Adaptive(300.dp),
|
||||
contentPadding = PaddingValues(16.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||
verticalItemSpacing = 24.dp,
|
||||
state = state
|
||||
) {
|
||||
librariesPanel(
|
||||
librariesScreenUiState = librariesScreenUiState,
|
||||
onClickLicense = handleOnClickLicense
|
||||
)
|
||||
}
|
||||
|
||||
state.DraggableScrollbar(
|
||||
modifier = Modifier
|
||||
.fillMaxHeight()
|
||||
.windowInsetsPadding(WindowInsets.systemBars)
|
||||
.padding(horizontal = 2.dp)
|
||||
.align(Alignment.CenterEnd),
|
||||
state = scrollbarState,
|
||||
orientation = Orientation.Vertical,
|
||||
onThumbMoved = state.rememberDraggableScroller(itemsAvailable = itemsAvailable)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (showDialog) {
|
||||
AlertDialog(
|
||||
modifier = Modifier
|
||||
.widthIn(max = configuration.screenWidthDp.dp - 80.dp)
|
||||
.heightIn(max = configuration.screenHeightDp.dp - 40.dp),
|
||||
onDismissRequest = { showDialog = false },
|
||||
title = {
|
||||
Text(
|
||||
text = dialogTitle,
|
||||
style = MaterialTheme.typography.titleLarge
|
||||
)
|
||||
},
|
||||
text = {
|
||||
Column(
|
||||
modifier = Modifier.verticalScroll(state = rememberScrollState())
|
||||
) {
|
||||
Text(text = dialogContent)
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
TextButton(onClick = {
|
||||
context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(dialogUrl)))
|
||||
}) {
|
||||
Text(text = stringResource(R.string.core_website))
|
||||
}
|
||||
TextButton(onClick = { showDialog = false }) {
|
||||
Text(text = stringResource(R.string.core_close))
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun howManyItems(librariesScreenUiState: LibrariesScreenUiState) =
|
||||
when (librariesScreenUiState) {
|
||||
LibrariesScreenUiState.Loading, LibrariesScreenUiState.Nothing, LibrariesScreenUiState.NotFound -> 0
|
||||
|
||||
is LibrariesScreenUiState.Success -> librariesScreenUiState.dependencies.libraries.size
|
||||
}
|
||||
|
||||
@OxygenPreviews
|
||||
@Composable
|
||||
private fun LibrariesScreenLoadingPreview() {
|
||||
OxygenTheme {
|
||||
LibrariesScreen(
|
||||
librariesScreenUiState = LibrariesScreenUiState.Loading,
|
||||
onBackClick = {},
|
||||
onSearch = {})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package top.fatweb.oxygen.toolbox.ui.about
|
||||
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import top.fatweb.oxygen.toolbox.model.lib.Dependencies
|
||||
import top.fatweb.oxygen.toolbox.repository.lib.DepRepository
|
||||
import javax.inject.Inject
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
@HiltViewModel
|
||||
class LibrariesScreenViewModel @Inject constructor(
|
||||
private val depRepository: DepRepository,
|
||||
private val savedStateHandle: SavedStateHandle
|
||||
) : ViewModel() {
|
||||
private val searchValue = savedStateHandle.getStateFlow(SEARCH_VALUE, "")
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
val librariesScreenUiState: StateFlow<LibrariesScreenUiState> =
|
||||
depRepository.getSearchNameCount()
|
||||
.flatMapLatest { totalCount ->
|
||||
if (totalCount < SEARCH_MIN_COUNT) {
|
||||
flowOf(LibrariesScreenUiState.Nothing)
|
||||
} else {
|
||||
searchValue.flatMapLatest { value ->
|
||||
depRepository.searchName(value).map {
|
||||
if (it.libraries.isEmpty()) {
|
||||
LibrariesScreenUiState.NotFound
|
||||
} else {
|
||||
LibrariesScreenUiState.Success(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.stateIn(
|
||||
scope = viewModelScope,
|
||||
initialValue = LibrariesScreenUiState.Loading,
|
||||
started = SharingStarted.WhileSubscribed(stopTimeoutMillis = 5.seconds.inWholeMilliseconds)
|
||||
)
|
||||
|
||||
fun onSearchValueChange(value: String) {
|
||||
savedStateHandle[SEARCH_VALUE] = value
|
||||
}
|
||||
}
|
||||
|
||||
sealed interface LibrariesScreenUiState {
|
||||
data object Loading : LibrariesScreenUiState
|
||||
|
||||
data object Nothing : LibrariesScreenUiState
|
||||
|
||||
data object NotFound : LibrariesScreenUiState
|
||||
|
||||
data class Success(val dependencies: Dependencies) : LibrariesScreenUiState
|
||||
}
|
||||
|
||||
private const val SEARCH_MIN_COUNT = 1
|
||||
private const val SEARCH_VALUE = "searchValue"
|
||||
@@ -1,6 +1,5 @@
|
||||
package top.fatweb.oxygen.toolbox.ui.component
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.size
|
||||
@@ -15,12 +14,12 @@ import androidx.compose.ui.draw.drawWithCache
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import top.fatweb.oxygen.toolbox.ui.theme.GradientColors
|
||||
import top.fatweb.oxygen.toolbox.ui.theme.LocalBackgroundTheme
|
||||
import top.fatweb.oxygen.toolbox.ui.theme.LocalGradientColors
|
||||
import top.fatweb.oxygen.toolbox.ui.theme.OxygenPreviews
|
||||
import top.fatweb.oxygen.toolbox.ui.theme.OxygenTheme
|
||||
import kotlin.math.tan
|
||||
|
||||
@@ -92,11 +91,7 @@ fun OxygenGradientBackground(
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO, name = "Light theme")
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES, name = "Dark theme")
|
||||
annotation class ThemePreviews
|
||||
|
||||
@ThemePreviews
|
||||
@OxygenPreviews
|
||||
@Composable
|
||||
fun BackgroundDefault() {
|
||||
OxygenTheme(dynamicColor = false) {
|
||||
@@ -104,7 +99,7 @@ fun BackgroundDefault() {
|
||||
}
|
||||
}
|
||||
|
||||
@ThemePreviews
|
||||
@OxygenPreviews
|
||||
@Composable
|
||||
fun BackgroundDynamic() {
|
||||
OxygenTheme(dynamicColor = true) {
|
||||
@@ -112,7 +107,7 @@ fun BackgroundDynamic() {
|
||||
}
|
||||
}
|
||||
|
||||
@ThemePreviews
|
||||
@OxygenPreviews
|
||||
@Composable
|
||||
fun BackgroundAndroid() {
|
||||
OxygenTheme(androidTheme = true) {
|
||||
@@ -120,7 +115,7 @@ fun BackgroundAndroid() {
|
||||
}
|
||||
}
|
||||
|
||||
@ThemePreviews
|
||||
@OxygenPreviews
|
||||
@Composable
|
||||
fun GradientBackgroundDefault() {
|
||||
OxygenTheme(dynamicColor = false) {
|
||||
@@ -128,7 +123,7 @@ fun GradientBackgroundDefault() {
|
||||
}
|
||||
}
|
||||
|
||||
@ThemePreviews
|
||||
@OxygenPreviews
|
||||
@Composable
|
||||
fun GradientBackgroundDynamic() {
|
||||
OxygenTheme(dynamicColor = true) {
|
||||
@@ -136,7 +131,7 @@ fun GradientBackgroundDynamic() {
|
||||
}
|
||||
}
|
||||
|
||||
@ThemePreviews
|
||||
@OxygenPreviews
|
||||
@Composable
|
||||
fun GradientBackgroundAndroid() {
|
||||
OxygenTheme(androidTheme = true) {
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package top.fatweb.oxygen.toolbox.ui.component
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.LinkAnnotation
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.withStyle
|
||||
import top.fatweb.oxygen.toolbox.ui.util.ResourcesUtils
|
||||
|
||||
@Composable
|
||||
fun ClickableText(
|
||||
@StringRes text: Int,
|
||||
@StringRes replaceText: Int,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val primaryColor = MaterialTheme.colorScheme.primary
|
||||
|
||||
val annotatedString = buildAnnotatedString {
|
||||
val clickablePart = ResourcesUtils.getString(
|
||||
context = context,
|
||||
resId = replaceText
|
||||
)
|
||||
val mainText = ResourcesUtils.getString(
|
||||
context = context,
|
||||
resId = text,
|
||||
clickablePart
|
||||
)
|
||||
append(mainText.substringBefore(clickablePart))
|
||||
pushLink(LinkAnnotation.Clickable(
|
||||
tag = "Click",
|
||||
linkInteractionListener = { onClick() }
|
||||
))
|
||||
withStyle(style = SpanStyle(color = primaryColor)) {
|
||||
append(clickablePart)
|
||||
}
|
||||
pop()
|
||||
append(mainText.substringAfter(clickablePart))
|
||||
}
|
||||
|
||||
Text(text = annotatedString)
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package top.fatweb.oxygen.toolbox.ui.component
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.selection.selectable
|
||||
import androidx.compose.foundation.selection.selectableGroup
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.RadioButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.semantics.Role
|
||||
import androidx.compose.ui.unit.dp
|
||||
import top.fatweb.oxygen.toolbox.icon.OxygenIcons
|
||||
|
||||
@Composable
|
||||
fun DialogTitle(
|
||||
modifier: Modifier = Modifier,
|
||||
text: String
|
||||
) {
|
||||
Text(
|
||||
modifier = modifier.padding(16.dp),
|
||||
text = text,
|
||||
style = MaterialTheme.typography.titleLarge
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DialogSectionTitle(
|
||||
modifier: Modifier = Modifier,
|
||||
text: String
|
||||
) {
|
||||
Text(
|
||||
modifier = modifier.padding(top = 16.dp, bottom = 8.dp),
|
||||
text = text,
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DialogSectionGroup(
|
||||
modifier: Modifier = Modifier,
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier.selectableGroup()
|
||||
) {
|
||||
content()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DialogChooserRow(
|
||||
modifier: Modifier = Modifier,
|
||||
text: String,
|
||||
selected: Boolean,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.selectable(
|
||||
selected = selected,
|
||||
role = Role.RadioButton,
|
||||
onClick = onClick
|
||||
)
|
||||
.padding(12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
RadioButton(
|
||||
selected = selected,
|
||||
onClick = null
|
||||
)
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Text(text)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DialogClickerRow(
|
||||
modifier: Modifier = Modifier,
|
||||
icon: ImageVector? = null,
|
||||
text: String,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(
|
||||
onClick = onClick
|
||||
)
|
||||
.padding(12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(imageVector = icon ?: OxygenIcons.Reorder, contentDescription = null)
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Text(text)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package top.fatweb.oxygen.toolbox.ui.component
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.LocalContentColor
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.shadow
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@Composable
|
||||
fun Indicator(
|
||||
modifier: Modifier = Modifier,
|
||||
containerColor: Color = MaterialTheme.colorScheme.surfaceContainerHigh,
|
||||
contentColor: Color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
CompositionLocalProvider(value = LocalContentColor provides contentColor) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
.size(SpinnerContainerSize)
|
||||
.shadow(
|
||||
elevation = Elevation,
|
||||
shape = CircleShape,
|
||||
clip = true
|
||||
)
|
||||
.background(color = containerColor, shape = CircleShape)
|
||||
) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier
|
||||
.align(Alignment.Center)
|
||||
.size(SpinnerSize),
|
||||
strokeWidth = StrokeWidth,
|
||||
color = LocalContentColor.current
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val SpinnerContainerSize = 40.dp
|
||||
private val Elevation = 3.dp
|
||||
private val StrokeWidth = 2.5.dp
|
||||
private val SpinnerSize = 16.dp
|
||||
File diff suppressed because one or more lines are too long
@@ -17,6 +17,7 @@ import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import top.fatweb.oxygen.toolbox.navigation.TopLevelDestination
|
||||
import top.fatweb.oxygen.toolbox.ui.theme.OxygenPreviews
|
||||
import top.fatweb.oxygen.toolbox.ui.theme.OxygenTheme
|
||||
|
||||
@Composable
|
||||
@@ -26,16 +27,15 @@ fun RowScope.OxygenNavigationBarItem(
|
||||
label: @Composable (() -> Unit)? = null,
|
||||
icon: @Composable () -> Unit,
|
||||
selectedIcon: @Composable () -> Unit,
|
||||
onClick: () -> Unit,
|
||||
enabled: Boolean = true,
|
||||
alwaysShowLabel: Boolean = false
|
||||
alwaysShowLabel: Boolean = false,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
NavigationBarItem(
|
||||
modifier = modifier,
|
||||
selected = selected,
|
||||
label = label,
|
||||
icon = if (selected) selectedIcon else icon,
|
||||
onClick = onClick,
|
||||
enabled = enabled,
|
||||
alwaysShowLabel = alwaysShowLabel,
|
||||
colors = NavigationBarItemDefaults.colors(
|
||||
@@ -44,7 +44,8 @@ fun RowScope.OxygenNavigationBarItem(
|
||||
selectedTextColor = OxygenNavigationDefaults.navigationSelectedItemColor(),
|
||||
unselectedTextColor = OxygenNavigationDefaults.navigationContentColor(),
|
||||
indicatorColor = OxygenNavigationDefaults.navigationIndicatorColor()
|
||||
)
|
||||
),
|
||||
onClick = onClick
|
||||
)
|
||||
}
|
||||
|
||||
@@ -68,16 +69,15 @@ fun OxygenNavigationRailItem(
|
||||
label: @Composable (() -> Unit)? = null,
|
||||
icon: @Composable () -> Unit,
|
||||
selectedIcon: @Composable () -> Unit,
|
||||
onClick: () -> Unit,
|
||||
enabled: Boolean = true,
|
||||
alwaysShowLabel: Boolean = true
|
||||
alwaysShowLabel: Boolean = true,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
NavigationRailItem(
|
||||
modifier = modifier,
|
||||
selected = selected,
|
||||
label = label,
|
||||
icon = if (selected) selectedIcon else icon,
|
||||
onClick = onClick,
|
||||
enabled = enabled,
|
||||
alwaysShowLabel = alwaysShowLabel,
|
||||
colors = NavigationRailItemDefaults.colors(
|
||||
@@ -86,7 +86,8 @@ fun OxygenNavigationRailItem(
|
||||
selectedTextColor = OxygenNavigationDefaults.navigationSelectedItemColor(),
|
||||
unselectedTextColor = OxygenNavigationDefaults.navigationContentColor(),
|
||||
indicatorColor = OxygenNavigationDefaults.navigationIndicatorColor()
|
||||
)
|
||||
),
|
||||
onClick = onClick
|
||||
)
|
||||
}
|
||||
|
||||
@@ -116,9 +117,9 @@ object OxygenNavigationDefaults {
|
||||
fun navigationIndicatorColor() = MaterialTheme.colorScheme.primaryContainer
|
||||
}
|
||||
|
||||
@ThemePreviews
|
||||
@OxygenPreviews
|
||||
@Composable
|
||||
fun OxygenNavigationBarPreview() {
|
||||
private fun OxygenNavigationBarPreview() {
|
||||
val items = TopLevelDestination.entries
|
||||
|
||||
OxygenTheme {
|
||||
@@ -130,13 +131,13 @@ fun OxygenNavigationBarPreview() {
|
||||
icon = {
|
||||
Icon(
|
||||
imageVector = item.unselectedIcon,
|
||||
contentDescription = stringResource(item.titleTextId)
|
||||
contentDescription = stringResource(item.iconTextId)
|
||||
)
|
||||
},
|
||||
selectedIcon = {
|
||||
Icon(
|
||||
imageVector = item.selectedIcon, contentDescription = stringResource(
|
||||
item.titleTextId
|
||||
item.iconTextId
|
||||
)
|
||||
)
|
||||
},
|
||||
@@ -147,9 +148,9 @@ fun OxygenNavigationBarPreview() {
|
||||
}
|
||||
}
|
||||
|
||||
@ThemePreviews
|
||||
@OxygenPreviews
|
||||
@Composable
|
||||
fun OxygenNavigationRailPreview() {
|
||||
private fun OxygenNavigationRailPreview() {
|
||||
val items = TopLevelDestination.entries
|
||||
|
||||
OxygenTheme {
|
||||
@@ -161,13 +162,13 @@ fun OxygenNavigationRailPreview() {
|
||||
icon = {
|
||||
Icon(
|
||||
imageVector = item.unselectedIcon,
|
||||
contentDescription = stringResource(item.titleTextId)
|
||||
contentDescription = stringResource(item.iconTextId)
|
||||
)
|
||||
},
|
||||
selectedIcon = {
|
||||
Icon(
|
||||
imageVector = item.selectedIcon, contentDescription = stringResource(
|
||||
item.titleTextId
|
||||
item.iconTextId
|
||||
)
|
||||
)
|
||||
},
|
||||
@@ -176,4 +177,4 @@ fun OxygenNavigationRailPreview() {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,44 @@
|
||||
package top.fatweb.oxygen.toolbox.ui.component
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.animation.core.animateIntAsState
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material3.CenterAlignedTopAppBar
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextField
|
||||
import androidx.compose.material3.TextFieldDefaults
|
||||
import androidx.compose.material3.TopAppBarColors
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.input.key.Key
|
||||
import androidx.compose.ui.input.key.key
|
||||
import androidx.compose.ui.input.key.onKeyEvent
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import top.fatweb.oxygen.toolbox.R
|
||||
import top.fatweb.oxygen.toolbox.icon.OxygenIcons
|
||||
import top.fatweb.oxygen.toolbox.ui.theme.OxygenPreviews
|
||||
import top.fatweb.oxygen.toolbox.ui.theme.OxygenTheme
|
||||
import android.R as androidR
|
||||
|
||||
@@ -22,51 +46,155 @@ import android.R as androidR
|
||||
@Composable
|
||||
fun OxygenTopAppBar(
|
||||
modifier: Modifier = Modifier,
|
||||
@StringRes titleRes: Int,
|
||||
navigationIcon: ImageVector,
|
||||
navigationIconContentDescription: String,
|
||||
actionIcon: ImageVector,
|
||||
actionIconContentDescription: String,
|
||||
expandedHeight: Dp = 48.dp,
|
||||
scrollBehavior: TopAppBarScrollBehavior? = null,
|
||||
title: @Composable () -> Unit = {},
|
||||
navigationIcon: ImageVector? = null,
|
||||
navigationIconContentDescription: String? = null,
|
||||
actionIcon: ImageVector? = null,
|
||||
actionIconContentDescription: String? = null,
|
||||
activeSearch: Boolean = false,
|
||||
searchButtonPosition: SearchButtonPosition = SearchButtonPosition.Action,
|
||||
query: String = "",
|
||||
colors: TopAppBarColors = TopAppBarDefaults.centerAlignedTopAppBarColors(),
|
||||
onNavigationClick: () -> Unit = {},
|
||||
onActionClick: () -> Unit = {}
|
||||
onActionClick: () -> Unit = {},
|
||||
onQueryChange: (String) -> Unit = {},
|
||||
onSearch: (String) -> Unit = {},
|
||||
onCancelSearch: () -> Unit = {}
|
||||
) {
|
||||
val topInset by animateIntAsState(
|
||||
targetValue = if (scrollBehavior != null && -scrollBehavior.state.heightOffset >= with(
|
||||
LocalDensity.current
|
||||
) { expandedHeight.toPx() }
|
||||
) 0
|
||||
else TopAppBarDefaults.windowInsets.getTop(LocalDensity.current),
|
||||
label = ""
|
||||
)
|
||||
val keyboardController = LocalSoftwareKeyboardController.current
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
|
||||
val onSearchExplicitlyTriggered = {
|
||||
keyboardController?.hide()
|
||||
onSearch(query)
|
||||
}
|
||||
|
||||
LaunchedEffect(activeSearch) {
|
||||
if (activeSearch) {
|
||||
focusRequester.requestFocus()
|
||||
}
|
||||
}
|
||||
|
||||
CenterAlignedTopAppBar(
|
||||
modifier = modifier,
|
||||
title = { Text(stringResource(titleRes)) },
|
||||
scrollBehavior = scrollBehavior,
|
||||
expandedHeight = expandedHeight,
|
||||
title = {
|
||||
if (activeSearch) TextField(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(Color.Transparent)
|
||||
.focusRequester(focusRequester)
|
||||
.onKeyEvent {
|
||||
if (it.key == Key.Enter) {
|
||||
onSearchExplicitlyTriggered()
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
},
|
||||
value = query,
|
||||
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
|
||||
keyboardActions = KeyboardActions(
|
||||
onSearch = {
|
||||
onSearchExplicitlyTriggered()
|
||||
}
|
||||
),
|
||||
colors = TextFieldDefaults.colors(
|
||||
unfocusedContainerColor = Color.Transparent,
|
||||
focusedContainerColor = Color.Transparent,
|
||||
unfocusedIndicatorColor = Color.Transparent,
|
||||
focusedIndicatorColor = Color.Transparent
|
||||
),
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
imageVector = OxygenIcons.Search,
|
||||
contentDescription = stringResource(R.string.core_search)
|
||||
)
|
||||
},
|
||||
maxLines = 1,
|
||||
singleLine = true,
|
||||
textStyle = MaterialTheme.typography.titleSmall,
|
||||
onValueChange = {
|
||||
if ("\n" !in it) onQueryChange(it)
|
||||
}
|
||||
)
|
||||
else title()
|
||||
},
|
||||
navigationIcon = {
|
||||
IconButton(onClick = onNavigationClick) {
|
||||
if (activeSearch && searchButtonPosition == SearchButtonPosition.Navigation) IconButton(
|
||||
onClick = onCancelSearch
|
||||
) {
|
||||
Icon(
|
||||
imageVector = navigationIcon,
|
||||
contentDescription = navigationIconContentDescription,
|
||||
tint = MaterialTheme.colorScheme.onSurface
|
||||
imageVector = OxygenIcons.Close,
|
||||
contentDescription = stringResource(R.string.core_close)
|
||||
)
|
||||
}
|
||||
else navigationIcon?.let {
|
||||
IconButton(onClick = onNavigationClick) {
|
||||
Icon(
|
||||
imageVector = navigationIcon,
|
||||
contentDescription = navigationIconContentDescription,
|
||||
tint = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
IconButton(onClick = onActionClick) {
|
||||
if (activeSearch && searchButtonPosition == SearchButtonPosition.Action) IconButton(
|
||||
onClick = onCancelSearch
|
||||
) {
|
||||
Icon(
|
||||
imageVector = actionIcon,
|
||||
contentDescription = actionIconContentDescription,
|
||||
tint = MaterialTheme.colorScheme.onSurface
|
||||
imageVector = OxygenIcons.Close,
|
||||
contentDescription = stringResource(R.string.core_close)
|
||||
)
|
||||
}
|
||||
else actionIcon?.let {
|
||||
IconButton(onClick = onActionClick) {
|
||||
Icon(
|
||||
imageVector = actionIcon,
|
||||
contentDescription = actionIconContentDescription,
|
||||
tint = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
colors = colors
|
||||
colors = colors,
|
||||
windowInsets = WindowInsets(top = topInset)
|
||||
)
|
||||
}
|
||||
|
||||
enum class SearchButtonPosition {
|
||||
Navigation, Action
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Preview
|
||||
@OxygenPreviews
|
||||
@Composable
|
||||
private fun OxygenTopAppBarPreview() {
|
||||
OxygenTheme {
|
||||
OxygenTopAppBar(
|
||||
titleRes = androidR.string.untitled,
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(androidR.string.untitled),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
},
|
||||
navigationIcon = OxygenIcons.Search,
|
||||
navigationIconContentDescription = "Navigation icon",
|
||||
actionIcon = OxygenIcons.MoreVert,
|
||||
actionIconContentDescription = "Action icon"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,385 @@
|
||||
package top.fatweb.oxygen.toolbox.ui.component
|
||||
|
||||
import androidx.compose.animation.core.LinearEasing
|
||||
import androidx.compose.animation.core.RepeatMode
|
||||
import androidx.compose.animation.core.infiniteRepeatable
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
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.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
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.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.valentinilk.shimmer.LocalShimmerTheme
|
||||
import com.valentinilk.shimmer.ShimmerBounds
|
||||
import com.valentinilk.shimmer.rememberShimmer
|
||||
import com.valentinilk.shimmer.shimmer
|
||||
import com.valentinilk.shimmer.shimmerSpec
|
||||
import top.fatweb.oxygen.toolbox.R
|
||||
import top.fatweb.oxygen.toolbox.icon.OxygenIcons
|
||||
import top.fatweb.oxygen.toolbox.model.tool.ToolEntity
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun ToolCard(
|
||||
modifier: Modifier = Modifier,
|
||||
tool: ToolEntity,
|
||||
specifyVer: String? = null,
|
||||
actionIcon: ImageVector? = null,
|
||||
actionIconContentDescription: String = "",
|
||||
onAction: () -> Unit = {},
|
||||
onClick: () -> Unit = {},
|
||||
onLongClick: () -> Unit = {}
|
||||
) {
|
||||
Card(
|
||||
modifier = modifier
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.combinedClickable(
|
||||
onClick = onClick,
|
||||
onLongClick = onLongClick
|
||||
),
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(16.dp)
|
||||
) {
|
||||
ToolHeader(
|
||||
ver = specifyVer ?: tool.ver,
|
||||
actionIcon = actionIcon,
|
||||
actionIconContentDescription = actionIconContentDescription,
|
||||
onAction = onAction
|
||||
)
|
||||
Spacer(Modifier.height(16.dp))
|
||||
ToolIcon(icon = tool.icon)
|
||||
Spacer(Modifier.height(16.dp))
|
||||
ToolInfo(
|
||||
toolName = tool.name,
|
||||
toolId = tool.toolId,
|
||||
toolDesc = tool.description
|
||||
)
|
||||
Spacer(Modifier.height(16.dp))
|
||||
AuthorInfo(
|
||||
avatar = tool.authorAvatar,
|
||||
nickname = tool.authorNickname
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ToolHeader(
|
||||
modifier: Modifier = Modifier,
|
||||
ver: String,
|
||||
actionIcon: ImageVector?,
|
||||
actionIconContentDescription: String,
|
||||
onAction: () -> Unit
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.height(28.dp)
|
||||
) {
|
||||
ToolVer(ver = ver)
|
||||
Spacer(Modifier.weight(1f))
|
||||
actionIcon?.let {
|
||||
ToolAction(
|
||||
actionIcon = actionIcon,
|
||||
actionIconContentDescription = actionIconContentDescription,
|
||||
onAction = onAction
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ToolVer(
|
||||
modifier: Modifier = Modifier,
|
||||
ver: String
|
||||
) {
|
||||
Card(
|
||||
modifier = modifier
|
||||
.fillMaxHeight(),
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
colors = CardDefaults.cardColors(contentColor = MaterialTheme.colorScheme.onSecondaryContainer)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxHeight()
|
||||
.background(color = MaterialTheme.colorScheme.surfaceContainer)
|
||||
.padding(horizontal = 8.dp, vertical = 4.dp),
|
||||
Arrangement.Center,
|
||||
Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
text = ver
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ToolAction(
|
||||
modifier: Modifier = Modifier,
|
||||
actionIcon: ImageVector,
|
||||
actionIconContentDescription: String,
|
||||
onAction: () -> Unit
|
||||
) {
|
||||
Card(
|
||||
modifier = modifier
|
||||
.fillMaxHeight()
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.clickable(
|
||||
onClick = onAction
|
||||
),
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
colors = CardDefaults.cardColors(contentColor = MaterialTheme.colorScheme.onSecondaryContainer)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxHeight()
|
||||
.background(color = MaterialTheme.colorScheme.surfaceContainer)
|
||||
.padding(horizontal = 6.dp, vertical = 6.dp)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = actionIcon,
|
||||
contentDescription = actionIconContentDescription
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ToolIcon(
|
||||
modifier: Modifier = Modifier,
|
||||
icon: String
|
||||
) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
.fillMaxWidth(),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Image(
|
||||
modifier = Modifier.size(80.dp),
|
||||
bitmap = OxygenIcons.fromSvgBase64(icon),
|
||||
contentDescription = ""
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ToolInfo(
|
||||
modifier: Modifier = Modifier,
|
||||
toolName: String,
|
||||
toolId: String,
|
||||
toolDesc: String?
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
Text(
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
fontWeight = FontWeight.ExtraBold,
|
||||
textAlign = TextAlign.Center,
|
||||
text = toolName
|
||||
)
|
||||
Text(
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
textAlign = TextAlign.Center,
|
||||
text = "ID: $toolId"
|
||||
)
|
||||
toolDesc?.let {
|
||||
Text(
|
||||
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.outline,
|
||||
text = "${stringResource(R.string.feature_tools_description)}: $it"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AuthorInfo(
|
||||
modifier: Modifier = Modifier,
|
||||
avatar: String,
|
||||
nickname: String
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterHorizontally)
|
||||
) {
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.size(24.dp),
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
) {
|
||||
Image(
|
||||
modifier = Modifier
|
||||
.background(MaterialTheme.colorScheme.surfaceContainer),
|
||||
bitmap = OxygenIcons.fromPngBase64(avatar), contentDescription = "Avatar"
|
||||
)
|
||||
}
|
||||
Text(
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
text = nickname
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ToolCardSkeleton(
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val shimmer = rememberShimmer(
|
||||
shimmerBounds = ShimmerBounds.Window,
|
||||
theme = LocalShimmerTheme.current.copy(
|
||||
animationSpec = infiniteRepeatable(
|
||||
animation = shimmerSpec(
|
||||
durationMillis = 1_500,
|
||||
easing = LinearEasing,
|
||||
delayMillis = 200
|
||||
),
|
||||
repeatMode = RepeatMode.Restart
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
Card(
|
||||
modifier = modifier
|
||||
.clip(RoundedCornerShape(8.dp)),
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(16.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.height(28.dp)
|
||||
) {
|
||||
Surface(
|
||||
modifier = modifier
|
||||
.fillMaxHeight()
|
||||
.width(64.dp)
|
||||
.shimmer(shimmer),
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
color = MaterialTheme.colorScheme.surfaceContainer
|
||||
) {}
|
||||
}
|
||||
Spacer(Modifier.height(16.dp))
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.size(80.dp)
|
||||
.shimmer(shimmer),
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
color = MaterialTheme.colorScheme.surfaceContainer
|
||||
) {}
|
||||
}
|
||||
Spacer(Modifier.height(16.dp))
|
||||
Column(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(6.dp)
|
||||
) {
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.size(width = 40.dp, height = 22.dp)
|
||||
.shimmer(shimmer),
|
||||
shape = RoundedCornerShape(4.dp),
|
||||
color = MaterialTheme.colorScheme.surfaceContainer
|
||||
) {}
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.size(width = 60.dp, height = 20.dp)
|
||||
.shimmer(shimmer),
|
||||
shape = RoundedCornerShape(4.dp),
|
||||
color = MaterialTheme.colorScheme.surfaceContainer
|
||||
) {}
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(2.dp)
|
||||
) {
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.size(width = 120.dp, height = 12.dp)
|
||||
.shimmer(shimmer),
|
||||
shape = RoundedCornerShape(4.dp),
|
||||
color = MaterialTheme.colorScheme.surfaceContainer
|
||||
) {}
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.size(width = 120.dp, height = 12.dp)
|
||||
.shimmer(shimmer),
|
||||
shape = RoundedCornerShape(4.dp),
|
||||
color = MaterialTheme.colorScheme.surfaceContainer
|
||||
) {}
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.size(width = 80.dp, height = 12.dp)
|
||||
.shimmer(shimmer),
|
||||
shape = RoundedCornerShape(4.dp),
|
||||
color = MaterialTheme.colorScheme.surfaceContainer
|
||||
) {}
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.height(16.dp))
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterHorizontally)
|
||||
) {
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.size(24.dp)
|
||||
.shimmer(shimmer),
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
color = MaterialTheme.colorScheme.surfaceContainer
|
||||
) {}
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.size(width = 120.dp, height = 12.dp)
|
||||
.shimmer(shimmer),
|
||||
shape = RoundedCornerShape(4.dp),
|
||||
color = MaterialTheme.colorScheme.surfaceContainer
|
||||
) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const val DEFAULT_TOOL_CARD_SKELETON_COUNT = 20
|
||||
@@ -0,0 +1,206 @@
|
||||
/*
|
||||
package top.fatweb.oxygen.toolbox.ui.component
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||
import androidx.compose.foundation.layout.FlowRow
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
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.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
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.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.rotate
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.unit.dp
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import top.fatweb.oxygen.toolbox.data.tool.ToolDataSource
|
||||
import top.fatweb.oxygen.toolbox.icon.OxygenIcons
|
||||
import top.fatweb.oxygen.toolbox.model.tool.Tool
|
||||
import top.fatweb.oxygen.toolbox.model.tool.ToolGroup
|
||||
import top.fatweb.oxygen.toolbox.ui.theme.OxygenPreviews
|
||||
import top.fatweb.oxygen.toolbox.ui.theme.OxygenTheme
|
||||
|
||||
@Composable
|
||||
fun ToolGroupCard(
|
||||
modifier: Modifier = Modifier,
|
||||
toolGroup: ToolGroup
|
||||
) {
|
||||
val (_, icon, title, tools) = toolGroup
|
||||
|
||||
var isExpanded by remember { mutableStateOf(true) }
|
||||
|
||||
Card(
|
||||
modifier = modifier,
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface)
|
||||
) {
|
||||
Column {
|
||||
ToolGroupTitle(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
icon = icon,
|
||||
title = title,
|
||||
isExpanded = isExpanded,
|
||||
onClick = { isExpanded = !isExpanded }
|
||||
)
|
||||
AnimatedVisibility(visible = isExpanded) {
|
||||
ToolGroupContent(modifier = Modifier.padding(16.dp), toolList = tools)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ToolGroupTitle(
|
||||
modifier: Modifier = Modifier,
|
||||
icon: ImageVector,
|
||||
title: String,
|
||||
isExpanded: Boolean,
|
||||
onClick: (() -> Unit)? = null
|
||||
) {
|
||||
Surface(onClick = onClick ?: {}) {
|
||||
Row(
|
||||
modifier = modifier,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Icon(modifier = Modifier.size(18.dp), imageVector = icon, contentDescription = title)
|
||||
Spacer(Modifier.width(10.dp))
|
||||
Text(text = title, style = MaterialTheme.typography.titleMedium)
|
||||
Spacer(Modifier.weight(1f))
|
||||
SwitchableIcon(icon = OxygenIcons.ArrowDown, switched = !isExpanded)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
fun ToolGroupContent(
|
||||
modifier: Modifier = Modifier,
|
||||
toolList: List<Tool>
|
||||
) {
|
||||
FlowRow(
|
||||
modifier = modifier,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
toolList.map {
|
||||
ToolGroupItem(icon = it.icon, title = it.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ToolGroupItem(
|
||||
modifier: Modifier = Modifier,
|
||||
icon: ImageVector,
|
||||
title: String,
|
||||
onClick: (() -> Unit)? = null
|
||||
) {
|
||||
Card(
|
||||
modifier = modifier,
|
||||
shape = RoundedCornerShape(100),
|
||||
onClick = onClick ?: { }
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier.padding(8.dp)
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(modifier = Modifier.size(16.dp), imageVector = icon, contentDescription = null)
|
||||
Spacer(Modifier.width(4.dp))
|
||||
Text(text = title, style = MaterialTheme.typography.bodyMedium)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SwitchableIcon(
|
||||
modifier: Modifier = Modifier,
|
||||
icon: ImageVector,
|
||||
switched: Boolean,
|
||||
defaultRotate: Float = 0f,
|
||||
switchedRotate: Float = 180f,
|
||||
) {
|
||||
val rotate by animateFloatAsState(
|
||||
if (switched) switchedRotate else defaultRotate,
|
||||
label = "Rotate"
|
||||
)
|
||||
|
||||
Icon(
|
||||
modifier = modifier
|
||||
.rotate(rotate), imageVector = icon, contentDescription = null
|
||||
)
|
||||
}
|
||||
|
||||
@OxygenPreviews
|
||||
@Composable
|
||||
private fun ToolGroupCardPreview() {
|
||||
val groups = runBlocking { ToolDataSource().tool.first() }
|
||||
|
||||
OxygenTheme {
|
||||
LazyColumn {
|
||||
itemsIndexed(groups) { index, item ->
|
||||
if (index != 0) {
|
||||
Spacer(Modifier.height(10.dp))
|
||||
}
|
||||
ToolGroupCard(
|
||||
toolGroup = item
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OxygenPreviews
|
||||
@Composable
|
||||
fun SwitchableIconPreview() {
|
||||
var switched by remember { mutableStateOf(false) }
|
||||
|
||||
OxygenTheme {
|
||||
Surface(
|
||||
onClick = { switched = !switched }
|
||||
) {
|
||||
SwitchableIcon(icon = OxygenIcons.ArrowDown, switched = switched)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OxygenPreviews
|
||||
@Composable
|
||||
fun ToolGroupItemPreview() {
|
||||
OxygenTheme {
|
||||
ToolGroupItem(icon = OxygenIcons.Time, title = "Time Screen")
|
||||
}
|
||||
}
|
||||
|
||||
@OxygenPreviews
|
||||
@Composable
|
||||
fun ToolGroupContentPreview() {
|
||||
OxygenTheme {
|
||||
ToolGroupContent(toolList = runBlocking {
|
||||
ToolDataSource().tool.first().map { it.tools }.flatten()
|
||||
})
|
||||
}
|
||||
}*/
|
||||
@@ -0,0 +1,237 @@
|
||||
package top.fatweb.oxygen.toolbox.ui.component.scrollbar
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import androidx.compose.animation.animateColorAsState
|
||||
import androidx.compose.animation.core.Spring
|
||||
import androidx.compose.animation.core.SpringSpec
|
||||
import androidx.compose.foundation.gestures.Orientation
|
||||
import androidx.compose.foundation.gestures.ScrollableState
|
||||
import androidx.compose.foundation.interaction.InteractionSource
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.interaction.collectIsDraggedAsState
|
||||
import androidx.compose.foundation.interaction.collectIsHoveredAsState
|
||||
import androidx.compose.foundation.interaction.collectIsPressedAsState
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.State
|
||||
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.geometry.Size
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.ColorProducer
|
||||
import androidx.compose.ui.graphics.Outline
|
||||
import androidx.compose.ui.graphics.drawOutline
|
||||
import androidx.compose.ui.graphics.drawscope.ContentDrawScope
|
||||
import androidx.compose.ui.node.DrawModifierNode
|
||||
import androidx.compose.ui.node.ModifierNodeElement
|
||||
import androidx.compose.ui.node.invalidateDraw
|
||||
import androidx.compose.ui.unit.LayoutDirection
|
||||
import androidx.compose.ui.unit.dp
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
/**
|
||||
* The time period for showing the scrollbar thumb after interacting with it, before it fades away
|
||||
*/
|
||||
private const val SCROLLBAR_INACTIVE_TO_DORMANT_TIME_IN_MS = 2_000L
|
||||
|
||||
/**
|
||||
* A [Scrollbar] that allows for fast scrolling of content by dragging its thumb.
|
||||
* Its thumb disappears when the scrolling container is dormant.
|
||||
* @param modifier a [Modifier] for the [Scrollbar]
|
||||
* @param state the driving state for the [Scrollbar]
|
||||
* @param orientation the orientation of the scrollbar
|
||||
* @param onThumbMoved the fast scroll implementation
|
||||
*/
|
||||
@Composable
|
||||
fun ScrollableState.DraggableScrollbar(
|
||||
modifier: Modifier = Modifier,
|
||||
state: ScrollbarState,
|
||||
orientation: Orientation,
|
||||
onThumbMoved: (Float) -> Unit
|
||||
) {
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
Scrollbar(
|
||||
modifier = modifier,
|
||||
orientation = orientation,
|
||||
interactionSource = interactionSource,
|
||||
state = state,
|
||||
thumb = {
|
||||
DraggableScrollbarThumb(
|
||||
interactionSource = interactionSource,
|
||||
orientation = orientation
|
||||
)
|
||||
},
|
||||
onThumbMoved = onThumbMoved
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A scrollbar thumb that is intended to also be a touch target for fast scrolling.
|
||||
*/
|
||||
@Composable
|
||||
private fun ScrollableState.DraggableScrollbarThumb(
|
||||
interactionSource: InteractionSource,
|
||||
orientation: Orientation
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.run {
|
||||
when (orientation) {
|
||||
Orientation.Vertical -> width(2.dp).fillMaxHeight()
|
||||
Orientation.Horizontal -> height(2.dp).fillMaxWidth()
|
||||
}
|
||||
}
|
||||
.scrollThumb(this, interactionSource)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple [Scrollbar].
|
||||
* Its thumb disappears when the scrolling container is dormant.
|
||||
* @param modifier a [Modifier] for the [Scrollbar]
|
||||
* @param state the driving state for the [Scrollbar]
|
||||
* @param orientation the orientation of the scrollbar
|
||||
*/
|
||||
@Composable
|
||||
fun ScrollableState.DecorativeScrollbar(
|
||||
modifier: Modifier,
|
||||
state: ScrollbarState,
|
||||
orientation: Orientation
|
||||
) {
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
Scrollbar(
|
||||
modifier = modifier,
|
||||
orientation = orientation,
|
||||
interactionSource = interactionSource,
|
||||
state = state,
|
||||
thumb = {
|
||||
DecorativeScrollbarThumb(
|
||||
interactionSource = interactionSource,
|
||||
orientation = orientation
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A decorative scrollbar thumb used solely for communicating a user's position in a list.
|
||||
*/
|
||||
@Composable
|
||||
private fun ScrollableState.DecorativeScrollbarThumb(
|
||||
interactionSource: InteractionSource,
|
||||
orientation: Orientation
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.run {
|
||||
when (orientation) {
|
||||
Orientation.Vertical -> width(2.dp).fillMaxHeight()
|
||||
Orientation.Horizontal -> height(2.dp).fillMaxWidth()
|
||||
}
|
||||
}
|
||||
.scrollThumb(this, interactionSource)
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Modifier.scrollThumb(
|
||||
scrollbarState: ScrollableState,
|
||||
interactionSource: InteractionSource
|
||||
): Modifier {
|
||||
val colorState =
|
||||
scrollbarThumbColor(scrollableState = scrollbarState, interactionSource = interactionSource)
|
||||
return this then ScrollThumbElement { colorState.value }
|
||||
}
|
||||
|
||||
@SuppressLint("ModifierNodeInspectableProperties")
|
||||
private data class ScrollThumbElement(val colorProducer: ColorProducer) :
|
||||
ModifierNodeElement<ScrollThumbNode>() {
|
||||
override fun create(): ScrollThumbNode = ScrollThumbNode(colorProducer)
|
||||
|
||||
override fun update(node: ScrollThumbNode) {
|
||||
node.colorProducer = colorProducer
|
||||
node.invalidateDraw()
|
||||
}
|
||||
}
|
||||
|
||||
private class ScrollThumbNode(var colorProducer: ColorProducer) : DrawModifierNode,
|
||||
Modifier.Node() {
|
||||
private val shape = RoundedCornerShape(16.dp)
|
||||
|
||||
// Naive cache outline calculation if size is th same
|
||||
private var lastSize: Size? = null
|
||||
private var lastLayoutDirection: LayoutDirection? = null
|
||||
private var lastoutline: Outline? = null
|
||||
|
||||
override fun ContentDrawScope.draw() {
|
||||
val color = colorProducer()
|
||||
val outline =
|
||||
if (size == lastSize && layoutDirection == lastLayoutDirection) {
|
||||
lastoutline!!
|
||||
} else {
|
||||
shape.createOutline(size, layoutDirection, this)
|
||||
}
|
||||
if (color != Color.Unspecified) drawOutline(outline, color)
|
||||
|
||||
lastoutline = outline
|
||||
lastSize = size
|
||||
lastLayoutDirection = layoutDirection
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The color of the scrollbar thumb as a function of its interaction state.
|
||||
* @param interactionSource source of interactions in the scrolling container
|
||||
*/
|
||||
@Composable
|
||||
private fun scrollbarThumbColor(
|
||||
scrollableState: ScrollableState,
|
||||
interactionSource: InteractionSource
|
||||
): State<Color> {
|
||||
var state by remember { mutableStateOf(ThumbState.Dormant) }
|
||||
val pressed by interactionSource.collectIsPressedAsState()
|
||||
val hovered by interactionSource.collectIsHoveredAsState()
|
||||
val dragged by interactionSource.collectIsDraggedAsState()
|
||||
val active =
|
||||
(scrollableState.canScrollForward || scrollableState.canScrollBackward && (pressed || hovered || dragged || scrollableState.isScrollInProgress))
|
||||
|
||||
val color = animateColorAsState(
|
||||
targetValue = when (state) {
|
||||
ThumbState.Active -> MaterialTheme.colorScheme.onSurface.copy(0.5f)
|
||||
ThumbState.Inactive -> MaterialTheme.colorScheme.onSurface.copy(0.2f)
|
||||
ThumbState.Dormant -> Color.Transparent
|
||||
},
|
||||
animationSpec = SpringSpec(
|
||||
stiffness = Spring.StiffnessLow
|
||||
),
|
||||
label = "Scrollbar thumb color"
|
||||
)
|
||||
LaunchedEffect(active) {
|
||||
when (active) {
|
||||
true -> state = ThumbState.Active
|
||||
false -> if (state == ThumbState.Active) {
|
||||
state = ThumbState.Inactive
|
||||
delay(SCROLLBAR_INACTIVE_TO_DORMANT_TIME_IN_MS)
|
||||
state = ThumbState.Dormant
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return color
|
||||
}
|
||||
|
||||
private enum class ThumbState {
|
||||
Active,
|
||||
Inactive,
|
||||
Dormant,
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user