update configurations screen

This commit is contained in:
X1nto 2022-02-09 18:34:05 +04:00
parent 4477180d2d
commit db0f2b44c9
9 changed files with 309 additions and 240 deletions

View File

@ -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() }
}

View File

@ -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<InstallationOptionItem>
@Parcelize
data class MultiSelect(
@StringRes val titleId: Int,
val items: List<InstallationOptionItem>,
override val titleId: Int,
override val items: List<InstallationOptionItem>,
val getOption: () -> Set<String>,
val addOption: (String) -> Unit,
val removeOption: (String) -> Unit
) : InstallationOption(titleId)
) : InstallationOption()
@Parcelize
data class SingleSelect(
@StringRes val titleId: Int,
val items: List<InstallationOptionItem>,
override val titleId: Int,
override val items: List<InstallationOptionItem>,
val getOption: () -> String,
val setOption: (String) -> Unit,
) : InstallationOption(titleId)
) : InstallationOption()
}

View File

@ -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,

View File

@ -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<InstallationOption>,
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<InstallationOption>,
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 <S> AnimatedContentScope<S>.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)
)

View File

@ -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<InstallationOption>,
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 <S> AnimatedContentScope<S>.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)
)

View File

@ -28,7 +28,7 @@ sealed class Screen(
displayName = R.string.toolbar_logs,
)
data class InstallPreferences(
data class Configuration(
val appName: String,
val appVersions: List<String>?,
val appInstallationOptions: List<InstallationOption>

View File

@ -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
}
}

View File

@ -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,

View File

@ -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,