From db0f2b44c9f7b3b2e547d718369e1e59fe5b7fcf Mon Sep 17 00:00:00 2001 From: X1nto Date: Wed, 9 Feb 2022 18:34:05 +0400 Subject: [PATCH] update configurations screen --- .../com/vanced/manager/di/ViewModelModule.kt | 6 + .../domain/model/InstallationOption.kt | 20 +- .../com/vanced/manager/ui/MainActivity.kt | 8 +- .../manager/ui/screens/ConfigurationScreen.kt | 259 ++++++++++++++++++ .../ui/screens/InstallPreferencesScreen.kt | 225 --------------- .../java/com/vanced/manager/ui/util/Screen.kt | 2 +- .../ui/viewmodel/ConfigurationViewModel.kt | 25 ++ .../manager/ui/widget/list/CheckboxItem.kt | 2 + .../manager/ui/widget/list/RadiobuttonItem.kt | 2 + 9 files changed, 309 insertions(+), 240 deletions(-) create mode 100644 app/src/main/java/com/vanced/manager/ui/screens/ConfigurationScreen.kt delete mode 100644 app/src/main/java/com/vanced/manager/ui/screens/InstallPreferencesScreen.kt create mode 100644 app/src/main/java/com/vanced/manager/ui/viewmodel/ConfigurationViewModel.kt diff --git a/app/src/main/java/com/vanced/manager/di/ViewModelModule.kt b/app/src/main/java/com/vanced/manager/di/ViewModelModule.kt index 7e6a3f15..3752c1e2 100644 --- a/app/src/main/java/com/vanced/manager/di/ViewModelModule.kt +++ b/app/src/main/java/com/vanced/manager/di/ViewModelModule.kt @@ -10,6 +10,7 @@ import com.vanced.manager.core.installer.impl.VancedInstaller import com.vanced.manager.repository.DataRepository import com.vanced.manager.repository.MainRepository import com.vanced.manager.repository.MirrorRepository +import com.vanced.manager.ui.viewmodel.ConfigurationViewModel import com.vanced.manager.ui.viewmodel.InstallViewModel import com.vanced.manager.ui.viewmodel.MainViewModel import org.koin.android.ext.koin.androidApplication @@ -34,6 +35,11 @@ val viewModelModule = module { microgInstaller: MicrogInstaller, ) = InstallViewModel(vancedDownloader, musicDownloader, microgDownloader, vancedInstaller, musicInstaller, microgInstaller) + fun provideConfigurationViewModel(): ConfigurationViewModel { + return ConfigurationViewModel() + } + viewModel { provideMainViewModel(get(), get(), androidApplication()) } viewModel { provideInstallViewModel(get(), get(), get(), get(), get(), get()) } + viewModel { provideConfigurationViewModel() } } \ No newline at end of file diff --git a/app/src/main/java/com/vanced/manager/domain/model/InstallationOption.kt b/app/src/main/java/com/vanced/manager/domain/model/InstallationOption.kt index c79cd4e2..4f7471d0 100644 --- a/app/src/main/java/com/vanced/manager/domain/model/InstallationOption.kt +++ b/app/src/main/java/com/vanced/manager/domain/model/InstallationOption.kt @@ -1,29 +1,29 @@ package com.vanced.manager.domain.model import android.os.Parcelable -import androidx.annotation.StringRes import kotlinx.parcelize.Parcelize -sealed class InstallationOption( - @StringRes val itemTitleId: Int, -) : Parcelable { +sealed class InstallationOption : Parcelable { + + abstract val titleId: Int + abstract val items: List @Parcelize data class MultiSelect( - @StringRes val titleId: Int, - val items: List, + override val titleId: Int, + override val items: List, val getOption: () -> Set, val addOption: (String) -> Unit, val removeOption: (String) -> Unit - ) : InstallationOption(titleId) + ) : InstallationOption() @Parcelize data class SingleSelect( - @StringRes val titleId: Int, - val items: List, + override val titleId: Int, + override val items: List, val getOption: () -> String, val setOption: (String) -> Unit, - ) : InstallationOption(titleId) + ) : InstallationOption() } diff --git a/app/src/main/java/com/vanced/manager/ui/MainActivity.kt b/app/src/main/java/com/vanced/manager/ui/MainActivity.kt index 61c26842..4909f7d1 100644 --- a/app/src/main/java/com/vanced/manager/ui/MainActivity.kt +++ b/app/src/main/java/com/vanced/manager/ui/MainActivity.kt @@ -89,7 +89,7 @@ class MainActivity : ComponentActivity() { onAppInstallPress = { appName, appVersions, installationOptions -> if (installationOptions != null) { backStack.push( - Screen.InstallPreferences( + Screen.Configuration( appName, appVersions, installationOptions @@ -118,13 +118,13 @@ class MainActivity : ComponentActivity() { is Screen.Logs -> { } - is Screen.InstallPreferences -> { - InstallPreferencesScreen( + is Screen.Configuration -> { + ConfigurationScreen( installationOptions = screen.appInstallationOptions, onToolbarBackButtonClick = { backStack.pop() }, - onDoneClick = { + onFinishClick = { backStack.push( Screen.Install( screen.appName, diff --git a/app/src/main/java/com/vanced/manager/ui/screens/ConfigurationScreen.kt b/app/src/main/java/com/vanced/manager/ui/screens/ConfigurationScreen.kt new file mode 100644 index 00000000..6b82d129 --- /dev/null +++ b/app/src/main/java/com/vanced/manager/ui/screens/ConfigurationScreen.kt @@ -0,0 +1,259 @@ +package com.vanced.manager.ui.screens + +import androidx.compose.animation.* +import androidx.compose.animation.core.tween +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.ListItem +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.ArrowBackIosNew +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.vanced.manager.R +import com.vanced.manager.domain.model.InstallationOption +import com.vanced.manager.ui.component.card.ManagerTonalCard +import com.vanced.manager.ui.component.text.ManagerText +import com.vanced.manager.ui.component.topappbar.ManagerTopAppBar +import com.vanced.manager.ui.resources.managerString +import com.vanced.manager.ui.theme.LargeShape +import com.vanced.manager.ui.util.DefaultContentPaddingHorizontal +import com.vanced.manager.ui.util.DefaultContentPaddingVertical +import com.vanced.manager.ui.viewmodel.ConfigurationViewModel +import com.vanced.manager.ui.widget.layout.managerCategory +import org.koin.androidx.compose.getViewModel + +@ExperimentalFoundationApi +@ExperimentalAnimationApi +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun ConfigurationScreen( + installationOptions: List, + onToolbarBackButtonClick: () -> Unit, + onFinishClick: () -> Unit, +) { + val viewModel: ConfigurationViewModel = getViewModel() + Scaffold( + topBar = { + ManagerTopAppBar( + title = managerString(R.string.toolbar_installation_preferences), + navigationIcon = { + IconButton( + onClick = { + onToolbarBackButtonClick() + viewModel.reset() + } + ) { + Icon( + imageVector = Icons.Rounded.ArrowBackIosNew, + contentDescription = "Back" + ) + } + } + ) + }, + bottomBar = { + ConfigurationButtons( + modifier = Modifier + .padding( + horizontal = DefaultContentPaddingHorizontal, + vertical = DefaultContentPaddingVertical + ), + lastIndex = installationOptions.lastIndex, + currentIndex = viewModel.currentIndex, + onBackClick = { + viewModel.back() + }, + onNextClick = { + viewModel.next() + }, + onFinishClick = { + onFinishClick() + viewModel.reset() + } + ) + } + ) { paddingValues -> + ConfigurationBody( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues), + currentIndex = viewModel.currentIndex, + installationOptions = installationOptions + ) + } +} + +@OptIn(ExperimentalAnimationApi::class, ExperimentalMaterial3Api::class) +@Composable +private fun ConfigurationBody( + currentIndex: Int, + installationOptions: List, + modifier: Modifier = Modifier +) { + AnimatedContent( + modifier = modifier, + targetState = currentIndex, + transitionSpec = { + slideAnimationSpec( + if (targetState > initialState) { + AnimatedContentScope.SlideDirection.Start + } else { + AnimatedContentScope.SlideDirection.End + } + ) + } + ) { optionIndex -> + val installationOption = installationOptions[optionIndex] + val categoryName = managerString(installationOption.titleId) + LazyColumn { + when (installationOption) { + is InstallationOption.SingleSelect -> { + managerCategory( + categoryName = categoryName, + items = installationOption.items + ) { item -> + val preference = installationOption.getOption() + ConfigurationItem( + modifier = Modifier + .fillMaxWidth(), + text = item.displayText(item.key), + onClick = { + installationOption.setOption(item.key) + }, + trailing = { + RadioButton( + selected = preference == item.key, + onClick = null + ) + } + ) + } + } + is InstallationOption.MultiSelect -> { + managerCategory( + categoryName = categoryName, + items = installationOption.items + ) { item -> + val preference = installationOption.getOption() + ConfigurationItem( + modifier = Modifier + .fillMaxWidth(), + text = item.displayText(item.key), + onClick = { + if (preference.contains(item.key)) { + installationOption.removeOption(item.key) + } else { + installationOption.addOption(item.key) + } + }, + trailing = { + Checkbox( + checked = preference.contains(item.key), + onCheckedChange = null + ) + } + ) + } + } + } + } + } +} + +@OptIn(ExperimentalAnimationApi::class) +@Composable +private fun ConfigurationButtons( + currentIndex: Int, + lastIndex: Int, + onBackClick: () -> Unit, + onNextClick: () -> Unit, + onFinishClick: () -> Unit, + modifier: Modifier = Modifier, +) { + Row(modifier = modifier) { + AnimatedVisibility( + modifier = Modifier + .wrapContentWidth(Alignment.Start) + .weight(1f), + visible = currentIndex > 0) { + TextButton(onClick = onBackClick) { + ManagerText(text = "Back") + } + } + AnimatedContent( + modifier = Modifier + .wrapContentWidth(Alignment.End) + .weight(1f), + targetState = currentIndex == lastIndex, + transitionSpec = { + slideAnimationSpec( + if (initialState && !targetState) { + AnimatedContentScope.SlideDirection.Up + } else { + AnimatedContentScope.SlideDirection.Down + } + ) + } + ) { isLastIndex -> + if (isLastIndex) { + ElevatedButton(onClick = onFinishClick) { + ManagerText(text = "Finish") + } + } else { + OutlinedButton(onClick = onNextClick) { + ManagerText(text = "Next") + } + } + } + } +} + +@OptIn(ExperimentalMaterialApi::class) +@Composable +private fun ConfigurationItem( + text: String, + onClick: () -> Unit, + trailing: @Composable () -> Unit, + modifier: Modifier = Modifier, +) { + ManagerTonalCard( + modifier = modifier + .padding( + start = 6.dp, + end = 6.dp, + bottom = 8.dp + ), + shape = LargeShape, + onClick = onClick + ) { + ListItem( + text = { + ManagerText( + text = text, + textStyle = MaterialTheme.typography.titleSmall + ) + }, + trailing = trailing, + ) + } +} + +@ExperimentalAnimationApi +private fun AnimatedContentScope.slideAnimationSpec( + slideDirection: AnimatedContentScope.SlideDirection +) = slideIntoContainer( + towards = slideDirection, + animationSpec = tween(400) +) + fadeIn( + animationSpec = tween(400) +) with slideOutOfContainer( + towards = slideDirection, + animationSpec = tween(400) +) + fadeOut( + animationSpec = tween(400) +) \ No newline at end of file diff --git a/app/src/main/java/com/vanced/manager/ui/screens/InstallPreferencesScreen.kt b/app/src/main/java/com/vanced/manager/ui/screens/InstallPreferencesScreen.kt deleted file mode 100644 index f1cb4514..00000000 --- a/app/src/main/java/com/vanced/manager/ui/screens/InstallPreferencesScreen.kt +++ /dev/null @@ -1,225 +0,0 @@ -package com.vanced.manager.ui.screens - -import androidx.compose.animation.* -import androidx.compose.animation.core.tween -import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.lazy.items -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.rounded.ArrowBackIosNew -import androidx.compose.material.icons.rounded.Done -import androidx.compose.material.icons.rounded.NavigateBefore -import androidx.compose.material.icons.rounded.NavigateNext -import androidx.compose.material3.* -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp -import com.vanced.manager.R -import com.vanced.manager.domain.model.InstallationOption -import com.vanced.manager.ui.component.card.ManagerTonalCard -import com.vanced.manager.ui.component.layout.ManagerLazyColumn -import com.vanced.manager.ui.component.layout.ManagerScaffold -import com.vanced.manager.ui.component.text.ManagerText -import com.vanced.manager.ui.component.topappbar.ManagerTopAppBar -import com.vanced.manager.ui.resources.managerString -import com.vanced.manager.ui.theme.LargeShape -import com.vanced.manager.ui.util.DefaultContentPaddingHorizontal -import com.vanced.manager.ui.util.DefaultContentPaddingVertical -import com.vanced.manager.ui.widget.list.CheckboxItem -import com.vanced.manager.ui.widget.list.RadiobuttonItem - -@ExperimentalFoundationApi -@ExperimentalAnimationApi -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun InstallPreferencesScreen( - installationOptions: List, - onToolbarBackButtonClick: () -> Unit, - onDoneClick: () -> Unit, -) { - var currentOptionIndex by rememberSaveable { mutableStateOf(0) } - - ManagerScaffold( - topBar = { - ManagerTopAppBar( - title = managerString(R.string.toolbar_installation_preferences), - navigationIcon = { - IconButton( - onClick = onToolbarBackButtonClick - ) { - Icon( - imageVector = Icons.Rounded.ArrowBackIosNew, - contentDescription = "Back" - ) - } - } - ) - }, - ) { paddingValues -> - Column( - modifier = Modifier - .fillMaxSize() - .padding(paddingValues), - ) { - AnimatedContent( - modifier = Modifier - .padding( - horizontal = DefaultContentPaddingHorizontal, - vertical = DefaultContentPaddingVertical - ) - .weight(1f), - targetState = currentOptionIndex, - transitionSpec = { - getSlideAnimationSpec( - if (targetState > initialState) { - AnimatedContentScope.SlideDirection.Start - } else { - AnimatedContentScope.SlideDirection.End - } - ) - } - ) { optionIndex -> - val installationOption = installationOptions[optionIndex] - ManagerTonalCard( - modifier = Modifier - .wrapContentHeight( - align = Alignment.Top - ), - shape = LargeShape - ) { - Column { - ManagerText( - modifier = Modifier - .fillMaxWidth() - .padding( - horizontal = DefaultContentPaddingHorizontal, - vertical = DefaultContentPaddingVertical - ), - text = managerString(installationOption.itemTitleId), - textStyle = MaterialTheme.typography.titleLarge, - ) - ManagerLazyColumn( - contentPadding = PaddingValues( - start = 4.dp, - end = 4.dp, - bottom = DefaultContentPaddingVertical - ) - ) { - when (installationOption) { - is InstallationOption.MultiSelect -> { - items(installationOption.items) { item -> - val preference = installationOption.getOption() - CheckboxItem( - modifier = Modifier.fillMaxWidth(), - text = item.displayText(item.key), - checked = preference.contains(item.key), - onCheckedChange = { - if (it) { - installationOption.addOption(item.key) - } else { - installationOption.removeOption(item.key) - } - } - ) - } - } - is InstallationOption.SingleSelect -> { - items(installationOption.items) { item -> - val preference = installationOption.getOption() - RadiobuttonItem( - modifier = Modifier.fillMaxWidth(), - text = item.displayText(item.key), - selected = preference == item.key, - onClick = { - installationOption.setOption(item.key) - }, - ) - } - } - } - } - } - } - } - Row( - modifier = Modifier - .fillMaxWidth() - .padding( - horizontal = DefaultContentPaddingHorizontal, - vertical = DefaultContentPaddingVertical - ), - ) { - AnimatedVisibility( - visible = currentOptionIndex > 0 - ) { - FloatingActionButton( - onClick = { - currentOptionIndex-- - } - ) { - Icon( - imageVector = Icons.Rounded.NavigateBefore, - contentDescription = "Back" - ) - } - } - Spacer(modifier = Modifier.weight(1f)) - AnimatedContent( - targetState = currentOptionIndex == installationOptions.lastIndex, - transitionSpec = { - - getSlideAnimationSpec( - if (initialState && !targetState) { - AnimatedContentScope.SlideDirection.Up - } else { - AnimatedContentScope.SlideDirection.Down - } - ) - } - ) { lastIndex -> - if (lastIndex) { - FloatingActionButton( - onClick = onDoneClick - ) { - Icon( - imageVector = Icons.Rounded.Done, - contentDescription = "Done" - ) - } - } else { - FloatingActionButton( - onClick = { - currentOptionIndex++ - } - ) { - Icon( - imageVector = Icons.Rounded.NavigateNext, - contentDescription = "Next" - ) - } - } - } - } - } - } -} - -@ExperimentalAnimationApi -private fun AnimatedContentScope.getSlideAnimationSpec( - slideDirection: AnimatedContentScope.SlideDirection -) = slideIntoContainer( - towards = slideDirection, - animationSpec = tween(400) -) + fadeIn( - animationSpec = tween(400) -) with slideOutOfContainer( - towards = slideDirection, - animationSpec = tween(400) -) + fadeOut( - animationSpec = tween(400) -) \ No newline at end of file diff --git a/app/src/main/java/com/vanced/manager/ui/util/Screen.kt b/app/src/main/java/com/vanced/manager/ui/util/Screen.kt index cfb77b62..0c193b14 100644 --- a/app/src/main/java/com/vanced/manager/ui/util/Screen.kt +++ b/app/src/main/java/com/vanced/manager/ui/util/Screen.kt @@ -28,7 +28,7 @@ sealed class Screen( displayName = R.string.toolbar_logs, ) - data class InstallPreferences( + data class Configuration( val appName: String, val appVersions: List?, val appInstallationOptions: List diff --git a/app/src/main/java/com/vanced/manager/ui/viewmodel/ConfigurationViewModel.kt b/app/src/main/java/com/vanced/manager/ui/viewmodel/ConfigurationViewModel.kt new file mode 100644 index 00000000..42052f6c --- /dev/null +++ b/app/src/main/java/com/vanced/manager/ui/viewmodel/ConfigurationViewModel.kt @@ -0,0 +1,25 @@ +package com.vanced.manager.ui.viewmodel + +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.lifecycle.ViewModel + +class ConfigurationViewModel : ViewModel() { + + var currentIndex by mutableStateOf(0) + private set + + fun next() { + currentIndex++ + } + + fun back() { + currentIndex-- + } + + fun reset() { + currentIndex = 0 + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/vanced/manager/ui/widget/list/CheckboxItem.kt b/app/src/main/java/com/vanced/manager/ui/widget/list/CheckboxItem.kt index 974c5fae..71d59961 100644 --- a/app/src/main/java/com/vanced/manager/ui/widget/list/CheckboxItem.kt +++ b/app/src/main/java/com/vanced/manager/ui/widget/list/CheckboxItem.kt @@ -1,6 +1,7 @@ package com.vanced.manager.ui.widget.list import androidx.compose.material3.Checkbox +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -9,6 +10,7 @@ import com.vanced.manager.ui.component.list.ManagerSelectableListItem import com.vanced.manager.ui.component.modifier.managerClickable import com.vanced.manager.ui.component.text.ManagerText +@OptIn(ExperimentalMaterial3Api::class) @Composable fun CheckboxItem( text: String, diff --git a/app/src/main/java/com/vanced/manager/ui/widget/list/RadiobuttonItem.kt b/app/src/main/java/com/vanced/manager/ui/widget/list/RadiobuttonItem.kt index 0a4aeb71..01fb8ad2 100644 --- a/app/src/main/java/com/vanced/manager/ui/widget/list/RadiobuttonItem.kt +++ b/app/src/main/java/com/vanced/manager/ui/widget/list/RadiobuttonItem.kt @@ -1,5 +1,6 @@ package com.vanced.manager.ui.widget.list +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.material3.RadioButton import androidx.compose.runtime.Composable @@ -9,6 +10,7 @@ import com.vanced.manager.ui.component.list.ManagerSelectableListItem import com.vanced.manager.ui.component.modifier.managerClickable import com.vanced.manager.ui.component.text.ManagerText +@OptIn(ExperimentalMaterial3Api::class) @Composable fun RadiobuttonItem( text: String,