diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 5940e486e4..1a189c4f53 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -141,10 +141,13 @@ android { versionName = "$versionMajor.$versionMinor.$versionPatch" multiDexEnabled = true testInstrumentationRunner = "one.mixin.android.CustomTestRunner" - resourceConfigurations += listOf("en", "es", "in", "ja", "ms", "ru", "zh-rCN", "zh-rTW") vectorDrawables.useSupportLibrary = true } + androidResources { + localeFilters += listOf("en", "es", "in", "ja", "ms", "ru", "zh-rCN", "zh-rTW") + } + packaging { resources { excludes += "**/*.kotlin_metadata" @@ -153,6 +156,7 @@ android { excludes += "META-INF/DISCLAIMER" excludes += "META-INF/NOTICE.md" excludes += "/META-INF/{AL2.0,LGPL2.1}" + excludes += "google/protobuf/descriptor.proto" } jniLibs { useLegacyPackaging = true @@ -184,11 +188,11 @@ android { sourceSets { val sharedTestDir = "src/sharedTest/java" getByName("test") { - java.srcDirs(sharedTestDir) + java.directories.add(sharedTestDir) } getByName("androidTest") { - java.srcDirs(sharedTestDir) - assets.srcDirs(files("$projectDir/schemas")) + java.directories.add(sharedTestDir) + assets.directories.add("$projectDir/schemas") } } @@ -326,7 +330,9 @@ dependencies { } coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:$desugarJdkLibsVersion") implementation(platform("com.google.firebase:firebase-bom:$firebaseBomVersion")) - implementation("com.google.firebase:firebase-perf") + implementation("com.google.firebase:firebase-perf") { + exclude(group = "com.google.firebase", module = "protolite-well-known-types") + } implementation(fileTree(mapOf("include" to listOf("*.aar"), "dir" to "libs"))) implementation("androidx.fragment:fragment-ktx:$fragmentVersion") implementation("androidx.activity:activity-ktx:$activity_version") @@ -436,11 +442,7 @@ dependencies { implementation("com.google.firebase:firebase-analytics") implementation("com.google.firebase:firebase-crashlytics") - implementation("com.google.protobuf:protobuf-javalite") { - version { - strictly("3.11.0") - } - } + implementation("com.google.protobuf:protobuf-javalite:4.29.3") implementation("com.android.billingclient:billing-ktx:$billingKtxVersion") implementation("com.google.mlkit:barcode-scanning:$mlkitBarcodeVersion") @@ -552,6 +554,7 @@ dependencies { androidTestImplementation("androidx.test.espresso:espresso-contrib:$espressoVersion") { exclude(group = "com.android.support", module = "support-annotations") exclude(group = "org.checkerframework", module = "checker") + exclude(group = "com.google.protobuf", module = "protobuf-lite") } androidTestImplementation("androidx.test.espresso:espresso-idling-resource:$espressoVersion") androidTestImplementation("androidx.test.ext:junit:$androidxJunitVersion") @@ -566,11 +569,8 @@ dependencies { // ML Kit implementation("com.google.mlkit:entity-extraction:16.0.0-beta6") - testImplementation("com.google.protobuf:protobuf-javalite") { - version { - strictly("3.11.0") - } - } + testImplementation("com.google.protobuf:protobuf-javalite:4.29.3") + androidTestImplementation("com.google.protobuf:protobuf-javalite:4.29.3") // SumSub implementation("com.sumsub.sns:idensic-mobile-sdk:$sumsubVersion") { @@ -599,9 +599,8 @@ dependencies { } composeCompiler { - enableStrongSkippingMode = true reportsDestination = layout.buildDirectory.dir("compose_compiler") - stabilityConfigurationFile = rootProject.layout.projectDirectory.file("stability_config.conf") + stabilityConfigurationFiles.add(rootProject.layout.projectDirectory.file("stability_config.conf")) } secrets { @@ -617,24 +616,3 @@ tasks.register("allTests") { description = "Run unit tests and instrumentation tests" } -tasks.register("syncStrings") { - doLast { - listOf("en", "zh", "zh-TW", "ja", "ru", "in", "ms").forEach { lang -> - project.extensions.getByName("download").let { ext -> - val downloadExt = ext as de.undercouch.gradle.tasks.download.DownloadExtension - downloadExt.run { - src("https://raw.githubusercontent.com/Tougee/sync-google-sheet/master/generated/output/Android/value-$lang/strings.xml") - dest( - when (lang) { - "en" -> "src/main/res/values" - "zh" -> "src/main/res/values-zh-rCN" - "zh-TW" -> "src/main/res/values-zh-rTW" - "zh-HK" -> "src/main/res/values-zh-rHK" - else -> "src/main/res/values-$lang" - } - ) - } - } - } - } -} diff --git a/app/src/androidTest/java/one/mixin/android/db/BaseMigrationTest.kt b/app/src/androidTest/java/one/mixin/android/db/BaseMigrationTest.kt index 368c19a166..c43009a6e6 100644 --- a/app/src/androidTest/java/one/mixin/android/db/BaseMigrationTest.kt +++ b/app/src/androidTest/java/one/mixin/android/db/BaseMigrationTest.kt @@ -1,9 +1,65 @@ package one.mixin.android.db +import androidx.room.migration.Migration import androidx.room.testing.MigrationTestHelper import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory import androidx.test.platform.app.InstrumentationRegistry import one.mixin.android.Constants +import one.mixin.android.Constants.DataBase.CURRENT_VERSION +import one.mixin.android.db.MixinDatabaseMigrations.Companion.MIGRATION_15_16 +import one.mixin.android.db.MixinDatabaseMigrations.Companion.MIGRATION_16_17 +import one.mixin.android.db.MixinDatabaseMigrations.Companion.MIGRATION_17_18 +import one.mixin.android.db.MixinDatabaseMigrations.Companion.MIGRATION_18_19 +import one.mixin.android.db.MixinDatabaseMigrations.Companion.MIGRATION_19_20 +import one.mixin.android.db.MixinDatabaseMigrations.Companion.MIGRATION_20_21 +import one.mixin.android.db.MixinDatabaseMigrations.Companion.MIGRATION_21_22 +import one.mixin.android.db.MixinDatabaseMigrations.Companion.MIGRATION_22_23 +import one.mixin.android.db.MixinDatabaseMigrations.Companion.MIGRATION_23_24 +import one.mixin.android.db.MixinDatabaseMigrations.Companion.MIGRATION_24_25 +import one.mixin.android.db.MixinDatabaseMigrations.Companion.MIGRATION_25_26 +import one.mixin.android.db.MixinDatabaseMigrations.Companion.MIGRATION_26_27 +import one.mixin.android.db.MixinDatabaseMigrations.Companion.MIGRATION_27_28 +import one.mixin.android.db.MixinDatabaseMigrations.Companion.MIGRATION_28_29 +import one.mixin.android.db.MixinDatabaseMigrations.Companion.MIGRATION_29_30 +import one.mixin.android.db.MixinDatabaseMigrations.Companion.MIGRATION_30_31 +import one.mixin.android.db.MixinDatabaseMigrations.Companion.MIGRATION_31_32 +import one.mixin.android.db.MixinDatabaseMigrations.Companion.MIGRATION_32_33 +import one.mixin.android.db.MixinDatabaseMigrations.Companion.MIGRATION_33_34 +import one.mixin.android.db.MixinDatabaseMigrations.Companion.MIGRATION_34_35 +import one.mixin.android.db.MixinDatabaseMigrations.Companion.MIGRATION_35_36 +import one.mixin.android.db.MixinDatabaseMigrations.Companion.MIGRATION_36_37 +import one.mixin.android.db.MixinDatabaseMigrations.Companion.MIGRATION_37_38 +import one.mixin.android.db.MixinDatabaseMigrations.Companion.MIGRATION_38_39 +import one.mixin.android.db.MixinDatabaseMigrations.Companion.MIGRATION_39_40 +import one.mixin.android.db.MixinDatabaseMigrations.Companion.MIGRATION_40_41 +import one.mixin.android.db.MixinDatabaseMigrations.Companion.MIGRATION_41_42 +import one.mixin.android.db.MixinDatabaseMigrations.Companion.MIGRATION_42_43 +import one.mixin.android.db.MixinDatabaseMigrations.Companion.MIGRATION_43_44 +import one.mixin.android.db.MixinDatabaseMigrations.Companion.MIGRATION_44_45 +import one.mixin.android.db.MixinDatabaseMigrations.Companion.MIGRATION_45_46 +import one.mixin.android.db.MixinDatabaseMigrations.Companion.MIGRATION_46_47 +import one.mixin.android.db.MixinDatabaseMigrations.Companion.MIGRATION_47_48 +import one.mixin.android.db.MixinDatabaseMigrations.Companion.MIGRATION_48_49 +import one.mixin.android.db.MixinDatabaseMigrations.Companion.MIGRATION_49_50 +import one.mixin.android.db.MixinDatabaseMigrations.Companion.MIGRATION_50_51 +import one.mixin.android.db.MixinDatabaseMigrations.Companion.MIGRATION_51_52 +import one.mixin.android.db.MixinDatabaseMigrations.Companion.MIGRATION_52_53 +import one.mixin.android.db.MixinDatabaseMigrations.Companion.MIGRATION_53_54 +import one.mixin.android.db.MixinDatabaseMigrations.Companion.MIGRATION_54_55 +import one.mixin.android.db.MixinDatabaseMigrations.Companion.MIGRATION_55_56 +import one.mixin.android.db.MixinDatabaseMigrations.Companion.MIGRATION_56_57 +import one.mixin.android.db.MixinDatabaseMigrations.Companion.MIGRATION_57_58 +import one.mixin.android.db.MixinDatabaseMigrations.Companion.MIGRATION_58_59 +import one.mixin.android.db.MixinDatabaseMigrations.Companion.MIGRATION_59_60 +import one.mixin.android.db.MixinDatabaseMigrations.Companion.MIGRATION_60_61 +import one.mixin.android.db.MixinDatabaseMigrations.Companion.MIGRATION_61_62 +import one.mixin.android.db.MixinDatabaseMigrations.Companion.MIGRATION_62_63 +import one.mixin.android.db.MixinDatabaseMigrations.Companion.MIGRATION_63_64 +import one.mixin.android.db.MixinDatabaseMigrations.Companion.MIGRATION_64_65 +import one.mixin.android.db.MixinDatabaseMigrations.Companion.MIGRATION_65_66 +import one.mixin.android.db.MixinDatabaseMigrations.Companion.MIGRATION_66_67 +import one.mixin.android.db.MixinDatabaseMigrations.Companion.MIGRATION_67_68 +import one.mixin.android.db.MixinDatabaseMigrations.Companion.MIGRATION_68_69 import org.junit.After import org.junit.Before import org.junit.Rule @@ -14,10 +70,68 @@ open class BaseMigrationTest { val migrationTestHelper = MigrationTestHelper( InstrumentationRegistry.getInstrumentation(), - MixinDatabase::class.java.canonicalName, + requireNotNull(MixinDatabase::class.java.canonicalName), FrameworkSQLiteOpenHelperFactory(), ) + protected val allMixinMigrations = + arrayOf( + MIGRATION_15_16, + MIGRATION_16_17, + MIGRATION_17_18, + MIGRATION_18_19, + MIGRATION_19_20, + MIGRATION_20_21, + MIGRATION_21_22, + MIGRATION_22_23, + MIGRATION_23_24, + MIGRATION_24_25, + MIGRATION_25_26, + MIGRATION_26_27, + MIGRATION_27_28, + MIGRATION_28_29, + MIGRATION_29_30, + MIGRATION_30_31, + MIGRATION_31_32, + MIGRATION_32_33, + MIGRATION_33_34, + MIGRATION_34_35, + MIGRATION_35_36, + MIGRATION_36_37, + MIGRATION_37_38, + MIGRATION_38_39, + MIGRATION_39_40, + MIGRATION_40_41, + MIGRATION_41_42, + MIGRATION_42_43, + MIGRATION_43_44, + MIGRATION_44_45, + MIGRATION_45_46, + MIGRATION_46_47, + MIGRATION_47_48, + MIGRATION_48_49, + MIGRATION_49_50, + MIGRATION_50_51, + MIGRATION_51_52, + MIGRATION_52_53, + MIGRATION_53_54, + MIGRATION_54_55, + MIGRATION_55_56, + MIGRATION_56_57, + MIGRATION_57_58, + MIGRATION_58_59, + MIGRATION_59_60, + MIGRATION_60_61, + MIGRATION_61_62, + MIGRATION_62_63, + MIGRATION_63_64, + MIGRATION_64_65, + MIGRATION_65_66, + MIGRATION_66_67, + MIGRATION_67_68, + MIGRATION_68_69, + ) + @Before fun setUp() { } @@ -31,6 +145,29 @@ open class BaseMigrationTest { db.close() } + protected fun createDatabase(version: Int, dbName: String = Constants.DataBase.DB_NAME) { + val db = migrationTestHelper.createDatabase(dbName, version) + db.close() + } + + protected fun runMixinMigrationsAndValidate( + fromVersion: Int, + toVersion: Int = CURRENT_VERSION, + ) { + val migrations = + allMixinMigrations + .filter { it.startVersion >= fromVersion && it.endVersion <= toVersion } + .sortedBy { it.startVersion } + .toTypedArray() + + migrationTestHelper.runMigrationsAndValidate( + Constants.DataBase.DB_NAME, + toVersion, + true, + *migrations, + ) + } + fun create16() { val db = migrationTestHelper.createDatabase(Constants.DataBase.DB_NAME, 16) db.close() diff --git a/app/src/androidTest/java/one/mixin/android/db/MigrationCurrentVersionTest.kt b/app/src/androidTest/java/one/mixin/android/db/MigrationCurrentVersionTest.kt new file mode 100644 index 0000000000..8f27f8ea8f --- /dev/null +++ b/app/src/androidTest/java/one/mixin/android/db/MigrationCurrentVersionTest.kt @@ -0,0 +1,19 @@ +package one.mixin.android.db + +import one.mixin.android.Constants.DataBase.CURRENT_VERSION +import one.mixin.android.Constants.DataBase.MINI_VERSION +import org.junit.Test + +class MigrationCurrentVersionTest : BaseMigrationTest() { + @Test + fun migrate_all_historical_versions_to_current() { + for (fromVersion in MINI_VERSION until CURRENT_VERSION) { + try { + createDatabase(fromVersion) + runMixinMigrationsAndValidate(fromVersion) + } catch (t: Throwable) { + throw AssertionError("Failed to migrate MixinDatabase from $fromVersion to $CURRENT_VERSION", t) + } + } + } +} diff --git a/app/src/main/java/one/mixin/android/compose/AppBar.kt b/app/src/main/java/one/mixin/android/compose/AppBar.kt index 4bca21152b..c8a328c021 100644 --- a/app/src/main/java/one/mixin/android/compose/AppBar.kt +++ b/app/src/main/java/one/mixin/android/compose/AppBar.kt @@ -127,21 +127,23 @@ fun MixinTopAppBar( @Preview @Composable fun PreviewMixinAppBar() { - MixinTopAppBar( - navigationIcon = { - MixinBackButton() - }, - title = { - Text(text = "Title") - }, - actions = { - IconButton(onClick = { }) { - Icon( - painter = painterResource(id = R.drawable.ic_more), - contentDescription = null, - tint = MixinAppTheme.colors.icon, - ) - } - }, - ) + MixinAppTheme { + MixinTopAppBar( + navigationIcon = { + MixinBackButton() + }, + title = { + Text(text = "Title") + }, + actions = { + IconButton(onClick = { }) { + Icon( + painter = painterResource(id = R.drawable.ic_more), + contentDescription = null, + tint = MixinAppTheme.colors.icon, + ) + } + }, + ) + } } diff --git a/app/src/main/java/one/mixin/android/compose/Dialogs.kt b/app/src/main/java/one/mixin/android/compose/Dialogs.kt index 4730dcac8d..f1f0ddd731 100644 --- a/app/src/main/java/one/mixin/android/compose/Dialogs.kt +++ b/app/src/main/java/one/mixin/android/compose/Dialogs.kt @@ -25,6 +25,11 @@ fun IndeterminateProgressDialog( title: String = "", cancelable: Boolean? = null, ) { + val isInPreview = androidx.compose.ui.platform.LocalInspectionMode.current + if (isInPreview) { + // Don't show legacy ProgressDialog in previews + return + } val context = LocalContext.current val activity = context.findFragmentActivityOrNull() diff --git a/app/src/main/java/one/mixin/android/compose/InputAmountUsageExample.kt b/app/src/main/java/one/mixin/android/compose/InputAmountUsageExample.kt deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/app/src/main/java/one/mixin/android/compose/theme/Theme.kt b/app/src/main/java/one/mixin/android/compose/theme/Theme.kt index 5438cc4dbf..223994817f 100644 --- a/app/src/main/java/one/mixin/android/compose/theme/Theme.kt +++ b/app/src/main/java/one/mixin/android/compose/theme/Theme.kt @@ -13,6 +13,7 @@ import androidx.compose.material.lightColors import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.compositionLocalOf +import androidx.compose.ui.platform.LocalInspectionMode import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.colorspace.ColorSpaces import androidx.compose.ui.platform.LocalContext @@ -21,8 +22,6 @@ import one.mixin.android.extension.isNightMode import one.mixin.android.extension.isScreenWideColorGamut import one.mixin.android.util.isCurrChinese -val isP3Supported = MixinApplication.appContext.isScreenWideColorGamut() - class AppColors( val primary: Color, val accent: Color, @@ -45,20 +44,8 @@ class AppColors( val walletRed: Color = Color(0xFFF67070), val walletGreen: Color = Color(0xFF50BD5C), val walletOrange: Color = Color(0xFFFFAA00), - val marketRed: Color = if (isP3Supported) Color( - colorSpace = ColorSpaces.DisplayP3, - red = 0.898f, - green = 0.471f, - blue = 0.455f, - alpha = 1f - ) else Color(0xFFE57874), - val marketGreen: Color = if (isP3Supported) Color( - colorSpace = ColorSpaces.DisplayP3, - red = 0.314f, - green = 0.741f, - blue = 0.361f, - alpha = 1f - ) else Color(0xFF50BD5C), + val marketRed: Color, + val marketGreen: Color, val shadow: Color = Color(0x33AAAAAA), val unchecked: Color, val tipWarning: Color, @@ -90,8 +77,73 @@ object MixinAppTheme { } -private val LightColorPalette = - AppColors( +private fun createMarketRedColor(isP3Supported: Boolean): Color { + if (isP3Supported) { + return Color( + colorSpace = ColorSpaces.DisplayP3, + red = 0.898f, + green = 0.471f, + blue = 0.455f, + alpha = 1f, + ) + } + return Color(0xFFE57874) +} + +private fun createMarketGreenColor(isP3Supported: Boolean): Color { + if (isP3Supported) { + return Color( + colorSpace = ColorSpaces.DisplayP3, + red = 0.314f, + green = 0.741f, + blue = 0.361f, + alpha = 1f, + ) + } + return Color(0xFF50BD5C) +} + +private fun createAppColors( + isDarkTheme: Boolean, + isP3Supported: Boolean, +): AppColors { + val marketRed: Color = createMarketRedColor(isP3Supported) + val marketGreen: Color = createMarketGreenColor(isP3Supported) + if (isDarkTheme) { + return AppColors( + primary = Color(0xFF2c3136), + accent = Color(0xFF3D75E3), + textPrimary = Color(0xFFFFFFFF), + textAssist = Color(0xFF7F878F), + textMinor = Color(0xFFD3D4D5), + textRemarks = Color(0xFF6E7073), + icon = Color(0xFFEAEAEB), + iconGray = Color(0xFF808691), + iconAction = Color(0xFFFFFFFF), + backgroundWindow = Color(0xFF23272B), + background = Color(0xFF2c3136), + backgroundDark = Color(0xFF121212), + backgroundGrayLight = Color(0xFF3B3F44), + backgroundGray = Color(0xFF3B3F44), + unchecked = Color(0xFFECECEC), + tipWarning = Color(0xFF3E373B), + tipWarningBorder = Color(0xFFE86B67), + borderPrimary = Color(0x33FFFFFF), + bgGradientStart = Color(0xFF2C3136), + bgGradientEnd = Color(0xFF1C2029), + borderColor = Color(0xFF6E7073), + walletBlue = Color(0xFF64B5F6), + walletYellow = Color(0xFFFFEE58), + walletPurple = Color(0xFFBA68C8), + badgeRed = Color(0xFFF67070), + warning = Color(0xFFF6A417), + bgClip = Color(0xFF3B3F44), + borderGray = Color(0xFFD6D6D6), + marketRed = marketRed, + marketGreen = marketGreen, + ) + } + return AppColors( primary = Color(0xFFFFFFFF), accent = Color(0xFF3D75E3), textPrimary = Color(0xFF000000), @@ -120,53 +172,29 @@ private val LightColorPalette = warning = Color(0xFFF6A417), bgClip = Color(0xFFF5F7FA), borderGray = Color(0xFFD6D6D6), + marketRed = marketRed, + marketGreen = marketGreen, ) +} -private val DarkColorPalette = - AppColors( - primary = Color(0xFF2c3136), - accent = Color(0xFF3D75E3), - textPrimary = Color(0xFFFFFFFF), - textAssist = Color(0xFF7F878F), - textMinor = Color(0xFFD3D4D5), - textRemarks = Color(0xFF6E7073), - icon = Color(0xFFEAEAEB), - iconGray = Color(0xFF808691), - iconAction = Color(0xFFFFFFFF), - backgroundWindow = Color(0xFF23272B), - background = Color(0xFF2c3136), - backgroundDark = Color(0xFF121212), - backgroundGrayLight = Color(0xFF3B3F44), - backgroundGray = Color(0xFF3B3F44), - unchecked = Color(0xFFECECEC), - tipWarning = Color(0xFF3E373B), - tipWarningBorder = Color(0xFFE86B67), - borderPrimary = Color(0x33FFFFFF), - bgGradientStart = Color(0xFF2C3136), - bgGradientEnd = Color(0xFF1C2029), - borderColor = Color(0xFF6E7073), - walletBlue = Color(0xFF64B5F6), - walletYellow = Color(0xFFFFEE58), - walletPurple = Color(0xFFBA68C8), - badgeRed = Color(0xFFF67070), - warning = Color(0xFFF6A417), - bgClip = Color(0xFF3B3F44), - borderGray = Color(0xFFD6D6D6), - ) - -private val LocalColors = compositionLocalOf { LightColorPalette } +private val LocalColors = compositionLocalOf { createAppColors(isDarkTheme = false, isP3Supported = false) } @Composable fun MixinAppTheme( - darkTheme: Boolean = MixinApplication.get().isNightMode(), - content: @Composable () -> Unit, -) { - val colors = - if (darkTheme) { - DarkColorPalette + darkTheme: Boolean = run { + val isInPreview = LocalInspectionMode.current + if (isInPreview) { + false } else { - LightColorPalette + MixinApplication.get().isNightMode() } + }, + content: @Composable () -> Unit, +) { + val context = LocalContext.current + val isInPreview: Boolean = LocalInspectionMode.current + val isP3Supported: Boolean = if (isInPreview) false else context.isScreenWideColorGamut() + val colors: AppColors = createAppColors(isDarkTheme = darkTheme, isP3Supported = isP3Supported) val textSelectionColors = TextSelectionColors( handleColor = Color(0xFF3D75E3), @@ -196,8 +224,6 @@ fun MixinAppTheme( @Composable @DrawableRes fun languageBasedImage(@DrawableRes defaultImage:Int, @DrawableRes zh:Int) : Int{ - val context = LocalContext.current - val drawableRes = when { isCurrChinese() -> zh else -> defaultImage diff --git a/app/src/main/java/one/mixin/android/extension/ViewExtension.kt b/app/src/main/java/one/mixin/android/extension/ViewExtension.kt index d39534decf..a06cea5b42 100644 --- a/app/src/main/java/one/mixin/android/extension/ViewExtension.kt +++ b/app/src/main/java/one/mixin/android/extension/ViewExtension.kt @@ -1,6 +1,15 @@ +@file:Suppress( + "unused", + "FunctionName", + "FoldableIfThen", + "IfThenToElvis", + "UNUSED_PARAMETER", +) + package one.mixin.android.extension import android.animation.Animator +import android.animation.AnimatorListenerAdapter import android.animation.ObjectAnimator import android.animation.ValueAnimator import android.app.Activity @@ -35,9 +44,11 @@ import androidx.appcompat.widget.PopupMenu import androidx.core.animation.doOnEnd import androidx.core.animation.doOnStart import androidx.core.graphics.ColorUtils -import androidx.core.view.ViewCompat -import androidx.core.view.ViewPropertyAnimatorListener import androidx.core.view.drawToBitmap +import androidx.core.view.forEach +import androidx.core.view.forEachIndexed +import androidx.core.view.isVisible +import androidx.core.view.marginBottom import androidx.core.view.updateLayoutParams import androidx.navigation.NavController import androidx.navigation.NavOptions @@ -57,13 +68,15 @@ const val ANIMATION_DURATION_SHORT = 260L const val ANIMATION_DURATION_SHORTEST = 120L fun View.hideKeyboard() { - val inputMethodManager = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + val inputMethodManager: InputMethodManager = + context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager inputMethodManager.hideSoftInputFromWindow(windowToken, 0) } fun View.showKeyboard() { if (requestFocus()) { - val inputMethodManager = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + val inputMethodManager: InputMethodManager = + context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager inputMethodManager.showSoftInput(this, SHOW_IMPLICIT) } } @@ -78,17 +91,7 @@ fun View.fadeIn( ) { this.visibility = VISIBLE this.alpha = 0f - ViewCompat.animate(this).alpha(maxAlpha).setDuration(duration).setListener( - object : ViewPropertyAnimatorListener { - override fun onAnimationStart(view: View) { - } - - override fun onAnimationEnd(view: View) { - } - - override fun onAnimationCancel(view: View) {} - }, - ).start() + animate().alpha(maxAlpha).setDuration(duration).start() } fun View.fadeOut(isGone: Boolean = false) { @@ -101,21 +104,15 @@ fun View.fadeOut( isGone: Boolean = false, ) { this.alpha = 1f - ViewCompat.animate(this).alpha(0f).setStartDelay(delay).setDuration(duration).setListener( - object : ViewPropertyAnimatorListener { - override fun onAnimationStart(view: View) { - view.isDrawingCacheEnabled = true - } - - override fun onAnimationEnd(view: View) { - view.visibility = if (isGone) GONE else INVISIBLE - view.alpha = 0f - view.isDrawingCacheEnabled = false - } - - override fun onAnimationCancel(view: View) {} - }, - ) + animate().alpha(0f).setStartDelay(delay).setDuration(duration) + .setListener( + object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + visibility = if (isGone) GONE else INVISIBLE + alpha = 0f + } + }, + ) } fun View.translationX(value: Float) { @@ -126,7 +123,7 @@ fun View.translationX( value: Float, duration: Long, ) { - ViewCompat.animate(this).setDuration(duration).translationX(value).start() + animate().setDuration(duration).translationX(value).start() } fun View.translationY( @@ -141,18 +138,15 @@ fun View.translationY( duration: Long, endAction: (() -> Unit)? = null, ) { - ViewCompat.animate(this).setDuration(duration).translationY(value) + animate().setDuration(duration).translationY(value) .setListener( - object : ViewPropertyAnimatorListener { - override fun onAnimationEnd(view: View) { - endAction?.let { it() } + object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + endAction?.invoke() } - - override fun onAnimationCancel(view: View) { - endAction?.let { it() } + override fun onAnimationCancel(animation: Animator) { + endAction?.invoke() } - - override fun onAnimationStart(view: View) {} }, ) .start() @@ -318,27 +312,32 @@ fun View.circularReveal() { circularReveal.start() } +@Suppress("unused") fun EditText.showCursor() { this.requestFocus() this.isCursorVisible = true } +@Suppress("unused") fun EditText.hideCursor() { this.clearFocus() this.isCursorVisible = false } +@Suppress("unused") fun ViewGroup.inflate( @LayoutRes layoutRes: Int, attachToRoot: Boolean = false, -) = LayoutInflater.from(context).inflate(layoutRes, this, attachToRoot)!! +): View { + return LayoutInflater.from(context).inflate(layoutRes, this, attachToRoot) +} fun View.navigateUp() { try { findNavController().navigateUp() - } catch (e: IllegalArgumentException) { + } catch (_: IllegalArgumentException) { // Workaround with https://issuetracker.google.com/issues/128881182 - } catch (e: IllegalStateException) { + } catch (_: IllegalStateException) { Timber.w("View $this does not have a NavController set") } } @@ -346,10 +345,10 @@ fun View.navigateUp() { fun NavController.safeNavigateUp(): Boolean { return try { navigateUp() - } catch (e: IllegalArgumentException) { + } catch (_: IllegalArgumentException) { // Workaround with https://issuetracker.google.com/issues/128881182 false - } catch (e: IllegalStateException) { + } catch (_: IllegalStateException) { false } } @@ -361,9 +360,9 @@ fun View.navigate( ) { try { findNavController().navigate(resId, bundle, navOptions) - } catch (e: IllegalArgumentException) { + } catch (_: IllegalArgumentException) { // Workaround with https://issuetracker.google.com/issues/128881182 - } catch (e: IllegalStateException) { + } catch (_: IllegalStateException) { Timber.w("View $this does not have a NavController set") } } @@ -400,10 +399,21 @@ fun View.bounce() { spring.endValue = 1.0 } +@Suppress("UnusedReceiverParameter", "FunctionName", "unused") +@Deprecated("Use intProperty", ReplaceWith("intProperty(name, getAction, setAction)")) fun View.IntProperty( name: String, getAction: (View) -> Int, setAction: (View, Int) -> Unit, +): Property { + return intProperty(name = name, getAction = getAction, setAction = setAction) +} + +@Suppress("UnusedReceiverParameter", "unused") +fun View.intProperty( + name: String, + getAction: (View) -> Int, + setAction: (View, Int) -> Unit, ): Property { return object : Property(Int::class.java, name) { override fun get(obj: View): Int { @@ -441,16 +451,17 @@ fun Int.withAlpha(alpha: Float): Int { } fun PopupMenu.showIcon() { - val menuHelper: Any - val argTypes: Array?> + val menuHelper: Any? try { val fMenuHelper: Field = PopupMenu::class.java.getDeclaredField("mPopup") fMenuHelper.isAccessible = true menuHelper = fMenuHelper.get(this) - argTypes = arrayOf(Boolean::class.javaPrimitiveType) - menuHelper.javaClass.getDeclaredMethod("setForceShowIcon", *argTypes) - .invoke(menuHelper, true) - } catch (e: Exception) { + val argTypes: Array?> = arrayOf(Boolean::class.javaPrimitiveType) + if (menuHelper != null) { + menuHelper.javaClass.getDeclaredMethod("setForceShowIcon", *argTypes) + .invoke(menuHelper, true) + } + } catch (_: Exception) { } } @@ -501,7 +512,9 @@ var View.backgroundColor: Int var View.backgroundDrawable: Drawable? inline get() = background - set(value) = setBackgroundDrawable(value) + set(value) { + background = value + } var View.backgroundResource: Int @Deprecated("Property does not have a getter") diff --git a/app/src/main/java/one/mixin/android/job/AttachmentDownloadJob.kt b/app/src/main/java/one/mixin/android/job/AttachmentDownloadJob.kt index f81faeaa10..82f775faea 100644 --- a/app/src/main/java/one/mixin/android/job/AttachmentDownloadJob.kt +++ b/app/src/main/java/one/mixin/android/job/AttachmentDownloadJob.kt @@ -101,13 +101,14 @@ class AttachmentDownloadJob( shareable = this.shareable }.attachmentId } catch (e: Exception) { - message.content!! + requireNotNull(message.content) }, ) val body = attachmentCall!!.execute().body() - if (body != null && (body.isSuccess || !isCancelled) && body.data != null) { - val attachmentResponse = body.data!! - attachmentResponse.view_url?.let { + if (body != null && body.isSuccess && !isCancelled && body.data != null) { + val attachmentResponse = requireNotNull(body.data) + val viewUrl: String? = attachmentResponse.view_url + viewUrl?.let { val result = decryptAttachment(it) if (result) { val attachmentExtra = GsonHelper.customGson.toJson(AttachmentExtra(attachmentResponse.attachment_id, message.messageId, attachmentResponse.created_at, shareable)) @@ -193,7 +194,7 @@ class AttachmentDownloadJob( return true } else if (response.isSuccessful && !isCancelled && response.body != null) { val sink = destination.sink().buffer() - sink.writeAll(response.body!!.source()) + sink.writeAll(requireNotNull(response.body).source()) sink.close() if (message.category.endsWith("_IMAGE")) { val attachmentCipherInputStream = diff --git a/app/src/main/java/one/mixin/android/job/RefreshAddressJob.kt b/app/src/main/java/one/mixin/android/job/RefreshAddressJob.kt index 3b6b06cd33..db5c5fee93 100644 --- a/app/src/main/java/one/mixin/android/job/RefreshAddressJob.kt +++ b/app/src/main/java/one/mixin/android/job/RefreshAddressJob.kt @@ -15,9 +15,8 @@ class RefreshAddressJob(private val chainId: String) : BaseJob( override fun onRun() = runBlocking { val response = tokenService.addresses(chainId) if (response != null && response.isSuccess && response.data != null) { - response.data?.let { - addressDao.insertList(it) - } + val addresses = requireNotNull(response.data) + addressDao.insertList(addresses) } } } diff --git a/app/src/main/java/one/mixin/android/job/SendMessageJob.kt b/app/src/main/java/one/mixin/android/job/SendMessageJob.kt index 5dbaafb16c..7650178967 100644 --- a/app/src/main/java/one/mixin/android/job/SendMessageJob.kt +++ b/app/src/main/java/one/mixin/android/job/SendMessageJob.kt @@ -271,11 +271,12 @@ open class SendMessageJob( } else { message.content!!.toByteArray() } + val participantPublicKey: String = participantSessionKey.publicKey ?: return val encryptContent = encryptedProtocol.encryptMessage( keyPair, plaintext, - participantSessionKey.publicKey!!.base64RawURLDecode(), + participantPublicKey.base64RawURLDecode(), participantSessionKey.sessionId, extensionSessionKey?.publicKey?.base64RawURLDecode(), extensionSessionKey?.sessionId, diff --git a/app/src/main/java/one/mixin/android/job/TranscriptAttachmentDownloadJob.kt b/app/src/main/java/one/mixin/android/job/TranscriptAttachmentDownloadJob.kt index dbb2f1dff5..f7dc1085eb 100644 --- a/app/src/main/java/one/mixin/android/job/TranscriptAttachmentDownloadJob.kt +++ b/app/src/main/java/one/mixin/android/job/TranscriptAttachmentDownloadJob.kt @@ -87,8 +87,9 @@ class TranscriptAttachmentDownloadJob( } attachmentCall = conversationApi.getAttachment(attachmentId) val body = attachmentCall!!.execute().body() - if (body != null && (body.isSuccess || !isCancelled) && body.data != null) { - val viewUrl = body.data?.view_url + if (body != null && body.isSuccess && !isCancelled && body.data != null) { + val attachmentResponse = body.data + val viewUrl = attachmentResponse?.view_url if (viewUrl != null) { if (decryptAttachment(viewUrl, transcriptMessage)) { processTranscript() @@ -162,7 +163,7 @@ class TranscriptAttachmentDownloadJob( return true } else if (response.isSuccessful && !isCancelled && response.body != null) { val sink = destination.sink().buffer() - sink.writeAll(response.body!!.source()) + sink.writeAll(requireNotNull(response.body).source()) sink.close() when { transcriptMessage.type.endsWith("_IMAGE") -> { diff --git a/app/src/main/java/one/mixin/android/net/Diagnosis.kt b/app/src/main/java/one/mixin/android/net/Diagnosis.kt index 7d07ab52db..49a58e5600 100644 --- a/app/src/main/java/one/mixin/android/net/Diagnosis.kt +++ b/app/src/main/java/one/mixin/android/net/Diagnosis.kt @@ -133,13 +133,16 @@ private fun getExportIp( val client = OkHttpClient() var ipRequest = Request.Builder().url(EXPORT_IP_PRIMARY).build() try { - var data = - client.newCall(ipRequest).execute().body?.string() - ?: throw IOException("EXPORT_IP_PRIMARY no data") + var data: String = client.newCall(ipRequest).execute().body.string() + if (data.isBlank()) { + throw IOException("EXPORT_IP_PRIMARY no data") + } val url = data.substringIgnoreError(data.indexOf("src=") + 4, data.lastIndexOf("frameborder")).replace("'".toRegex(), "").replace(" ".toRegex(), "") ipRequest = Request.Builder().url(url).build() - data = client.newCall(ipRequest).execute().body?.string() - ?: throw IOException("EXPORT_IP_PRIMARY no data") + data = client.newCall(ipRequest).execute().body.string() + if (data.isBlank()) { + throw IOException("EXPORT_IP_PRIMARY no data") + } val dataIp = data.substringIgnoreError(data.indexOf("您的IP地址信息") + 10) val dataAddress = dataIp.substringIgnoreError(0, dataIp.indexOf("
")) val ips = dataAddress.split(" ").toTypedArray() @@ -149,7 +152,7 @@ private fun getExportIp( Timber.i("Get export ip from $EXPORT_IP_PRIMARY meet ${e.localizedMessage}") try { ipRequest = Request.Builder().url(EXPORT_IP_SECONDARY).build() - val exportIp = client.newCall(ipRequest).execute().body?.string() + val exportIp: String = client.newCall(ipRequest).execute().body.string() result.append("${context.getString(R.string.export_ip)}: $exportIp") } catch (e: Exception) { Timber.i("Get export ip from $EXPORT_IP_SECONDARY meet ${e.localizedMessage}") diff --git a/app/src/main/java/one/mixin/android/pay/Lighting.kt b/app/src/main/java/one/mixin/android/pay/Lighting.kt index ba6b98d6cd..7bb9f8f2e9 100644 --- a/app/src/main/java/one/mixin/android/pay/Lighting.kt +++ b/app/src/main/java/one/mixin/android/pay/Lighting.kt @@ -13,8 +13,9 @@ internal suspend fun parseLightning( parseLighting: suspend (String) -> PaymentResponse? ): ExternalTransfer? { val r = parseLighting(url) ?: return null - val assetId = r.asset?.assetId ?:return null - val chainId = r.asset?.chainId ?:return null + val asset = r.asset ?: return null + val assetId = asset.assetId ?: return null + val chainId = asset.chainId ?: return null val destination = r.destination ?: return null val addressResponse = validateAddress(assetId, chainId, destination) ?: return null diff --git a/app/src/main/java/one/mixin/android/ui/auth/compose/AuthBottomSheetDialogCompose.kt b/app/src/main/java/one/mixin/android/ui/auth/compose/AuthBottomSheetDialogCompose.kt index 7c788cd804..68175cf468 100644 --- a/app/src/main/java/one/mixin/android/ui/auth/compose/AuthBottomSheetDialogCompose.kt +++ b/app/src/main/java/one/mixin/android/ui/auth/compose/AuthBottomSheetDialogCompose.kt @@ -371,30 +371,32 @@ fun Modifier.verticalScrollbar( @Composable @Preview fun AuthBottomSheetDialogComposePreview() { - val context = LocalContext.current - AuthBottomSheetDialogCompose( - name = "Team Mixin", - iconUrl = "https://mixin-images.zeromesh.net/E2y0BnTopFK9qey0YI-8xV3M82kudNnTaGw0U5SU065864SsewNUo6fe9kDF1HIzVYhXqzws4lBZnLj1lPsjk-0=s256", - scopes = - listOf( - Scope.generateScopeFromString(context, "PROFILE:READ"), - Scope.generateScopeFromString(context, "PHONE:READ"), - Scope.generateScopeFromString(context, "MESSAGES:REPRESENT"), - Scope.generateScopeFromString(context, "CONTACTS:READ"), - Scope.generateScopeFromString(context, "ASSETS:READ"), - Scope.generateScopeFromString(context, "SNAPSHOTS:READ"), - Scope.generateScopeFromString(context, "APPS:READ"), - Scope.generateScopeFromString(context, "APPS:WRITE"), - Scope.generateScopeFromString(context, "CIRCLES:READ"), - Scope.generateScopeFromString(context, "CIRCLES:WRITE"), - Scope.generateScopeFromString(context, "COLLECTIBLES:READ"), - ), - {}, - AuthStep.INPUT, - "", - {}, - {}, - {}, - null, - ) + MixinAppTheme { + val context = LocalContext.current + AuthBottomSheetDialogCompose( + name = "Team Mixin", + iconUrl = "https://mixin-images.zeromesh.net/E2y0BnTopFK9qey0YI-8xV3M82kudNnTaGw0U5SU065864SsewNUo6fe9kDF1HIzVYhXqzws4lBZnLj1lPsjk-0=s256", + scopes = + listOf( + Scope.generateScopeFromString(context, "PROFILE:READ"), + Scope.generateScopeFromString(context, "PHONE:READ"), + Scope.generateScopeFromString(context, "MESSAGES:REPRESENT"), + Scope.generateScopeFromString(context, "CONTACTS:READ"), + Scope.generateScopeFromString(context, "ASSETS:READ"), + Scope.generateScopeFromString(context, "SNAPSHOTS:READ"), + Scope.generateScopeFromString(context, "APPS:READ"), + Scope.generateScopeFromString(context, "APPS:WRITE"), + Scope.generateScopeFromString(context, "CIRCLES:READ"), + Scope.generateScopeFromString(context, "CIRCLES:WRITE"), + Scope.generateScopeFromString(context, "COLLECTIBLES:READ"), + ), + {}, + AuthStep.INPUT, + "", + {}, + {}, + {}, + null, + ) + } } diff --git a/app/src/main/java/one/mixin/android/ui/home/inscription/component/ShareCard.kt b/app/src/main/java/one/mixin/android/ui/home/inscription/component/ShareCard.kt index a265a07ae3..2b01b93b26 100644 --- a/app/src/main/java/one/mixin/android/ui/home/inscription/component/ShareCard.kt +++ b/app/src/main/java/one/mixin/android/ui/home/inscription/component/ShareCard.kt @@ -36,6 +36,7 @@ import coil3.request.transformations import one.mixin.android.R import one.mixin.android.compose.CoilImage import one.mixin.android.compose.CoilImageCompat +import one.mixin.android.compose.theme.MixinAppTheme import one.mixin.android.inscription.compose.Barcode import one.mixin.android.inscription.compose.TextInscription import one.mixin.android.ui.home.web3.components.InscriptionState @@ -143,12 +144,14 @@ fun ShareCard(modifier: Modifier, qrcode: Bitmap, inscriptionHash: String, value @Preview @Composable private fun DashedDividerPreview() { - DashedDivider( - thickness = 1.dp, - modifier = Modifier - .fillMaxWidth() - .padding(16.dp) - ) + MixinAppTheme { + DashedDivider( + thickness = 1.dp, + modifier = Modifier + .fillMaxWidth() + .padding(16.dp) + ) + } } @Composable @@ -174,4 +177,4 @@ fun DashedDivider( ) ) } -} \ No newline at end of file +} diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/components/InputAction.kt b/app/src/main/java/one/mixin/android/ui/home/web3/components/InputAction.kt index cca601d60c..40bec1bc9f 100644 --- a/app/src/main/java/one/mixin/android/ui/home/web3/components/InputAction.kt +++ b/app/src/main/java/one/mixin/android/ui/home/web3/components/InputAction.kt @@ -85,5 +85,7 @@ fun InputAction( @Preview @Composable fun PreviewInputActionMax() { - InputAction("MAX") {} + MixinAppTheme { + InputAction("MAX") {} + } } diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/components/Review.kt b/app/src/main/java/one/mixin/android/ui/home/web3/components/Review.kt index 9e48f16378..5a2220e05b 100644 --- a/app/src/main/java/one/mixin/android/ui/home/web3/components/Review.kt +++ b/app/src/main/java/one/mixin/android/ui/home/web3/components/Review.kt @@ -210,6 +210,23 @@ fun ParsedTxPreview( asset: Token?, parsedTx: ParsedTx?, solanaTxSource: SolanaTxSource? = null, +) { + val viewModel = hiltViewModel() + val prices = mutableMapOf() + parsedTx?.balanceChanges?.forEach { bc -> + val priceUsd by viewModel.getTokenPriceUsdFlow(bc.assetId) + .collectAsStateWithLifecycle(initialValue = null) + prices[bc.assetId] = priceUsd + } + ParsedTxPreviewContent(asset, parsedTx, prices, solanaTxSource) +} + +@Composable +fun ParsedTxPreviewContent( + asset: Token?, + parsedTx: ParsedTx?, + prices: Map, + solanaTxSource: SolanaTxSource? = null, ) { Column( modifier = Modifier @@ -293,7 +310,7 @@ fun ParsedTxPreview( } BalanceChangeHead() parsedTx.balanceChanges.forEach { bc -> - BalanceChangeItem(balanceChange = bc) + BalanceChangeItemContent(balanceChange = bc, priceUsd = prices[bc.assetId]) Box(modifier = Modifier.height(10.dp)) } } else if (parsedTx.approves.isNullOrEmpty().not() && parsedTx.balanceChanges.isNullOrEmpty()){ @@ -307,11 +324,12 @@ fun ParsedTxPreview( val viewDetails = remember { mutableStateOf(false) } val rotation by animateFloatAsState(if (viewDetails.value) 90f else 0f, label = "rotation") if (parsedTx.balanceChanges?.size == 1) { - SingleBalanceChangeItem(bc = parsedTx.balanceChanges.first()) + val bc = parsedTx.balanceChanges.first() + SingleBalanceChangeItemContent(bc = bc, priceUsd = prices[bc.assetId]) Box(modifier = Modifier.height(10.dp)) } else { parsedTx.balanceChanges?.forEach { bc -> - BalanceChangeItem(balanceChange = bc) + BalanceChangeItemContent(balanceChange = bc, priceUsd = prices[bc.assetId]) Spacer(modifier = Modifier.height(10.dp)) } } @@ -502,6 +520,14 @@ private fun SingleBalanceChangeItem( val viewModel = hiltViewModel() val priceUsd: String? by viewModel.getTokenPriceUsdFlow(bc.assetId) .collectAsStateWithLifecycle(initialValue = null) + SingleBalanceChangeItemContent(bc, priceUsd) +} + +@Composable +private fun SingleBalanceChangeItemContent( + bc: BalanceChange, + priceUsd: String? +) { val fiatPrice = bc.formatPrice(priceUsd) Row( @@ -549,6 +575,14 @@ private fun BalanceChangeItem( val viewModel = hiltViewModel() val priceUsd: String? by viewModel.getTokenPriceUsdFlow(balanceChange.assetId) .collectAsStateWithLifecycle(initialValue = null) + BalanceChangeItemContent(balanceChange, priceUsd) +} + +@Composable +private fun BalanceChangeItemContent( + balanceChange: BalanceChange, + priceUsd: String? +) { val fiatPrice = balanceChange.formatPrice(priceUsd) Row( modifier = Modifier.fillMaxWidth(), @@ -834,7 +868,7 @@ fun InstructionPreview() { @Preview @Composable fun SolanaParsedTxNullPreview() { - ParsedTxPreview(parsedTx = null, asset = null, solanaTxSource = SolanaTxSource.Web) + ParsedTxPreviewContent(parsedTx = null, asset = null, prices = emptyMap(), solanaTxSource = SolanaTxSource.Web) } @Preview @@ -842,7 +876,7 @@ fun SolanaParsedTxNullPreview() { fun SolanaParsedTxInstructionNullPreview() { val data = """{"instructions":[]}""" val parsedTx = GsonHelper.customGson.fromJson(data, ParsedTx::class.java) - ParsedTxPreview(parsedTx = parsedTx, asset = null, solanaTxSource = SolanaTxSource.Web) + ParsedTxPreviewContent(parsedTx = parsedTx, asset = null, prices = emptyMap(), solanaTxSource = SolanaTxSource.Web) } @Preview @@ -850,7 +884,7 @@ fun SolanaParsedTxInstructionNullPreview() { fun SolanaParsedTxBalanceChangeNullWebPreview() { val data = """{"instructions":[{"program_id":"ComputeBudget111111111111111111111111111111","program_name":"ComputeBudget","instruction_name":"SetComputeUnitLimit","items":[{"key":"Compute Unit Limit","value":"600000 compute units"}]},{"program_id":"ComputeBudget111111111111111111111111111111","program_name":"ComputeBudget","instruction_name":"SetComputeUnitPrice","items":[{"key":"Compute Unit Price","value":"0.1 lamports per compute unit"}]},{"program_id":"ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL","program_name":"AssociatedTokenAccount","instruction_name":"Create"},{"program_id":"11111111111111111111111111111111","program_name":"System","instruction_name":"Transfer","items":[{"key":"Transfer Amount (SOL)","value":"0.01"}]},{"program_id":"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA","program_name":"Token","instruction_name":"SyncNative"},{"program_id":"JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4","program_name":"Jupiter","instruction_name":"Route","items":[{"key":"Route Plan","value":""},{"key":"In Amount","value":"824635312696"},{"key":"Quoted Out Amount","value":"824635312704"},{"key":"Slippage Bps","value":"824635312712"},{"key":"Platform Fee Bps","value":"50"}],"token_changes":[{"address":"So11111111111111111111111111111111111111112","amount":10000000,"is_pay":true},{"address":"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v","amount":1323264,"is_pay":false}]},{"program_id":"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA","program_name":"Token","instruction_name":"CloseAccount"}]}""" val parsedTx = GsonHelper.customGson.fromJson(data, ParsedTx::class.java) - ParsedTxPreview(parsedTx = parsedTx, asset = null, solanaTxSource = SolanaTxSource.Web) + ParsedTxPreviewContent(parsedTx = parsedTx, asset = null, prices = emptyMap(), solanaTxSource = SolanaTxSource.Web) } @Preview @@ -858,7 +892,7 @@ fun SolanaParsedTxBalanceChangeNullWebPreview() { fun SolanaParsedTxBalanceChangeNullInnerPreview() { val data = """{"instructions":[{"program_id":"ComputeBudget111111111111111111111111111111","program_name":"ComputeBudget","instruction_name":"SetComputeUnitLimit","items":[{"key":"Compute Unit Limit","value":"600000 compute units"}]},{"program_id":"ComputeBudget111111111111111111111111111111","program_name":"ComputeBudget","instruction_name":"SetComputeUnitPrice","items":[{"key":"Compute Unit Price","value":"0.1 lamports per compute unit"}]},{"program_id":"ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL","program_name":"AssociatedTokenAccount","instruction_name":"Create"},{"program_id":"11111111111111111111111111111111","program_name":"System","instruction_name":"Transfer","items":[{"key":"Transfer Amount (SOL)","value":"0.01"}]},{"program_id":"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA","program_name":"Token","instruction_name":"SyncNative"},{"program_id":"JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4","program_name":"Jupiter","instruction_name":"Route","items":[{"key":"Route Plan","value":""},{"key":"In Amount","value":"824635312696"},{"key":"Quoted Out Amount","value":"824635312704"},{"key":"Slippage Bps","value":"824635312712"},{"key":"Platform Fee Bps","value":"50"}],"token_changes":[{"address":"So11111111111111111111111111111111111111112","amount":10000000,"is_pay":true},{"address":"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v","amount":1323264,"is_pay":false}]},{"program_id":"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA","program_name":"Token","instruction_name":"CloseAccount"}]}""" val parsedTx = GsonHelper.customGson.fromJson(data, ParsedTx::class.java) - ParsedTxPreview(parsedTx = parsedTx, asset = null, solanaTxSource = SolanaTxSource.InnerSwap) + ParsedTxPreviewContent(parsedTx = parsedTx, asset = null, prices = emptyMap(), solanaTxSource = SolanaTxSource.InnerSwap) } @Preview @@ -866,5 +900,5 @@ fun SolanaParsedTxBalanceChangeNullInnerPreview() { fun SolanaParsedTxTokenNullPreview() { val data = """{"balance_changes":[{"address":"So11111111111111111111111111111111111111112","amount":-10000000},{"address":"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v","amount":1323264}],"instructions":[{"program_id":"ComputeBudget111111111111111111111111111111","program_name":"ComputeBudget","instruction_name":"SetComputeUnitLimit","items":[{"key":"Compute Unit Limit","value":"600000 compute units"}]},{"program_id":"ComputeBudget111111111111111111111111111111","program_name":"ComputeBudget","instruction_name":"SetComputeUnitPrice","items":[{"key":"Compute Unit Price","value":"0.1 lamports per compute unit"}]},{"program_id":"ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL","program_name":"AssociatedTokenAccount","instruction_name":"Create"},{"program_id":"11111111111111111111111111111111","program_name":"System","instruction_name":"Transfer","items":[{"key":"Transfer Amount (SOL)","value":"0.01"}]},{"program_id":"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA","program_name":"Token","instruction_name":"SyncNative"},{"program_id":"JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4","program_name":"Jupiter","instruction_name":"Route","items":[{"key":"Route Plan","value":""},{"key":"In Amount","value":"824635312696"},{"key":"Quoted Out Amount","value":"824635312704"},{"key":"Slippage Bps","value":"824635312712"},{"key":"Platform Fee Bps","value":"50"}],"token_changes":[{"address":"So11111111111111111111111111111111111111112","amount":10000000,"is_pay":true},{"address":"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v","amount":1323264,"is_pay":false}]},{"program_id":"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA","program_name":"Token","instruction_name":"CloseAccount"}]}""" val parsedTx = GsonHelper.customGson.fromJson(data, ParsedTx::class.java) - ParsedTxPreview(parsedTx = parsedTx, asset = null, solanaTxSource = SolanaTxSource.InnerSwap) + ParsedTxPreviewContent(parsedTx = parsedTx, asset = null, prices = emptyMap(), solanaTxSource = SolanaTxSource.InnerSwap) } diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/components/SlippageInfo.kt b/app/src/main/java/one/mixin/android/ui/home/web3/components/SlippageInfo.kt index 5f91c2bfb8..4f6e6fa25c 100644 --- a/app/src/main/java/one/mixin/android/ui/home/web3/components/SlippageInfo.kt +++ b/app/src/main/java/one/mixin/android/ui/home/web3/components/SlippageInfo.kt @@ -112,12 +112,15 @@ private fun SlippageInfo( @Preview @Composable fun PreviewSlippageInfo() { - SlippageInfo(slippageBps = 50, true) {} + MixinAppTheme { + SlippageInfo(slippageBps = 50, true) {} + } } @Preview @Composable fun PreviewSlippageInfoWarning() { - SlippageInfo(slippageBps = 600, true) {} + MixinAppTheme { + SlippageInfo(slippageBps = 600, true) {} + } } - diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/stake/StakePage.kt b/app/src/main/java/one/mixin/android/ui/home/web3/stake/StakePage.kt index 906579f102..db6fa57b72 100644 --- a/app/src/main/java/one/mixin/android/ui/home/web3/stake/StakePage.kt +++ b/app/src/main/java/one/mixin/android/ui/home/web3/stake/StakePage.kt @@ -345,11 +345,15 @@ fun Item( @Preview @Composable private fun InputPreview() { - Input(text = "123") {} + MixinAppTheme { + Input(text = "123") {} + } } @Preview @Composable private fun ValidatorInfoPreview() { - ValidatorInfo(Validator("J2nUHEAgZFRyuJbFjdqPrAa9gyWDuc7hErtDQHPhsYRp", "Mixin Validator", "", "", "", "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/So11111111111111111111111111111111111111112/logo.png", "", 123123131231231, 9, 123124, 123123)) -} \ No newline at end of file + MixinAppTheme { + ValidatorInfo(Validator("J2nUHEAgZFRyuJbFjdqPrAa9gyWDuc7hErtDQHPhsYRp", "Mixin Validator", "", "", "", "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/So11111111111111111111111111111111111111112/logo.png", "", 123123131231231, 9, 123124, 123123)) + } +} diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/stake/ValidatorsPage.kt b/app/src/main/java/one/mixin/android/ui/home/web3/stake/ValidatorsPage.kt index 8c4d81e05a..d67c09b6d0 100644 --- a/app/src/main/java/one/mixin/android/ui/home/web3/stake/ValidatorsPage.kt +++ b/app/src/main/java/one/mixin/android/ui/home/web3/stake/ValidatorsPage.kt @@ -221,11 +221,15 @@ private fun SearchInput( @Composable fun PreviewValidatorItem() { val validator = Validator("J2nUHEAgZFRyuJbFjdqPrAa9gyWDuc7hErtDQHPhsYRp", "Mixin Validator", "", "", "", "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/So11111111111111111111111111111111111111112/logo.png", "", 123123131231231, 9, 123124, 123123) - ValidatorItem(validator) { } + MixinAppTheme { + ValidatorItem(validator) { } + } } @Preview @Composable fun PreviewSearchInput() { - SearchInput("") -} \ No newline at end of file + MixinAppTheme { + SearchInput("") + } +} diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/trade/SwapSlippagePage.kt b/app/src/main/java/one/mixin/android/ui/home/web3/trade/SwapSlippagePage.kt index aa7dc0456c..e9a782e893 100644 --- a/app/src/main/java/one/mixin/android/ui/home/web3/trade/SwapSlippagePage.kt +++ b/app/src/main/java/one/mixin/android/ui/home/web3/trade/SwapSlippagePage.kt @@ -289,23 +289,27 @@ private fun String.isSlippageValid(): Boolean { @Preview @Composable fun PreviewAuto() { - Auto( - auto = - remember { - mutableStateOf(true) - }, - originAuto = true, - originBps = 80, - ) + MixinAppTheme { + Auto( + auto = + remember { + mutableStateOf(true) + }, + originAuto = true, + originBps = 80, + ) + } } @Preview @Composable fun PreviewCustom() { - Custom( - bps = - remember { - mutableStateOf("50") - }, - ) + MixinAppTheme { + Custom( + bps = + remember { + mutableStateOf("50") + }, + ) + } } diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/trade/SwapTokenPage.kt b/app/src/main/java/one/mixin/android/ui/home/web3/trade/SwapTokenPage.kt index cd17c5ffec..b888c68c0d 100644 --- a/app/src/main/java/one/mixin/android/ui/home/web3/trade/SwapTokenPage.kt +++ b/app/src/main/java/one/mixin/android/ui/home/web3/trade/SwapTokenPage.kt @@ -133,6 +133,8 @@ fun SwapTokenPage( @Preview(widthDp = 300) @Composable fun SwapTokenPagePreView() { - SwapTokenPage(token = SwapToken("","1111111111111111111111111", "", 9, "Solana", "SOL", "", SwapChain("", "Solana", "SOL", "", ""))) { + MixinAppTheme { + SwapTokenPage(token = SwapToken("", "1111111111111111111111111", "", 9, "Solana", "SOL", "", SwapChain("", "Solana", "SOL", "", ""))) { + } } } diff --git a/app/src/main/java/one/mixin/android/ui/landing/components/CreateAccountPage.kt b/app/src/main/java/one/mixin/android/ui/landing/components/CreateAccountPage.kt index 338aaef73b..1158d763eb 100644 --- a/app/src/main/java/one/mixin/android/ui/landing/components/CreateAccountPage.kt +++ b/app/src/main/java/one/mixin/android/ui/landing/components/CreateAccountPage.kt @@ -126,5 +126,7 @@ fun CreateItem(@DrawableRes iconId: Int, @StringRes titleId: Int, @StringRes sub @Preview @Composable fun CreateAccountPagePreview() { - CreateAccountPage({},{}, {}, {}, {}, {}) -} \ No newline at end of file + MixinAppTheme { + CreateAccountPage({}, {}, {}, {}, {}, {}) + } +} diff --git a/app/src/main/java/one/mixin/android/ui/landing/components/MnemonicPhraseInput.kt b/app/src/main/java/one/mixin/android/ui/landing/components/MnemonicPhraseInput.kt index 1f6cca90d1..9f95941eb3 100644 --- a/app/src/main/java/one/mixin/android/ui/landing/components/MnemonicPhraseInput.kt +++ b/app/src/main/java/one/mixin/android/ui/landing/components/MnemonicPhraseInput.kt @@ -951,11 +951,13 @@ fun InputBar(string: String, callback: (String) -> Unit) { @Preview(backgroundColor = 0xFFFFFFFF, showSystemUi = true) @Composable fun MnemonicPhraseInputPreview() { - MnemonicPhraseInput( - state = MnemonicState.Input, - onScan = {}, - onComplete = { mnemonicList -> /* Handle mnemonic change */ }, - ) + MixinAppTheme { + MnemonicPhraseInput( + state = MnemonicState.Input, + onScan = {}, + onComplete = { }, + ) + } } @Composable diff --git a/app/src/main/java/one/mixin/android/ui/media/pager/MediaPagerActivity.kt b/app/src/main/java/one/mixin/android/ui/media/pager/MediaPagerActivity.kt index 8d406cced3..fdd51e1c66 100644 --- a/app/src/main/java/one/mixin/android/ui/media/pager/MediaPagerActivity.kt +++ b/app/src/main/java/one/mixin/android/ui/media/pager/MediaPagerActivity.kt @@ -1,3 +1,5 @@ +@file:Suppress("DEPRECATION") + package one.mixin.android.ui.media.pager import android.Manifest diff --git a/app/src/main/java/one/mixin/android/ui/media/pager/transcript/TranscriptMediaPagerActivity.kt b/app/src/main/java/one/mixin/android/ui/media/pager/transcript/TranscriptMediaPagerActivity.kt index e06a1bd597..87b1d27882 100644 --- a/app/src/main/java/one/mixin/android/ui/media/pager/transcript/TranscriptMediaPagerActivity.kt +++ b/app/src/main/java/one/mixin/android/ui/media/pager/transcript/TranscriptMediaPagerActivity.kt @@ -593,7 +593,7 @@ class TranscriptMediaPagerActivity : BaseActivity(), DismissFrameLayout.OnDismis private fun dismiss() { binding.viewPager.visibility = View.INVISIBLE - overridePendingTransition(0, 0) + setExitTransition(enterAnim = 0, exitAnim = 0) super.finish() } @@ -750,7 +750,19 @@ class TranscriptMediaPagerActivity : BaseActivity(), DismissFrameLayout.OnDismis override fun finish() { super.finish() - overridePendingTransition(0, R.anim.scale_out) + setExitTransition(enterAnim = 0, exitAnim = R.anim.scale_out) + } + + private fun setExitTransition( + enterAnim: Int, + exitAnim: Int, + ) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + overrideActivityTransition(Activity.OVERRIDE_TRANSITION_CLOSE, enterAnim, exitAnim) + return + } + @Suppress("DEPRECATION") + overridePendingTransition(enterAnim, exitAnim) } private val mediaPagerAdapterListener = diff --git a/app/src/main/java/one/mixin/android/ui/setting/ui/page/AuthenticationsPage.kt b/app/src/main/java/one/mixin/android/ui/setting/ui/page/AuthenticationsPage.kt index c58b7821fa..6acfe40212 100644 --- a/app/src/main/java/one/mixin/android/ui/setting/ui/page/AuthenticationsPage.kt +++ b/app/src/main/java/one/mixin/android/ui/setting/ui/page/AuthenticationsPage.kt @@ -47,24 +47,29 @@ import one.mixin.android.vo.App @Composable fun AuthenticationsPage() { + val viewModel = hiltViewModel() + val response by viewModel.authentications.collectAsState() + AuthenticationsPageContent(response) +} + +@Composable +fun AuthenticationsPageContent( + response: Result>?, +) { SettingPageScaffold( title = stringResource(id = R.string.Authorizations), verticalScrollable = false, ) { - val viewModel = hiltViewModel() - val text = rememberSaveable { mutableStateOf("") } SearchTextField(text, stringResource(id = R.string.setting_auth_search_hint)) - val response by viewModel.authentications.collectAsState() - if (response == null) { Loading() - } else if (response?.isSuccess == true) { - val data = response?.getOrNull() ?: emptyList() + } else if (response.isSuccess) { + val data = response.getOrNull() ?: emptyList() if (data.isEmpty()) { EmptyLayout() } else { @@ -198,6 +203,14 @@ private fun AuthenticationItem( } } +@Composable +@Preview +fun AuthenticationsPagePreview() { + MixinAppTheme { + AuthenticationsPageContent(response = Result.success(emptyList())) + } +} + @Composable @Preview fun EmptyLayoutPreview() { diff --git a/app/src/main/java/one/mixin/android/ui/setting/ui/page/BlockedPage.kt b/app/src/main/java/one/mixin/android/ui/setting/ui/page/BlockedPage.kt index d9200ba330..7d8c228cee 100644 --- a/app/src/main/java/one/mixin/android/ui/setting/ui/page/BlockedPage.kt +++ b/app/src/main/java/one/mixin/android/ui/setting/ui/page/BlockedPage.kt @@ -40,6 +40,13 @@ import one.mixin.android.vo.User @Composable fun BlockedPage() { + val viewModel = hiltViewModel() + val users by viewModel.blockingUsers(rememberComposeScope()).observeAsState() + BlockedPageContent(users) +} + +@Composable +fun BlockedPageContent(users: List?) { Scaffold( backgroundColor = MixinAppTheme.colors.backgroundWindow, topBar = { @@ -58,12 +65,10 @@ fun BlockedPage() { .padding(it) .fillMaxSize(), ) { - val viewModel = hiltViewModel() - val users by viewModel.blockingUsers(rememberComposeScope()).observeAsState() if (users.isNullOrEmpty()) { EmptyBlockedView() } else { - BlockedList(users = users!!) + BlockedList(users = users) } } } @@ -140,6 +145,14 @@ private fun BlockedUserItem(user: User) { } } +@Composable +@Preview +fun BlockedPagePreview() { + MixinAppTheme { + BlockedPageContent(users = emptyList()) + } +} + @Composable @Preview fun EmptyBlockedPagePreview() { diff --git a/app/src/main/java/one/mixin/android/ui/setting/ui/page/ConversationSettingPage.kt b/app/src/main/java/one/mixin/android/ui/setting/ui/page/ConversationSettingPage.kt index c9931e2c55..fe4fdb2864 100644 --- a/app/src/main/java/one/mixin/android/ui/setting/ui/page/ConversationSettingPage.kt +++ b/app/src/main/java/one/mixin/android/ui/setting/ui/page/ConversationSettingPage.kt @@ -36,24 +36,37 @@ import one.mixin.android.vo.MessageSource @Composable fun ConversationSettingPage() { val viewModel = hiltViewModel() + val context = LocalContext.current + ConversationSettingPageContent( + initMessageSourcePreferences = { viewModel.initPreferences(context) }, + doUpdateMessageSource = { + viewModel.savePreferences(AccountUpdateRequest(receiveMessageSource = it.name)) + }, + initGroupPreferences = { viewModel.initGroupPreferences(context) }, + doUpdateGroupSource = { + viewModel.savePreferences(AccountUpdateRequest(acceptConversationSource = it.name)) + } + ) +} +@Composable +fun ConversationSettingPageContent( + initMessageSourcePreferences: () -> SettingConversationViewModel.BaseMessageSourcePreferences, + doUpdateMessageSource: suspend (source: MessageSource) -> MixinResponse, + initGroupPreferences: () -> SettingConversationViewModel.BaseMessageSourcePreferences, + doUpdateGroupSource: suspend (source: MessageSource) -> MixinResponse, +) { SettingPageScaffold(title = stringResource(id = R.string.Conversation)) { - val context = LocalContext.current - MessageSettingTips(stringResource(id = R.string.setting_conversation_tip)) SettingGroup( - initMessageSourcePreferences = { viewModel.initPreferences(context) }, - doUpdate = { - viewModel.savePreferences(AccountUpdateRequest(receiveMessageSource = it.name)) - }, + initMessageSourcePreferences = initMessageSourcePreferences, + doUpdate = doUpdateMessageSource, ) MessageSettingTips(stringResource(id = R.string.setting_conversation_group_tip)) SettingGroup( - initMessageSourcePreferences = { viewModel.initGroupPreferences(context) }, - doUpdate = { - viewModel.savePreferences(AccountUpdateRequest(acceptConversationSource = it.name)) - }, + initMessageSourcePreferences = initGroupPreferences, + doUpdate = doUpdateGroupSource, ) } } diff --git a/app/src/main/java/one/mixin/android/ui/transfer/compose/SelectDatePage.kt b/app/src/main/java/one/mixin/android/ui/transfer/compose/SelectDatePage.kt index cf2ff89c54..23a9bfcf8a 100644 --- a/app/src/main/java/one/mixin/android/ui/transfer/compose/SelectDatePage.kt +++ b/app/src/main/java/one/mixin/android/ui/transfer/compose/SelectDatePage.kt @@ -315,5 +315,7 @@ fun SegmentedControl( @Composable @Preview fun SelectDatePagePreview() { - SelectDatePage(onExit = {}, onResult = {}) + MixinAppTheme { + SelectDatePage(onExit = {}, onResult = {}) + } }