diff --git a/app/src/main/java/ru/tech/imageresizershrinker/batch_resize/BatchResizeScreen.kt b/app/src/main/java/ru/tech/imageresizershrinker/batch_resize/BatchResizeScreen.kt index 97e1e0cfe0..fd7e733ccc 100644 --- a/app/src/main/java/ru/tech/imageresizershrinker/batch_resize/BatchResizeScreen.kt +++ b/app/src/main/java/ru/tech/imageresizershrinker/batch_resize/BatchResizeScreen.kt @@ -78,10 +78,11 @@ fun BatchResizeScreen( pushNewUris: (List?) -> Unit, getSavingFolder: (name: String, ext: String) -> SavingFolder, savingPathString: String, + showConfetti: () -> Unit, viewModel: BatchResizeViewModel = viewModel() ) { val context = LocalContext.current as ComponentActivity - val toastHostState = rememberToastHostState() + val toastHostState = LocalToastHost.current val scope = rememberCoroutineScope() val themeState = LocalDynamicThemeState.current @@ -172,6 +173,7 @@ fun BatchResizeScreen( } } showSaveLoading = false + showConfetti() } } @@ -844,7 +846,6 @@ fun BatchResizeScreen( } } - ToastHost(hostState = toastHostState) BackHandler { if (viewModel.uris?.isNotEmpty() == true) showExitDialog = true else if (navController.backstack.entries.isNotEmpty()) { diff --git a/app/src/main/java/ru/tech/imageresizershrinker/bytes_resize_screen/BytesResizeScreen.kt b/app/src/main/java/ru/tech/imageresizershrinker/bytes_resize_screen/BytesResizeScreen.kt index a997256f90..0997d20919 100644 --- a/app/src/main/java/ru/tech/imageresizershrinker/bytes_resize_screen/BytesResizeScreen.kt +++ b/app/src/main/java/ru/tech/imageresizershrinker/bytes_resize_screen/BytesResizeScreen.kt @@ -80,10 +80,11 @@ fun BytesResizeScreen( pushNewUris: (List?) -> Unit, getSavingFolder: (name: String, ext: String) -> SavingFolder, savingPathString: String, + showConfetti: () -> Unit, viewModel: BytesResizeViewModel = viewModel() ) { val context = LocalContext.current as ComponentActivity - val toastHostState = rememberToastHostState() + val toastHostState = LocalToastHost.current val scope = rememberCoroutineScope() val themeState = LocalDynamicThemeState.current @@ -176,6 +177,7 @@ fun BytesResizeScreen( } } showSaveLoading = false + showConfetti() } } @@ -657,7 +659,6 @@ fun BytesResizeScreen( } } - ToastHost(hostState = toastHostState) BackHandler { if (viewModel.uris?.isNotEmpty() == true) showExitDialog = true else if (navController.backstack.entries.isNotEmpty()) { diff --git a/app/src/main/java/ru/tech/imageresizershrinker/compare_screen/CompareScreen.kt b/app/src/main/java/ru/tech/imageresizershrinker/compare_screen/CompareScreen.kt index 99714470c2..2b83ed5677 100644 --- a/app/src/main/java/ru/tech/imageresizershrinker/compare_screen/CompareScreen.kt +++ b/app/src/main/java/ru/tech/imageresizershrinker/compare_screen/CompareScreen.kt @@ -42,8 +42,7 @@ import ru.tech.imageresizershrinker.main_screen.components.Screen import ru.tech.imageresizershrinker.main_screen.components.block import ru.tech.imageresizershrinker.resize_screen.components.ImageNotPickedWidget import ru.tech.imageresizershrinker.resize_screen.components.LoadingDialog -import ru.tech.imageresizershrinker.resize_screen.components.ToastHost -import ru.tech.imageresizershrinker.resize_screen.components.rememberToastHostState +import ru.tech.imageresizershrinker.resize_screen.components.LocalToastHost import ru.tech.imageresizershrinker.utils.BitmapUtils.getBitmapByUri import ru.tech.imageresizershrinker.widget.Marquee @@ -57,7 +56,7 @@ fun CompareScreen( viewModel: CompareViewModel = viewModel() ) { val context = LocalContext.current - val toastHostState = rememberToastHostState() + val toastHostState = LocalToastHost.current val scope = rememberCoroutineScope() val themeState = LocalDynamicThemeState.current @@ -289,7 +288,6 @@ fun CompareScreen( if (viewModel.isLoading) LoadingDialog() - ToastHost(hostState = toastHostState) BackHandler { if (navController.backstack.entries.isNotEmpty()) navController.pop() onGoBack() diff --git a/app/src/main/java/ru/tech/imageresizershrinker/crop_screen/CropScreen.kt b/app/src/main/java/ru/tech/imageresizershrinker/crop_screen/CropScreen.kt index ed58d73931..f785764096 100644 --- a/app/src/main/java/ru/tech/imageresizershrinker/crop_screen/CropScreen.kt +++ b/app/src/main/java/ru/tech/imageresizershrinker/crop_screen/CropScreen.kt @@ -50,8 +50,7 @@ import ru.tech.imageresizershrinker.generate_palette.isScrollingUp import ru.tech.imageresizershrinker.main_screen.components.Screen import ru.tech.imageresizershrinker.resize_screen.components.ImageNotPickedWidget import ru.tech.imageresizershrinker.resize_screen.components.LoadingDialog -import ru.tech.imageresizershrinker.resize_screen.components.ToastHost -import ru.tech.imageresizershrinker.resize_screen.components.rememberToastHostState +import ru.tech.imageresizershrinker.resize_screen.components.LocalToastHost import ru.tech.imageresizershrinker.utils.BitmapUtils.decodeBitmapFromUri import ru.tech.imageresizershrinker.utils.BitmapUtils.shareBitmap import ru.tech.imageresizershrinker.utils.ContextUtils.isExternalStorageWritable @@ -68,10 +67,11 @@ fun CropScreen( pushNewUri: (Uri?) -> Unit, getSavingFolder: (name: String, ext: String) -> SavingFolder, savingPathString: String, + showConfetti: () -> Unit, viewModel: CropViewModel = viewModel() ) { val context = LocalContext.current as ComponentActivity - val toastHostState = rememberToastHostState() + val toastHostState = LocalToastHost.current val scope = rememberCoroutineScope() val themeState = LocalDynamicThemeState.current @@ -157,6 +157,7 @@ fun CropScreen( } } showSaveLoading = false + showConfetti() } } @@ -417,7 +418,6 @@ fun CropScreen( ) } - ToastHost(hostState = toastHostState) BackHandler { if (viewModel.bitmap != null) showExitDialog = true else if (navController.backstack.entries.isNotEmpty()) { diff --git a/app/src/main/java/ru/tech/imageresizershrinker/generate_palette/GeneratePaletteScreen.kt b/app/src/main/java/ru/tech/imageresizershrinker/generate_palette/GeneratePaletteScreen.kt index 3eb5582b4e..2423f9b728 100644 --- a/app/src/main/java/ru/tech/imageresizershrinker/generate_palette/GeneratePaletteScreen.kt +++ b/app/src/main/java/ru/tech/imageresizershrinker/generate_palette/GeneratePaletteScreen.kt @@ -49,8 +49,7 @@ import ru.tech.imageresizershrinker.pick_color_from_image.copyColorIntoClipboard import ru.tech.imageresizershrinker.pick_color_from_image.format import ru.tech.imageresizershrinker.resize_screen.components.ImageNotPickedWidget import ru.tech.imageresizershrinker.resize_screen.components.LoadingDialog -import ru.tech.imageresizershrinker.resize_screen.components.ToastHost -import ru.tech.imageresizershrinker.resize_screen.components.rememberToastHostState +import ru.tech.imageresizershrinker.resize_screen.components.LocalToastHost import ru.tech.imageresizershrinker.theme.PaletteSwatch import ru.tech.imageresizershrinker.utils.BitmapUtils.decodeBitmapFromUri import ru.tech.imageresizershrinker.utils.LocalWindowSizeClass @@ -66,7 +65,7 @@ fun GeneratePaletteScreen( viewModel: GeneratePaletteViewModel = viewModel() ) { val context = LocalContext.current - val toastHostState = rememberToastHostState() + val toastHostState = LocalToastHost.current val scope = rememberCoroutineScope() val themeState = LocalDynamicThemeState.current @@ -345,7 +344,6 @@ fun GeneratePaletteScreen( if (viewModel.isLoading) LoadingDialog() - ToastHost(hostState = toastHostState) BackHandler { if (navController.backstack.entries.isNotEmpty()) navController.pop() onGoBack() diff --git a/app/src/main/java/ru/tech/imageresizershrinker/main_screen/MainActivity.kt b/app/src/main/java/ru/tech/imageresizershrinker/main_screen/MainActivity.kt index dbefd856ef..3c90fe8083 100644 --- a/app/src/main/java/ru/tech/imageresizershrinker/main_screen/MainActivity.kt +++ b/app/src/main/java/ru/tech/imageresizershrinker/main_screen/MainActivity.kt @@ -25,6 +25,10 @@ import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.core.view.WindowCompat import dagger.hilt.android.AndroidEntryPoint import dev.olshevski.navigation.reimagined.* +import nl.dionsegijn.konfetti.compose.KonfettiView +import nl.dionsegijn.konfetti.compose.OnParticleSystemUpdateListener +import nl.dionsegijn.konfetti.core.* +import nl.dionsegijn.konfetti.core.emitter.Emitter import ru.tech.imageresizershrinker.R import ru.tech.imageresizershrinker.batch_resize.BatchResizeScreen import ru.tech.imageresizershrinker.bytes_resize_screen.BytesResizeScreen @@ -45,6 +49,7 @@ import ru.tech.imageresizershrinker.utils.IntentUtils.parcelableArrayList import ru.tech.imageresizershrinker.utils.getSavingFolder import ru.tech.imageresizershrinker.utils.setContentWithWindowSizeClass import ru.tech.imageresizershrinker.utils.toUiPath +import java.util.concurrent.TimeUnit @ExperimentalFoundationApi @ExperimentalMaterial3Api @@ -66,306 +71,332 @@ class MainActivity : ComponentActivity() { parseImageFromIntent(intent) setContentWithWindowSizeClass { + var showConfetti by remember { mutableStateOf(false) } var showExitDialog by rememberSaveable { mutableStateOf(false) } val saveFolderUri = viewModel.saveFolderUri ImageResizerTheme { - BackHandler { - if (viewModel.shouldShowDialog) showExitDialog = true - else finishAffinity() - } - - Surface(Modifier.fillMaxSize()) { - AnimatedNavHost( - controller = viewModel.navController, - transitionSpec = { _, _, to -> - if (to != Screen.Main) { - slideInVertically() + fadeIn() with fadeOut() - } else { - fadeIn() with fadeOut() + slideOutVertically() - } - } - ) { screen -> - when (screen) { - is Screen.Main -> { - MainScreen( - navController = viewModel.navController, - currentFolderUri = saveFolderUri, - onGetNewFolder = { - viewModel.updateSaveFolderUri(it) - } - ) - } - is Screen.SingleResize -> { - SingleResizeScreen( - uriState = viewModel.uris?.firstOrNull(), - navController = viewModel.navController, - onGoBack = { viewModel.updateUris(null) }, - pushNewUri = viewModel::updateUri, - getSavingFolder = { name, ext -> - getSavingFolder( - treeUri = saveFolderUri, - filename = name, - extension = ext - ) - }, - savingPathString = saveFolderUri.toUiPath( - context = this@MainActivity, - default = stringResource(R.string.default_folder) - ) - ) - } - is Screen.PickColorFromImage -> { - PickColorFromImageScreen( - uriState = viewModel.uris?.firstOrNull(), - navController = viewModel.navController, - onGoBack = { viewModel.updateUris(null) }, - pushNewUri = viewModel::updateUri - ) - } - is Screen.Crop -> { - CropScreen( - uriState = viewModel.uris?.firstOrNull(), - navController = viewModel.navController, - onGoBack = { viewModel.updateUris(null) }, - pushNewUri = viewModel::updateUri, - getSavingFolder = { name, ext -> - getSavingFolder( - treeUri = saveFolderUri, - filename = name, - extension = ext - ) - }, - savingPathString = saveFolderUri.toUiPath( - context = this@MainActivity, - default = stringResource(R.string.default_folder) - ) - ) - } - is Screen.BatchResize -> { - BatchResizeScreen( - uriState = viewModel.uris, - navController = viewModel.navController, - onGoBack = { viewModel.updateUris(null) }, - pushNewUris = viewModel::updateUris, - getSavingFolder = { name, ext -> - getSavingFolder( - treeUri = saveFolderUri, - filename = name, - extension = ext - ) - }, - savingPathString = saveFolderUri.toUiPath( - context = this@MainActivity, - default = stringResource(R.string.default_folder) - ) - ) - } - is Screen.GeneratePalette -> { - GeneratePaletteScreen( - uriState = viewModel.uris?.firstOrNull(), - navController = viewModel.navController, - onGoBack = { viewModel.updateUris(null) }, - pushNewUri = viewModel::updateUri - ) - } - is Screen.ResizeByBytes -> { - BytesResizeScreen( - uriState = viewModel.uris, - navController = viewModel.navController, - onGoBack = { viewModel.updateUris(null) }, - pushNewUris = viewModel::updateUris, - getSavingFolder = { name, ext -> - getSavingFolder( - treeUri = saveFolderUri, - filename = name, - extension = ext - ) - }, - savingPathString = saveFolderUri.toUiPath( - context = this@MainActivity, - default = stringResource(R.string.default_folder) - ) - ) - } - is Screen.Compare -> { - CompareScreen( - comparableUris = viewModel.uris?.takeIf { it.size == 2 } - ?.let { it[0] to it[1] }, - pushNewUris = viewModel::updateUris, - navController = viewModel.navController, - onGoBack = { viewModel.updateUris(null) }, - ) - } - } + CompositionLocalProvider( + LocalToastHost provides viewModel.toastHostState + ) { + BackHandler { + if (viewModel.shouldShowDialog) showExitDialog = true + else finishAffinity() } - } - if (showExitDialog) { - AlertDialog( - onDismissRequest = { showExitDialog = false }, - dismissButton = { - FilledTonalButton( - onClick = { - finishAffinity() + Surface(Modifier.fillMaxSize()) { + AnimatedNavHost( + controller = viewModel.navController, + transitionSpec = { _, _, to -> + if (to != Screen.Main) { + slideInVertically() + fadeIn() with fadeOut() + } else { + fadeIn() with fadeOut() + slideOutVertically() } - ) { - Text(stringResource(R.string.close)) } - }, - confirmButton = { - Button(onClick = { showExitDialog = false }) { - Text(stringResource(R.string.stay)) - } - }, - title = { Text(stringResource(R.string.app_closing)) }, - text = { - Text( - stringResource(R.string.app_closing_sub), - textAlign = TextAlign.Center - ) - }, - icon = { Icon(Icons.Outlined.DoorBack, null) } - ) - } else if (viewModel.showSelectDialog) { - AlertDialog( - onDismissRequest = {}, - title = { stringResource(R.string.image) }, - confirmButton = { - TextButton( - onClick = { - viewModel.hideSelectDialog() - viewModel.updateUris(null) - } - ) { - Text(stringResource(id = R.string.cancel)) - } - }, - text = { - if ((viewModel.uris?.size ?: 0) <= 1) { - Column(Modifier.verticalScroll(rememberScrollState())) { - SingleResizePreference( - onClick = { - viewModel.navController.popUpTo { it == Screen.Main } - viewModel.navController.navigate(Screen.SingleResize) - viewModel.hideSelectDialog() + ) { screen -> + when (screen) { + is Screen.Main -> { + MainScreen( + navController = viewModel.navController, + currentFolderUri = saveFolderUri, + onGetNewFolder = { + viewModel.updateSaveFolderUri(it) }, - color = MaterialTheme.colorScheme.secondaryContainer + showConfetti = { showConfetti = true } ) - Spacer(modifier = Modifier.height(8.dp)) - BytesResizePreference( - onClick = { - viewModel.navController.popUpTo { it == Screen.Main } - viewModel.navController.navigate(Screen.ResizeByBytes) - viewModel.hideSelectDialog() + } + is Screen.SingleResize -> { + SingleResizeScreen( + uriState = viewModel.uris?.firstOrNull(), + navController = viewModel.navController, + onGoBack = { viewModel.updateUris(null) }, + pushNewUri = viewModel::updateUri, + getSavingFolder = { name, ext -> + getSavingFolder( + treeUri = saveFolderUri, + filename = name, + extension = ext + ) }, - color = MaterialTheme.colorScheme.secondaryContainer + savingPathString = saveFolderUri.toUiPath( + context = this@MainActivity, + default = stringResource(R.string.default_folder) + ), + showConfetti = { showConfetti = true } ) - Spacer(modifier = Modifier.height(8.dp)) - CropPreference( - onClick = { - viewModel.navController.popUpTo { it == Screen.Main } - viewModel.navController.navigate(Screen.Crop) - viewModel.hideSelectDialog() + } + is Screen.BatchResize -> { + BatchResizeScreen( + uriState = viewModel.uris, + navController = viewModel.navController, + onGoBack = { viewModel.updateUris(null) }, + pushNewUris = viewModel::updateUris, + getSavingFolder = { name, ext -> + getSavingFolder( + treeUri = saveFolderUri, + filename = name, + extension = ext + ) }, - color = MaterialTheme.colorScheme.secondaryContainer + savingPathString = saveFolderUri.toUiPath( + context = this@MainActivity, + default = stringResource(R.string.default_folder) + ), + showConfetti = { showConfetti = true } ) - Spacer(modifier = Modifier.height(8.dp)) - PickColorPreference( - onClick = { - viewModel.navController.popUpTo { it == Screen.Main } - viewModel.navController.navigate(Screen.PickColorFromImage) - viewModel.hideSelectDialog() + } + is Screen.ResizeByBytes -> { + BytesResizeScreen( + uriState = viewModel.uris, + navController = viewModel.navController, + onGoBack = { viewModel.updateUris(null) }, + pushNewUris = viewModel::updateUris, + getSavingFolder = { name, ext -> + getSavingFolder( + treeUri = saveFolderUri, + filename = name, + extension = ext + ) }, - color = MaterialTheme.colorScheme.secondaryContainer + savingPathString = saveFolderUri.toUiPath( + context = this@MainActivity, + default = stringResource(R.string.default_folder) + ), + showConfetti = { showConfetti = true } ) - Spacer(modifier = Modifier.height(8.dp)) - GeneratePalettePreference( - onClick = { - viewModel.navController.popUpTo { it == Screen.Main } - viewModel.navController.navigate(Screen.GeneratePalette) - viewModel.hideSelectDialog() + } + is Screen.Crop -> { + CropScreen( + uriState = viewModel.uris?.firstOrNull(), + navController = viewModel.navController, + onGoBack = { viewModel.updateUris(null) }, + pushNewUri = viewModel::updateUri, + getSavingFolder = { name, ext -> + getSavingFolder( + treeUri = saveFolderUri, + filename = name, + extension = ext + ) }, - color = MaterialTheme.colorScheme.secondaryContainer + savingPathString = saveFolderUri.toUiPath( + context = this@MainActivity, + default = stringResource(R.string.default_folder) + ), + showConfetti = { showConfetti = true } ) } - } else { - Column(Modifier.verticalScroll(rememberScrollState())) { - BatchResizePreference( - onClick = { - viewModel.navController.popUpTo { it == Screen.Main } - viewModel.navController.navigate(Screen.BatchResize) - viewModel.hideSelectDialog() - }, - color = MaterialTheme.colorScheme.secondaryContainer + is Screen.PickColorFromImage -> { + PickColorFromImageScreen( + uriState = viewModel.uris?.firstOrNull(), + navController = viewModel.navController, + onGoBack = { viewModel.updateUris(null) }, + pushNewUri = viewModel::updateUri ) - Spacer(modifier = Modifier.height(8.dp)) - BytesResizePreference( - onClick = { - viewModel.navController.popUpTo { it == Screen.Main } - viewModel.navController.navigate(Screen.ResizeByBytes) - viewModel.hideSelectDialog() - }, - color = MaterialTheme.colorScheme.secondaryContainer + } + is Screen.GeneratePalette -> { + GeneratePaletteScreen( + uriState = viewModel.uris?.firstOrNull(), + navController = viewModel.navController, + onGoBack = { viewModel.updateUris(null) }, + pushNewUri = viewModel::updateUri + ) + } + is Screen.Compare -> { + CompareScreen( + comparableUris = viewModel.uris?.takeIf { it.size == 2 } + ?.let { it[0] to it[1] }, + pushNewUris = viewModel::updateUris, + navController = viewModel.navController, + onGoBack = { viewModel.updateUris(null) }, ) - if (viewModel.uris?.size == 2) { + } + } + } + + if (showConfetti) { + val primary = MaterialTheme.colorScheme.primary + KonfettiView( + modifier = Modifier.fillMaxSize(), + parties = remember { particles(primary) }, + updateListener = object : OnParticleSystemUpdateListener { + override fun onParticleSystemEnded( + system: PartySystem, + activeSystems: Int + ) { + if (activeSystems == 0) showConfetti = false + } + } + ) + } + } + + if (showExitDialog) { + AlertDialog( + onDismissRequest = { showExitDialog = false }, + dismissButton = { + FilledTonalButton( + onClick = { + finishAffinity() + } + ) { + Text(stringResource(R.string.close)) + } + }, + confirmButton = { + Button(onClick = { showExitDialog = false }) { + Text(stringResource(R.string.stay)) + } + }, + title = { Text(stringResource(R.string.app_closing)) }, + text = { + Text( + stringResource(R.string.app_closing_sub), + textAlign = TextAlign.Center + ) + }, + icon = { Icon(Icons.Outlined.DoorBack, null) } + ) + } else if (viewModel.showSelectDialog) { + AlertDialog( + onDismissRequest = {}, + title = { stringResource(R.string.image) }, + confirmButton = { + TextButton( + onClick = { + viewModel.hideSelectDialog() + viewModel.updateUris(null) + } + ) { + Text(stringResource(id = R.string.cancel)) + } + }, + text = { + if ((viewModel.uris?.size ?: 0) <= 1) { + Column(Modifier.verticalScroll(rememberScrollState())) { + SingleResizePreference( + onClick = { + viewModel.navController.popUpTo { it == Screen.Main } + viewModel.navController.navigate(Screen.SingleResize) + viewModel.hideSelectDialog() + }, + color = MaterialTheme.colorScheme.secondaryContainer + ) + Spacer(modifier = Modifier.height(8.dp)) + BytesResizePreference( + onClick = { + viewModel.navController.popUpTo { it == Screen.Main } + viewModel.navController.navigate(Screen.ResizeByBytes) + viewModel.hideSelectDialog() + }, + color = MaterialTheme.colorScheme.secondaryContainer + ) + Spacer(modifier = Modifier.height(8.dp)) + CropPreference( + onClick = { + viewModel.navController.popUpTo { it == Screen.Main } + viewModel.navController.navigate(Screen.Crop) + viewModel.hideSelectDialog() + }, + color = MaterialTheme.colorScheme.secondaryContainer + ) + Spacer(modifier = Modifier.height(8.dp)) + PickColorPreference( + onClick = { + viewModel.navController.popUpTo { it == Screen.Main } + viewModel.navController.navigate(Screen.PickColorFromImage) + viewModel.hideSelectDialog() + }, + color = MaterialTheme.colorScheme.secondaryContainer + ) + Spacer(modifier = Modifier.height(8.dp)) + GeneratePalettePreference( + onClick = { + viewModel.navController.popUpTo { it == Screen.Main } + viewModel.navController.navigate(Screen.GeneratePalette) + viewModel.hideSelectDialog() + }, + color = MaterialTheme.colorScheme.secondaryContainer + ) + } + } else { + Column(Modifier.verticalScroll(rememberScrollState())) { + BatchResizePreference( + onClick = { + viewModel.navController.popUpTo { it == Screen.Main } + viewModel.navController.navigate(Screen.BatchResize) + viewModel.hideSelectDialog() + }, + color = MaterialTheme.colorScheme.secondaryContainer + ) Spacer(modifier = Modifier.height(8.dp)) - ComparePreference( + BytesResizePreference( onClick = { viewModel.navController.popUpTo { it == Screen.Main } - viewModel.navController.navigate(Screen.Compare) + viewModel.navController.navigate(Screen.ResizeByBytes) viewModel.hideSelectDialog() }, color = MaterialTheme.colorScheme.secondaryContainer ) + if (viewModel.uris?.size == 2) { + Spacer(modifier = Modifier.height(8.dp)) + ComparePreference( + onClick = { + viewModel.navController.popUpTo { it == Screen.Main } + viewModel.navController.navigate(Screen.Compare) + viewModel.hideSelectDialog() + }, + color = MaterialTheme.colorScheme.secondaryContainer + ) + } } } } - } - ) - } else if (viewModel.showUpdateDialog) { - AlertDialog( - onDismissRequest = { viewModel.cancelledUpdate(true) }, - icon = { - Icon(Icons.Rounded.Github, null) - }, - title = { Text(stringResource(R.string.new_version, viewModel.tag)) }, - text = { - Box { - Divider(Modifier.align(Alignment.TopCenter)) - Column(Modifier.verticalScroll(rememberScrollState())) { - Spacer(Modifier.height(16.dp)) - HtmlText(viewModel.changelog) + ) + } else if (viewModel.showUpdateDialog) { + AlertDialog( + onDismissRequest = { viewModel.cancelledUpdate(true) }, + icon = { + Icon(Icons.Rounded.Github, null) + }, + title = { Text(stringResource(R.string.new_version, viewModel.tag)) }, + text = { + Box { + Divider(Modifier.align(Alignment.TopCenter)) + Column(Modifier.verticalScroll(rememberScrollState())) { + Spacer(Modifier.height(16.dp)) + HtmlText(viewModel.changelog) + } + Divider(Modifier.align(Alignment.BottomCenter)) } - Divider(Modifier.align(Alignment.BottomCenter)) - } - }, - confirmButton = { - Button( - onClick = { - startActivity( - Intent( - Intent.ACTION_VIEW, - Uri.parse("https://github.com/t8rin/imageresizer/releases/tag/${viewModel.tag}") + }, + confirmButton = { + Button( + onClick = { + startActivity( + Intent( + Intent.ACTION_VIEW, + Uri.parse("https://github.com/t8rin/imageresizer/releases/tag/${viewModel.tag}") + ) ) - ) + } + ) { + Text(stringResource(id = R.string.update)) + } + }, + dismissButton = { + FilledTonalButton(onClick = { viewModel.cancelledUpdate() }) { + Text(stringResource(id = R.string.close)) } - ) { - Text(stringResource(id = R.string.update)) - } - }, - dismissButton = { - FilledTonalButton(onClick = { viewModel.cancelledUpdate() }) { - Text(stringResource(id = R.string.close)) } - } - ) - } + ) + } - ToastHost(hostState = viewModel.toastHostState) + ToastHost(hostState = LocalToastHost.current) - SideEffect { viewModel.tryGetUpdate() } + SideEffect { viewModel.tryGetUpdate() } + } } } } @@ -403,4 +434,43 @@ class MainActivity : ComponentActivity() { ) } } -} \ No newline at end of file +} + +private fun particles(primary: Color) = listOf( + Party( + speed = 0f, + maxSpeed = 15f, + damping = 0.9f, + angle = Angle.BOTTOM, + spread = Spread.ROUND, + colors = listOf(0xfce18a, 0xff726d, 0xf4306d, 0xb48def).map { + it.blend(primary) + }, + emitter = Emitter(duration = 2, TimeUnit.SECONDS).perSecond(100), + position = Position.Relative(0.0, 0.0).between(Position.Relative(1.0, 0.0)) + ), + Party( + speed = 10f, + maxSpeed = 30f, + damping = 0.9f, + angle = Angle.RIGHT - 45, + spread = 60, + colors = listOf(0xfce18a, 0xff726d, 0xf4306d, 0xb48def).map { + it.blend(primary) + }, + emitter = Emitter(duration = 2, TimeUnit.SECONDS).perSecond(100), + position = Position.Relative(0.0, 1.0) + ), + Party( + speed = 10f, + maxSpeed = 30f, + damping = 0.9f, + angle = Angle.RIGHT - 135, + spread = 60, + colors = listOf(0xfce18a, 0xff726d, 0xf4306d, 0xb48def).map { + it.blend(primary) + }, + emitter = Emitter(duration = 2, TimeUnit.SECONDS).perSecond(100), + position = Position.Relative(1.0, 1.0) + ) +) \ No newline at end of file diff --git a/app/src/main/java/ru/tech/imageresizershrinker/main_screen/components/MainScreen.kt b/app/src/main/java/ru/tech/imageresizershrinker/main_screen/components/MainScreen.kt index 20f3ac083b..747ca7c3d7 100644 --- a/app/src/main/java/ru/tech/imageresizershrinker/main_screen/components/MainScreen.kt +++ b/app/src/main/java/ru/tech/imageresizershrinker/main_screen/components/MainScreen.kt @@ -38,10 +38,7 @@ import dev.olshevski.navigation.reimagined.NavController import dev.olshevski.navigation.reimagined.navigate import dev.olshevski.navigation.reimagined.popUpTo import kotlinx.coroutines.delay -import nl.dionsegijn.konfetti.compose.KonfettiView -import nl.dionsegijn.konfetti.compose.OnParticleSystemUpdateListener import nl.dionsegijn.konfetti.core.* -import nl.dionsegijn.konfetti.core.emitter.Emitter import ru.tech.imageresizershrinker.BuildConfig import ru.tech.imageresizershrinker.R import ru.tech.imageresizershrinker.resize_screen.components.blend @@ -51,14 +48,14 @@ import ru.tech.imageresizershrinker.theme.Sparkles import ru.tech.imageresizershrinker.utils.LocalWindowSizeClass import ru.tech.imageresizershrinker.utils.toUiPath import java.lang.Integer.max -import java.util.concurrent.TimeUnit @OptIn(ExperimentalMaterial3Api::class) @Composable fun MainScreen( navController: NavController, currentFolderUri: Uri?, - onGetNewFolder: (Uri?) -> Unit + onGetNewFolder: (Uri?) -> Unit, + showConfetti: () -> Unit ) { val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() @@ -66,7 +63,6 @@ fun MainScreen( val colorScheme = MaterialTheme.colorScheme val themeState = LocalDynamicThemeState.current - var showConfetti by remember { mutableStateOf(false) } var showSelectFolderDialog by rememberSaveable { mutableStateOf(false) } val colors = remember(colorScheme) { @@ -109,7 +105,7 @@ fun MainScreen( scaleState = 1.3f delay(200) tryAwaitRelease() - showConfetti = true + showConfetti() themeState.updateColor(colors.random()) scaleState = 0.8f delay(200) @@ -434,19 +430,6 @@ fun MainScreen( ) } - if (showConfetti) { - val primary = MaterialTheme.colorScheme.primary - KonfettiView( - modifier = Modifier.fillMaxSize(), - parties = remember { particles(primary) }, - updateListener = object : OnParticleSystemUpdateListener { - override fun onParticleSystemEnded(system: PartySystem, activeSystems: Int) { - if (activeSystems == 0) showConfetti = false - } - } - ) - } - if (showSelectFolderDialog) { val launcher = rememberLauncherForActivityResult( contract = object : ActivityResultContracts.OpenDocumentTree() { @@ -529,44 +512,4 @@ fun MainScreen( } ) } - -} - -private fun particles(primary: Color) = listOf( - Party( - speed = 0f, - maxSpeed = 15f, - damping = 0.9f, - angle = Angle.BOTTOM, - spread = Spread.ROUND, - colors = listOf(0xfce18a, 0xff726d, 0xf4306d, 0xb48def).map { - it.blend(primary) - }, - emitter = Emitter(duration = 2, TimeUnit.SECONDS).perSecond(100), - position = Position.Relative(0.0, 0.0).between(Position.Relative(1.0, 0.0)) - ), - Party( - speed = 10f, - maxSpeed = 30f, - damping = 0.9f, - angle = Angle.RIGHT - 45, - spread = 60, - colors = listOf(0xfce18a, 0xff726d, 0xf4306d, 0xb48def).map { - it.blend(primary) - }, - emitter = Emitter(duration = 2, TimeUnit.SECONDS).perSecond(100), - position = Position.Relative(0.0, 1.0) - ), - Party( - speed = 10f, - maxSpeed = 30f, - damping = 0.9f, - angle = Angle.RIGHT - 135, - spread = 60, - colors = listOf(0xfce18a, 0xff726d, 0xf4306d, 0xb48def).map { - it.blend(primary) - }, - emitter = Emitter(duration = 2, TimeUnit.SECONDS).perSecond(100), - position = Position.Relative(1.0, 1.0) - ) -) \ No newline at end of file +} \ No newline at end of file diff --git a/app/src/main/java/ru/tech/imageresizershrinker/pick_color_from_image/PickColorFromImageScreen.kt b/app/src/main/java/ru/tech/imageresizershrinker/pick_color_from_image/PickColorFromImageScreen.kt index d1ca97afbd..9f7b625a2c 100644 --- a/app/src/main/java/ru/tech/imageresizershrinker/pick_color_from_image/PickColorFromImageScreen.kt +++ b/app/src/main/java/ru/tech/imageresizershrinker/pick_color_from_image/PickColorFromImageScreen.kt @@ -51,8 +51,7 @@ import ru.tech.imageresizershrinker.main_screen.components.navBarsLandscapePaddi import ru.tech.imageresizershrinker.pick_color_from_image.viewModel.PickColorViewModel import ru.tech.imageresizershrinker.resize_screen.components.ImageNotPickedWidget import ru.tech.imageresizershrinker.resize_screen.components.LoadingDialog -import ru.tech.imageresizershrinker.resize_screen.components.ToastHost -import ru.tech.imageresizershrinker.resize_screen.components.rememberToastHostState +import ru.tech.imageresizershrinker.resize_screen.components.LocalToastHost import ru.tech.imageresizershrinker.theme.PaletteSwatch import ru.tech.imageresizershrinker.utils.BitmapUtils.decodeBitmapFromUri import ru.tech.imageresizershrinker.widget.Marquee @@ -67,7 +66,7 @@ fun PickColorFromImageScreen( viewModel: PickColorViewModel = viewModel() ) { val context = LocalContext.current - val toastHostState = rememberToastHostState() + val toastHostState = LocalToastHost.current val scope = rememberCoroutineScope() val themeState = LocalDynamicThemeState.current @@ -339,7 +338,6 @@ fun PickColorFromImageScreen( if (viewModel.isLoading) LoadingDialog() - ToastHost(hostState = toastHostState) BackHandler { if (navController.backstack.entries.isNotEmpty()) navController.pop() onGoBack() diff --git a/app/src/main/java/ru/tech/imageresizershrinker/resize_screen/SingeResizeScreen.kt b/app/src/main/java/ru/tech/imageresizershrinker/resize_screen/SingeResizeScreen.kt index 494774ec81..b157eaf419 100644 --- a/app/src/main/java/ru/tech/imageresizershrinker/resize_screen/SingeResizeScreen.kt +++ b/app/src/main/java/ru/tech/imageresizershrinker/resize_screen/SingeResizeScreen.kt @@ -83,9 +83,10 @@ fun Context.SingleResizeScreen( uriState: Uri?, getSavingFolder: (name: String, ext: String) -> SavingFolder, savingPathString: String, - navController: NavController + navController: NavController, + showConfetti: () -> Unit ) { - val toastHostState = rememberToastHostState() + val toastHostState = LocalToastHost.current val scope = rememberCoroutineScope() val context = LocalContext.current as ComponentActivity val themeState = LocalDynamicThemeState.current @@ -186,6 +187,7 @@ fun Context.SingleResizeScreen( } } showSaveLoading = false + showConfetti() } } @@ -1054,7 +1056,6 @@ fun Context.SingleResizeScreen( } } - ToastHost(hostState = toastHostState) BackHandler { if (viewModel.bitmap != null) showExitDialog = true else if (navController.backstack.entries.isNotEmpty()) { diff --git a/app/src/main/java/ru/tech/imageresizershrinker/resize_screen/components/ToastHost.kt b/app/src/main/java/ru/tech/imageresizershrinker/resize_screen/components/ToastHost.kt index a997f25784..9c7501a9b9 100644 --- a/app/src/main/java/ru/tech/imageresizershrinker/resize_screen/components/ToastHost.kt +++ b/app/src/main/java/ru/tech/imageresizershrinker/resize_screen/components/ToastHost.kt @@ -18,6 +18,7 @@ import androidx.compose.ui.platform.LocalAccessibilityManager import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp +import androidx.compose.ui.zIndex import kotlinx.coroutines.* import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock @@ -42,6 +43,7 @@ fun ToastHost( } AnimatedContent( + modifier = Modifier.zIndex(100f), targetState = currentToastData, transitionSpec = { ToastDefaults.transition } ) { @@ -240,4 +242,6 @@ private fun Color.harmonizeWithPrimary( ): Color = blend(MaterialTheme.colorScheme.primary, fraction) @Composable -fun rememberToastHostState() = remember { ToastHostState() } \ No newline at end of file +fun rememberToastHostState() = remember { ToastHostState() } + +val LocalToastHost = compositionLocalOf { ToastHostState() } \ No newline at end of file diff --git a/app/src/main/java/ru/tech/imageresizershrinker/utils/BitmapUtils.kt b/app/src/main/java/ru/tech/imageresizershrinker/utils/BitmapUtils.kt index a418f57181..7488a822fc 100644 --- a/app/src/main/java/ru/tech/imageresizershrinker/utils/BitmapUtils.kt +++ b/app/src/main/java/ru/tech/imageresizershrinker/utils/BitmapUtils.kt @@ -521,6 +521,7 @@ object BitmapUtils { } fun Bitmap.scaleByMaxBytes(maxBytes: Long): Pair { + val maxBytes = maxBytes - 1024 * 8 if (this.size() > maxBytes) { var streamLength = maxBytes var compressQuality = 100