diff --git a/app/build.gradle.kts b/app/build.gradle.kts index d482737f..7604bf6a 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -23,7 +23,7 @@ android { minSdk = 24 targetSdk = 35 versionCode = 67 - versionName = "2.3-alpha06" + versionName = "2.3-alpha07" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { @@ -147,27 +147,27 @@ tasks.withType(KotlinCompile::class.java).configureEach { dependencies { implementation("androidx.core:core-ktx:1.13.1") - implementation("androidx.compose.ui:ui:1.7.0") + implementation("androidx.compose.ui:ui:1.7.3") implementation("androidx.compose.material3:material3:1.3.0") implementation("androidx.compose.material3:material3-window-size-class:1.3.0") - implementation("androidx.compose.material:material:1.7.0") - implementation("androidx.compose.material:material-icons-extended:1.7.0") - implementation("androidx.compose.ui:ui-tooling-preview:1.7.0") + implementation("androidx.compose.material:material:1.7.3") + implementation("androidx.compose.material:material-icons-extended:1.7.3") + implementation("androidx.compose.ui:ui-tooling-preview:1.7.3") implementation("com.google.android.material:material:1.12.0") - implementation("androidx.lifecycle:lifecycle-runtime-compose:2.8.5") - implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.5") + implementation("androidx.lifecycle:lifecycle-runtime-compose:2.8.6") + implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.6") implementation("androidx.activity:activity-compose:1.9.2") implementation("androidx.palette:palette-ktx:1.0.0") implementation("com.google.dagger:hilt-android:2.52") ksp("com.google.dagger:hilt-android-compiler:2.52") implementation("androidx.hilt:hilt-navigation-compose:1.2.0") - implementation("androidx.navigation:navigation-compose:2.8.0") + implementation("androidx.navigation:navigation-compose:2.8.2") implementation("androidx.security:security-crypto:1.1.0-alpha06") implementation("com.google.accompanist:accompanist-drawablepainter:0.36.0") implementation("io.coil-kt:coil-compose:2.7.0") implementation("io.coil-kt:coil-gif:2.7.0") implementation("io.coil-kt:coil-svg:2.7.0") - implementation("androidx.profileinstaller:profileinstaller:1.3.1") + implementation("androidx.profileinstaller:profileinstaller:1.4.1") implementation("androidx.core:core-splashscreen:1.0.1") implementation("androidx.room:room-runtime:2.6.1") implementation("androidx.room:room-ktx:2.6.1") @@ -195,6 +195,6 @@ dependencies { implementation("com.github.penfeizhou.android.animation:apng:3.0.1") - debugImplementation("androidx.compose.ui:ui-tooling:1.7.0") - debugImplementation("androidx.compose.ui:ui-test-manifest:1.7.0") + debugImplementation("androidx.compose.ui:ui-tooling:1.7.3") + debugImplementation("androidx.compose.ui:ui-test-manifest:1.7.3") } \ No newline at end of file diff --git a/app/src/main/java/com/skyd/rays/base/BaseComposeActivity.kt b/app/src/main/java/com/skyd/rays/base/BaseComposeActivity.kt new file mode 100644 index 00000000..e468015e --- /dev/null +++ b/app/src/main/java/com/skyd/rays/base/BaseComposeActivity.kt @@ -0,0 +1,30 @@ +package com.skyd.rays.base + +import android.os.Bundle +import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge +import androidx.appcompat.app.AppCompatActivity +import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import com.skyd.rays.model.preference.SettingsProvider +import com.skyd.rays.ui.local.LocalDarkMode +import com.skyd.rays.ui.local.LocalWindowSizeClass +import com.skyd.rays.ui.theme.RaysTheme +import dagger.hilt.android.AndroidEntryPoint + +@AndroidEntryPoint +open class BaseComposeActivity : AppCompatActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + enableEdgeToEdge() + super.onCreate(savedInstanceState) + } + + fun setContentBase(content: @Composable () -> Unit) = setContent { + CompositionLocalProvider( + LocalWindowSizeClass provides calculateWindowSizeClass(this@BaseComposeActivity) + ) { + SettingsProvider { RaysTheme(darkTheme = LocalDarkMode.current, content) } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/skyd/rays/ext/NavExt.kt b/app/src/main/java/com/skyd/rays/ext/NavExt.kt index bb2beeb1..a0bb6faa 100644 --- a/app/src/main/java/com/skyd/rays/ext/NavExt.kt +++ b/app/src/main/java/com/skyd/rays/ext/NavExt.kt @@ -1,12 +1,9 @@ package com.skyd.rays.ext import android.os.Bundle -import androidx.core.net.toUri import androidx.lifecycle.Lifecycle import androidx.navigation.NavBackStackEntry import androidx.navigation.NavController -import androidx.navigation.NavDeepLinkRequest -import androidx.navigation.NavDestination import androidx.navigation.NavOptions import androidx.navigation.Navigator @@ -16,23 +13,9 @@ fun NavController.navigate( navOptions: NavOptions? = null, navigatorExtras: Navigator.Extras? = null ) { - val routeLink = NavDeepLinkRequest - .Builder - .fromUri(NavDestination.createRoute(route).toUri()) - .build() - - val deepLinkMatch = graph.matchDeepLink(routeLink) - if (deepLinkMatch != null) { - val destination = deepLinkMatch.destination - val id = destination.id - navigate( - id, - args.apply { putAll(deepLinkMatch.matchingArgs ?: Bundle()) }, - navOptions, - navigatorExtras - ) - } else { - navigate(route, navOptions, navigatorExtras) + val nodeId = graph.findNode(route = route)?.id + if (nodeId != null) { + navigate(nodeId, args, navOptions, navigatorExtras) } } diff --git a/app/src/main/java/com/skyd/rays/model/bean/StickerBean.kt b/app/src/main/java/com/skyd/rays/model/bean/StickerBean.kt index bbc0e28f..b0f870f7 100644 --- a/app/src/main/java/com/skyd/rays/model/bean/StickerBean.kt +++ b/app/src/main/java/com/skyd/rays/model/bean/StickerBean.kt @@ -34,8 +34,9 @@ data class StickerBean( constructor( title: String, createTime: Long = System.currentTimeMillis(), + uuid: String = UUID.randomUUID().toString(), ) : this( - uuid = UUID.randomUUID().toString(), + uuid = uuid, title = title, stickerMd5 = "", clickCount = 0L, diff --git a/app/src/main/java/com/skyd/rays/model/respository/AddRepository.kt b/app/src/main/java/com/skyd/rays/model/respository/AddRepository.kt index 0c543eef..4e4c9cfd 100644 --- a/app/src/main/java/com/skyd/rays/model/respository/AddRepository.kt +++ b/app/src/main/java/com/skyd/rays/model/respository/AddRepository.kt @@ -67,6 +67,9 @@ class AddRepository @Inject constructor( emit(stickerDao.getStickerWithTags(uuidGotByMd5)!!) } else { stickerWithTags.sticker.stickerMd5 = stickerMd5 + if (stickerWithTags.sticker.createTime == 0L) { + stickerWithTags.sticker.createTime = System.currentTimeMillis() + } val uuid = stickerDao.addStickerWithTags(stickerWithTags) if (!tempFile.renameTo(File(appContext.STICKER_DIR, uuid))) { tempFile.deleteRecursively() diff --git a/app/src/main/java/com/skyd/rays/ui/activity/CrashActivity.kt b/app/src/main/java/com/skyd/rays/ui/activity/CrashActivity.kt index 203bbd85..73608ed3 100644 --- a/app/src/main/java/com/skyd/rays/ui/activity/CrashActivity.kt +++ b/app/src/main/java/com/skyd/rays/ui/activity/CrashActivity.kt @@ -4,9 +4,6 @@ import android.content.Context import android.content.Intent import android.os.Build import android.os.Bundle -import androidx.activity.ComponentActivity -import androidx.activity.compose.setContent -import androidx.activity.enableEdgeToEdge import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -29,9 +26,7 @@ import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text import androidx.compose.material3.TextButton -import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier @@ -42,12 +37,9 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.unit.dp import com.skyd.rays.R +import com.skyd.rays.base.BaseComposeActivity import com.skyd.rays.config.GITHUB_NEW_ISSUE_URL import com.skyd.rays.ext.showSnackbar -import com.skyd.rays.model.preference.SettingsProvider -import com.skyd.rays.ui.local.LocalDarkMode -import com.skyd.rays.ui.local.LocalWindowSizeClass -import com.skyd.rays.ui.theme.RaysTheme import com.skyd.rays.util.CommonUtil.getAppVersionCode import com.skyd.rays.util.CommonUtil.getAppVersionName import com.skyd.rays.util.CommonUtil.openBrowser @@ -55,7 +47,7 @@ import com.skyd.rays.util.CommonUtil.openBrowser /** * CrashActivity */ -class CrashActivity : ComponentActivity() { +class CrashActivity : BaseComposeActivity() { companion object { const val CRASH_INFO = "crashInfo" @@ -68,7 +60,6 @@ class CrashActivity : ComponentActivity() { } override fun onCreate(savedInstanceState: Bundle?) { - enableEdgeToEdge() super.onCreate(savedInstanceState) val crashInfo = intent.getStringExtra(CRASH_INFO) @@ -83,19 +74,11 @@ class CrashActivity : ComponentActivity() { append(crashInfo) } - setContent { - CompositionLocalProvider( - LocalWindowSizeClass provides calculateWindowSizeClass(this) - ) { - SettingsProvider { - RaysTheme(darkTheme = LocalDarkMode.current) { - CrashScreen( - message = message, - onReport = { openBrowser(GITHUB_NEW_ISSUE_URL) } - ) - } - } - } + setContentBase { + CrashScreen( + message = message, + onReport = { openBrowser(GITHUB_NEW_ISSUE_URL) } + ) } } } diff --git a/app/src/main/java/com/skyd/rays/ui/activity/ImagePickerActivity.kt b/app/src/main/java/com/skyd/rays/ui/activity/ImagePickerActivity.kt new file mode 100644 index 00000000..ac4b3586 --- /dev/null +++ b/app/src/main/java/com/skyd/rays/ui/activity/ImagePickerActivity.kt @@ -0,0 +1,115 @@ +//package com.skyd.rays.ui.activity +// +//import android.os.Bundle +//import androidx.compose.animation.AnimatedVisibility +//import androidx.compose.foundation.layout.WindowInsets +//import androidx.compose.foundation.layout.WindowInsetsSides +//import androidx.compose.foundation.layout.imePadding +//import androidx.compose.foundation.layout.only +//import androidx.compose.foundation.layout.systemBars +//import androidx.compose.foundation.layout.windowInsetsPadding +//import androidx.compose.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridState +//import androidx.compose.material.icons.Icons +//import androidx.compose.material.icons.outlined.ArrowUpward +//import androidx.compose.material3.Icon +//import androidx.compose.material3.Scaffold +//import androidx.compose.material3.SnackbarHost +//import androidx.compose.material3.SnackbarHostState +//import androidx.compose.material3.Text +//import androidx.compose.runtime.Composable +//import androidx.compose.runtime.LaunchedEffect +//import androidx.compose.runtime.derivedStateOf +//import androidx.compose.runtime.getValue +//import androidx.compose.runtime.mutableStateOf +//import androidx.compose.runtime.remember +//import androidx.compose.runtime.rememberCoroutineScope +//import androidx.compose.runtime.setValue +//import androidx.compose.ui.Modifier +//import androidx.compose.ui.platform.LocalContext +//import androidx.compose.ui.platform.LocalSoftwareKeyboardController +//import androidx.compose.ui.res.stringResource +//import androidx.compose.ui.text.TextRange +//import androidx.compose.ui.text.input.TextFieldValue +//import androidx.compose.ui.unit.dp +//import com.skyd.rays.R +//import com.skyd.rays.base.BaseComposeActivity +//import com.skyd.rays.model.preference.search.QueryPreference +//import com.skyd.rays.ui.component.BackIcon +//import com.skyd.rays.ui.component.RaysFloatingActionButton +//import com.skyd.rays.ui.local.LocalWindowSizeClass +//import com.skyd.rays.ui.screen.search.SearchBarInputField +//import com.skyd.rays.ui.screen.search.TrailingIcon +//import kotlinx.coroutines.delay +//import kotlinx.coroutines.launch +// +//class ImagePickerActivity : BaseComposeActivity() { +// override fun onCreate(savedInstanceState: Bundle?) { +// super.onCreate(savedInstanceState) +// +// setContentBase { +// ImagePicker() +// } +// } +//} +// +//@Composable +//private fun ImagePicker() { +// val context = LocalContext.current +// val snackbarHostState = remember { SnackbarHostState() } +// val scope = rememberCoroutineScope() +// val searchResultListState = rememberLazyStaggeredGridState() +// val windowSizeClass = LocalWindowSizeClass.current +// val keyboardController = LocalSoftwareKeyboardController.current +// var fabHeight by remember { mutableStateOf(0.dp) } +// Scaffold( +// modifier = Modifier.imePadding(), +// snackbarHost = { SnackbarHost(hostState = snackbarHostState) }, +// floatingActionButton = { +// AnimatedVisibility( +// visible = remember { +// derivedStateOf { searchResultListState.firstVisibleItemIndex > 2 } +// }.value +// ) { +// RaysFloatingActionButton( +// onClick = { scope.launch { searchResultListState.animateScrollToItem(0) } }, +// onSizeWithSinglePaddingChanged = { _, height -> fabHeight = height }, +// contentDescription = stringResource(R.string.home_screen_search_result_list_to_top), +// ) { +// Icon( +// imageVector = Icons.Outlined.ArrowUpward, +// contentDescription = null +// ) +// } +// } +// }, +// topBar = { +// LaunchedEffect(searchFieldValueState.text) { +// delay(60) +// QueryPreference.put(context, scope, searchFieldValueState.text) +// } +// SearchBarInputField( +// modifier = Modifier.windowInsetsPadding( +// WindowInsets.systemBars +// .only(WindowInsetsSides.Horizontal + WindowInsetsSides.Top) +// ), +// onQueryChange = { searchFieldValueState = it }, +// query = searchFieldValueState, +// onSearch = { state -> +// keyboardController?.hide() +// searchFieldValueState = state +// }, +// placeholder = { Text(text = stringResource(R.string.home_screen_search_hint)) }, +// leadingIcon = { BackIcon() }, +// trailingIcon = { +// TrailingIcon(showClearButton = searchFieldValueState.text.isNotEmpty()) { +// searchFieldValueState = TextFieldValue( +// text = QueryPreference.default, +// selection = TextRange(QueryPreference.default.length) +// ) +// } +// } +// ) +// } +// ) { innerPaddings -> +// } +//} \ No newline at end of file diff --git a/app/src/main/java/com/skyd/rays/ui/activity/MainActivity.kt b/app/src/main/java/com/skyd/rays/ui/activity/MainActivity.kt index 260ecd85..49f16496 100644 --- a/app/src/main/java/com/skyd/rays/ui/activity/MainActivity.kt +++ b/app/src/main/java/com/skyd/rays/ui/activity/MainActivity.kt @@ -4,9 +4,7 @@ import android.content.Intent import android.net.Uri import android.os.Bundle import android.view.WindowManager -import androidx.activity.compose.setContent import androidx.activity.viewModels -import androidx.appcompat.app.AppCompatActivity import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut @@ -14,7 +12,6 @@ import androidx.compose.animation.scaleIn import androidx.compose.animation.scaleOut import androidx.compose.foundation.background import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.DisposableEffect @@ -36,16 +33,14 @@ import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import androidx.navigation.navArgument import androidx.navigation.navDeepLink +import com.skyd.rays.base.BaseComposeActivity import com.skyd.rays.ext.dataStore import com.skyd.rays.ext.getOrDefault import com.skyd.rays.ext.startWith import com.skyd.rays.model.bean.UriWithStickerUuidBean -import com.skyd.rays.model.preference.SettingsProvider import com.skyd.rays.model.preference.privacy.DisableScreenshotPreference import com.skyd.rays.ui.local.LocalCurrentStickerUuid -import com.skyd.rays.ui.local.LocalDarkMode import com.skyd.rays.ui.local.LocalNavController -import com.skyd.rays.ui.local.LocalWindowSizeClass import com.skyd.rays.ui.screen.about.ABOUT_SCREEN_ROUTE import com.skyd.rays.ui.screen.about.AboutScreen import com.skyd.rays.ui.screen.about.license.LICENSE_SCREEN_ROUTE @@ -109,7 +104,6 @@ import com.skyd.rays.ui.screen.settings.shareconfig.uristringshare.URI_STRING_SH import com.skyd.rays.ui.screen.settings.shareconfig.uristringshare.UriStringShareScreen import com.skyd.rays.ui.screen.stickerslist.STICKERS_LIST_SCREEN_ROUTE import com.skyd.rays.ui.screen.stickerslist.StickersListScreen -import com.skyd.rays.ui.theme.RaysTheme import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.channels.Channel @@ -120,7 +114,7 @@ import kotlinx.coroutines.launch @AndroidEntryPoint -class MainActivity : AppCompatActivity() { +class MainActivity : BaseComposeActivity() { private lateinit var navController: NavHostController private val viewModel: MainViewModel by viewModels() private val intentChannel = Channel(Channel.UNLIMITED) @@ -148,35 +142,30 @@ class MainActivity : AppCompatActivity() { .collect() } - setContent { + setContentBase { navController = rememberNavController() - SettingsProvider { - // 更新主题色 - val stickerUuid = LocalCurrentStickerUuid.current - LaunchedEffect(stickerUuid) { - dispatch(MainIntent.UpdateThemeColor(stickerUuid)) - } + // 更新主题色 + val stickerUuid = LocalCurrentStickerUuid.current + LaunchedEffect(stickerUuid) { + dispatch(MainIntent.UpdateThemeColor(stickerUuid)) + } - CompositionLocalProvider( - LocalNavController provides navController, - LocalWindowSizeClass provides calculateWindowSizeClass(this) - ) { - AppContent() - var needHandleIntent by rememberSaveable { mutableStateOf(true) } - if (needHandleIntent) { - LaunchedEffect(Unit) { - needHandleIntent = false - navController.handleDeepLink(intent) - } + CompositionLocalProvider(LocalNavController provides navController) { + AppContent() + var needHandleIntent by rememberSaveable { mutableStateOf(true) } + if (needHandleIntent) { + LaunchedEffect(Unit) { + needHandleIntent = false + navController.handleDeepLink(intent) } - DisposableEffect(navController) { - val listener = Consumer { newIntent -> - navController.handleDeepLink(newIntent)/*initIntent(newIntent)*/ - } - addOnNewIntentListener(listener) - onDispose { removeOnNewIntentListener(listener) } + } + DisposableEffect(navController) { + val listener = Consumer { newIntent -> + navController.handleDeepLink(newIntent)/*initIntent(newIntent)*/ } + addOnNewIntentListener(listener) + onDispose { removeOnNewIntentListener(listener) } } } } @@ -186,176 +175,174 @@ class MainActivity : AppCompatActivity() { private fun AppContent() { var openUpdateDialog by rememberSaveable { mutableStateOf(true) } - RaysTheme(darkTheme = LocalDarkMode.current) { - NavHost( - modifier = Modifier.background(MaterialTheme.colorScheme.background), - navController = navController, - startDestination = MAIN_SCREEN_ROUTE, - enterTransition = { - fadeIn(animationSpec = tween(220, delayMillis = 30)) + scaleIn( - animationSpec = tween(220, delayMillis = 30), - initialScale = 0.92f, - ) - }, - exitTransition = { fadeOut(animationSpec = tween(90)) }, - popEnterTransition = { - fadeIn(animationSpec = tween(220)) + scaleIn( - animationSpec = tween(220), - initialScale = 0.92f, - ) - }, - popExitTransition = { - fadeOut(animationSpec = tween(220)) + scaleOut( - animationSpec = tween(220), - targetScale = 0.92f, - ) - }, + NavHost( + modifier = Modifier.background(MaterialTheme.colorScheme.background), + navController = navController, + startDestination = MAIN_SCREEN_ROUTE, + enterTransition = { + fadeIn(animationSpec = tween(220, delayMillis = 30)) + scaleIn( + animationSpec = tween(220, delayMillis = 30), + initialScale = 0.92f, + ) + }, + exitTransition = { fadeOut(animationSpec = tween(90)) }, + popEnterTransition = { + fadeIn(animationSpec = tween(220)) + scaleIn( + animationSpec = tween(220), + initialScale = 0.92f, + ) + }, + popExitTransition = { + fadeOut(animationSpec = tween(220)) + scaleOut( + animationSpec = tween(220), + targetScale = 0.92f, + ) + }, + ) { + composable(route = MAIN_SCREEN_ROUTE) { + MainScreen() + } + composable( + route = "$ADD_SCREEN_ROUTE?isEdit={isEdit}", + arguments = listOf(navArgument("isEdit") { defaultValue = false }), + deepLinks = listOf( + navDeepLink { + action = Intent.ACTION_SEND + mimeType = "image/*" + }, + navDeepLink { + action = Intent.ACTION_SEND_MULTIPLE + mimeType = "image/*" + }, + ) ) { - composable(route = MAIN_SCREEN_ROUTE) { - MainScreen() - } - composable( - route = "$ADD_SCREEN_ROUTE?isEdit={isEdit}", - arguments = listOf(navArgument("isEdit") { defaultValue = false }), - deepLinks = listOf( - navDeepLink { - action = Intent.ACTION_SEND - mimeType = "image/*" - }, - navDeepLink { - action = Intent.ACTION_SEND_MULTIPLE - mimeType = "image/*" - }, - ) - ) { - val arguments = it.arguments - val externalUris: MutableList = if (arguments != null) { - // stickers from external - initIntent( - BundleCompat.getParcelable( - arguments, - NavController.KEY_DEEP_LINK_INTENT, - Intent::class.java, - ) + val arguments = it.arguments + val externalUris: MutableList = if (arguments != null) { + // stickers from external + initIntent( + BundleCompat.getParcelable( + arguments, + NavController.KEY_DEEP_LINK_INTENT, + Intent::class.java, ) - } else mutableListOf() + ) + } else mutableListOf() - if (externalUris.isNotEmpty()) { - // stickers from external - AddScreen( - initStickers = externalUris, - isEdit = false, - ) - } else { - // stickers from self - AddScreen( - initStickers = arguments?.let { bundle -> - BundleCompat.getParcelableArrayList( - bundle, "stickers", UriWithStickerUuidBean::class.java, - ) - } ?: mutableListOf(), - isEdit = it.arguments?.getBoolean("isEdit") ?: false, - ) - } - } - composable(route = SETTINGS_SCREEN_ROUTE) { - SettingsScreen() - } - composable(route = ML_SCREEN_ROUTE) { - MlScreen() - } - composable(route = CLASSIFICATION_SCREEN_ROUTE) { - ClassificationScreen() - } - composable(route = CLASSIFICATION_MODEL_SCREEN_ROUTE) { - ClassificationModelScreen() - } - composable(route = TEXT_RECOGNIZE_SCREEN_ROUTE) { - TextRecognizeScreen() - } - composable(route = SEARCH_CONFIG_SCREEN_ROUTE) { - SearchConfigScreen() - } - composable(route = APPEARANCE_SCREEN_ROUTE) { - AppearanceScreen() - } - composable(route = SEARCH_STYLE_SCREEN_ROUTE) { - SearchStyleScreen() - } - composable(route = ABOUT_SCREEN_ROUTE) { - AboutScreen() - } - composable(route = LICENSE_SCREEN_ROUTE) { - LicenseScreen() - } - composable(route = IMPORT_EXPORT_SCREEN_ROUTE) { - ImportExportScreen() - } - composable(route = WEBDAV_SCREEN_ROUTE) { - WebDavScreen() - } - composable(route = EXPORT_FILES_SCREEN_ROUTE) { - ExportFilesScreen( - exportStickers = it.arguments?.getStringArrayList("exportStickers") + if (externalUris.isNotEmpty()) { + // stickers from external + AddScreen( + initStickers = externalUris, + isEdit = false, + ) + } else { + // stickers from self + AddScreen( + initStickers = arguments?.let { bundle -> + BundleCompat.getParcelableArrayList( + bundle, "stickers", UriWithStickerUuidBean::class.java, + ) + } ?: mutableListOf(), + isEdit = it.arguments?.getBoolean("isEdit") ?: false, ) - } - composable(route = IMPORT_FILES_SCREEN_ROUTE) { - ImportFilesScreen() - } - composable(route = DATA_SCREEN_ROUTE) { - DataScreen() - } - composable(route = SHARE_CONFIG_SCREEN_ROUTE) { - ShareConfigScreen() - } - composable(route = URI_STRING_SHARE_SCREEN_ROUTE) { - UriStringShareScreen() - } - composable(route = STYLE_TRANSFER_SCREEN_ROUTE) { - StyleTransferScreen() - } - composable(route = SELFIE_SEGMENTATION_SCREEN_ROUTE) { - SelfieSegmentationScreen() - } - composable(route = API_SCREEN_ROUTE) { - ApiScreen() - } - composable(route = API_GRANT_SCREEN_ROUTE) { - ApiGrantScreen() - } - composable(route = AUTO_SHARE_SCREEN_ROUTE) { - AutoShareScreen() - } - composable(route = PRIVACY_SCREEN_ROUTE) { - PrivacyScreen() - } - composable(route = "$DETAIL_SCREEN_ROUTE?stickerUuid={stickerUuid}") { - DetailScreen(stickerUuid = it.arguments?.getString("stickerUuid").orEmpty()) - } - composable(route = "$STICKERS_LIST_SCREEN_ROUTE?query={query}") { - StickersListScreen(query = it.arguments?.getString("query").orEmpty()) - } - composable(route = SEARCH_SCREEN_ROUTE) { - SearchScreen() - } - composable(route = IMAGE_SOURCE_SCREEN_ROUTE) { - ImageSourceScreen() - } - composable(route = BLUR_STICKERS_SCREEN_ROUTE) { - BlurStickersScreen() - } - composable(route = CACHE_SCREEN_ROUTE) { - CacheScreen() } } - - if (openUpdateDialog) { - UpdateDialog( - silence = true, - onClosed = { openUpdateDialog = false }, - onError = { openUpdateDialog = false }, + composable(route = SETTINGS_SCREEN_ROUTE) { + SettingsScreen() + } + composable(route = ML_SCREEN_ROUTE) { + MlScreen() + } + composable(route = CLASSIFICATION_SCREEN_ROUTE) { + ClassificationScreen() + } + composable(route = CLASSIFICATION_MODEL_SCREEN_ROUTE) { + ClassificationModelScreen() + } + composable(route = TEXT_RECOGNIZE_SCREEN_ROUTE) { + TextRecognizeScreen() + } + composable(route = SEARCH_CONFIG_SCREEN_ROUTE) { + SearchConfigScreen() + } + composable(route = APPEARANCE_SCREEN_ROUTE) { + AppearanceScreen() + } + composable(route = SEARCH_STYLE_SCREEN_ROUTE) { + SearchStyleScreen() + } + composable(route = ABOUT_SCREEN_ROUTE) { + AboutScreen() + } + composable(route = LICENSE_SCREEN_ROUTE) { + LicenseScreen() + } + composable(route = IMPORT_EXPORT_SCREEN_ROUTE) { + ImportExportScreen() + } + composable(route = WEBDAV_SCREEN_ROUTE) { + WebDavScreen() + } + composable(route = EXPORT_FILES_SCREEN_ROUTE) { + ExportFilesScreen( + exportStickers = it.arguments?.getStringArrayList("exportStickers") ) } + composable(route = IMPORT_FILES_SCREEN_ROUTE) { + ImportFilesScreen() + } + composable(route = DATA_SCREEN_ROUTE) { + DataScreen() + } + composable(route = SHARE_CONFIG_SCREEN_ROUTE) { + ShareConfigScreen() + } + composable(route = URI_STRING_SHARE_SCREEN_ROUTE) { + UriStringShareScreen() + } + composable(route = STYLE_TRANSFER_SCREEN_ROUTE) { + StyleTransferScreen() + } + composable(route = SELFIE_SEGMENTATION_SCREEN_ROUTE) { + SelfieSegmentationScreen() + } + composable(route = API_SCREEN_ROUTE) { + ApiScreen() + } + composable(route = API_GRANT_SCREEN_ROUTE) { + ApiGrantScreen() + } + composable(route = AUTO_SHARE_SCREEN_ROUTE) { + AutoShareScreen() + } + composable(route = PRIVACY_SCREEN_ROUTE) { + PrivacyScreen() + } + composable(route = "$DETAIL_SCREEN_ROUTE?stickerUuid={stickerUuid}") { + DetailScreen(stickerUuid = it.arguments?.getString("stickerUuid").orEmpty()) + } + composable(route = "$STICKERS_LIST_SCREEN_ROUTE?query={query}") { + StickersListScreen(query = it.arguments?.getString("query").orEmpty()) + } + composable(route = SEARCH_SCREEN_ROUTE) { + SearchScreen() + } + composable(route = IMAGE_SOURCE_SCREEN_ROUTE) { + ImageSourceScreen() + } + composable(route = BLUR_STICKERS_SCREEN_ROUTE) { + BlurStickersScreen() + } + composable(route = CACHE_SCREEN_ROUTE) { + CacheScreen() + } + } + + if (openUpdateDialog) { + UpdateDialog( + silence = true, + onClosed = { openUpdateDialog = false }, + onError = { openUpdateDialog = false }, + ) } } diff --git a/app/src/main/java/com/skyd/rays/ui/screen/add/AddEvent.kt b/app/src/main/java/com/skyd/rays/ui/screen/add/AddEvent.kt index 219031e0..a35819d2 100644 --- a/app/src/main/java/com/skyd/rays/ui/screen/add/AddEvent.kt +++ b/app/src/main/java/com/skyd/rays/ui/screen/add/AddEvent.kt @@ -10,7 +10,5 @@ sealed interface AddEvent : MviSingleEvent { data class Failed(val msg: String) : AddStickersResultEvent } - data object CurrentStickerChanged : AddEvent - data object GetStickersWithTagsStateChanged : AddEvent data class InitFailed(val msg: String) : AddEvent } \ No newline at end of file diff --git a/app/src/main/java/com/skyd/rays/ui/screen/add/AddIntent.kt b/app/src/main/java/com/skyd/rays/ui/screen/add/AddIntent.kt index 8a2f76a0..66decc2b 100644 --- a/app/src/main/java/com/skyd/rays/ui/screen/add/AddIntent.kt +++ b/app/src/main/java/com/skyd/rays/ui/screen/add/AddIntent.kt @@ -7,23 +7,30 @@ import com.skyd.rays.model.bean.UriWithStickerUuidBean sealed interface AddIntent : MviIntent { data class Init(val initStickers: List) : AddIntent - data class ProcessNext(val nextSticker: UriWithStickerUuidBean?) : AddIntent + data class UpdateTitleText(val title: String) : AddIntent + data class UpdateCurrentTagText(val currentTag: String) : AddIntent + data class AddNewStickerWithTags(val stickerWithTags: StickerWithTags, val stickerUri: Uri) : AddIntent - data class GetStickerWithTags(val stickerUuid: String) : AddIntent - data class GetSuggestTags(val sticker: Uri) : AddIntent data class RemoveSuggestTag(val text: String) : AddIntent data class ReplaceWaitingListSingleSticker( val sticker: UriWithStickerUuidBean, val index: Int, ) : AddIntent - data class RemoveWaitingListSingleSticker(val index: Int) : AddIntent + data class RemoveWaitingListSingleSticker( + val index: Int, + val onSticker: (Int) -> UriWithStickerUuidBean?, + ) : AddIntent data class AddTag(val text: String) : AddIntent data class RemoveTag(val text: String) : AddIntent - data class AddToWaitingList(val stickers: List) : AddIntent + data class AddToWaitingList( + val stickers: List, + val currentListIsEmpty: Boolean, + ) : AddIntent + data class AddAddToAllTag(val text: String) : AddIntent data class RemoveAddToAllTag(val text: String) : AddIntent } \ No newline at end of file diff --git a/app/src/main/java/com/skyd/rays/ui/screen/add/AddPartialStateChange.kt b/app/src/main/java/com/skyd/rays/ui/screen/add/AddPartialStateChange.kt index fb7efe66..69fc7062 100644 --- a/app/src/main/java/com/skyd/rays/ui/screen/add/AddPartialStateChange.kt +++ b/app/src/main/java/com/skyd/rays/ui/screen/add/AddPartialStateChange.kt @@ -16,10 +16,6 @@ internal sealed interface AddPartialStateChange { } } - data object GetStickersWithTagsStateChanged : AddPartialStateChange { - override fun reduce(oldState: AddState) = oldState - } - sealed interface Init : AddPartialStateChange { data class Success( val stickerWithTags: StickerWithTags?, @@ -34,6 +30,7 @@ internal sealed interface AddPartialStateChange { suggestTags = suggestTags.toList(), addedTags = stickerWithTags?.tags?.map { it.tag }.orEmpty(), addToAllTags = emptyList(), + titleText = stickerWithTags?.sticker?.title.orEmpty(), loadingDialog = false, ) } @@ -43,29 +40,53 @@ internal sealed interface AddPartialStateChange { } } - data object ProcessNext : AddPartialStateChange { - override fun reduce(oldState: AddState) = oldState.copy( - waitingList = oldState.waitingList.toMutableList().apply { removeFirstOrNull() }, - getStickersWithTagsState = GetStickersWithTagsState.Init, - suggestTags = emptyList(), - addedTags = oldState.addToAllTags, - loadingDialog = false, - ) + data class UpdateTitleText(val title: String) : AddPartialStateChange { + override fun reduce(oldState: AddState) = oldState.copy(titleText = title) + } + + data class UpdateCurrentTagText(val currentTag: String) : AddPartialStateChange { + override fun reduce(oldState: AddState) = oldState.copy(currentTagText = currentTag) } data class ReplaceWaitingListSingleSticker( val sticker: UriWithStickerUuidBean, - val index: Int + val index: Int, + val getStickersWithTagsState: (oldState: AddState) -> GetStickersWithTagsState = { it.getStickersWithTagsState }, + val suggestTags: List? = null, + val currentStickerChanged: Boolean = false, ) : AddPartialStateChange { - override fun reduce(oldState: AddState) = oldState.copy( - waitingList = oldState.waitingList.toMutableList().apply { set(index, sticker) } - ) + override fun reduce(oldState: AddState): AddState { + val getStickersWithTagsState = getStickersWithTagsState(oldState) + return oldState.copy( + waitingList = oldState.waitingList.toMutableList().apply { set(index, sticker) }, + getStickersWithTagsState = getStickersWithTagsState, + suggestTags = suggestTags ?: oldState.suggestTags, + addedTags = if (currentStickerChanged) emptyList() else oldState.addedTags, + titleText = if (currentStickerChanged) { + (getStickersWithTagsState as? GetStickersWithTagsState.Success)?.stickerWithTags?.sticker?.title.orEmpty() + } else oldState.titleText, + ) + } } - data class RemoveWaitingListSingleSticker(val index: Int) : AddPartialStateChange { - override fun reduce(oldState: AddState) = oldState.copy( - waitingList = oldState.waitingList.toMutableList().apply { removeAt(index) } - ) + data class RemoveWaitingListSingleSticker( + val willSticker: UriWithStickerUuidBean, + val getStickersWithTagsState: (oldState: AddState) -> GetStickersWithTagsState = { it.getStickersWithTagsState }, + val suggestTags: List? = null, + val currentStickerChanged: Boolean = false, + ) : AddPartialStateChange { + override fun reduce(oldState: AddState): AddState { + val getStickersWithTagsState = getStickersWithTagsState(oldState) + return oldState.copy( + waitingList = oldState.waitingList.toMutableList().apply { remove(willSticker) }, + getStickersWithTagsState = getStickersWithTagsState, + suggestTags = suggestTags ?: oldState.suggestTags, + addedTags = if (currentStickerChanged) emptyList() else oldState.addedTags, + titleText = if (currentStickerChanged) { + (getStickersWithTagsState as? GetStickersWithTagsState.Success)?.stickerWithTags?.sticker?.title.orEmpty() + } else oldState.titleText, + ) + } } data class AddTag(val tag: String) : AddPartialStateChange { @@ -80,11 +101,24 @@ internal sealed interface AddPartialStateChange { ) } - data class AddToWaitingList(val stickers: List) : - AddPartialStateChange { - override fun reduce(oldState: AddState) = oldState.copy( - waitingList = oldState.waitingList + stickers - ) + data class AddToWaitingList( + val stickers: List, + val getStickersWithTagsState: (oldState: AddState) -> GetStickersWithTagsState = { it.getStickersWithTagsState }, + val suggestTags: List? = null, + val currentStickerChanged: Boolean = false, + ) : AddPartialStateChange { + override fun reduce(oldState: AddState): AddState { + val getStickersWithTagsState = getStickersWithTagsState(oldState) + return oldState.copy( + waitingList = oldState.waitingList + stickers, + getStickersWithTagsState = getStickersWithTagsState, + suggestTags = suggestTags ?: oldState.suggestTags, + addedTags = if (currentStickerChanged) emptyList() else oldState.addedTags, + titleText = if (currentStickerChanged) { + (getStickersWithTagsState as? GetStickersWithTagsState.Success)?.stickerWithTags?.sticker?.title.orEmpty() + } else oldState.titleText, + ) + } } sealed interface AllToAllTag : AddPartialStateChange { @@ -121,22 +155,14 @@ internal sealed interface AddPartialStateChange { data class Failed(val stickerUuid: String) : GetStickersWithTags } - sealed interface GetSuggestTags : AddPartialStateChange { - - data class Success(val texts: Set) : GetSuggestTags { - override fun reduce(oldState: AddState): AddState = oldState.copy( - suggestTags = texts.toList(), - loadingDialog = false, - ) - } - - data class Remove(val text: String) : GetSuggestTags { + sealed interface RemoveSuggestTag : AddPartialStateChange { + data class Success(val text: String) : RemoveSuggestTag { override fun reduce(oldState: AddState): AddState = oldState.copy( suggestTags = oldState.suggestTags - text ) } - data class Failed(val msg: String) : GetSuggestTags { + data class Failed(val msg: String) : RemoveSuggestTag { override fun reduce(oldState: AddState): AddState = oldState } } diff --git a/app/src/main/java/com/skyd/rays/ui/screen/add/AddScreen.kt b/app/src/main/java/com/skyd/rays/ui/screen/add/AddScreen.kt index f42df046..3313b306 100644 --- a/app/src/main/java/com/skyd/rays/ui/screen/add/AddScreen.kt +++ b/app/src/main/java/com/skyd/rays/ui/screen/add/AddScreen.kt @@ -71,7 +71,6 @@ import androidx.compose.material3.rememberTooltipState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableLongStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope @@ -114,6 +113,7 @@ import com.skyd.rays.ui.local.LocalNavController import com.skyd.rays.util.launchImagePicker import com.skyd.rays.util.rememberImagePicker import kotlinx.coroutines.launch +import java.util.UUID const val ADD_SCREEN_ROUTE = "addScreen" @@ -145,29 +145,22 @@ fun AddScreen( val snackbarHostState = remember { SnackbarHostState() } val scope = rememberCoroutineScope() val navController = LocalNavController.current - var titleText by rememberSaveable { mutableStateOf("") } - var currentTagText by rememberSaveable { mutableStateOf("") } - var stickerCreateTime by rememberSaveable { mutableLongStateOf(System.currentTimeMillis()) } var openMoreMenu by rememberSaveable { mutableStateOf(false) } var openErrorDialog by rememberSaveable { mutableStateOf(null) } var saveButtonEnable by rememberSaveable { mutableStateOf(true) } val uiState by viewModel.viewState.collectAsStateWithLifecycle() val dispatch = viewModel.getDispatcher(startWith = AddIntent.Init(initStickers)) - // 添加/修改完成后重设页面数据 - fun resetStickerData() { - titleText = "" - currentTagText = "" - stickerCreateTime = System.currentTimeMillis() - } - fun processNext() { - if (uiState.waitingList.size <= 1) { - if (isEdit) { - navController.popBackStackWithLifecycle() - } + if (uiState.waitingList.size <= 1 && isEdit) { + navController.popBackStackWithLifecycle() + } else { + dispatch( + AddIntent.RemoveWaitingListSingleSticker( + index = 0, onSticker = { uiState.waitingList.getOrNull(it) } + ) + ) } - dispatch(AddIntent.ProcessNext(uiState.waitingList.getOrNull(1))) } var currentReplaceIndex = rememberSaveable { 0 } @@ -186,7 +179,12 @@ fun AddScreen( } val pickStickersLauncher = rememberImagePicker(multiple = true) { result -> if (result.isEmpty()) return@rememberImagePicker - dispatch(AddIntent.AddToWaitingList(result.map { UriWithStickerUuidBean(uri = it) })) + dispatch( + AddIntent.AddToWaitingList( + result.map { UriWithStickerUuidBean(uri = it) }, + currentListIsEmpty = uiState.waitingList.isEmpty(), + ) + ) } Scaffold( @@ -204,10 +202,7 @@ fun AddScreen( }, actions = { RaysIconButton( - onClick = { - resetStickerData() - processNext() - }, + onClick = { processNext() }, contentDescription = stringResource(R.string.add_screen_skip_current_sticker), imageVector = Icons.Outlined.EditOff, ) @@ -225,23 +220,17 @@ fun AddScreen( val getStickersWithTagsState = uiState.getStickersWithTagsState val stickerBean = if (getStickersWithTagsState is GetStickersWithTagsState.Success) { - getStickersWithTagsState.stickerWithTags.sticker + getStickersWithTagsState.stickerWithTags.sticker.copy( + title = uiState.titleText + ) } else StickerBean( - title = titleText, - createTime = stickerCreateTime + title = uiState.titleText, + createTime = System.currentTimeMillis(), + uuid = uiState.currentSticker?.stickerUuid.orEmpty() + .ifBlank { UUID.randomUUID().toString() } ) val stickerWithTags = StickerWithTags( - sticker = stickerBean.copy( - title = titleText, - createTime = stickerCreateTime - ).apply { - uuid = uiState.currentSticker?.stickerUuid.orEmpty() - .ifBlank { - (uiState.getStickersWithTagsState as? - GetStickersWithTagsState.Success) - ?.stickerWithTags?.sticker?.uuid.orEmpty() - } - }, + sticker = stickerBean, tags = uiState.addedTags.distinct().map { TagBean(tag = it) } ) dispatch( @@ -284,7 +273,11 @@ fun AddScreen( pickStickerLauncher.launchImagePicker() }, onRemoveStickerFromWaitingListClick = { index -> - dispatch(AddIntent.RemoveWaitingListSingleSticker(index)) + dispatch( + AddIntent.RemoveWaitingListSingleSticker( + index = index, + onSticker = { uiState.waitingList.getOrNull(it) }) + ) }, ) AnimatedVisibility( @@ -300,14 +293,14 @@ fun AddScreen( } } titleInputFieldItem( - value = titleText, - onValueChange = { titleText = it }, + value = uiState.titleText, + onValueChange = { dispatch(AddIntent.UpdateTitleText(it)) }, ) tagsInputFieldItem( - value = currentTagText, - onValueChange = { currentTagText = it }, - onAddClick = { dispatch(AddIntent.AddTag(currentTagText)) }, - onAddToAllClick = { dispatch(AddIntent.AddAddToAllTag(currentTagText)) } + value = uiState.currentTagText, + onValueChange = { dispatch(AddIntent.UpdateCurrentTagText(it)) }, + onAddClick = { dispatch(AddIntent.AddTag(uiState.currentTagText)) }, + onAddToAllClick = { dispatch(AddIntent.AddAddToAllTag(uiState.currentTagText)) } ) item { AnimatedVisibility( @@ -322,27 +315,18 @@ fun AddScreen( } item { SuggestedTags(suggestedTags = uiState.suggestTags, onClick = { index -> - currentTagText = uiState.suggestTags[index] + dispatch(AddIntent.UpdateCurrentTagText(uiState.suggestTags[index])) dispatch(AddIntent.AddTag(uiState.suggestTags[index])) dispatch(AddIntent.RemoveSuggestTag(uiState.suggestTags[index])) }) } } - fun onGetStickersWithTagsStateChanged() { - val getStickersWithTagsState = uiState.getStickersWithTagsState - if (getStickersWithTagsState is GetStickersWithTagsState.Success) { - titleText = getStickersWithTagsState.stickerWithTags.sticker.title - stickerCreateTime = getStickersWithTagsState.stickerWithTags.sticker.createTime - } - } - MviEventListener(viewModel.singleEvent) { event -> when (event) { is AddEvent.AddStickersResultEvent.Duplicate -> { saveButtonEnable = true openDuplicateDialog = true - onGetStickersWithTagsStateChanged() } is AddEvent.AddStickersResultEvent.Failed -> { @@ -354,26 +338,9 @@ fun AddScreen( is AddEvent.AddStickersResultEvent.Success -> { saveButtonEnable = true - resetStickerData() processNext() } - AddEvent.CurrentStickerChanged -> { - val currentSticker = uiState.currentSticker - resetStickerData() - if (currentSticker?.stickerUuid.isNullOrBlank()) { - onGetStickersWithTagsStateChanged() - } else { - dispatch(AddIntent.GetStickerWithTags(currentSticker!!.stickerUuid)) - } - if (currentSticker != null) { - currentSticker.uri?.let { uri -> - dispatch(AddIntent.GetSuggestTags(uri)) - } - } - } - - AddEvent.GetStickersWithTagsStateChanged -> onGetStickersWithTagsStateChanged() is AddEvent.InitFailed -> openErrorDialog = event.msg } } diff --git a/app/src/main/java/com/skyd/rays/ui/screen/add/AddState.kt b/app/src/main/java/com/skyd/rays/ui/screen/add/AddState.kt index 00573c72..f810a839 100644 --- a/app/src/main/java/com/skyd/rays/ui/screen/add/AddState.kt +++ b/app/src/main/java/com/skyd/rays/ui/screen/add/AddState.kt @@ -10,6 +10,8 @@ data class AddState( val suggestTags: List, val addedTags: List, val addToAllTags: List, + val titleText: String, + val currentTagText: String, val loadingDialog: Boolean, ) : MviViewState { val currentSticker: UriWithStickerUuidBean? = waitingList.firstOrNull() @@ -21,6 +23,8 @@ data class AddState( suggestTags = emptyList(), addedTags = emptyList(), addToAllTags = emptyList(), + titleText = "", + currentTagText = "", loadingDialog = false, ) } @@ -30,4 +34,10 @@ sealed interface GetStickersWithTagsState { data class Success(val stickerWithTags: StickerWithTags) : GetStickersWithTagsState data object Init : GetStickersWithTagsState data object Failed : GetStickersWithTagsState + + companion object { + fun fromStickersWithTags(stickerWithTags: StickerWithTags?): GetStickersWithTagsState { + return if (stickerWithTags == null) Failed else Success(stickerWithTags) + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/skyd/rays/ui/screen/add/AddViewModel.kt b/app/src/main/java/com/skyd/rays/ui/screen/add/AddViewModel.kt index 9eb2918a..2645d0ad 100644 --- a/app/src/main/java/com/skyd/rays/ui/screen/add/AddViewModel.kt +++ b/app/src/main/java/com/skyd/rays/ui/screen/add/AddViewModel.kt @@ -8,6 +8,7 @@ import com.skyd.rays.ext.checkUriReadPermission import com.skyd.rays.ext.endWith import com.skyd.rays.ext.startWith import com.skyd.rays.model.bean.StickerWithTags +import com.skyd.rays.model.bean.UriWithStickerUuidBean import com.skyd.rays.model.preference.CurrentStickerUuidPreference import com.skyd.rays.model.respository.AddRepository import dagger.hilt.android.lifecycle.HiltViewModel @@ -20,19 +21,20 @@ import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.filterNot import kotlinx.coroutines.flow.flatMapConcat +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.scan import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.take +import kotlinx.coroutines.flow.zip import javax.inject.Inject @HiltViewModel class AddViewModel @Inject constructor(private var addRepository: AddRepository) : AbstractMviViewModel() { - override val viewState: StateFlow init { @@ -55,7 +57,6 @@ class AddViewModel @Inject constructor(private var addRepository: AddRepository) ) } - private fun Flow.sendSingleEvent(): Flow { return onEach { change -> val event = when (change) { @@ -71,23 +72,10 @@ class AddViewModel @Inject constructor(private var addRepository: AddRepository) AddEvent.AddStickersResultEvent.Failed(change.msg) } - is AddPartialStateChange.GetStickersWithTagsStateChanged -> { - AddEvent.GetStickersWithTagsStateChanged - } - is AddPartialStateChange.Init.Failed -> { AddEvent.InitFailed(change.msg) } - is AddPartialStateChange.RemoveWaitingListSingleSticker -> { - if (change.index == 0) AddEvent.CurrentStickerChanged else return@onEach - } - - is AddPartialStateChange.Init.Success, - is AddPartialStateChange.ProcessNext, - is AddPartialStateChange.ReplaceWaitingListSingleSticker, - is AddPartialStateChange.AddToWaitingList -> AddEvent.CurrentStickerChanged - else -> return@onEach } sendEvent(event) @@ -119,17 +107,62 @@ class AddViewModel @Inject constructor(private var addRepository: AddRepository) }.startWith(AddPartialStateChange.LoadingDialog.Show) .catchMap { AddPartialStateChange.Init.Failed(it.message.orEmpty()) } }, - filterIsInstance().map { - AddPartialStateChange.ProcessNext + filterIsInstance().map { intent -> + AddPartialStateChange.UpdateTitleText(intent.title) }, - filterIsInstance().map { intent -> - AddPartialStateChange.ReplaceWaitingListSingleSticker( - sticker = intent.sticker, - index = intent.index, - ) + filterIsInstance().map { intent -> + AddPartialStateChange.UpdateCurrentTagText(intent.currentTag) }, - filterIsInstance().map { intent -> - AddPartialStateChange.RemoveWaitingListSingleSticker(index = intent.index) + filterIsInstance().flatMapConcat { intent -> + val currentStickerChanged = intent.index == 0 + if (currentStickerChanged) { + currentStickerChange(intent.sticker) + } else { + flowOf(null to null) + }.map { (stickersWithTags, suggestTags) -> + AddPartialStateChange.ReplaceWaitingListSingleSticker( + sticker = intent.sticker, + index = intent.index, + getStickersWithTagsState = { + if (currentStickerChanged) { + GetStickersWithTagsState.fromStickersWithTags(stickersWithTags) + } else it.getStickersWithTagsState + }, + suggestTags = if (currentStickerChanged) { + suggestTags.orEmpty().toList() + } else null, + currentStickerChanged = currentStickerChanged, + ) + }.startWith(AddPartialStateChange.LoadingDialog.Show).catchMap { + AddPartialStateChange.ReplaceWaitingListSingleSticker( + sticker = intent.sticker, + index = intent.index, + ) + }.endWith(AddPartialStateChange.LoadingDialog.Close) + }, + filterIsInstance().flatMapConcat { intent -> + val currentStickerChanged = intent.index == 0 + val willRemove = intent.onSticker(intent.index)!! + if (currentStickerChanged) { + currentStickerChange(intent.onSticker(1)) + } else { + flowOf(null to null) + }.map { (stickersWithTags, suggestTags) -> + AddPartialStateChange.RemoveWaitingListSingleSticker( + willSticker = willRemove, + getStickersWithTagsState = { + if (currentStickerChanged) { + GetStickersWithTagsState.fromStickersWithTags(stickersWithTags) + } else it.getStickersWithTagsState + }, + suggestTags = if (currentStickerChanged) { + suggestTags.orEmpty().toList() + } else null, + currentStickerChanged = currentStickerChanged, + ) + }.startWith(AddPartialStateChange.LoadingDialog.Show).catchMap { + AddPartialStateChange.RemoveWaitingListSingleSticker(willSticker = willRemove) + }.endWith(AddPartialStateChange.LoadingDialog.Close) }, filterIsInstance().map { intent -> AddPartialStateChange.AddTag(intent.text) @@ -137,8 +170,28 @@ class AddViewModel @Inject constructor(private var addRepository: AddRepository) filterIsInstance().map { intent -> AddPartialStateChange.RemoveTag(intent.text) }, - filterIsInstance().map { intent -> - AddPartialStateChange.AddToWaitingList(intent.stickers) + filterIsInstance().flatMapConcat { intent -> + val currentStickerChanged = intent.currentListIsEmpty + if (currentStickerChanged) { + currentStickerChange(intent.stickers[0]) + } else { + flowOf(null to null) + }.map { (stickersWithTags, suggestTags) -> + AddPartialStateChange.AddToWaitingList( + stickers = intent.stickers, + getStickersWithTagsState = { + if (currentStickerChanged) { + GetStickersWithTagsState.fromStickersWithTags(stickersWithTags) + } else it.getStickersWithTagsState + }, + suggestTags = if (currentStickerChanged) { + suggestTags.orEmpty().toList() + } else null, + currentStickerChanged = currentStickerChanged, + ) + }.startWith(AddPartialStateChange.LoadingDialog.Show).catchMap { + AddPartialStateChange.AddToWaitingList(stickers = intent.stickers) + }.endWith(AddPartialStateChange.LoadingDialog.Close) }, filterIsInstance().map { intent -> AddPartialStateChange.AllToAllTag.Add(intent.text) @@ -147,15 +200,7 @@ class AddViewModel @Inject constructor(private var addRepository: AddRepository) AddPartialStateChange.AllToAllTag.Remove(intent.text) }, filterIsInstance().map { intent -> - AddPartialStateChange.GetSuggestTags.Remove(intent.text) - }, - - filterIsInstance().flatMapConcat { intent -> - addRepository.requestGetStickerWithTags(intent.stickerUuid).map { - if (it == null) AddPartialStateChange.GetStickersWithTags.Failed(intent.stickerUuid) - else AddPartialStateChange.GetStickersWithTags.Success(it) - }.startWith(AddPartialStateChange.LoadingDialog.Show) - .endWith(AddPartialStateChange.GetStickersWithTagsStateChanged) + AddPartialStateChange.RemoveSuggestTag.Success(intent.text) }, filterIsInstance().flatMapConcat { intent -> @@ -180,15 +225,21 @@ class AddViewModel @Inject constructor(private var addRepository: AddRepository) .endWith(AddPartialStateChange.LoadingDialog.Close) .catchMap { AddPartialStateChange.AddStickers.Failed(it.message.orEmpty()) } }, - - filterIsInstance().flatMapConcat { intent -> - addRepository.requestSuggestTags(intent.sticker) - .catchMap { emptySet() }.map { result -> - AddPartialStateChange.GetSuggestTags.Success(result) - }.catchMap { - AddPartialStateChange.GetSuggestTags.Failed(it.message.orEmpty()) - } - }, ) } + + private suspend fun currentStickerChange(newCurrentSticker: UriWithStickerUuidBean?): + Flow?>> { + if (newCurrentSticker == null) return flowOf(null to null) + var getStickerWithTags: Flow = flowOf(null) + if (newCurrentSticker.stickerUuid.isNotBlank()) { + getStickerWithTags = + addRepository.requestGetStickerWithTags(newCurrentSticker.stickerUuid) + } + return getStickerWithTags.zip( + addRepository.requestSuggestTags(newCurrentSticker.uri!!) + ) { stickerWithTags, suggestTags -> + stickerWithTags to suggestTags + } + } } \ No newline at end of file