diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 9dee60e..db3e91c 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -1,3 +1,8 @@
+import java.io.ByteArrayOutputStream
+import java.text.SimpleDateFormat
+import java.util.Date
+import java.util.TimeZone
+
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
@@ -19,6 +24,10 @@ android {
versionCode = 1
versionName = "1.0"
+ buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
+ buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"")
+ buildConfigField("String", "BUILD_TIME", "\"${getBuildTime()}\"")
+
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary = true
@@ -79,6 +88,7 @@ dependencies {
implementation("androidx.compose.material3:material3-window-size-class:1.2.0-rc01")
implementation("androidx.hilt:hilt-navigation-compose:1.1.0")
implementation("androidx.navigation:navigation-compose:2.7.6")
+ implementation("androidx.profileinstaller:profileinstaller:1.3.1")
implementation("androidx.room:room-runtime:2.6.1")
implementation("androidx.room:room-ktx:2.6.1")
implementation("androidx.work:work-runtime-ktx:2.9.0")
@@ -97,6 +107,7 @@ dependencies {
implementation("com.squareup.okio:okio:3.7.0")
implementation("com.tencent:mmkv:1.3.2")
implementation("org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3.7")
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-guava:1.7.3")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2")
implementation("io.coil-kt:coil-compose:2.5.0")
testImplementation("junit:junit:4.13.2")
@@ -106,4 +117,31 @@ dependencies {
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
debugImplementation("androidx.compose.ui:ui-tooling")
debugImplementation("androidx.compose.ui:ui-test-manifest")
+}
+
+// Git is needed in your system PATH for these commands to work.
+// If it's not installed, you can return a random value as a workaround
+fun Project.getCommitCount(): String {
+ return runCommand("git rev-list --count HEAD")
+ // return "1"
+}
+
+fun Project.getGitSha(): String {
+ return runCommand("git rev-parse --short HEAD")
+ // return "1"
+}
+
+fun Project.getBuildTime(): String {
+ val df = SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'")
+ df.timeZone = TimeZone.getTimeZone("UTC")
+ return df.format(Date())
+}
+
+fun Project.runCommand(command: String): String {
+ val byteOut = ByteArrayOutputStream()
+ project.exec {
+ commandLine = command.split(" ")
+ standardOutput = byteOut
+ }
+ return String(byteOut.toByteArray()).trim()
}
\ No newline at end of file
diff --git a/app/src/main/java/org/xtimms/tokusho/core/Navigation.kt b/app/src/main/java/org/xtimms/tokusho/core/Navigation.kt
index 57333de..847df6e 100644
--- a/app/src/main/java/org/xtimms/tokusho/core/Navigation.kt
+++ b/app/src/main/java/org/xtimms/tokusho/core/Navigation.kt
@@ -140,7 +140,8 @@ fun Navigation(
SettingsView(
navigateBack = navigateBack,
navigateToAppearance = { navController.navigate(APPEARANCE_DESTINATION) },
- navigateToAbout = { navController.navigate(ABOUT_DESTINATION) }
+ navigateToAbout = { navController.navigate(ABOUT_DESTINATION) },
+ navigateToAdvanced = { navController.navigate(ADVANCED_DESTINATION) }
)
}
@@ -165,6 +166,12 @@ fun Navigation(
)
}
+ composable(ADVANCED_DESTINATION) {
+ AdvancedView(
+ navigateBack = navigateBack,
+ )
+ }
+
composable(
route = LIST_DESTINATION,
arguments = listOf(
diff --git a/app/src/main/java/org/xtimms/tokusho/core/components/PreferenceItem.kt b/app/src/main/java/org/xtimms/tokusho/core/components/PreferenceItem.kt
index a2ae13a..a04f420 100644
--- a/app/src/main/java/org/xtimms/tokusho/core/components/PreferenceItem.kt
+++ b/app/src/main/java/org/xtimms/tokusho/core/components/PreferenceItem.kt
@@ -121,8 +121,13 @@ fun PreferenceItem(
Column(
modifier = Modifier
.weight(1f)
- .padding(horizontal = 16.dp)
- .padding(end = 8.dp)
+ .then(
+ if (icon != null)
+ Modifier
+ .padding(horizontal = 16.dp)
+ .padding(end = 8.dp)
+ else Modifier.padding(horizontal = 8.dp)
+ )
) {
PreferenceItemTitle(text = title, enabled = enabled)
if (!description.isNullOrEmpty()) PreferenceItemDescription(
@@ -465,7 +470,8 @@ fun PreferenceSwitch(
)
}
Column(
- modifier = Modifier.weight(1f)
+ modifier = Modifier
+ .weight(1f)
.padding(horizontal = 16.dp)
) {
PreferenceItemTitle(
@@ -542,7 +548,7 @@ fun PreferencesHintCard(
@Preview
fun PreferenceItemPreview() {
Column {
- PreferenceItem(title = "title", description = "description", icon = 0)
+ PreferenceItem(title = "title", description = "description")
PreferenceItem(title = "title", description = "description", icon = Icons.Outlined.Update)
}
}
diff --git a/app/src/main/java/org/xtimms/tokusho/sections/settings/SettingsView.kt b/app/src/main/java/org/xtimms/tokusho/sections/settings/SettingsView.kt
index 07860c1..cf0e1aa 100644
--- a/app/src/main/java/org/xtimms/tokusho/sections/settings/SettingsView.kt
+++ b/app/src/main/java/org/xtimms/tokusho/sections/settings/SettingsView.kt
@@ -3,6 +3,7 @@ package org.xtimms.tokusho.sections.settings
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.Code
import androidx.compose.material.icons.outlined.Info
import androidx.compose.material.icons.outlined.Palette
import androidx.compose.runtime.Composable
@@ -19,6 +20,7 @@ fun SettingsView(
navigateBack: () -> Unit,
navigateToAppearance: () -> Unit,
navigateToAbout: () -> Unit,
+ navigateToAdvanced: () -> Unit
) {
ScaffoldWithTopAppBar(
title = stringResource(R.string.settings),
@@ -36,6 +38,14 @@ fun SettingsView(
onClick = navigateToAppearance
)
}
+ item {
+ SettingItem(
+ title = stringResource(id = R.string.advanced),
+ description = stringResource(id = R.string.advanced_page),
+ icon = Icons.Outlined.Code,
+ onClick = navigateToAdvanced
+ )
+ }
item {
SettingItem(
title = stringResource(id = R.string.about),
diff --git a/app/src/main/java/org/xtimms/tokusho/sections/settings/advanced/AdvancedView.kt b/app/src/main/java/org/xtimms/tokusho/sections/settings/advanced/AdvancedView.kt
new file mode 100644
index 0000000..ec8a4f0
--- /dev/null
+++ b/app/src/main/java/org/xtimms/tokusho/sections/settings/advanced/AdvancedView.kt
@@ -0,0 +1,142 @@
+package org.xtimms.tokusho.sections.settings.advanced
+
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.ReadOnlyComposable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.produceState
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
+import androidx.profileinstaller.ProfileVerifier
+import kotlinx.coroutines.guava.await
+import org.xtimms.tokusho.BuildConfig
+import org.xtimms.tokusho.R
+import org.xtimms.tokusho.core.components.PreferenceItem
+import org.xtimms.tokusho.core.components.PreferenceSubtitle
+import org.xtimms.tokusho.core.components.ScaffoldWithTopAppBar
+import org.xtimms.tokusho.utils.WebViewUtil
+import org.xtimms.tokusho.utils.lang.toDateTimestampString
+import java.text.DateFormat
+import java.text.SimpleDateFormat
+import java.util.Locale
+import java.util.TimeZone
+
+const val ADVANCED_DESTINATION = "advanced"
+
+@Composable
+fun AdvancedView(
+ navigateBack: () -> Unit,
+) {
+
+ ScaffoldWithTopAppBar(
+ title = stringResource(R.string.advanced),
+ navigateBack = navigateBack
+ ) { padding ->
+ LazyColumn(
+ modifier = Modifier
+ .padding(padding)
+ ) {
+ item {
+ PreferenceSubtitle(text = stringResource(id = R.string.app_info))
+ }
+ item {
+ PreferenceItem(
+ title = stringResource(id = R.string.app_version),
+ description = getVersionName(false)
+ )
+ }
+ item {
+ PreferenceItem(
+ title = stringResource(id = R.string.build_time),
+ description = getFormattedBuildTime()
+ )
+ }
+ item {
+ getProfileVerifierPreference()
+ }
+ item {
+ PreferenceItem(
+ title = stringResource(id = R.string.webview_version),
+ description = getWebViewVersion()
+ )
+ }
+ }
+ }
+}
+
+@Composable
+@ReadOnlyComposable
+private fun getWebViewVersion(): String {
+ return WebViewUtil.getVersion(LocalContext.current)
+}
+
+@Composable
+private fun getProfileVerifierPreference() {
+ val status by produceState(initialValue = "-") {
+ val result = ProfileVerifier.getCompilationStatusAsync().await().profileInstallResultCode
+ value = when (result) {
+ ProfileVerifier.CompilationStatus.RESULT_CODE_NO_PROFILE -> "No profile installed"
+ ProfileVerifier.CompilationStatus.RESULT_CODE_COMPILED_WITH_PROFILE -> "Compiled"
+ ProfileVerifier.CompilationStatus.RESULT_CODE_COMPILED_WITH_PROFILE_NON_MATCHING ->
+ "Compiled non-matching"
+
+ ProfileVerifier.CompilationStatus.RESULT_CODE_ERROR_CACHE_FILE_EXISTS_BUT_CANNOT_BE_READ,
+ ProfileVerifier.CompilationStatus.RESULT_CODE_ERROR_CANT_WRITE_PROFILE_VERIFICATION_RESULT_CACHE_FILE,
+ ProfileVerifier.CompilationStatus.RESULT_CODE_ERROR_PACKAGE_NAME_DOES_NOT_EXIST,
+ -> "Error $result"
+
+ ProfileVerifier.CompilationStatus.RESULT_CODE_ERROR_UNSUPPORTED_API_VERSION -> "Not supported"
+ ProfileVerifier.CompilationStatus.RESULT_CODE_PROFILE_ENQUEUED_FOR_COMPILATION -> "Pending compilation"
+ else -> "Unknown code $result"
+ }
+ }
+ return PreferenceItem(
+ title = "Profile compilation status",
+ description = status,
+ )
+}
+
+fun getVersionName(withBuildDate: Boolean): String {
+ return when {
+ BuildConfig.DEBUG -> {
+ "Debug ${BuildConfig.COMMIT_SHA}".let {
+ if (withBuildDate) {
+ "$it (${getFormattedBuildTime()})"
+ } else {
+ it
+ }
+ }
+ }
+
+ else -> {
+ "Stable ${BuildConfig.VERSION_NAME}".let {
+ if (withBuildDate) {
+ "$it (${getFormattedBuildTime()})"
+ } else {
+ it
+ }
+ }
+ }
+ }
+}
+
+internal fun getFormattedBuildTime(): String {
+ return try {
+ val inputDf = SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'", Locale.US)
+ inputDf.timeZone = TimeZone.getTimeZone("UTC")
+ val buildTime = inputDf.parse(BuildConfig.BUILD_TIME)
+
+ val outputDf = DateFormat.getDateTimeInstance(
+ DateFormat.MEDIUM,
+ DateFormat.SHORT,
+ Locale.getDefault(),
+ )
+ outputDf.timeZone = TimeZone.getDefault()
+
+ buildTime!!.toDateTimestampString(DateFormat.getDateTimeInstance())
+ } catch (e: Exception) {
+ BuildConfig.BUILD_TIME
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/xtimms/tokusho/utils/WebViewUtil.kt b/app/src/main/java/org/xtimms/tokusho/utils/WebViewUtil.kt
new file mode 100644
index 0000000..0610f51
--- /dev/null
+++ b/app/src/main/java/org/xtimms/tokusho/utils/WebViewUtil.kt
@@ -0,0 +1,16 @@
+package org.xtimms.tokusho.utils
+
+import android.content.Context
+import android.webkit.WebView
+
+object WebViewUtil {
+
+ fun getVersion(context: Context): String {
+ val webView = WebView.getCurrentWebViewPackage() ?: return "o_O"
+ val pm = context.packageManager
+ val label = webView.applicationInfo.loadLabel(pm)
+ val version = webView.versionName
+ return "$label $version"
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/xtimms/tokusho/utils/lang/Date.kt b/app/src/main/java/org/xtimms/tokusho/utils/lang/Date.kt
new file mode 100644
index 0000000..8a7e36a
--- /dev/null
+++ b/app/src/main/java/org/xtimms/tokusho/utils/lang/Date.kt
@@ -0,0 +1,10 @@
+package org.xtimms.tokusho.utils.lang
+
+import java.text.DateFormat
+import java.util.Date
+
+fun Date.toDateTimestampString(dateFormatter: DateFormat): String {
+ val date = dateFormatter.format(this)
+ val time = DateFormat.getTimeInstance(DateFormat.SHORT).format(this)
+ return "$date $time"
+}
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 8091ebf..b1ae4b4 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -73,4 +73,10 @@
More
Copy to clipboard
No description
+ Advanced
+ App info
+ App version
+ Build time
+ WebView version
+ Dump crash logs, debug info
\ No newline at end of file