Compare commits

...

27 Commits

Author SHA1 Message Date
71119ac4c4 Fix(OxygenTopAppBar): Fix the bug that cannot hide completely when scrolling 2024-11-06 14:12:06 +08:00
c91eaaf1a5 Build(gradle): Upgrade dependencies 2024-11-05 17:57:41 +08:00
77802d3dc9 Refactor(WebView): Change background color to transparent 2024-11-05 17:45:25 +08:00
8669a2c9ef Feat(Tool): Support theme 2024-11-05 11:45:56 +08:00
d589df860e Refactor(ToolView): Remove safeDrawingPadding 2024-10-15 12:12:19 +08:00
db25ab4e5f Feat(App): Support sliding down the notification bar to exit full screen 2024-10-15 12:01:30 +08:00
e855a414a4 Feat(App): Support full screen 2024-10-14 18:07:03 +08:00
2823788765 Feat(ToolView): Support alert, confirm and prompt 2024-10-14 16:49:10 +08:00
fc4baca21d Build(gradle): Upgrade dependencies 2024-10-14 16:48:06 +08:00
e188d743f0 Refactor(.gitignore): Ignore key store 2024-10-12 15:59:37 +08:00
f7dd806885 Refactor(FileUtils): Optimize code 2024-10-12 15:59:07 +08:00
326e777b7f Refactor(TopAppBar): Optimize size of TopAppBar 2024-10-12 14:48:52 +08:00
0eb0667d2a Perf(ToolView): Optimize webview loading performance 2024-10-12 11:55:15 +08:00
cd97f6156f Refactor(minSdk): Upgrade minSdk from 21 to 24 2024-10-12 09:49:39 +08:00
3979027471 Refactor(NativeWebApi): Change data transfer type from ByteArray to Base64 String 2024-10-11 09:42:43 +08:00
d3f3fba413 Feat(ToolView): Support filltering file types 2024-10-11 09:42:40 +08:00
7b77069744 Refactor(SplashScreen): Optimize the splash screen of lower version 2024-10-11 09:42:35 +08:00
585a261bb8 Feat(ToolView): Support upload and download file 2024-09-30 15:06:28 +08:00
2631c22e52 Refactor(BottomAppBar): Adapt virtual button navigation bar 2024-09-30 12:09:43 +08:00
af3edb1d30 Refactor(ToolViewScreenViewModel): Save tool detail in cache 2024-09-29 15:52:12 +08:00
ba72ef2759 Refactor(Any): Optimize codes 2024-09-27 09:21:13 +08:00
2639d88492 Refactor(ClickableText): Replace ClickableText with Text 2024-09-26 18:44:59 +08:00
04812cf56b Build(gradle): Upgrade dependencies 2024-09-26 18:44:03 +08:00
112e30804e Refactor(Icon): Update APP icon and add startup animation 2024-09-24 17:55:14 +08:00
7e5554cfcd Refactor(Gradle): Optimize build script
Optimize version number rule. Optimize generated file name.
2024-09-02 16:36:46 +08:00
7e91f267e3 Refactor(AboutScreen): Optimize version info 2024-09-02 16:35:11 +08:00
21bb9a729f Build(Gradle): Auto sign release package 2024-09-01 23:02:07 +08:00
76 changed files with 2928 additions and 955 deletions

4
.gitignore vendored
View File

@@ -17,6 +17,10 @@ build/
# Local configuration file (sdk path, etc) # Local configuration file (sdk path, etc)
local.properties local.properties
# Key Store
keystore.properties
*.jks
# Eclipse project files # Eclipse project files
.classpath .classpath
.project .project

View File

@@ -1,5 +1,23 @@
import com.android.build.gradle.internal.api.BaseVariantOutputImpl
import com.mikepenz.aboutlibraries.plugin.AboutLibrariesTask 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 { plugins {
alias(libs.plugins.androidApplication) alias(libs.plugins.androidApplication)
@@ -17,19 +35,38 @@ plugins {
android { android {
namespace = "top.fatweb.oxygen.toolbox" namespace = "top.fatweb.oxygen.toolbox"
compileSdk = 34 compileSdk = 35
defaultConfig { defaultConfig {
applicationId = "top.fatweb.oxygen.toolbox" applicationId = "top.fatweb.oxygen.toolbox"
minSdk = 21 minSdk = 24
targetSdk = 34 targetSdk = 35
versionCode = 1 versionCode = baseVersionCode
versionName = "1.0.0-SNAPSHOT" versionName = "$baseVersionName${
if (baseVersionCode % 100 != 0) ".${
localDateTime.format(
DateTimeFormatter.ofPattern("yyMMdd")
)
}" else ""
}"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables { vectorDrawables {
useSupportLibrary = true useSupportLibrary = 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 { buildTypes {
@@ -40,13 +77,7 @@ android {
getDefaultProguardFile("proguard-android-optimize.txt"), getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro" "proguard-rules.pro"
) )
} signingConfig = signingConfigs.findByName("release")
}
android.applicationVariants.all {
outputs.all {
(this as BaseVariantOutputImpl).outputFileName =
"${project.name}_${defaultConfig.versionName}-${defaultConfig.versionCode}_${buildType.name}.apk"
} }
} }
@@ -64,9 +95,6 @@ android {
compose = true compose = true
buildConfig = true buildConfig = true
} }
composeOptions {
kotlinCompilerExtensionVersion = "1.5.12"
}
packaging { packaging {
resources { resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}" excludes += "/META-INF/{AL2.0,LGPL2.1}"
@@ -155,6 +183,7 @@ dependencies {
implementation(libs.material.icons.core) implementation(libs.material.icons.core)
implementation(libs.material.icons.extended) implementation(libs.material.icons.extended)
implementation(libs.material3.window.size) implementation(libs.material3.window.size)
implementation(libs.animation.graphics)
implementation(libs.androidx.core.ktx) implementation(libs.androidx.core.ktx)
implementation(libs.androidx.activity.compose) implementation(libs.androidx.activity.compose)
implementation(libs.androidx.appcompat) implementation(libs.androidx.appcompat)

View File

@@ -4,6 +4,9 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="32"
tools:ignore="ScopedStorage" />
<application <application
android:name=".OxygenApplication" android:name=".OxygenApplication"

View 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;
}

View File

@@ -0,0 +1,517 @@
const globalVariables = {
OxygenTheme: {
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]
}

View 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;
}

View File

@@ -0,0 +1,518 @@
const globalVariables = {
OxygenTheme: {
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]
}

View File

@@ -1,18 +1,23 @@
<!doctype html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Preview</title> <title>Preview</title>
<!-- es-module-shims --> <script id="global-js-variables">
</head> {{replace_global_js_variables}}
<body> </script>
<script type="module" id="appSrc">{{replace_code}}</script> <style id="global-css-variables">
<div id="root"> {{replace_global_css_variables}}
<div </style>
style="position:absolute;top: 0;left:0;width:100%;height:100%;display: flex;justify-content: center;align-items: center;"> </head>
Loading... <body>
</div> <script type="module" id="appDictSrc">{{replace_dict_code}}</script>
</div> <script type="module" id="appBaseSrc">{{replace_base_code}}</script>
</body> <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> </html>

View File

@@ -22,4 +22,30 @@ class ToolDataSource @Inject constructor(
} }
) )
}.flowOn(ioDispatcher) }.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)
} }

View File

@@ -10,6 +10,8 @@ import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.Code import androidx.compose.material.icons.filled.Code
import androidx.compose.material.icons.filled.Delete import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Download 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.Inbox
import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material.icons.filled.Reorder import androidx.compose.material.icons.filled.Reorder
@@ -43,6 +45,8 @@ object OxygenIcons {
val Delete = Icons.Default.Delete val Delete = Icons.Default.Delete
val Download = Icons.Default.Download val Download = Icons.Default.Download
val Error = Icons.Default.Cancel val Error = Icons.Default.Cancel
val FullScreen = Icons.Default.Fullscreen
val FullScreenExit = Icons.Default.FullscreenExit
val Home = Icons.Rounded.Home val Home = Icons.Rounded.Home
val HomeBorder = Icons.Outlined.Home val HomeBorder = Icons.Outlined.Home
val Info = Icons.Outlined.Info val Info = Icons.Outlined.Info

View File

@@ -57,14 +57,8 @@ internal class ConnectivityManagerNetworkMonitor @Inject constructor(
} }
.conflate() .conflate()
@Suppress("DEPRECATION")
private fun ConnectivityManager.isCurrentlyConnected() = private fun ConnectivityManager.isCurrentlyConnected() =
when { activeNetwork
VERSION.SDK_INT >= VERSION_CODES.M -> ?.let(::getNetworkCapabilities)
activeNetwork ?.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) == true
?.let(::getNetworkCapabilities)
?.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
else -> activeNetworkInfo?.isConnected
} ?: false
} }

View File

@@ -1,9 +1,14 @@
package top.fatweb.oxygen.toolbox.navigation package top.fatweb.oxygen.toolbox.navigation
import android.os.Bundle
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.navigation.NavController
import androidx.navigation.NavDestination
import androidx.navigation.compose.NavHost import androidx.navigation.compose.NavHost
import top.fatweb.oxygen.toolbox.ui.OxygenAppState import top.fatweb.oxygen.toolbox.ui.OxygenAppState
import top.fatweb.oxygen.toolbox.ui.util.LocalFullScreen
@Composable @Composable
fun OxygenNavHost( fun OxygenNavHost(
@@ -16,6 +21,21 @@ fun OxygenNavHost(
onShowSnackbar: suspend (message: String, action: String?) -> Boolean onShowSnackbar: suspend (message: String, action: String?) -> Boolean
) { ) {
val navController = appState.navController 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( NavHost(
modifier = modifier, modifier = modifier,
navController = navController, navController = navController,

View File

@@ -6,6 +6,10 @@ import top.fatweb.oxygen.toolbox.model.tool.ToolEntity
interface ToolRepository { interface ToolRepository {
val toolViewTemplate: Flow<String> val toolViewTemplate: Flow<String>
fun getGlobalJsVariables(isDarkMode: Boolean): Flow<String>
fun getGlobalCssVariables(isDarkMode: Boolean): Flow<String>
fun getAllToolsStream(searchValue: String): Flow<List<ToolEntity>> fun getAllToolsStream(searchValue: String): Flow<List<ToolEntity>>
fun getStarToolsStream(searchValue: String): Flow<List<ToolEntity>> fun getStarToolsStream(searchValue: String): Flow<List<ToolEntity>>

View File

@@ -14,6 +14,12 @@ class OfflineToolRepository @Inject constructor(
override val toolViewTemplate: Flow<String> override val toolViewTemplate: Flow<String>
get() = toolDataSource.toolViewTemplate 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>> = override fun getAllToolsStream(searchValue: String): Flow<List<ToolEntity>> =
toolDao.selectAllTools(searchValue) toolDao.selectAllTools(searchValue)

View File

@@ -1,18 +1,14 @@
package top.fatweb.oxygen.toolbox.ui package top.fatweb.oxygen.toolbox.ui
import androidx.activity.ComponentActivity
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.consumeWindowInsets import androidx.compose.foundation.layout.consumeWindowInsets
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.foundation.layout.safeDrawingPadding import androidx.compose.foundation.layout.safeDrawingPadding
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.material3.BottomAppBar
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
@@ -24,6 +20,8 @@ import androidx.compose.material3.SnackbarResult
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableIntStateOf
@@ -34,7 +32,11 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource 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.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavDestination import androidx.navigation.NavDestination
import androidx.navigation.NavDestination.Companion.hierarchy import androidx.navigation.NavDestination.Companion.hierarchy
@@ -57,6 +59,8 @@ import top.fatweb.oxygen.toolbox.ui.component.SearchButtonPosition
import top.fatweb.oxygen.toolbox.ui.settings.SettingsDialog import top.fatweb.oxygen.toolbox.ui.settings.SettingsDialog
import top.fatweb.oxygen.toolbox.ui.theme.GradientColors import top.fatweb.oxygen.toolbox.ui.theme.GradientColors
import top.fatweb.oxygen.toolbox.ui.theme.LocalGradientColors 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) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
@@ -67,70 +71,100 @@ fun OxygenApp(appState: OxygenAppState) {
mutableStateOf(false) mutableStateOf(false)
} }
OxygenBackground { val context = LocalContext.current
OxygenGradientBackground( val window = (context as ComponentActivity).window
gradientColors = if (shouldShowGradientBackground) LocalGradientColors.current else GradientColors() val windowInsetsController = WindowInsetsControllerCompat(window, window.decorView)
) { var isFullScreen by remember { mutableStateOf(false) }
val destination = appState.currentTopLevelDestination
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.core_no_connect) LaunchedEffect(isFullScreen) {
if (isFullScreen) {
windowInsetsController.hide(WindowInsetsCompat.Type.systemBars())
} else {
windowInsetsController.show(WindowInsetsCompat.Type.systemBars())
}
}
var canScroll by remember { mutableStateOf(true) } CompositionLocalProvider(LocalFullScreen provides fullScreen) {
val topAppBarScrollBehavior = OxygenBackground {
if (canScroll) TopAppBarDefaults.enterAlwaysScrollBehavior() else TopAppBarDefaults.pinnedScrollBehavior() OxygenGradientBackground(
gradientColors = if (shouldShowGradientBackground) LocalGradientColors.current else GradientColors()
) {
val destination = appState.currentTopLevelDestination
var activeSearch by remember { mutableStateOf(false) } val snackbarHostState = remember { SnackbarHostState() }
var searchValue by remember { mutableStateOf("") }
var searchCount by remember { mutableIntStateOf(0) }
LaunchedEffect(activeSearch) { val isOffline by appState.isOffline.collectAsStateWithLifecycle()
canScroll = !activeSearch
}
LaunchedEffect(destination) { val noConnectMessage = stringResource(R.string.core_no_connect)
activeSearch = false
searchValue = "" var canScroll by remember { mutableStateOf(true) }
if (searchCount == 0) { val topAppBarScrollBehavior =
searchCount++ if (canScroll) TopAppBarDefaults.enterAlwaysScrollBehavior() else TopAppBarDefaults.pinnedScrollBehavior()
} else {
searchCount = 0 var activeSearch by remember { mutableStateOf(false) }
var searchValue by remember { mutableStateOf("") }
var searchCount by remember { mutableIntStateOf(0) }
LaunchedEffect(activeSearch) {
canScroll = !activeSearch
} }
}
LaunchedEffect(isOffline) { LaunchedEffect(destination) {
if (isOffline) { activeSearch = false
snackbarHostState.showSnackbar( searchValue = ""
message = noConnectMessage, if (searchCount == 0) {
duration = SnackbarDuration.Indefinite 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) { Scaffold(
SettingsDialog( modifier = Modifier
onDismiss = { showSettingsDialog = false }, .nestedScroll(connection = topAppBarScrollBehavior.nestedScrollConnection),
onNavigateToLibraries = appState::navigateToLibraries, containerColor = Color.Transparent,
onNavigateToAbout = appState::navigateToAbout contentColor = MaterialTheme.colorScheme.onBackground,
) contentWindowInsets = WindowInsets(left = 0, top = 0, right = 0, bottom = 0),
} snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
bottomBar = {
Scaffold( AnimatedVisibility(
modifier = Modifier visible = appState.shouldShowBottomBar && destination != null
.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
) {
BottomAppBar(
windowInsets = WindowInsets(0)
) { ) {
OxygenBottomBar( OxygenBottomBar(
destinations = appState.topLevelDestinations, destinations = appState.topLevelDestinations,
@@ -139,85 +173,87 @@ fun OxygenApp(appState: OxygenAppState) {
) )
} }
} }
} ) { padding ->
) { padding -> Row(
Row( Modifier
Modifier .fillMaxSize()
.fillMaxSize()
.padding(padding)
.consumeWindowInsets(padding)
.windowInsetsPadding(
WindowInsets.safeDrawing.only(
WindowInsetsSides.Horizontal
)
)
) {
AnimatedVisibility(
visible = appState.shouldShowNavRail && destination != null
) {
OxygenNavRail(
modifier = Modifier.safeDrawingPadding(),
destinations = appState.topLevelDestinations,
currentDestination = appState.currentDestination,
onNavigateToDestination = appState::navigateToTopLevelDestination
)
}
Column(
Modifier.fillMaxSize()
) { ) {
AnimatedVisibility( AnimatedVisibility(
visible = destination != null visible = appState.shouldShowNavRail && destination != null
) { ) {
OxygenTopAppBar( OxygenNavRail(
scrollBehavior = topAppBarScrollBehavior, modifier = Modifier
title = { .padding(padding)
destination?.let { Text(text = stringResource(destination.titleTextId)) } .consumeWindowInsets(padding)
}, .safeDrawingPadding(),
navigationIcon = OxygenIcons.Search, destinations = appState.topLevelDestinations,
navigationIconContentDescription = stringResource(R.string.feature_settings_top_app_bar_navigation_icon_description), currentDestination = appState.currentDestination,
actionIcon = OxygenIcons.MoreVert, onNavigateToDestination = appState::navigateToTopLevelDestination
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( Column(
appState = appState, Modifier.fillMaxSize()
startDestination = when (appState.launchPageConfig) { ) {
LaunchPageConfig.Tools -> TOOLS_ROUTE AnimatedVisibility(
LaunchPageConfig.Star -> STAR_ROUTE visible = destination != null
}, ) {
isVertical = appState.shouldShowBottomBar, OxygenTopAppBar(
searchValue = searchValue, scrollBehavior = topAppBarScrollBehavior,
searchCount = searchCount, title = {
onShowSnackbar = { message, action -> destination?.let {
snackbarHostState.showSnackbar( Text(
message = message, text = stringResource(destination.titleTextId),
actionLabel = action, maxLines = 1,
duration = SnackbarDuration.Short overflow = TextOverflow.Ellipsis
) == SnackbarResult.ActionPerformed )
}
},
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
}
)
}
} }
} }
} }
@@ -295,5 +331,5 @@ private fun OxygenNavRail(
private fun NavDestination?.isTopLevelDestinationInHierarchy(destination: TopLevelDestination) = private fun NavDestination?.isTopLevelDestinationInHierarchy(destination: TopLevelDestination) =
this?.hierarchy?.any { this?.hierarchy?.any {
it.route?.equals(destination.route) ?: false it.route?.equals(destination.route) == true
} ?: false } == true

View File

@@ -1,5 +1,10 @@
package top.fatweb.oxygen.toolbox.ui.about 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.Image
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
@@ -13,6 +18,7 @@ import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawing import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.foundation.layout.safeDrawingPadding import androidx.compose.foundation.layout.safeDrawingPadding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
@@ -23,16 +29,21 @@ import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable 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.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import kotlinx.coroutines.delay
import top.fatweb.oxygen.toolbox.R import top.fatweb.oxygen.toolbox.R
import top.fatweb.oxygen.toolbox.icon.OxygenIcons import top.fatweb.oxygen.toolbox.icon.OxygenIcons
import top.fatweb.oxygen.toolbox.ui.component.OxygenTopAppBar import top.fatweb.oxygen.toolbox.ui.component.OxygenTopAppBar
@@ -86,7 +97,11 @@ internal fun AboutScreen(
OxygenTopAppBar( OxygenTopAppBar(
scrollBehavior = topAppBarScrollBehavior, scrollBehavior = topAppBarScrollBehavior,
title = { title = {
Text(text = stringResource(R.string.feature_settings_more_about)) Text(
text = stringResource(R.string.feature_settings_more_about),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}, },
navigationIcon = OxygenIcons.Back, navigationIcon = OxygenIcons.Back,
navigationIconContentDescription = stringResource(R.string.core_back), navigationIconContentDescription = stringResource(R.string.core_back),
@@ -106,16 +121,26 @@ internal fun AboutScreen(
} }
} }
@OptIn(ExperimentalAnimationGraphicsApi::class)
@Composable @Composable
private fun AboutAppInfo( private fun AboutAppInfo(
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
val logo = AnimatedImageVector.animatedVectorResource(R.drawable.ic_launcher)
var atEnd by remember { mutableStateOf(false) }
LaunchedEffect(Unit) {
delay(AnimationConstants.DefaultDurationMillis.toLong())
atEnd = true
}
Column( Column(
modifier = modifier, modifier = modifier,
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {
Image( Image(
imageVector = ImageVector.vectorResource(R.drawable.ic_oxygen), modifier = Modifier
.width(160.dp),
painter = rememberAnimatedVectorPainter(animatedImageVector = logo, atEnd = atEnd),
contentDescription = stringResource(R.string.app_full_name) contentDescription = stringResource(R.string.app_full_name)
) )
Spacer(Modifier.height(16.dp)) Spacer(Modifier.height(16.dp))
@@ -135,9 +160,12 @@ private fun AboutAppInfo(
Text( Text(
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.outline, color = MaterialTheme.colorScheme.outline,
text = "${ResourcesUtils.getAppVersionName(LocalContext.current)}(${ text = "${ResourcesUtils.getAppVersionName(LocalContext.current)} (${
ResourcesUtils.getAppVersionCode( stringResource(
LocalContext.current if (ResourcesUtils.getAppVersionCode(LocalContext.current) % 100 == 0L)
R.string.core_ga_version
else
R.string.core_beta_version
) )
})" })"
) )

View File

@@ -47,6 +47,7 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
@@ -132,7 +133,11 @@ internal fun LibrariesScreen(
OxygenTopAppBar( OxygenTopAppBar(
scrollBehavior = topAppBarScrollBehavior, scrollBehavior = topAppBarScrollBehavior,
title = { title = {
Text(text = stringResource(R.string.feature_settings_open_source_license)) Text(
text = stringResource(R.string.feature_settings_open_source_license),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}, },
navigationIcon = OxygenIcons.Back, navigationIcon = OxygenIcons.Back,
navigationIconContentDescription = stringResource(R.string.core_back), navigationIconContentDescription = stringResource(R.string.core_back),
@@ -159,7 +164,7 @@ internal fun LibrariesScreen(
onSearch("") onSearch("")
} }
) )
Box(modifier = Modifier) { Box {
when (librariesScreenUiState) { when (librariesScreenUiState) {
LibrariesScreenUiState.Loading -> { LibrariesScreenUiState.Loading -> {
Indicator() Indicator()
@@ -247,10 +252,7 @@ internal fun LibrariesScreen(
Column( Column(
modifier = Modifier.verticalScroll(state = rememberScrollState()) modifier = Modifier.verticalScroll(state = rememberScrollState())
) { ) {
Text( Text(text = dialogContent)
modifier = Modifier,
text = dialogContent
)
} }
}, },
confirmButton = { confirmButton = {

View File

@@ -1,10 +1,11 @@
package top.fatweb.oxygen.toolbox.ui.component package top.fatweb.oxygen.toolbox.ui.component
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.compose.foundation.text.ClickableText
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.LinkAnnotation
import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.withStyle import androidx.compose.ui.text.withStyle
@@ -14,8 +15,8 @@ import top.fatweb.oxygen.toolbox.ui.util.ResourcesUtils
fun ClickableText( fun ClickableText(
@StringRes text: Int, @StringRes text: Int,
@StringRes replaceText: Int, @StringRes replaceText: Int,
onClick: (Int) -> Unit onClick: () -> Unit
) { ) {
val context = LocalContext.current val context = LocalContext.current
val primaryColor = MaterialTheme.colorScheme.primary val primaryColor = MaterialTheme.colorScheme.primary
@@ -30,7 +31,10 @@ fun ClickableText(
clickablePart clickablePart
) )
append(mainText.substringBefore(clickablePart)) append(mainText.substringBefore(clickablePart))
pushStringAnnotation(tag = "Click", annotation = clickablePart) pushLink(LinkAnnotation.Clickable(
tag = "Click",
linkInteractionListener = { onClick() }
))
withStyle(style = SpanStyle(color = primaryColor)) { withStyle(style = SpanStyle(color = primaryColor)) {
append(clickablePart) append(clickablePart)
} }
@@ -38,5 +42,5 @@ fun ClickableText(
append(mainText.substringAfter(clickablePart)) append(mainText.substringAfter(clickablePart))
} }
ClickableText(text = annotatedString, onClick = onClick) Text(text = annotatedString)
} }

View File

@@ -33,6 +33,8 @@ import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction 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 androidx.compose.ui.unit.dp
import top.fatweb.oxygen.toolbox.R import top.fatweb.oxygen.toolbox.R
import top.fatweb.oxygen.toolbox.icon.OxygenIcons import top.fatweb.oxygen.toolbox.icon.OxygenIcons
@@ -44,6 +46,7 @@ import android.R as androidR
@Composable @Composable
fun OxygenTopAppBar( fun OxygenTopAppBar(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
expandedHeight: Dp = 48.dp,
scrollBehavior: TopAppBarScrollBehavior? = null, scrollBehavior: TopAppBarScrollBehavior? = null,
title: @Composable () -> Unit = {}, title: @Composable () -> Unit = {},
navigationIcon: ImageVector? = null, navigationIcon: ImageVector? = null,
@@ -61,8 +64,12 @@ fun OxygenTopAppBar(
onCancelSearch: () -> Unit = {} onCancelSearch: () -> Unit = {}
) { ) {
val topInset by animateIntAsState( val topInset by animateIntAsState(
if (scrollBehavior != null && -scrollBehavior.state.heightOffset >= with(LocalDensity.current) { 64.0.dp.toPx() }) 0 targetValue = if (scrollBehavior != null && -scrollBehavior.state.heightOffset >= with(
else TopAppBarDefaults.windowInsets.getTop(LocalDensity.current), label = "" LocalDensity.current
) { expandedHeight.toPx() }
) 0
else TopAppBarDefaults.windowInsets.getTop(LocalDensity.current),
label = ""
) )
val keyboardController = LocalSoftwareKeyboardController.current val keyboardController = LocalSoftwareKeyboardController.current
val focusRequester = remember { FocusRequester() } val focusRequester = remember { FocusRequester() }
@@ -81,6 +88,7 @@ fun OxygenTopAppBar(
CenterAlignedTopAppBar( CenterAlignedTopAppBar(
modifier = modifier, modifier = modifier,
scrollBehavior = scrollBehavior, scrollBehavior = scrollBehavior,
expandedHeight = expandedHeight,
title = { title = {
if (activeSearch) TextField( if (activeSearch) TextField(
modifier = Modifier modifier = Modifier
@@ -116,6 +124,7 @@ fun OxygenTopAppBar(
}, },
maxLines = 1, maxLines = 1,
singleLine = true, singleLine = true,
textStyle = MaterialTheme.typography.titleSmall,
onValueChange = { onValueChange = {
if ("\n" !in it) onQueryChange(it) if ("\n" !in it) onQueryChange(it)
} }
@@ -175,7 +184,13 @@ enum class SearchButtonPosition {
private fun OxygenTopAppBarPreview() { private fun OxygenTopAppBarPreview() {
OxygenTheme { OxygenTheme {
OxygenTopAppBar( OxygenTopAppBar(
title = { Text(text = stringResource(androidR.string.untitled)) }, title = {
Text(
text = stringResource(androidR.string.untitled),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
},
navigationIcon = OxygenIcons.Search, navigationIcon = OxygenIcons.Search,
navigationIconContentDescription = "Navigation icon", navigationIconContentDescription = "Navigation icon",
actionIcon = OxygenIcons.MoreVert, actionIcon = OxygenIcons.MoreVert,

View File

@@ -167,7 +167,6 @@ private fun ToolAction(
.padding(horizontal = 6.dp, vertical = 6.dp) .padding(horizontal = 6.dp, vertical = 6.dp)
) { ) {
Icon( Icon(
modifier = Modifier,
imageVector = actionIcon, imageVector = actionIcon,
contentDescription = actionIconContentDescription contentDescription = actionIconContentDescription
) )

View File

@@ -221,7 +221,7 @@ fun Scrollbar(
withTimeout(viewConfiguration.longPressTimeoutMillis) { withTimeout(viewConfiguration.longPressTimeoutMillis) {
tryAwaitRelease() tryAwaitRelease()
} }
} catch (e: TimeoutCancellationException) { } catch (_: TimeoutCancellationException) {
// Start the press triggered scroll // Start the press triggered scroll
val initialPress = PressInteraction.Press(offset) val initialPress = PressInteraction.Press(offset)
interactionSource?.tryEmit(initialPress) interactionSource?.tryEmit(initialPress)

View File

@@ -4,7 +4,6 @@ import android.widget.Toast
import androidx.activity.compose.ReportDrawnWhen import androidx.activity.compose.ReportDrawnWhen
import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
@@ -33,7 +32,7 @@ import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.material3.pulltorefresh.PullToRefreshContainer import androidx.compose.material3.pulltorefresh.PullToRefreshBox
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
@@ -44,8 +43,6 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@@ -114,15 +111,11 @@ internal fun ToolStoreScreen(
ReportDrawnWhen { !isToolLoading } ReportDrawnWhen { !isToolLoading }
val pullToRefreshState = rememberPullToRefreshState() var isRefreshing by remember { mutableStateOf(false) }
LaunchedEffect(pullToRefreshState.isRefreshing) {
if (pullToRefreshState.isRefreshing) {
toolStorePagingItems.refresh()
}
}
LaunchedEffect(toolStorePagingItems.loadState.refresh) { LaunchedEffect(toolStorePagingItems.loadState.refresh) {
if (toolStorePagingItems.loadState.refresh != LoadState.Loading) { if (toolStorePagingItems.loadState.refresh != LoadState.Loading && isRefreshing) {
pullToRefreshState.endRefresh() isRefreshing = false
} }
if (toolStorePagingItems.loadState.refresh is LoadState.Error) { if (toolStorePagingItems.loadState.refresh is LoadState.Error) {
Toast.makeText( Toast.makeText(
@@ -136,6 +129,12 @@ internal fun ToolStoreScreen(
} }
} }
LaunchedEffect(isRefreshing) {
if (isRefreshing) {
toolStorePagingItems.refresh()
}
}
val itemsAvailable = toolStorePagingItems.itemCount val itemsAvailable = toolStorePagingItems.itemCount
val state = rememberLazyStaggeredGridState() val state = rememberLazyStaggeredGridState()
@@ -151,11 +150,14 @@ internal fun ToolStoreScreen(
) )
} }
Box( PullToRefreshBox(
modifier modifier = modifier
.fillMaxSize() .fillMaxSize(),
.clipToBounds() state = rememberPullToRefreshState(),
.nestedScroll(pullToRefreshState.nestedScrollConnection) isRefreshing = isRefreshing,
onRefresh = {
isRefreshing = true
}
) { ) {
if (itemsAvailable > 0 || (toolStorePagingItems.loadState.refresh == LoadState.Loading && itemsAvailable == 0)) { if (itemsAvailable > 0 || (toolStorePagingItems.loadState.refresh == LoadState.Loading && itemsAvailable == 0)) {
LazyVerticalStaggeredGrid( LazyVerticalStaggeredGrid(
@@ -232,12 +234,6 @@ internal fun ToolStoreScreen(
} }
} }
PullToRefreshContainer(
modifier = Modifier
.align(Alignment.TopCenter),
state = pullToRefreshState,
)
state.DraggableScrollbar( state.DraggableScrollbar(
modifier = Modifier modifier = Modifier
.fillMaxHeight() .fillMaxHeight()

View File

@@ -204,7 +204,7 @@ fun supportsDynamicTheming() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
uiMode = Configuration.UI_MODE_NIGHT_YES or Configuration.UI_MODE_TYPE_NORMAL uiMode = Configuration.UI_MODE_NIGHT_YES or Configuration.UI_MODE_TYPE_NORMAL
) )
@Preview( @Preview(
name = "Api 21 Light", group = "ApiLevelPreviews", apiLevel = 21, name = "Api 24 Light", group = "ApiLevelPreviews", apiLevel = 24,
uiMode = Configuration.UI_MODE_NIGHT_NO or Configuration.UI_MODE_TYPE_NORMAL uiMode = Configuration.UI_MODE_NIGHT_NO or Configuration.UI_MODE_TYPE_NORMAL
) )
annotation class OxygenPreviews annotation class OxygenPreviews

View File

@@ -0,0 +1,10 @@
package top.fatweb.oxygen.toolbox.ui.util
import androidx.compose.runtime.compositionLocalOf
data class FullScreen(
val enable: Boolean = false,
val onStateChange: (Boolean) -> Unit = {}
)
val LocalFullScreen = compositionLocalOf { FullScreen() }

View File

@@ -2,9 +2,7 @@ package top.fatweb.oxygen.toolbox.ui.util
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
import android.os.Build
import android.os.LocaleList import android.os.LocaleList
import androidx.annotation.RequiresApi
import top.fatweb.oxygen.toolbox.model.userdata.LanguageConfig import top.fatweb.oxygen.toolbox.model.userdata.LanguageConfig
import java.util.Locale import java.util.Locale
@@ -24,11 +22,7 @@ object LocaleUtils {
fun attachBaseContext(context: Context, languageConfig: LanguageConfig): Context { fun attachBaseContext(context: Context, languageConfig: LanguageConfig): Context {
val locale: Locale = getLocaleFromLanguageConfig(languageConfig) val locale: Locale = getLocaleFromLanguageConfig(languageConfig)
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { return createConfigurationContext(context, locale)
createConfigurationContext(context, locale)
} else {
updateConfiguration(context, locale)
}
} }
private fun getLocaleFromLanguageConfig(languageConfig: LanguageConfig): Locale = private fun getLocaleFromLanguageConfig(languageConfig: LanguageConfig): Locale =
@@ -38,21 +32,10 @@ object LocaleUtils {
LanguageConfig.English -> Locale("en") LanguageConfig.English -> Locale("en")
} }
@RequiresApi(Build.VERSION_CODES.N)
private fun createConfigurationContext(context: Context, locale: Locale): Context { private fun createConfigurationContext(context: Context, locale: Locale): Context {
val configuration = context.resources.configuration val configuration = context.resources.configuration
configuration.setLocales(LocaleList(locale)) configuration.setLocales(LocaleList(locale))
return context.createConfigurationContext(configuration) return context.createConfigurationContext(configuration)
} }
@Suppress("DEPRECATION")
private fun updateConfiguration(context: Context, locale: Locale): Context {
val resources = context.resources
val configuration = resources.configuration
configuration.locale = locale
resources.updateConfiguration(configuration, resources.displayMetrics)
return context
}
} }

View File

@@ -2,6 +2,7 @@ package top.fatweb.oxygen.toolbox.ui.util
import android.content.Context import android.content.Context
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.content.res.Configuration
import android.content.res.Resources import android.content.res.Resources
import android.os.Build import android.os.Build
import androidx.annotation.StringRes import androidx.annotation.StringRes
@@ -10,14 +11,9 @@ import androidx.core.os.LocaleListCompat
import java.util.Locale import java.util.Locale
object ResourcesUtils { object ResourcesUtils {
fun getConfiguration(context: Context) = context.resources.configuration fun getConfiguration(context: Context): Configuration = context.resources.configuration
fun getDisplayMetrics(context: Context) = context.resources.displayMetrics fun getAppLocale(context: Context): Locale = getConfiguration(context).locales.get(0)
@Suppress("DEPRECATION")
fun getAppLocale(context: Context): Locale =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) getConfiguration(context).locales.get(0)
else getConfiguration(context).locale
fun getSystemLocale(): LocaleListCompat = fun getSystemLocale(): LocaleListCompat =
ConfigurationCompat.getLocales(Resources.getSystem().configuration) ConfigurationCompat.getLocales(Resources.getSystem().configuration)
@@ -25,7 +21,7 @@ object ResourcesUtils {
fun getAppVersionName(context: Context): String = fun getAppVersionName(context: Context): String =
try { try {
context.packageManager.getPackageInfo(context.packageName, 0)?.versionName ?: "Unknown" context.packageManager.getPackageInfo(context.packageName, 0)?.versionName ?: "Unknown"
} catch (e: PackageManager.NameNotFoundException) { } catch (_: PackageManager.NameNotFoundException) {
"Unknown" "Unknown"
} }
@@ -38,7 +34,7 @@ object ResourcesUtils {
context.packageName, context.packageName,
0 0
)?.versionCode?.toLong() ?: -1 )?.versionCode?.toLong() ?: -1
} catch (e: PackageManager.NameNotFoundException) { } catch (_: PackageManager.NameNotFoundException) {
-1 -1
} }

View File

@@ -1,32 +1,56 @@
package top.fatweb.oxygen.toolbox.ui.view package top.fatweb.oxygen.toolbox.ui.view
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Activity
import android.app.DownloadManager
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.util.Log import android.util.Log
import android.webkit.ConsoleMessage import android.webkit.ConsoleMessage
import android.webkit.JsPromptResult
import android.webkit.JsResult
import android.webkit.ValueCallback
import android.webkit.WebView
import androidx.activity.compose.ManagedActivityResultLauncher
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.ActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.consumeWindowInsets
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.layout.safeDrawing import androidx.compose.foundation.verticalScroll
import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.material3.AlertDialog
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Scaffold import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TextField
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource 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.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.kevinnzou.web.AccompanistWebChromeClient import com.kevinnzou.web.AccompanistWebChromeClient
@@ -37,7 +61,11 @@ import top.fatweb.oxygen.toolbox.R
import top.fatweb.oxygen.toolbox.icon.OxygenIcons import top.fatweb.oxygen.toolbox.icon.OxygenIcons
import top.fatweb.oxygen.toolbox.ui.component.Indicator import top.fatweb.oxygen.toolbox.ui.component.Indicator
import top.fatweb.oxygen.toolbox.ui.component.OxygenTopAppBar import top.fatweb.oxygen.toolbox.ui.component.OxygenTopAppBar
import top.fatweb.oxygen.toolbox.ui.util.LocalFullScreen
import top.fatweb.oxygen.toolbox.ui.util.ResourcesUtils
import top.fatweb.oxygen.toolbox.util.NativeWebApi import top.fatweb.oxygen.toolbox.util.NativeWebApi
import top.fatweb.oxygen.toolbox.util.Permissions
import kotlin.coroutines.resume
@Composable @Composable
internal fun ToolViewRoute( internal fun ToolViewRoute(
@@ -45,12 +73,14 @@ internal fun ToolViewRoute(
viewModel: ToolViewScreenViewModel = hiltViewModel(), viewModel: ToolViewScreenViewModel = hiltViewModel(),
onBackClick: () -> Unit onBackClick: () -> Unit
) { ) {
val isPreview by viewModel.isPreview.collectAsStateWithLifecycle()
val toolViewUiState by viewModel.toolViewUiState.collectAsStateWithLifecycle() val toolViewUiState by viewModel.toolViewUiState.collectAsStateWithLifecycle()
val webViewInstanceState by viewModel.webviewInstance.collectAsStateWithLifecycle()
val isPreview by viewModel.isPreview.collectAsStateWithLifecycle()
ToolViewScreen( ToolViewScreen(
modifier = modifier, modifier = modifier,
toolViewUiState = toolViewUiState, toolViewUiState = toolViewUiState,
webViewInstanceState = webViewInstanceState,
isPreview = isPreview, isPreview = isPreview,
onBackClick = onBackClick onBackClick = onBackClick
) )
@@ -62,48 +92,92 @@ internal fun ToolViewRoute(
internal fun ToolViewScreen( internal fun ToolViewScreen(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
toolViewUiState: ToolViewUiState, toolViewUiState: ToolViewUiState,
webViewInstanceState: WebViewInstanceState,
isPreview: Boolean, isPreview: Boolean,
onBackClick: () -> Unit onBackClick: () -> Unit
) { ) {
val (isFullScreen, onFullScreenStateChange) = LocalFullScreen.current
Column(modifier.fillMaxWidth()) {
AnimatedVisibility(!isFullScreen) {
TopBar(
toolViewUiState = toolViewUiState,
isPreview = isPreview,
isFullScreen = isFullScreen,
onBackClick = onBackClick,
onFullScreenChange = onFullScreenStateChange
)
}
Content(
toolViewUiState = toolViewUiState,
webViewInstanceState = webViewInstanceState
)
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun TopBar(
toolViewUiState: ToolViewUiState,
isPreview: Boolean,
isFullScreen: Boolean,
onBackClick: () -> Unit,
onFullScreenChange: (Boolean) -> Unit
) = OxygenTopAppBar(
title = {
Text(
text = when (toolViewUiState) {
ToolViewUiState.Loading -> stringResource(R.string.core_loading)
ToolViewUiState.Error -> stringResource(R.string.feature_tools_can_not_open)
is ToolViewUiState.Success -> if (isPreview) stringResource(
R.string.feature_tool_view_preview_suffix,
toolViewUiState.toolName
) else toolViewUiState.toolName
},
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
},
navigationIcon = OxygenIcons.Back,
navigationIconContentDescription = stringResource(R.string.core_back),
actionIcon = if (isFullScreen) OxygenIcons.FullScreenExit else OxygenIcons.FullScreen,
actionIconContentDescription = stringResource(if (isFullScreen) R.string.core_exit_full_screen else R.string.core_full_screen),
colors = TopAppBarDefaults.centerAlignedTopAppBarColors(
containerColor = Color.Transparent,
scrolledContainerColor = Color.Transparent
),
onNavigationClick = onBackClick,
onActionClick = {
onFullScreenChange(!isFullScreen)
}
)
@Composable
private fun Content(
toolViewUiState: ToolViewUiState,
webViewInstanceState: WebViewInstanceState
) {
val configuration = LocalConfiguration.current
val context = LocalContext.current val context = LocalContext.current
Scaffold( var fileChooserCallback by remember { mutableStateOf<ValueCallback<Array<Uri>>?>(null) }
modifier = Modifier, val fileChooserLauncher = rememberFileChooserLauncher(fileChooserCallback)
containerColor = Color.Transparent, val permissionLauncher = rememberPermissionLauncher()
contentWindowInsets = WindowInsets(left = 0, top = 0, right = 0, bottom = 0)
) { padding -> var isShowDialog = remember { mutableStateOf(false) }
Column( var dialogType = remember { mutableStateOf(DialogType.Alert) }
modifier var dialogTitle by remember { mutableStateOf("") }
.fillMaxWidth() var dialogText = remember { mutableStateOf("") }
.padding(padding) var dialogInputValue = remember { mutableStateOf("") }
.consumeWindowInsets(padding) var onDialogConfirm = remember { mutableStateOf<((String) -> Unit)?>(null) }
.windowInsetsPadding( var onDialogCancel = remember { mutableStateOf<(() -> Unit)?>(null) }
WindowInsets.safeDrawing.only(
WindowInsetsSides.Horizontal when (webViewInstanceState) {
) WebViewInstanceState.Loading -> {
) Indicator()
) { }
OxygenTopAppBar(
title = { is WebViewInstanceState.Success -> {
Text(
text = when (toolViewUiState) {
ToolViewUiState.Loading -> stringResource(R.string.core_loading)
ToolViewUiState.Error -> stringResource(R.string.feature_tools_can_not_open)
is ToolViewUiState.Success -> if (isPreview) stringResource(
R.string.feature_tool_view_preview_suffix,
toolViewUiState.toolName
) else toolViewUiState.toolName
}
)
},
navigationIcon = OxygenIcons.Back,
navigationIconContentDescription = stringResource(R.string.core_back),
colors = TopAppBarDefaults.centerAlignedTopAppBarColors(
containerColor = Color.Transparent,
scrolledContainerColor = Color.Transparent
),
onNavigationClick = onBackClick
)
when (toolViewUiState) { when (toolViewUiState) {
ToolViewUiState.Loading -> { ToolViewUiState.Loading -> {
Indicator() Indicator()
@@ -120,6 +194,7 @@ internal fun ToolViewScreen(
} }
is ToolViewUiState.Success -> { is ToolViewUiState.Success -> {
dialogTitle = toolViewUiState.toolName
val webViewState = rememberWebViewStateWithHTMLData( val webViewState = rememberWebViewStateWithHTMLData(
data = toolViewUiState.htmlData, data = toolViewUiState.htmlData,
) )
@@ -128,37 +203,255 @@ internal fun ToolViewScreen(
.fillMaxSize() .fillMaxSize()
.imePadding(), .imePadding(),
state = webViewState, state = webViewState,
chromeClient = remember { chromeClient = rememberChromeClient(
object : AccompanistWebChromeClient() { context = context,
override fun onConsoleMessage(consoleMessage: ConsoleMessage?): Boolean { fileChooserLauncher = fileChooserLauncher,
consoleMessage?.let { isShowDialog = isShowDialog,
Timber.tag("WebView").log( dialogType = dialogType,
priority = when (it.messageLevel()) { dialogText = dialogText,
ConsoleMessage.MessageLevel.TIP -> Log.VERBOSE dialogInputValue = dialogInputValue,
ConsoleMessage.MessageLevel.LOG -> Log.INFO onDialogConfirm = onDialogConfirm,
ConsoleMessage.MessageLevel.WARNING -> Log.WARN onDialogCancel = onDialogCancel
ConsoleMessage.MessageLevel.ERROR -> Log.ERROR ) {
else -> Log.DEBUG fileChooserCallback = it
},
message = "${it.message()} (${it.lineNumber()})"
)
return true
}
return false
}
}
}, },
onCreated = { onCreated = initWebView(
it.settings.javaScriptEnabled = true context = context,
it.settings.domStorageEnabled = true permissionLauncher = permissionLauncher
it.addJavascriptInterface( ),
NativeWebApi(context = context, webView = it), factory = {
"NativeApi" webViewInstanceState.webView
) },
} captureBackPresses = false
) )
} }
} }
} }
} }
if (isShowDialog.value) {
AlertDialog(
modifier = Modifier
.widthIn(max = configuration.screenWidthDp.dp - 80.dp)
.heightIn(max = configuration.screenHeightDp.dp - 40.dp),
onDismissRequest = {},
title = {
Text(
text = dialogTitle,
style = MaterialTheme.typography.titleLarge
)
},
text = {
Column {
Column(
modifier = Modifier.verticalScroll(state = rememberScrollState())
) {
Text(text = dialogText.value)
}
if (dialogType.value == DialogType.Prompt) {
Spacer(modifier = Modifier.height(8.dp))
TextField(
value = dialogInputValue.value,
onValueChange = {
dialogInputValue.value = it
},
textStyle = MaterialTheme.typography.bodyMedium
)
}
}
},
confirmButton = {
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
TextButton(onClick = {
isShowDialog.value = false
onDialogConfirm.value?.invoke(dialogInputValue.value)
}) {
Text(text = stringResource(R.string.core_ok))
}
if (dialogType.value != DialogType.Alert) {
TextButton(onClick = {
isShowDialog.value = false
onDialogCancel.value?.invoke()
}) {
Text(text = stringResource(R.string.core_cancel))
}
}
}
}
)
}
}
@Composable
private fun rememberFileChooserLauncher(fileChooserCallback: ValueCallback<Array<Uri>>?) =
rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == Activity.RESULT_OK) {
it.data?.data?.let { uri ->
fileChooserCallback?.onReceiveValue(arrayOf(uri))
} ?: {
fileChooserCallback?.onReceiveValue(emptyArray())
}
} else {
fileChooserCallback?.onReceiveValue(emptyArray())
}
}
@Composable
private fun rememberPermissionLauncher() =
rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) {
Permissions.continuation?.resume(it)
}
@SuppressLint("SetJavaScriptEnabled")
private fun initWebView(
context: Context,
permissionLauncher: ManagedActivityResultLauncher<String, Boolean>
) = { webview: WebView ->
webview.setBackgroundColor(Color.Transparent.toArgb())
webview.settings.javaScriptEnabled = true
webview.settings.domStorageEnabled = true
webview.addJavascriptInterface(
NativeWebApi(context = context, permissionLauncher = permissionLauncher),
"NativeApi"
)
webview.setDownloadListener { url, userAgent, _, mimetype, _ ->
if (!listOf("http://", "https://").any(url::startsWith)) {
webview.evaluateJavascript(
"alert('${
ResourcesUtils.getString(
context = context,
resId = R.string.core_can_only_download_http_https,
url
)
}')"
) {}
return@setDownloadListener
}
val request = DownloadManager.Request(Uri.parse(url)).apply {
addRequestHeader("User-Agent", userAgent)
setMimeType(mimetype)
setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
}
val downloadManager: DownloadManager =
context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
downloadManager.enqueue(request)
}
}
@Composable
private fun rememberChromeClient(
context: Context,
fileChooserLauncher: ManagedActivityResultLauncher<Intent, ActivityResult>,
isShowDialog: MutableState<Boolean>,
dialogType: MutableState<DialogType>,
dialogText: MutableState<String>,
dialogInputValue: MutableState<String>,
onDialogConfirm: MutableState<((String) -> Unit)?>,
onDialogCancel: MutableState<(() -> Unit)?>,
processCallback: (ValueCallback<Array<Uri>>?) -> Unit
) = remember {
object : AccompanistWebChromeClient() {
override fun onConsoleMessage(consoleMessage: ConsoleMessage?): Boolean {
consoleMessage?.let {
Timber.tag("WebView").log(
priority = when (it.messageLevel()) {
ConsoleMessage.MessageLevel.TIP -> Log.VERBOSE
ConsoleMessage.MessageLevel.LOG -> Log.INFO
ConsoleMessage.MessageLevel.WARNING -> Log.WARN
ConsoleMessage.MessageLevel.ERROR -> Log.ERROR
else -> Log.DEBUG
},
message = "${it.message()} (${it.lineNumber()})"
)
return true
}
return false
}
override fun onShowFileChooser(
webView: WebView?,
filePathCallback: ValueCallback<Array<Uri>>?,
fileChooserParams: FileChooserParams?
): Boolean {
processCallback(filePathCallback)
val intent = fileChooserParams?.createIntent()
?: Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "*/*"
}
fileChooserLauncher.launch(
Intent.createChooser(
intent,
fileChooserParams?.title ?: ResourcesUtils.getString(
context = context,
resId = R.string.core_file_select_one_text
)
)
)
return true
}
override fun onJsAlert(
view: WebView?,
url: String?,
message: String?,
result: JsResult?
): Boolean {
isShowDialog.value = true
dialogType.value = DialogType.Alert
dialogText.value = message ?: ""
onDialogConfirm.value = {
result?.confirm()
}
return true
}
override fun onJsConfirm(
view: WebView?,
url: String?,
message: String?,
result: JsResult?
): Boolean {
isShowDialog.value = true
dialogType.value = DialogType.Confirm
dialogText.value = message ?: ""
onDialogConfirm.value = {
result?.confirm()
}
onDialogCancel.value = {
result?.cancel()
}
return true
}
override fun onJsPrompt(
view: WebView?,
url: String?,
message: String?,
defaultValue: String?,
result: JsPromptResult?
): Boolean {
isShowDialog.value = true
dialogType.value = DialogType.Prompt
dialogText.value = message ?: ""
dialogInputValue.value = defaultValue ?: ""
onDialogConfirm.value = {
result?.confirm(dialogInputValue.value)
}
onDialogCancel.value = {
result?.cancel()
}
return true
}
}
}
private enum class DialogType {
Alert, Confirm, Prompt
} }

View File

@@ -1,21 +1,38 @@
package top.fatweb.oxygen.toolbox.ui.view package top.fatweb.oxygen.toolbox.ui.view
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.res.Configuration
import android.webkit.WebView
import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import timber.log.Timber import timber.log.Timber
import top.fatweb.oxygen.toolbox.model.Result import top.fatweb.oxygen.toolbox.model.Result
import top.fatweb.oxygen.toolbox.model.tool.ToolEntity
import top.fatweb.oxygen.toolbox.model.userdata.DarkThemeConfig
import top.fatweb.oxygen.toolbox.navigation.ToolViewArgs import top.fatweb.oxygen.toolbox.navigation.ToolViewArgs
import top.fatweb.oxygen.toolbox.repository.tool.StoreRepository import top.fatweb.oxygen.toolbox.repository.tool.StoreRepository
import top.fatweb.oxygen.toolbox.repository.tool.ToolRepository import top.fatweb.oxygen.toolbox.repository.tool.ToolRepository
import top.fatweb.oxygen.toolbox.repository.userdata.UserDataRepository
import top.fatweb.oxygen.toolbox.ui.util.ResourcesUtils
import top.fatweb.oxygen.toolbox.util.decodeToStringWithZip import top.fatweb.oxygen.toolbox.util.decodeToStringWithZip
import javax.inject.Inject import javax.inject.Inject
import kotlin.io.encoding.Base64 import kotlin.io.encoding.Base64
@@ -24,6 +41,8 @@ import kotlin.time.Duration.Companion.seconds
@HiltViewModel @HiltViewModel
class ToolViewScreenViewModel @Inject constructor( class ToolViewScreenViewModel @Inject constructor(
@ApplicationContext context: Context,
userDataRepository: UserDataRepository,
storeRepository: StoreRepository, storeRepository: StoreRepository,
toolRepository: ToolRepository, toolRepository: ToolRepository,
savedStateHandle: SavedStateHandle savedStateHandle: SavedStateHandle
@@ -35,82 +54,159 @@ class ToolViewScreenViewModel @Inject constructor(
private val toolId = toolViewArgs.toolId private val toolId = toolViewArgs.toolId
private val preview = toolViewArgs.preview private val preview = toolViewArgs.preview
private val storeDetailCache = MutableStateFlow<Result<ToolEntity>?>(null)
val toolViewUiState: StateFlow<ToolViewUiState> = toolViewUiState( val toolViewUiState: StateFlow<ToolViewUiState> = toolViewUiState(
context = context,
savedStateHandle = savedStateHandle, savedStateHandle = savedStateHandle,
username = username, username = username,
toolId = toolId, toolId = toolId,
preview = preview, preview = preview,
userDataRepository = userDataRepository,
storeRepository = storeRepository, storeRepository = storeRepository,
toolRepository = toolRepository toolRepository = toolRepository,
storeDetailCache = storeDetailCache
) )
.stateIn( .stateIn(
scope = viewModelScope, scope = viewModelScope,
initialValue = ToolViewUiState.Loading, initialValue = ToolViewUiState.Loading,
started = SharingStarted.WhileSubscribed(stopTimeoutMillis = 5.seconds.inWholeMilliseconds) started = SharingStarted.WhileSubscribed(stopTimeoutMillis = 5.seconds.inWholeMilliseconds)
) )
val webviewInstance = flow<WebViewInstanceState> {
val webviewInstance = WebView(context)
emit(WebViewInstanceState.Success(webviewInstance))
}.stateIn(
viewModelScope,
initialValue = WebViewInstanceState.Loading,
started = SharingStarted.WhileSubscribed(stopTimeoutMillis = 5.seconds.inWholeMilliseconds)
)
} }
@OptIn(ExperimentalCoroutinesApi::class)
private fun toolViewUiState( private fun toolViewUiState(
context: Context,
savedStateHandle: SavedStateHandle, savedStateHandle: SavedStateHandle,
username: String, username: String,
toolId: String, toolId: String,
preview: Boolean, preview: Boolean,
userDataRepository: UserDataRepository,
storeRepository: StoreRepository, storeRepository: StoreRepository,
toolRepository: ToolRepository toolRepository: ToolRepository,
storeDetailCache: MutableStateFlow<Result<ToolEntity>?>
): Flow<ToolViewUiState> { ): Flow<ToolViewUiState> {
val toolViewTemplate = toolRepository.toolViewTemplate val toolViewTemplate = toolRepository.toolViewTemplate
val entityFlow = val entityFlow =
if (!preview) toolRepository.getToolByUsernameAndToolId(username, toolId) else flowOf(null) if (!preview) toolRepository.getToolByUsernameAndToolId(username, toolId) else flowOf(null)
return flow { val isSystemDarkModeFlow = callbackFlow<Boolean> {
combine(entityFlow, toolViewTemplate, ::Pair).collect { (entityFlow, toolViewTemplate) -> val receiver = object : BroadcastReceiver() {
if (entityFlow == null) { override fun onReceive(context: Context?, instent: Intent?) {
savedStateHandle[IS_PREVIEW] = true context?.let(ResourcesUtils::getConfiguration)?.run {
storeRepository.detail(username, toolId).collect { trySend((uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES)
emit( }
when (it) { }
is Result.Success -> { }
val dist = it.data.dist!! val filter = IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED)
val base = it.data.base!! context.registerReceiver(receiver, filter)
trySend((ResourcesUtils.getConfiguration(context).uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES)
awaitClose { context.unregisterReceiver(receiver) }
}
return isSystemDarkModeFlow.flatMapLatest { isSystemDarkMode ->
flow {
userDataRepository.userData.collect { userData ->
val isDarkMode: Boolean = when (userData.darkThemeConfig) {
DarkThemeConfig.FollowSystem -> isSystemDarkMode
DarkThemeConfig.Light -> false
DarkThemeConfig.Dark -> true
}
val globalJsVariables = toolRepository.getGlobalJsVariables(isDarkMode)
val globalCssVariables = toolRepository.getGlobalCssVariables(isDarkMode)
combine(entityFlow, toolViewTemplate, globalJsVariables, ::Triple)
.combine(globalCssVariables) { triple, globalCssVariables ->
Quadruple(triple.first, triple.second, triple.third, globalCssVariables)
}
.collect { (entityFlow, toolViewTemplate, globalJsVariables, globalCssVariables) ->
if (entityFlow == null) {
savedStateHandle[IS_PREVIEW] = true
val cachedDetail = storeDetailCache.value
if (cachedDetail != null) {
emitResult(
result = cachedDetail,
toolViewTemplate = toolViewTemplate,
globalJsVariables = globalJsVariables,
globalCssVariables = globalCssVariables
)
} else {
storeRepository.detail(username, toolId).collect { result ->
storeDetailCache.value = result
emitResult(
result = result,
toolViewTemplate = toolViewTemplate,
globalJsVariables = globalJsVariables,
globalCssVariables = globalCssVariables
)
}
}
} else {
savedStateHandle[IS_PREVIEW] = false
emit(
ToolViewUiState.Success( ToolViewUiState.Success(
it.data.name, entityFlow.name,
processHtml( processHtml(
toolViewTemplate = toolViewTemplate, toolViewTemplate = toolViewTemplate,
distBase64 = dist, globalJsVariables = globalJsVariables,
baseBase64 = base globalCssVariables = globalCssVariables,
distBase64 = entityFlow.dist!!,
baseBase64 = entityFlow.base!!
) )
) )
} )
is Result.Loading -> ToolViewUiState.Loading
is Result.Error -> {
Timber.e("Can not load tool", it.exception)
ToolViewUiState.Error
}
is Result.Fail -> ToolViewUiState.Error
} }
) }
}
} else {
savedStateHandle[IS_PREVIEW] = false
emit(
ToolViewUiState.Success(
entityFlow.name,
processHtml(
toolViewTemplate = toolViewTemplate,
distBase64 = entityFlow.dist!!,
baseBase64 = entityFlow.base!!
)
)
)
} }
} }
} }
} }
private suspend fun FlowCollector<ToolViewUiState>.emitResult(
result: Result<ToolEntity>,
toolViewTemplate: String,
globalJsVariables: String,
globalCssVariables: String
) {
emit(
when (result) {
is Result.Success -> {
val dist = result.data.dist!!
val base = result.data.base!!
ToolViewUiState.Success(
result.data.name,
processHtml(
toolViewTemplate = toolViewTemplate,
globalJsVariables = globalJsVariables,
globalCssVariables = globalCssVariables,
distBase64 = dist,
baseBase64 = base
)
)
}
is Result.Loading -> ToolViewUiState.Loading
is Result.Error -> {
Timber.e(result.exception, "Can not load tool")
ToolViewUiState.Error
}
is Result.Fail -> ToolViewUiState.Error
}
)
}
sealed interface ToolViewUiState { sealed interface ToolViewUiState {
data class Success( data class Success(
val toolName: String, val toolName: String,
@@ -122,12 +218,32 @@ sealed interface ToolViewUiState {
data object Loading : ToolViewUiState data object Loading : ToolViewUiState
} }
sealed interface WebViewInstanceState {
data class Success(
val webView: WebView
) : WebViewInstanceState
data object Loading : WebViewInstanceState
}
@OptIn(ExperimentalEncodingApi::class) @OptIn(ExperimentalEncodingApi::class)
private fun processHtml(toolViewTemplate: String, distBase64: String, baseBase64: String): String { private fun processHtml(
toolViewTemplate: String,
globalJsVariables: String,
globalCssVariables: String,
distBase64: String,
baseBase64: String
): String {
val dist = Base64.decodeToStringWithZip(distBase64) val dist = Base64.decodeToStringWithZip(distBase64)
val base = Base64.decodeToStringWithZip(baseBase64) val base = Base64.decodeToStringWithZip(baseBase64)
return toolViewTemplate.replace("{{replace_code}}", "$dist\n$base") return toolViewTemplate
.replace(oldValue = "{{replace_global_js_variables}}", newValue = globalJsVariables)
.replace(oldValue = "{{replace_global_css_variables}}", newValue = globalCssVariables)
.replace(oldValue = "{{replace_dict_code}}", newValue = dist)
.replace(oldValue = "{{replace_base_code}}", newValue = base)
} }
data class Quadruple<A, B, C, D>(val first: A, val second: B, val third: C, val fourth: D)
private const val IS_PREVIEW = "IS_PREVIEW" private const val IS_PREVIEW = "IS_PREVIEW"

View File

@@ -0,0 +1,95 @@
package top.fatweb.oxygen.toolbox.util
import android.content.ContentValues
import android.content.Context
import android.os.Build
import android.os.Environment
import android.provider.MediaStore
import androidx.activity.compose.ManagedActivityResultLauncher
import androidx.annotation.RequiresApi
import kotlinx.coroutines.runBlocking
import timber.log.Timber
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import kotlin.let
fun saveToDownloads(
context: Context,
permissionLauncher: ManagedActivityResultLauncher<String, Boolean>,
data: ByteArray,
fileName: String
) = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
saveFileToDownloads(context = context, data = data, fileName = fileName)
} else {
saveFileToDownloads(
context = context,
permissionLauncher = permissionLauncher,
data = data,
fileName = fileName
)
}
@RequiresApi(Build.VERSION_CODES.Q)
private fun saveFileToDownloads(
context: Context,
data: ByteArray,
fileName: String
): Boolean {
val resolver = context.contentResolver
val contentValues = ContentValues().apply {
put(MediaStore.Downloads.DISPLAY_NAME, fileName)
put(MediaStore.Downloads.MIME_TYPE, "application/octet-stream")
put(MediaStore.Downloads.IS_PENDING, 1)
}
val collection = MediaStore.Downloads.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
val fileUri = resolver.insert(collection, contentValues)
return fileUri?.let { uri ->
resolver.openOutputStream(uri)?.use { outputStream ->
outputStream.write(data)
outputStream.flush()
}
contentValues.clear()
contentValues.put(MediaStore.Downloads.IS_PENDING, 0)
resolver.update(uri, contentValues, null, null)
true
} ?: run {
Timber.e("Could not save file $fileName to Downloads")
false
}
}
private fun saveFileToDownloads(
context: Context,
permissionLauncher: ManagedActivityResultLauncher<String, Boolean>,
data: ByteArray,
fileName: String
): Boolean {
if (!runBlocking {
Permissions.requestWriteExternalStoragePermission(
context,
permissionLauncher
)
}) {
return false
}
val downloadsDir =
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
val file = File(downloadsDir, fileName)
return try {
FileOutputStream(file).apply {
write(data)
close()
}
true
} catch (e: IOException) {
Timber.e(e, "Could not save file $fileName to ${file.absolutePath}")
false
}
}

View File

@@ -4,12 +4,13 @@ import android.content.ClipData
import android.content.ClipboardManager import android.content.ClipboardManager
import android.content.Context import android.content.Context
import android.content.Context.CLIPBOARD_SERVICE import android.content.Context.CLIPBOARD_SERVICE
import android.util.Base64
import android.webkit.JavascriptInterface import android.webkit.JavascriptInterface
import android.webkit.WebView import androidx.activity.compose.ManagedActivityResultLauncher
class NativeWebApi( class NativeWebApi(
private val context: Context, private val context: Context,
private val webView: WebView private val permissionLauncher: ManagedActivityResultLauncher<String, Boolean>
) { ) {
@JavascriptInterface @JavascriptInterface
fun copyToClipboard(text: String): Boolean { fun copyToClipboard(text: String): Boolean {
@@ -17,7 +18,7 @@ class NativeWebApi(
return clipboardManager?.let { return clipboardManager?.let {
it.setPrimaryClip(ClipData.newPlainText("copy", text)) it.setPrimaryClip(ClipData.newPlainText("copy", text))
true true
} ?: false } == true
} }
@JavascriptInterface @JavascriptInterface
@@ -26,9 +27,15 @@ class NativeWebApi(
return clipboardManager?.primaryClip?.getItemAt(0)?.text?.toString() ?: "" return clipboardManager?.primaryClip?.getItemAt(0)?.text?.toString() ?: ""
} }
private fun callback(callback: String, vararg args: Any) { @JavascriptInterface
val jsCode = fun saveToDownloads(dataBase64: String, fileName: String): Boolean {
"$callback(${args.map { if (it is String) "'$it'" else it }.joinToString(", ")})" val data = Base64.decode(dataBase64, Base64.DEFAULT)
webView.evaluateJavascript(jsCode, null)
return saveToDownloads(
context = context,
permissionLauncher = permissionLauncher,
data = data,
fileName = fileName
)
} }
} }

View File

@@ -0,0 +1,41 @@
package top.fatweb.oxygen.toolbox.util
import android.Manifest
import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import androidx.activity.compose.ManagedActivityResultLauncher
import androidx.core.content.ContextCompat
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
object Permissions {
var continuation: Continuation<Boolean>? = null
suspend fun requestWriteExternalStoragePermission(
context: Context,
permissionLauncher: ManagedActivityResultLauncher<String, Boolean>
): Boolean {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
return true
}
return suspendCancellableCoroutine { continuation ->
if (ContextCompat.checkSelfPermission(
context,
Manifest.permission.WRITE_EXTERNAL_STORAGE
) == PackageManager.PERMISSION_GRANTED
) {
continuation.resume(true)
} else {
this.continuation = continuation
permissionLauncher.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)
}
continuation.invokeOnCancellation {
continuation.resume(false)
}
}
}
}

View File

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

View File

@@ -1,194 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>

File diff suppressed because one or more lines are too long

View File

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

View File

@@ -1,12 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp" xmlns:tools="http://schemas.android.com/tools"
android:height="108dp" android:width="200dp"
android:viewportWidth="108" android:height="200dp"
android:viewportHeight="108"> android:viewportWidth="1024"
android:viewportHeight="1024">
<path <path
android:fillColor="#FFFFFF" android:fillColor="#FFFFFF"
android:fillType="nonZero" android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z" android:pathData="M307.1,499.6c0,-70.5 86.8,-42.6 58.5,-33.2 -31,10.3 2.1,73.6 11.1,51.3c19.7,-48.9 27,27.9 50.1,27.9c23.1,0 69.4,-119.3 82.4,-88.7c46.9,109.9 66.2,-4.6 86.7,59.3c20.7,64.4 41,-58.9 50.6,-24.7c7.5,26.9 18.8,5.8 28.3,-19.5c10.7,-28.4 41.8,-37.4 41.8,27.5c0,111.5 -88.7,204.7 -204.7,204.7 -116.1,0 -204.7,-93.5 -204.7,-204.7ZM426.7,504c7,0 12.2,6.2 12.2,12.2s-4.9,12.2 -12.2,12.2c-7.3,0 -12.2,-6 -12.2,-12.2c0,-6.2 5.2,-12.2 12.2,-12.2ZM414.5,382.8c5.3,0 9.2,4.7 9.2,9.2s-3.7,9.2 -9.2,9.2 -9.2,-4.5 -9.2,-9.2c0,-4.7 3.9,-9.2 9.2,-9.2ZM377.8,433.6c4.3,0 7.6,3.8 7.6,7.6c0,3.7 -3,7.6 -7.6,7.6 -4.5,0 -7.6,-3.7 -7.6,-7.6c0,-3.8 3.2,-7.6 7.6,-7.6ZM466.2,408.8c11.6,0 20.3,10.3 20.3,20.3s-8.1,20.3 -20.3,20.3 -20.3,-10 -20.3,-20.3s8.7,-20.3 20.3,-20.3ZM614.9,433.6c9.4,0 16.4,8.3 16.4,16.4c0,8.1 -6.6,16.4 -16.4,16.4 -9.8,0 -16.4,-8.1 -16.4,-16.4c0,-8.3 7,-16.4 16.4,-16.4ZM405.9,440c3,0 5.3,2.7 5.3,5.3s-2.1,5.3 -5.3,5.3c-3.2,0 -5.3,-2.6 -5.3,-5.3c0,-2.7 2.3,-5.3 5.3,-5.3ZM528.1,437.2c5.6,0 9.8,5 9.8,9.8c0,4.8 -3.9,9.8 -9.8,9.8s-9.8,-4.9 -9.8,-9.8c0,-5 4.2,-9.8 9.8,-9.8ZM560.5,467.9c8.4,0 14.7,7.5 14.7,14.7s-5.9,14.7 -14.7,14.7 -14.7,-7.3 -14.7,-14.7c0,-7.4 6.3,-14.7 14.7,-14.7ZM459.7,350.8c5.2,0 9,4.6 9,9c0,4.4 -3.6,9 -9,9 -5.4,0 -9,-4.4 -9,-9c0,-4.6 3.8,-9 9,-9ZM352.3,556.4c0,12.9 10.2,25.4 25.4,25.4c15.2,0 25.4,-12.9 25.4,-25.4s-10.9,-25.4 -25.4,-25.4c-14.6,0 -25.4,12.6 -25.4,25.4ZM377.8,535.4c12,0 21,10.7 21,21s-8.4,21 -21,21c-12.6,0 -21,-10.4 -21,-21c0,-10.6 9,-21 21,-21ZM560.1,547.6c0,10.2 8.1,20.1 20.1,20.1s20.1,-10.2 20.1,-20.1 -8.6,-20.1 -20.1,-20.1 -20.1,9.9 -20.1,20.1ZM580.2,532c8.9,0 15.6,7.9 15.6,15.6s-6.3,15.6 -15.6,15.6 -15.6,-7.7 -15.6,-15.6c0,-7.9 6.7,-15.6 15.6,-15.6ZM403.2,615.3c0,4.5 3.6,8.9 8.9,8.9s8.9,-4.5 8.9,-8.9 -3.8,-8.9 -8.9,-8.9c-5.1,0 -8.9,4.4 -8.9,8.9ZM412.2,609.1c3.6,0 6.2,3.2 6.2,6.2s-2.5,6.2 -6.2,6.2c-3.7,0 -6.2,-3.1 -6.2,-6.2s2.7,-6.2 6.2,-6.2ZM441,566.5c0,3 2.4,5.9 5.9,5.9s5.9,-3 5.9,-5.9c0,-2.9 -2.5,-5.9 -5.9,-5.9 -3.4,0 -5.9,2.9 -5.9,5.9ZM446.9,562.4c2.4,0 4.1,2.1 4.1,4.1c0,2 -1.7,4.1 -4.1,4.1s-4.1,-2 -4.1,-4.1c0,-2.1 1.8,-4.1 4.1,-4.1ZM587.6,592.6c0,3 2.4,5.9 5.9,5.9s5.9,-3 5.9,-5.9 -2.5,-5.9 -5.9,-5.9 -5.9,2.9 -5.9,5.9ZM593.5,588.5c2.4,0 4.1,2.1 4.1,4.1c0,2 -1.7,4.1 -4.1,4.1s-4.1,-2 -4.1,-4.1c0,-2.1 1.8,-4.1 4.1,-4.1ZM634.6,592.6c0,3 2.4,5.9 5.9,5.9s5.9,-3 5.9,-5.9 -2.5,-5.9 -5.9,-5.9c-3.4,0 -5.9,2.9 -5.9,5.9ZM640.5,588.5c2.4,0 4.1,2.1 4.1,4.1c0,2 -1.7,4.1 -4.1,4.1s-4.1,-2 -4.1,-4.1c0,-2.1 1.8,-4.1 4.1,-4.1ZM566.1,582.8c0,3 2.4,5.9 5.9,5.9s5.9,-3 5.9,-5.9c0,-2.9 -2.5,-5.9 -5.9,-5.9s-5.9,2.9 -5.9,5.9ZM572,578.7c2.4,0 4.1,2.1 4.1,4.1c0,2 -1.7,4.1 -4.1,4.1s-4.1,-2 -4.1,-4.1c0,-2.1 1.8,-4.1 4.1,-4.1ZM480.7,624.3c0,3 2.4,5.9 5.9,5.9s5.9,-3 5.9,-5.9c0,-2.9 -2.5,-5.9 -5.9,-5.9s-5.9,2.9 -5.9,5.9ZM486.6,620.1c2.4,0 4.1,2.1 4.1,4.1c0,2 -1.7,4.1 -4.1,4.1s-4.1,-2 -4.1,-4.1c0,-2.1 1.8,-4.1 4.1,-4.1ZM652.3,553.7c0,6.5 5.2,12.9 12.9,12.9s12.9,-6.5 12.9,-12.9 -5.5,-12.9 -12.9,-12.9c-7.4,0 -12.9,6.4 -12.9,12.9ZM665.2,544.1c5.5,0 9.5,4.8 9.5,9.5s-3.8,9.5 -9.5,9.5 -9.5,-4.7 -9.5,-9.5s4.1,-9.5 9.5,-9.5ZM492.4,495.5c0,6.5 5.2,12.9 12.9,12.9c7.7,0 12.9,-6.5 12.9,-12.9s-5.5,-12.9 -12.9,-12.9c-7.4,0 -12.9,6.4 -12.9,12.9ZM505.3,485.9c5.5,0 9.5,4.8 9.5,9.5s-3.8,9.5 -9.5,9.5 -9.5,-4.7 -9.5,-9.5s4.1,-9.5 9.5,-9.5ZM492.5,588.8c0,5 3.9,9.8 9.8,9.8s9.8,-5 9.8,-9.8c0,-4.8 -4.2,-9.8 -9.8,-9.8s-9.8,4.9 -9.8,9.8ZM502.3,581.9c3.9,0 6.9,3.5 6.9,6.9c0,3.4 -2.8,6.9 -6.9,6.9s-6.9,-3.4 -6.9,-6.9c0,-3.5 2.9,-6.9 6.9,-6.9ZM600.5,625.1c0,5 3.9,9.8 9.8,9.8s9.8,-5 9.8,-9.8c0,-4.8 -4.2,-9.8 -9.8,-9.8s-9.8,4.9 -9.8,9.8ZM610.4,618.3c3.9,0 6.9,3.5 6.9,6.9c0,3.4 -2.7,6.9 -6.9,6.9s-6.9,-3.4 -6.9,-6.9c0,-3.5 2.9,-6.9 6.9,-6.9ZM518.3,527.5c0,5 3.9,9.8 9.8,9.8s9.8,-5 9.8,-9.8 -4.2,-9.8 -9.8,-9.8 -9.8,4.9 -9.8,9.8ZM528.1,520.6c3.9,0 6.9,3.5 6.9,6.9s-2.7,6.9 -6.9,6.9 -6.9,-3.4 -6.9,-6.9s2.9,-6.9 6.9,-6.9ZM466.2,531c0,5 3.9,9.8 9.8,9.8s9.8,-5 9.8,-9.8 -4.2,-9.8 -9.8,-9.8 -9.8,4.9 -9.8,9.8ZM476,524.1c3.9,0 6.9,3.5 6.9,6.9s-2.8,6.9 -6.9,6.9c-4.1,0 -6.9,-3.4 -6.9,-6.9s2.9,-6.9 6.9,-6.9ZM550.4,318.3c27.5,0 45.7,20.8 45.7,45.7c0,24.9 -19.8,45.7 -45.7,45.7s-45.7,-20.8 -45.7,-45.7c0,-24.9 18.2,-45.7 45.7,-45.7ZM550.4,318.3"
android:strokeWidth="1" tools:ignore="VectorPath" />
android:strokeColor="#00000000" />
</vector> </vector>

View File

@@ -1,176 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 857 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 982 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 463 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

View File

@@ -4,6 +4,8 @@
<string name="app_full_name">氧工具</string> <string name="app_full_name">氧工具</string>
<string name="app_description">All in One</string> <string name="app_description">All in One</string>
<string name="core_beta_version">测试版</string>
<string name="core_ga_version">正式版</string>
<string name="core_ok">完成</string> <string name="core_ok">完成</string>
<string name="core_back">返回</string> <string name="core_back">返回</string>
<string name="core_close">关闭</string> <string name="core_close">关闭</string>
@@ -12,6 +14,8 @@
<string name="core_search">搜索</string> <string name="core_search">搜索</string>
<string name="core_loading">加载中…</string> <string name="core_loading">加载中…</string>
<string name="core_no_connect">⚠️ 无法连接至互联网</string> <string name="core_no_connect">⚠️ 无法连接至互联网</string>
<string name="core_full_screen">全屏</string>
<string name="core_exit_full_screen">退出全屏</string>
<string name="core_install">安装</string> <string name="core_install">安装</string>
<string name="core_installing">安装中……</string> <string name="core_installing">安装中……</string>
<string name="core_upgrade">更新</string> <string name="core_upgrade">更新</string>
@@ -24,6 +28,11 @@
<string name="core_undo">撤消</string> <string name="core_undo">撤消</string>
<string name="core_nothing">空空如也</string> <string name="core_nothing">空空如也</string>
<string name="core_nothing_found">未找到相关内容</string> <string name="core_nothing_found">未找到相关内容</string>
<string name="core_file_select_one">选择一个文件</string>
<string name="core_file_select_multi">选择文件</string>
<string name="core_file_select_one_text">选择一个文本文件</string>
<string name="core_file_select_multi_text">选择文本文件</string>
<string name="core_can_only_download_http_https">只能下载 HTTP/HTTPS URI%1$s</string>
<string name="feature_store_title">商店</string> <string name="feature_store_title">商店</string>
<string name="feature_store_retry">重试</string> <string name="feature_store_retry">重试</string>

View File

@@ -3,6 +3,8 @@
<string name="app_full_name">Oxygen Toolbox</string> <string name="app_full_name">Oxygen Toolbox</string>
<string name="app_description">All in One</string> <string name="app_description">All in One</string>
<string name="core_beta_version">Beta</string>
<string name="core_ga_version">GA</string>
<string name="core_ok">OK</string> <string name="core_ok">OK</string>
<string name="core_back">Back</string> <string name="core_back">Back</string>
<string name="core_close">Close</string> <string name="core_close">Close</string>
@@ -11,6 +13,8 @@
<string name="core_search">Search</string> <string name="core_search">Search</string>
<string name="core_loading">Loading…</string> <string name="core_loading">Loading…</string>
<string name="core_no_connect">⚠️ Unable to connect to the internet</string> <string name="core_no_connect">⚠️ Unable to connect to the internet</string>
<string name="core_full_screen">Full Screen</string>
<string name="core_exit_full_screen">Exit Full Screen</string>
<string name="core_install">Install</string> <string name="core_install">Install</string>
<string name="core_installing">Installing…</string> <string name="core_installing">Installing…</string>
<string name="core_upgrade">Upgrade</string> <string name="core_upgrade">Upgrade</string>
@@ -23,6 +27,11 @@
<string name="core_undo">Undo</string> <string name="core_undo">Undo</string>
<string name="core_nothing">Nothing</string> <string name="core_nothing">Nothing</string>
<string name="core_nothing_found">Nothing found</string> <string name="core_nothing_found">Nothing found</string>
<string name="core_file_select_one">Select a file</string>
<string name="core_file_select_multi">Select files</string>
<string name="core_file_select_one_text">Select a text file</string>
<string name="core_file_select_multi_text">Select text files</string>
<string name="core_can_only_download_http_https">Can only download HTTP/HTTPS URIs: %1$s</string>
<string name="feature_store_title">Store</string> <string name="feature_store_title">Store</string>
<string name="feature_store_retry">try again</string> <string name="feature_store_retry">try again</string>

View File

@@ -11,7 +11,7 @@
</style> </style>
<style name="Theme.Oxygen.Splash" parent="NightAdjusted.Theme.Splash"> <style name="Theme.Oxygen.Splash" parent="NightAdjusted.Theme.Splash">
<item name="windowSplashScreenAnimatedIcon">@drawable/ic_oxygen</item> <item name="windowSplashScreenAnimatedIcon">@drawable/ic_launcher</item>
<item name="postSplashScreenTheme">@style/Theme.Oxygen</item> <item name="postSplashScreenTheme">@style/Theme.Oxygen</item>
</style> </style>
</resources> </resources>

View File

@@ -1,31 +1,32 @@
[versions] [versions]
agp = "8.5.2" agp = "8.7.2"
kotlin = "2.0.10" kotlin = "2.0.20"
ksp = "2.0.10-1.0.24" ksp = "2.0.20-1.0.25"
aboutlibraries = "11.2.2" aboutlibraries = "11.2.3"
protobufPlugin = "0.9.4" protobufPlugin = "0.9.4"
kotlinxSerialization = "2.0.0" kotlinxSerialization = "2.0.20"
secrets = "2.0.1" secrets = "2.0.1"
paging = "3.3.2" paging = "3.3.2"
desugarJdkLibs = "2.1.0" desugarJdkLibs = "2.1.2"
composeBom = "2024.08.00" composeBom = "2024.10.01"
junit = "4.13.2" junit = "4.13.2"
coreKtx = "1.13.1" coreKtx = "1.15.0"
junitVersion = "1.2.1" junitVersion = "1.2.1"
espressoCore = "3.6.1" espressoCore = "3.6.1"
activityCompose = "1.9.1" uiTestManifest = "1.7.5"
activityCompose = "1.9.3"
appcompat = "1.7.0" appcompat = "1.7.0"
androidxLifecycle = "2.8.4" androidxLifecycle = "2.8.7"
androidxCoreSplashscreen = "1.0.1" androidxCoreSplashscreen = "1.0.1"
hilt = "2.52" hilt = "2.52"
coil = "2.7.0" coil = "2.7.0"
kotlinxDatetime = "0.6.0" kotlinxDatetime = "0.6.1"
androidxDataStore = "1.1.1" androidxDataStore = "1.1.1"
protobuf = "4.27.3" protobuf = "4.28.3"
androidxNavigation = "2.7.7" androidxNavigation = "2.8.3"
androidxHiltNavigationCompose = "1.2.0" androidxHiltNavigationCompose = "1.2.0"
kotlinxSerializationJson = "1.7.1" kotlinxSerializationJson = "1.7.3"
retrofit = "2.11.0" retrofit = "2.11.0"
retrofitKotlinxSerializationJson = "1.0.0" retrofitKotlinxSerializationJson = "1.0.0"
okhttp = "4.12.0" okhttp = "4.12.0"
@@ -59,7 +60,7 @@ androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "j
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" } androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest", version.ref = "uiTestManifest" }
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" } androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
androidx-ui = { group = "androidx.compose.ui", name = "ui" } androidx-ui = { group = "androidx.compose.ui", name = "ui" }
@@ -69,6 +70,7 @@ androidx-material3 = { group = "androidx.compose.material3", name = "material3"
material-icons-core = { group = "androidx.compose.material", name = "material-icons-core" } material-icons-core = { group = "androidx.compose.material", name = "material-icons-core" }
material-icons-extended = { group = "androidx.compose.material", name = "material-icons-extended" } material-icons-extended = { group = "androidx.compose.material", name = "material-icons-extended" }
material3-window-size = { group = "androidx.compose.material3", name = "material3-window-size-class" } material3-window-size = { group = "androidx.compose.material3", name = "material3-window-size-class" }
animation-graphics = { group = "androidx.compose.animation", name = "animation-graphics" }
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" } androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }