From 306ac356352f9761fbc65ebdd1b4d95286438b63 Mon Sep 17 00:00:00 2001 From: Ushie Date: Sun, 7 Aug 2022 07:25:09 +0300 Subject: [PATCH] feat: redesign navigation and screen layout (#11) * feat: redesign navigation and screen layout * chore: remove ksp --- app/build.gradle.kts | 17 +- .../com/aliucord/manager/MainActivity.kt | 43 ++++- .../aliucord/manager/ui/component/AppBar.kt | 137 -------------- .../manager/ui/component/ManagerScaffold.kt | 48 ----- .../manager/ui/component/PluginsList.kt | 113 ------------ .../ui/component/plugins/PluginCard.kt | 11 +- .../manager/ui/navigation/AppDestination.kt | 44 +++++ .../aliucord/manager/ui/screen/AboutScreen.kt | 65 ++++--- .../manager/ui/screen/CommitsScreen.kt | 96 ---------- .../manager/ui/screen/HomeRootScreen.kt | 171 ++++++++++++++++++ .../aliucord/manager/ui/screen/HomeScreen.kt | 122 ++++++++----- .../manager/ui/screen/InstallScreen.kt | 64 ++++--- .../manager/ui/screen/PluginsScreen.kt | 130 +++++++++++++ .../manager/ui/screen/SettingsScreen.kt | 163 +++++++++-------- .../aliucord/manager/ui/screen/StoreScreen.kt | 71 +++++--- app/src/main/res/values/strings.xml | 1 + 16 files changed, 709 insertions(+), 587 deletions(-) delete mode 100644 app/src/main/kotlin/com/aliucord/manager/ui/component/AppBar.kt delete mode 100644 app/src/main/kotlin/com/aliucord/manager/ui/component/ManagerScaffold.kt delete mode 100644 app/src/main/kotlin/com/aliucord/manager/ui/component/PluginsList.kt create mode 100644 app/src/main/kotlin/com/aliucord/manager/ui/navigation/AppDestination.kt delete mode 100644 app/src/main/kotlin/com/aliucord/manager/ui/screen/CommitsScreen.kt create mode 100644 app/src/main/kotlin/com/aliucord/manager/ui/screen/HomeRootScreen.kt create mode 100644 app/src/main/kotlin/com/aliucord/manager/ui/screen/PluginsScreen.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 05c4bf7..7fdf04d 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -5,7 +5,7 @@ val ktorVersion = "2.0.3" plugins { id("com.android.application") id("kotlin-android") - id("com.google.devtools.ksp") version "1.7.10-1.0.6" + id("kotlin-parcelize") kotlin("plugin.serialization") version "1.7.10" } @@ -61,31 +61,34 @@ dependencies { implementation("androidx.appcompat:appcompat:1.4.2") implementation("androidx.core:core-ktx:1.8.0") implementation("androidx.core:core-splashscreen:1.0.0") - implementation("com.android:zipflinger:7.4.0-alpha08") + implementation("com.android:zipflinger:7.4.0-alpha09") // implementation("com.github.Aliucord:libzip:1.0.0") + // androidX activity + implementation("androidx.activity:activity-compose:1.6.0-alpha05") + // compose dependencies implementation("androidx.compose.ui:ui:${composeVersion}") implementation("androidx.compose.ui:ui-tooling:${composeVersion}") implementation("androidx.compose.material:material-icons-extended:${composeVersion}") implementation("androidx.compose.material3:material3:1.0.0-alpha15") implementation("androidx.paging:paging-compose:1.0.0-alpha15") + implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.0-alpha01") // accompanist dependencies implementation("com.google.accompanist:accompanist-systemuicontroller:${accompanistVersion}") implementation("com.google.accompanist:accompanist-permissions:${accompanistVersion}") - // compose destinations - implementation("io.github.raamcosta.compose-destinations:core:1.7.15-beta") - ksp("io.github.raamcosta.compose-destinations:ksp:1.7.15-beta") - // other dependencies implementation("io.coil-kt:coil-compose:2.1.0") implementation("org.bouncycastle:bcpkix-jdk15on:1.70") implementation("de.upb.cs.swt:axml:2.1.2") // 2.1.2 is broken btw - implementation("com.android.tools.build:apksig:7.2.1") + implementation("com.android.tools.build:apksig:7.2.2") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.3") + // taxi + implementation("com.github.X1nto:Taxi:1.0.0") + // ktor implementation("io.ktor:ktor-client-core:$ktorVersion") implementation("io.ktor:ktor-client-android:$ktorVersion") diff --git a/app/src/main/kotlin/com/aliucord/manager/MainActivity.kt b/app/src/main/kotlin/com/aliucord/manager/MainActivity.kt index e96abc2..9804228 100644 --- a/app/src/main/kotlin/com/aliucord/manager/MainActivity.kt +++ b/app/src/main/kotlin/com/aliucord/manager/MainActivity.kt @@ -11,17 +11,25 @@ import android.content.Intent import android.os.* import android.provider.Settings import androidx.activity.ComponentActivity +import androidx.activity.compose.BackHandler import androidx.activity.compose.setContent +import androidx.compose.animation.* import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.ui.Modifier import androidx.core.net.toUri import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import com.aliucord.manager.preferences.Prefs import com.aliucord.manager.preferences.sharedPreferences -import com.aliucord.manager.ui.component.ManagerScaffold +import com.aliucord.manager.ui.navigation.AppDestination +import com.aliucord.manager.ui.screen.* import com.aliucord.manager.ui.theme.ManagerTheme import com.aliucord.manager.ui.theme.Theme +import com.xinto.taxi.Taxi +import com.xinto.taxi.rememberBackstackNavigator class MainActivity : ComponentActivity() { + @OptIn(ExperimentalAnimationApi::class) override fun onCreate(savedInstanceState: Bundle?) { installSplashScreen() @@ -45,7 +53,38 @@ class MainActivity : ComponentActivity() { theme == Theme.SYSTEM && isSystemInDarkTheme() || theme == Theme.DARK } ) { - ManagerScaffold() + val navigator = rememberBackstackNavigator(AppDestination.Home) + + BackHandler { + navigator.pop() + } + + Taxi( + modifier = Modifier.fillMaxSize(), + navigator = navigator, + transitionSpec = { fadeIn() with fadeOut() } + ) { destination -> + when (destination) { + is AppDestination.Home -> MainRootScreen( + navigator = navigator + ) + is AppDestination.Plugins -> PluginsScreen( + navigator = navigator + ) + is AppDestination.Install -> InstallerScreen( + onClickBack = navigator::pop, + ) + is AppDestination.Settings -> SettingsScreen( + onClickBack = navigator::pop + ) + is AppDestination.About -> AboutScreen( + onClickBack = navigator::pop + ) + is AppDestination.Store -> StoreScreen( + onClickBack = navigator::pop + ) + } + } } } } diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/component/AppBar.kt b/app/src/main/kotlin/com/aliucord/manager/ui/component/AppBar.kt deleted file mode 100644 index 3ad440c..0000000 --- a/app/src/main/kotlin/com/aliucord/manager/ui/component/AppBar.kt +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright (c) 2022 Juby210 & zt - * Licensed under the Open Software License version 3.0 - */ - -package com.aliucord.manager.ui.component - -import androidx.annotation.StringRes -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.wrapContentSize -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.* -import androidx.compose.material3.* -import androidx.compose.runtime.* -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalUriHandler -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.DpOffset -import androidx.compose.ui.unit.dp -import androidx.navigation.NavController -import androidx.navigation.compose.currentBackStackEntryAsState -import com.aliucord.manager.BuildConfig -import com.aliucord.manager.R -import com.aliucord.manager.ui.screen.appDestination -import com.aliucord.manager.ui.screen.destinations.* -import com.ramcosta.composedestinations.navigation.navigate -import com.ramcosta.composedestinations.navigation.popUpTo - -@get:StringRes -private val Destination.title - get() = when (this) { - HomeScreenDestination -> R.string.home - InstallerScreenDestination -> R.string.installer - CommitsScreenDestination -> R.string.commits - StoreScreenDestination -> R.string.store - SettingsScreenDestination -> R.string.settings - AboutScreenDestination -> R.string.about - } - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun AppBar(navController: NavController) { - val destination = navController.currentBackStackEntryAsState().value?.appDestination() ?: HomeScreenDestination - var isMenuExpanded by remember { mutableStateOf(false) } - - MediumTopAppBar( - navigationIcon = { - if (destination != HomeScreenDestination) { - IconButton(onClick = { navController.popBackStack() }) { - Icon(Icons.Default.ArrowBack, stringResource(R.string.back), tint = MaterialTheme.colorScheme.onBackground) - } - } - }, - title = { - Text(stringResource(destination.title)) - }, - actions = { - val localUriHandler = LocalUriHandler.current - - IconButton( - onClick = { - navController.navigate(StoreScreenDestination) { - popUpTo(HomeScreenDestination) { saveState = true } - } - }, - ) { - Icon( - painter = painterResource(R.drawable.ic_plugin_24dp), - contentDescription = stringResource(R.string.plugins), - tint = MaterialTheme.colorScheme.onBackground - ) - } - - IconButton( - onClick = { - localUriHandler.openUri("https://discord.gg/${BuildConfig.SUPPORT_SERVER}") - }, - ) { - Icon( - painter = painterResource(R.drawable.ic_discord), - contentDescription = stringResource(R.string.support_server), - tint = MaterialTheme.colorScheme.onBackground - ) - } - - Box( - modifier = Modifier.wrapContentSize(Alignment.TopEnd) - ) { - IconButton( - onClick = { isMenuExpanded = !isMenuExpanded } - ) { - Icon( - imageVector = Icons.Default.MoreVert, - contentDescription = stringResource(R.string.show_menu), - tint = MaterialTheme.colorScheme.onBackground - ) - } - - DropdownMenu( - offset = DpOffset(x = 12.dp, y = 0.dp), - expanded = isMenuExpanded, - onDismissRequest = { isMenuExpanded = false } - ) { - DropdownMenuItem( - onClick = { - isMenuExpanded = false - navController.navigate(AboutScreenDestination) - }, - leadingIcon = { - Icon( - imageVector = Icons.Default.Info, - contentDescription = "About" - ) - }, - text = { Text(stringResource(AboutScreenDestination.title)) } - ) - - DropdownMenuItem( - onClick = { - isMenuExpanded = false - navController.navigate(SettingsScreenDestination) - }, - leadingIcon = { - Icon( - imageVector = Icons.Default.Settings, - contentDescription = "Settings" - ) - }, - text = { Text(stringResource(SettingsScreenDestination.title)) } - ) - } - } - } - ) -} diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/component/ManagerScaffold.kt b/app/src/main/kotlin/com/aliucord/manager/ui/component/ManagerScaffold.kt deleted file mode 100644 index 8a9e9bc..0000000 --- a/app/src/main/kotlin/com/aliucord/manager/ui/component/ManagerScaffold.kt +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) 2022 Juby210 & zt - * Licensed under the Open Software License version 3.0 - */ - -package com.aliucord.manager.ui.component - -import android.Manifest -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Scaffold -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp -import androidx.navigation.compose.rememberNavController -import com.aliucord.manager.ui.screen.NavGraphs -import com.google.accompanist.permissions.* -import com.ramcosta.composedestinations.DestinationsNavHost -import com.ramcosta.composedestinations.rememberNavHostEngine - -@OptIn( - ExperimentalMaterial3Api::class, - ExperimentalPermissionsApi::class -) -@Composable -fun ManagerScaffold() { - val navController = rememberNavController() - - val storagePermissionState = rememberPermissionState(Manifest.permission.WRITE_EXTERNAL_STORAGE) - - when (storagePermissionState.status) { - is PermissionStatus.Denied -> GrantPermission(storagePermissionState) - PermissionStatus.Granted -> { - Scaffold( - topBar = { AppBar(navController) } - ) { paddingValues -> - DestinationsNavHost( - modifier = Modifier - .padding(paddingValues) - .padding(horizontal = 12.dp, vertical = 14.dp), - navGraph = NavGraphs.root, - navController = navController, - engine = rememberNavHostEngine() - ) - } - } - } -} diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/component/PluginsList.kt b/app/src/main/kotlin/com/aliucord/manager/ui/component/PluginsList.kt deleted file mode 100644 index 39eda98..0000000 --- a/app/src/main/kotlin/com/aliucord/manager/ui/component/PluginsList.kt +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (c) 2022 Juby210 & zt - * Licensed under the Open Software License version 3.0 - */ - -package com.aliucord.manager.ui.component - -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.text.KeyboardActions -import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Delete -import androidx.compose.material.icons.filled.Search -import androidx.compose.material3.* -import androidx.compose.runtime.* -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalFocusManager -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.input.ImeAction -import androidx.compose.ui.unit.dp -import androidx.compose.ui.window.Dialog -import com.aliucord.manager.R -import com.aliucord.manager.model.Plugin -import com.aliucord.manager.ui.component.plugins.Changelog -import com.aliucord.manager.ui.component.plugins.PluginCard - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun PluginsList() = Column( - modifier = Modifier.fillMaxSize(), - verticalArrangement = Arrangement.spacedBy(8.dp) -) { - var plugins by remember { mutableStateOf(Plugin.loadAll()) } - val focusManager = LocalFocusManager.current - var search by remember { mutableStateOf("") } - - if (plugins.isNotEmpty()) { - OutlinedTextField( - maxLines = 1, - value = search, - onValueChange = { search = it }, - modifier = Modifier.fillMaxWidth(), - label = { Text(stringResource(R.string.search)) }, - trailingIcon = { Icon(Icons.Default.Search, stringResource(R.string.search_plugins)) }, - keyboardOptions = KeyboardOptions(autoCorrect = false, imeAction = ImeAction.Search), - keyboardActions = KeyboardActions { focusManager.clearFocus() }, - ) - - LazyColumn( - verticalArrangement = Arrangement.spacedBy(8.dp) - ) { - items(plugins.filter { plugin -> - plugin.manifest.run { - name.contains(search, true) - || description.contains(search, true) - || authors.any { a -> a.name.contains(search, true) } - } - }) { plugin -> - var showDeleteDialog by remember { mutableStateOf(false) } - var showChangelogDialog by remember { mutableStateOf(false) } - - if (showDeleteDialog) AlertDialog( - onDismissRequest = { showDeleteDialog = false }, - icon = { - Icon( - imageVector = Icons.Default.Delete, - contentDescription = stringResource(R.string.uninstall_plugin, plugin.manifest.name) - ) - }, - title = { Text(stringResource(R.string.uninstall_plugin, plugin.manifest.name)) }, - text = { Text(stringResource(R.string.delete_plugin_body)) }, - confirmButton = { - Button( - onClick = { - showDeleteDialog = false - plugin.file.delete() - plugins = plugins.filter { it != plugin } - } - ) { - Text(stringResource(android.R.string.ok)) - } - }, - dismissButton = { - Button( - onClick = { showDeleteDialog = false }, - colors = ButtonDefaults.buttonColors( - containerColor = MaterialTheme.colorScheme.secondaryContainer, - contentColor = MaterialTheme.colorScheme.onSecondaryContainer - ) - ) { - Text(stringResource(android.R.string.cancel)) - } - } - ) - - if (showChangelogDialog) Dialog( - onDismissRequest = { showChangelogDialog = false }, - content = { Changelog(plugin, onDismiss = { showChangelogDialog = false }) } - ) - - PluginCard( - plugin = plugin, - onDelete = { showDeleteDialog = true }, - onShowChangelog = { showChangelogDialog = true } - ) - } - } - } else { - Text(stringResource(R.string.no_plugins_installed)) - } -} diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/component/plugins/PluginCard.kt b/app/src/main/kotlin/com/aliucord/manager/ui/component/plugins/PluginCard.kt index b51318a..1dbe804 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/component/plugins/PluginCard.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/component/plugins/PluginCard.kt @@ -116,7 +116,7 @@ fun PluginCard( Row( horizontalArrangement = Arrangement.spacedBy(6.dp), ) { - FilledTonalButton( + IconButton( onClick = { uriHandler.openUri( plugin.manifest.updateUrl @@ -132,7 +132,7 @@ fun PluginCard( } if (plugin.manifest.changelog != null) { - FilledTonalButton(onClick = onShowChangelog) { + IconButton(onClick = onShowChangelog) { Icon( painter = painterResource(R.drawable.ic_history_white_24dp), contentDescription = stringResource(R.string.view_plugin_changelog, plugin.manifest.name) @@ -142,10 +142,13 @@ fun PluginCard( Spacer(Modifier.weight(1f, true)) - Button(onClick = onDelete) { + IconButton( + onClick = onDelete + ) { Icon( imageVector = Icons.Default.Delete, - contentDescription = stringResource(R.string.uninstall_plugin, plugin.manifest.name) + contentDescription = stringResource(R.string.uninstall_plugin, plugin.manifest.name), + tint = MaterialTheme.colorScheme.error ) } } diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/navigation/AppDestination.kt b/app/src/main/kotlin/com/aliucord/manager/ui/navigation/AppDestination.kt new file mode 100644 index 0000000..59ac561 --- /dev/null +++ b/app/src/main/kotlin/com/aliucord/manager/ui/navigation/AppDestination.kt @@ -0,0 +1,44 @@ +package com.aliucord.manager.ui.navigation + +import androidx.annotation.StringRes +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Extension +import androidx.compose.material.icons.filled.Home +import androidx.compose.material.icons.outlined.Extension +import androidx.compose.material.icons.outlined.Home +import androidx.compose.ui.graphics.vector.ImageVector +import com.aliucord.manager.R +import com.xinto.taxi.Destination +import kotlinx.parcelize.Parcelize +import kotlinx.parcelize.RawValue + +@Parcelize +sealed interface AppDestination : Destination { + @Parcelize + object Home : AppDestination + + @Parcelize + object Plugins : AppDestination + + @Parcelize + object Install : AppDestination + + @Parcelize + object Store : AppDestination + + @Parcelize + object About : AppDestination + + @Parcelize + object Settings : AppDestination +} + +@Parcelize +enum class HomeDestination( + val selectedIcon: @RawValue ImageVector, + val unselectedIcon: @RawValue ImageVector, + @StringRes val label: Int +) : Destination { + HOME(Icons.Default.Home, Icons.Outlined.Home, R.string.home), + PLUGINS(Icons.Default.Extension, Icons.Outlined.Extension, R.string.plugins) +} diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/screen/AboutScreen.kt b/app/src/main/kotlin/com/aliucord/manager/ui/screen/AboutScreen.kt index 299bbd4..4718d9b 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/screen/AboutScreen.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/screen/AboutScreen.kt @@ -6,6 +6,8 @@ package com.aliucord.manager.ui.screen import androidx.compose.foundation.layout.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.NavigateBefore import androidx.compose.material3.* import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -18,32 +20,49 @@ import com.aliucord.manager.R import com.aliucord.manager.ui.component.about.ContributorsCard import com.aliucord.manager.ui.component.about.TeamCard import com.aliucord.manager.ui.viewmodel.AboutViewModel -import com.ramcosta.composedestinations.annotation.Destination -@Destination +@OptIn(ExperimentalMaterial3Api::class) @Composable -fun AboutScreen() { - val uriHandler = LocalUriHandler.current - val viewModel: AboutViewModel = viewModel() - - Column( - verticalArrangement = Arrangement.spacedBy(8.dp) - ) { - TeamCard() - ContributorsCard(viewModel.contributors) - - Spacer(Modifier.weight(1f, true)) - - Button( - modifier = Modifier.fillMaxWidth(), - onClick = { uriHandler.openUri("https://github.com/Aliucord/Aliucord") } - ) { - Icon( - painter = painterResource(R.drawable.ic_account_github_white_24dp), - contentDescription = "GitHub" +fun AboutScreen(onClickBack: () -> Unit) { + Scaffold( + topBar = { + MediumTopAppBar( + title = { Text(stringResource(R.string.about)) }, + navigationIcon = { + IconButton(onClick = onClickBack) { + Icon( + imageVector = Icons.Default.NavigateBefore, + contentDescription = stringResource(R.string.back) + ) + } + } ) - Spacer(Modifier.padding(4.dp)) - Text(stringResource(R.string.github)) + } + ) { paddingValues -> + Column( + verticalArrangement = Arrangement.spacedBy(8.dp), + modifier = Modifier + .padding(paddingValues) + .padding(horizontal = 12.dp, vertical = 14.dp) + ) { + val uriHandler = LocalUriHandler.current + val viewModel: AboutViewModel = viewModel() + TeamCard() + ContributorsCard(viewModel.contributors) + + Spacer(Modifier.weight(1f, true)) + + Button( + modifier = Modifier.fillMaxWidth(), + onClick = { uriHandler.openUri("https://github.com/Aliucord/Aliucord") } + ) { + Icon( + painter = painterResource(R.drawable.ic_account_github_white_24dp), + contentDescription = "GitHub" + ) + Spacer(Modifier.padding(4.dp)) + Text(stringResource(R.string.github)) + } } } } diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/screen/CommitsScreen.kt b/app/src/main/kotlin/com/aliucord/manager/ui/screen/CommitsScreen.kt deleted file mode 100644 index a3ad8a9..0000000 --- a/app/src/main/kotlin/com/aliucord/manager/ui/screen/CommitsScreen.kt +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (c) 2022 Juby210 & zt - * Licensed under the Open Software License version 3.0 - */ - -package com.aliucord.manager.ui.screen - -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.material3.* -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalUriHandler -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp -import androidx.paging.* -import androidx.paging.compose.collectAsLazyPagingItems -import androidx.paging.compose.items -import com.aliucord.manager.R -import com.aliucord.manager.model.github.Commit -import com.aliucord.manager.util.Github -import com.ramcosta.composedestinations.annotation.Destination - -@OptIn(ExperimentalMaterial3Api::class) -@Destination -@Composable -fun CommitsScreen() { - val pager = remember { - Pager( - PagingConfig( - pageSize = 30, - enablePlaceholders = true, - maxSize = 200 - ) - ) { - object : PagingSource() { - override suspend fun load(params: LoadParams): LoadResult { - val pageNumber = params.key ?: 0 - - val response = Github.getCommits("page" to pageNumber.toString()) - val prevKey = if (pageNumber > 0) pageNumber - 1 else null - val nextKey = if (response.isNotEmpty()) pageNumber + 1 else null - - return LoadResult.Page( - data = response, - prevKey = prevKey, - nextKey = nextKey - ) - } - - override fun getRefreshKey(state: PagingState) = state.anchorPosition?.let { - state.closestPageToPosition(it)?.prevKey?.plus(1) ?: state.closestPageToPosition(it)?.nextKey?.minus(1) - } - } - } - } - - val lazyPagingItems = pager.flow.collectAsLazyPagingItems() - - LazyColumn( - modifier = Modifier.fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(14.dp) - ) { - if (lazyPagingItems.loadState.refresh == LoadState.Loading) item { - Text( - text = stringResource(R.string.paging_initial_load), - modifier = Modifier - .fillMaxWidth() - .wrapContentWidth(Alignment.CenterHorizontally) - ) - } - - items(lazyPagingItems) { commitData -> - if (commitData == null) return@items - - val localUriHandler = LocalUriHandler.current - - ListItem( - modifier = Modifier.clickable { localUriHandler.openUri(commitData.htmlUrl) }, - overlineText = { Text(commitData.sha.substring(0, 7)) }, - headlineText = { Text("${commitData.commit.message.split("\n").first()} - ${commitData.author.name}") } - ) - } - - if (lazyPagingItems.loadState.append == LoadState.Loading) item { - CircularProgressIndicator( - modifier = Modifier - .fillMaxWidth() - .wrapContentWidth(Alignment.CenterHorizontally) - ) - } - } -} diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/screen/HomeRootScreen.kt b/app/src/main/kotlin/com/aliucord/manager/ui/screen/HomeRootScreen.kt new file mode 100644 index 0000000..7369678 --- /dev/null +++ b/app/src/main/kotlin/com/aliucord/manager/ui/screen/HomeRootScreen.kt @@ -0,0 +1,171 @@ +package com.aliucord.manager.ui.screen + +import android.content.res.Configuration +import androidx.compose.animation.* +import androidx.compose.foundation.layout.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.* +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalUriHandler +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.DpOffset +import androidx.compose.ui.unit.dp +import com.aliucord.manager.BuildConfig +import com.aliucord.manager.R +import com.aliucord.manager.ui.navigation.AppDestination +import com.aliucord.manager.ui.navigation.HomeDestination +import com.xinto.taxi.* + +@OptIn(ExperimentalMaterial3Api::class, ExperimentalAnimationApi::class) +@Composable +fun MainRootScreen(navigator: BackstackNavigator) { + val mainRootNavigator = rememberNavigator(HomeDestination.HOME) + val currentDestination = mainRootNavigator.currentDestination + val orientation = LocalConfiguration.current.orientation + var isMenuExpanded by remember { mutableStateOf(false) } + + Scaffold( + modifier = Modifier + .fillMaxSize(), + topBar = { + MediumTopAppBar( + title = { + Text(stringResource(mainRootNavigator.currentDestination.label)) + }, + actions = { + val localUriHandler = LocalUriHandler.current + + IconButton( + onClick = { + localUriHandler.openUri("https://discord.gg/${BuildConfig.SUPPORT_SERVER}") + }, + ) { + Icon( + painter = painterResource(R.drawable.ic_discord), + contentDescription = stringResource(R.string.support_server), + tint = MaterialTheme.colorScheme.onBackground + ) + } + + Box( + modifier = Modifier.wrapContentSize(Alignment.TopEnd) + ) { + IconButton( + onClick = { isMenuExpanded = !isMenuExpanded } + ) { + Icon( + imageVector = Icons.Default.MoreVert, + contentDescription = stringResource(R.string.show_menu), + tint = MaterialTheme.colorScheme.onBackground + ) + } + + DropdownMenu( + offset = DpOffset(x = 12.dp, y = 0.dp), + expanded = isMenuExpanded, + onDismissRequest = { isMenuExpanded = false } + ) { + DropdownMenuItem( + onClick = { + isMenuExpanded = false + navigator.push(AppDestination.About) + }, + leadingIcon = { + Icon( + imageVector = Icons.Default.Info, + contentDescription = "About" + ) + }, + text = { Text(stringResource(R.string.about)) } + ) + + DropdownMenuItem( + onClick = { + isMenuExpanded = false + navigator.push(AppDestination.Settings) + }, + leadingIcon = { + Icon( + imageVector = Icons.Default.Settings, + contentDescription = "Settings" + ) + }, + text = { Text(stringResource(R.string.settings)) } + ) + } + } + } + ) + }, + bottomBar = { + if (orientation == Configuration.ORIENTATION_PORTRAIT) { + NavigationBar { + + HomeDestination.values().forEach { destination -> + NavigationBarItem( + selected = currentDestination == destination, + icon = { + if (currentDestination == destination) Icon( + destination.selectedIcon, + stringResource(destination.label) + ) else Icon(destination.unselectedIcon, stringResource(destination.label)) + }, + label = { Text(stringResource(destination.label)) }, + onClick = { mainRootNavigator.replace(destination) } + ) + } + } + } + } + ) { paddingValues -> + Row( + modifier = Modifier + .padding(paddingValues) + .padding(horizontal = 12.dp, vertical = 14.dp) + ) { + if (orientation == Configuration.ORIENTATION_LANDSCAPE) { + NavRail( + currentDestination = currentDestination, + onClickDestination = mainRootNavigator::replace + ) + } + + Taxi( + modifier = Modifier.weight(1f, true), + navigator = mainRootNavigator, + transitionSpec = { fadeIn() with fadeOut() } + ) { destination -> + when (destination) { + HomeDestination.HOME -> HomeScreen(navigator = navigator) + HomeDestination.PLUGINS -> PluginsScreen(navigator = navigator) + } + } + } + + } +} + +@Composable +fun NavRail( + currentDestination: HomeDestination, + onClickDestination: (HomeDestination) -> Unit +) = NavigationRail { + HomeDestination.values().forEach { destination -> + NavigationRailItem( + selected = currentDestination == destination, + icon = { + if (currentDestination == destination) Icon( + destination.selectedIcon, + stringResource(destination.label) + ) else Icon(destination.unselectedIcon, stringResource(destination.label)) + }, + label = { Text(stringResource(destination.label)) }, + onClick = { onClickDestination(destination) } + ) + } +} diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/screen/HomeScreen.kt b/app/src/main/kotlin/com/aliucord/manager/ui/screen/HomeScreen.kt index d00f26c..9ccd3a6 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/screen/HomeScreen.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/screen/HomeScreen.kt @@ -8,36 +8,38 @@ package com.aliucord.manager.ui.screen import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Delete import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.* import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.paging.* +import androidx.paging.compose.collectAsLazyPagingItems +import androidx.paging.compose.items import com.aliucord.manager.R +import com.aliucord.manager.model.github.Commit import com.aliucord.manager.preferences.Prefs -import com.aliucord.manager.ui.component.PluginsList import com.aliucord.manager.ui.component.installer.DownloadMethod import com.aliucord.manager.ui.component.installer.InstallerDialog -import com.aliucord.manager.ui.screen.destinations.CommitsScreenDestination -import com.aliucord.manager.ui.screen.destinations.InstallerScreenDestination +import com.aliucord.manager.ui.navigation.AppDestination import com.aliucord.manager.ui.viewmodel.HomeViewModel -import com.ramcosta.composedestinations.annotation.Destination -import com.ramcosta.composedestinations.annotation.RootNavGraph -import com.ramcosta.composedestinations.navigation.DestinationsNavigator +import com.aliucord.manager.util.Github +import com.xinto.taxi.BackstackNavigator @OptIn(ExperimentalMaterial3Api::class) -@RootNavGraph(start = true) -@Destination @Composable -fun HomeScreen(navigator: DestinationsNavigator) { +fun HomeScreen(navigator: BackstackNavigator) { val viewModel: HomeViewModel = viewModel() var showOptionsDialog by remember { mutableStateOf(false) } @@ -46,14 +48,14 @@ fun HomeScreen(navigator: DestinationsNavigator) { val filePicker = rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) { uri -> if (uri == null) return@rememberLauncherForActivityResult - navigator.navigate(InstallerScreenDestination) + navigator.push(AppDestination.Install) } InstallerDialog( onDismissRequest = { showOptionsDialog = false }, onConfirm = { method -> if (method == DownloadMethod.DOWNLOAD) { - navigator.navigate(InstallerScreenDestination()) + navigator.push(AppDestination.Install) } else { filePicker.launch(arrayOf("application/octet-stream")) } @@ -75,7 +77,7 @@ fun HomeScreen(navigator: DestinationsNavigator) { ) { Column { Text( - "Aliucord${Prefs.packageName.get().let { if (it != "com.aliucord") " ($it)" else ""}}", + "Aliucord${Prefs.packageName.get().let { if (it != "com.aliucord") " ($it)" else "" }}", style = MaterialTheme.typography.titleLarge, color = MaterialTheme.colorScheme.primary ) @@ -114,7 +116,7 @@ fun HomeScreen(navigator: DestinationsNavigator) { if (Prefs.devMode.get()) { showOptionsDialog = true } else { - navigator.navigate(InstallerScreenDestination()) + navigator.push(AppDestination.Install) } } ) { @@ -127,19 +129,6 @@ fun HomeScreen(navigator: DestinationsNavigator) { Text(stringResource(description)) } - - if (viewModel.installedVersion == "-") Button( - modifier = Modifier.wrapContentSize(), - onClick = { navigator.navigate(CommitsScreenDestination) }, - colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.secondaryContainer) - ) { - Icon( - modifier = Modifier.padding(8.dp), - painter = painterResource(R.drawable.ic_update_24dp), - contentDescription = "Commits", - tint = MaterialTheme.colorScheme.onSecondaryContainer - ) - } } if (viewModel.installedVersion != "-") { @@ -149,7 +138,7 @@ fun HomeScreen(navigator: DestinationsNavigator) { horizontalArrangement = Arrangement.spacedBy(12.dp) ) { CompositionLocalProvider( - LocalContentColor provides MaterialTheme.colorScheme.secondaryContainer + LocalContentColor provides MaterialTheme.colorScheme.secondaryContainer ) { FilledTonalButton( modifier = Modifier.weight(1f), @@ -174,23 +163,41 @@ fun HomeScreen(navigator: DestinationsNavigator) { contentDescription = "Launch" ) } - - FilledTonalButton( - modifier = Modifier.weight(1f), - onClick = { navigator.navigate(CommitsScreenDestination) } - ) { - Icon( - modifier = Modifier.padding(8.dp), - painter = painterResource(R.drawable.ic_update_24dp), - contentDescription = "Commits" - ) - } } } } } } } + val pager = remember { + Pager( + PagingConfig( + pageSize = 30, + enablePlaceholders = true, + maxSize = 200 + ) + ) { + object : PagingSource() { + override suspend fun load(params: LoadParams): LoadResult { + val pageNumber = params.key ?: 0 + + val response = Github.getCommits("page" to pageNumber.toString()) + val prevKey = if (pageNumber > 0) pageNumber - 1 else null + val nextKey = if (response.isNotEmpty()) pageNumber + 1 else null + + return LoadResult.Page( + data = response, + prevKey = prevKey, + nextKey = nextKey + ) + } + + override fun getRefreshKey(state: PagingState) = state.anchorPosition?.let { + state.closestPageToPosition(it)?.prevKey?.plus(1) ?: state.closestPageToPosition(it)?.nextKey?.minus(1) + } + } + } + } ElevatedCard( Modifier @@ -198,22 +205,55 @@ fun HomeScreen(navigator: DestinationsNavigator) { .weight(1f) ) { Column( - modifier = Modifier.padding(16.dp), + modifier = Modifier.padding(16.dp, 16.dp, 16.dp), verticalArrangement = Arrangement.spacedBy(8.dp) ) { Row( verticalAlignment = Alignment.CenterVertically ) { Text( - text = "Plugins", + text = stringResource(R.string.commits), style = MaterialTheme.typography.titleLarge, color = MaterialTheme.colorScheme.primary ) Spacer(Modifier.weight(1f, true)) } + val lazyPagingItems = pager.flow.collectAsLazyPagingItems() + LazyColumn( + modifier = Modifier + .fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(14.dp) + ) { + if (lazyPagingItems.loadState.refresh == LoadState.Loading) item { + Text( + text = stringResource(R.string.paging_initial_load), + modifier = Modifier + .fillMaxWidth() + .wrapContentWidth(Alignment.CenterHorizontally) + ) + } - PluginsList() + items(lazyPagingItems) { commitData -> + if (commitData == null) return@items + + val localUriHandler = LocalUriHandler.current + + ListItem( + modifier = Modifier.clickable { localUriHandler.openUri(commitData.htmlUrl) }, + overlineText = { Text(commitData.sha.substring(0, 7)) }, + headlineText = { Text("${commitData.commit.message.split("\n").first()} - ${commitData.author.name}") } + ) + } + + if (lazyPagingItems.loadState.append == LoadState.Loading) item { + CircularProgressIndicator( + modifier = Modifier + .fillMaxWidth() + .wrapContentWidth(Alignment.CenterHorizontally) + ) + } + } } } } diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/screen/InstallScreen.kt b/app/src/main/kotlin/com/aliucord/manager/ui/screen/InstallScreen.kt index 4ac92c0..5acd937 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/screen/InstallScreen.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/screen/InstallScreen.kt @@ -5,47 +5,69 @@ package com.aliucord.manager.ui.screen +import android.os.Build +import androidx.annotation.RequiresApi import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.material3.LinearProgressIndicator -import androidx.compose.material3.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.NavigateBefore +import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel -import com.aliucord.manager.ui.screen.destinations.HomeScreenDestination import com.aliucord.manager.ui.viewmodel.InstallViewModel -import com.ramcosta.composedestinations.annotation.Destination -import com.ramcosta.composedestinations.navigation.DestinationsNavigator -@OptIn(ExperimentalFoundationApi::class) -@Destination +@RequiresApi(Build.VERSION_CODES.O) +@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class) @Composable -fun InstallerScreen(navigator: DestinationsNavigator) { +fun InstallerScreen( + onClickBack: () -> Unit +) { val viewModel: InstallViewModel = viewModel() val navigateMain by viewModel.returnToHome.collectAsState(initial = false) - if (navigateMain) navigator.navigate(HomeScreenDestination) + if (navigateMain) onClickBack LaunchedEffect(Unit) { viewModel.startInstallation() } - LazyColumn( - modifier = Modifier.fillMaxSize(), - ) { - stickyHeader { - LinearProgressIndicator( - modifier = Modifier.fillParentMaxWidth() + + Scaffold( + topBar = { + MediumTopAppBar( + title = { Text("Installer") }, + navigationIcon = { + IconButton(onClick = onClickBack) { + Icon( + imageVector = Icons.Default.NavigateBefore, + contentDescription = "Back" + ) + } + } ) } - item { - Text( - text = viewModel.log, - modifier = Modifier.padding(8.dp) - ) + ) { paddingValues -> + LazyColumn( + modifier = Modifier + .fillMaxWidth() + .padding(paddingValues) + .padding(horizontal = 12.dp, vertical = 14.dp) + ) { + stickyHeader { + LinearProgressIndicator( + modifier = Modifier.fillParentMaxWidth() + ) + } + item { + Text( + text = viewModel.log, + modifier = Modifier.padding(8.dp) + ) + } } } } diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/screen/PluginsScreen.kt b/app/src/main/kotlin/com/aliucord/manager/ui/screen/PluginsScreen.kt new file mode 100644 index 0000000..1ba8f1d --- /dev/null +++ b/app/src/main/kotlin/com/aliucord/manager/ui/screen/PluginsScreen.kt @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2022 Juby210 & zt + * Licensed under the Open Software License version 3.0 + */ + +package com.aliucord.manager.ui.screen + +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.* +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Dialog +import com.aliucord.manager.R +import com.aliucord.manager.model.Plugin +import com.aliucord.manager.ui.component.plugins.Changelog +import com.aliucord.manager.ui.component.plugins.PluginCard +import com.aliucord.manager.ui.navigation.AppDestination +import com.xinto.taxi.BackstackNavigator + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun PluginsScreen(navigator: BackstackNavigator) { + Column( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + var plugins by remember { mutableStateOf(Plugin.loadAll()) } + val focusManager = LocalFocusManager.current + var search by remember { mutableStateOf("") } + + if (plugins.isNotEmpty()) { + OutlinedTextField( + maxLines = 1, + value = search, + onValueChange = { search = it }, + singleLine = true, + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(12.dp), + label = { Text(stringResource(R.string.search)) }, + trailingIcon = { + if (search.isNotBlank()) { + IconButton(onClick = { search = "" }) { + Icon( + imageVector = Icons.Default.Close, + contentDescription = stringResource(R.string.clear) + ) + } + } else { + Icon(Icons.Default.Search, stringResource(R.string.search_plugins)) + } + }, + keyboardOptions = KeyboardOptions(autoCorrect = false, imeAction = ImeAction.Search), + keyboardActions = KeyboardActions { focusManager.clearFocus() }, + ) + + LazyColumn( + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + items(plugins.filter { plugin -> + plugin.manifest.run { + name.contains(search, true) + || description.contains(search, true) + || authors.any { a -> a.name.contains(search, true) } + } + }) { plugin -> + var showDeleteDialog by remember { mutableStateOf(false) } + var showChangelogDialog by remember { mutableStateOf(false) } + + if (showDeleteDialog) AlertDialog( + onDismissRequest = { showDeleteDialog = false }, + icon = { + Icon( + imageVector = Icons.Default.Delete, + contentDescription = stringResource(R.string.uninstall_plugin, plugin.manifest.name) + ) + }, + title = { Text(stringResource(R.string.uninstall_plugin, plugin.manifest.name)) }, + text = { Text(stringResource(R.string.delete_plugin_body)) }, + confirmButton = { + Button( + onClick = { + showDeleteDialog = false + plugin.file.delete() + plugins = plugins.filter { it != plugin } + } + ) { + Text(stringResource(android.R.string.ok)) + } + }, + dismissButton = { + Button( + onClick = { showDeleteDialog = false }, + colors = ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.secondaryContainer, + contentColor = MaterialTheme.colorScheme.onSecondaryContainer + ) + ) { + Text(stringResource(android.R.string.cancel)) + } + } + ) + + if (showChangelogDialog) Dialog( + onDismissRequest = { showChangelogDialog = false }, + content = { Changelog(plugin, onDismiss = { showChangelogDialog = false }) } + ) + + PluginCard( + plugin = plugin, + onDelete = { showDeleteDialog = true }, + onShowChangelog = { showChangelogDialog = true } + ) + } + } + } else { + Text(stringResource(R.string.no_plugins_installed)) + } + } +} diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/screen/SettingsScreen.kt b/app/src/main/kotlin/com/aliucord/manager/ui/screen/SettingsScreen.kt index 6e07671..fbeb3d0 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/screen/SettingsScreen.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/screen/SettingsScreen.kt @@ -10,6 +10,8 @@ import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.NavigateBefore import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Modifier @@ -21,91 +23,110 @@ import com.aliucord.manager.R import com.aliucord.manager.preferences.Prefs import com.aliucord.manager.ui.component.settings.* import com.aliucord.manager.ui.viewmodel.SettingsViewModel -import com.ramcosta.composedestinations.annotation.Destination @OptIn(ExperimentalMaterial3Api::class) -@Destination @Composable -fun SettingsScreen() = Column( - modifier = Modifier.verticalScroll(state = rememberScrollState()), - verticalArrangement = Arrangement.spacedBy(12.dp) -) { - val viewModifier: SettingsViewModel = viewModel() - - var showThemeDialog by remember { mutableStateOf(false) } - - if (showThemeDialog) { - ThemeDialog( - onDismissRequest = { showThemeDialog = false }, - onConfirm = { Prefs.theme.set(it.ordinal) } - ) - } - - ThemeSetting(onClick = { showThemeDialog = true }) - - PreferenceItem( - title = { Text(stringResource(R.string.use_black)) }, - description = { Text(stringResource(R.string.use_black_description)) }, - preference = Prefs.useBlack - ) - - Divider() - - OutlinedTextField( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 10.dp), - value = Prefs.appName.get(), - onValueChange = Prefs.appName::set, - label = { Text(stringResource(R.string.app_name_setting)) }, - placeholder = { Text("Aliucord") }, - singleLine = true - ) - - PreferenceItem( - title = { Text(stringResource(R.string.replace_bg)) }, - preference = Prefs.replaceBg - ) - - val devModeOn = Prefs.devMode.get() - - PreferenceItem( - title = { Text(stringResource(R.string.developer_mode)) }, - preference = Prefs.devMode - ) - - AnimatedVisibility( - visible = devModeOn, - exit = shrinkVertically() - ) { +fun SettingsScreen(onClickBack: () -> Unit) { + Scaffold( + topBar = { + MediumTopAppBar( + title = { Text(stringResource(R.string.settings)) }, + navigationIcon = { + IconButton(onClick = onClickBack) { + Icon( + imageVector = Icons.Default.NavigateBefore, + contentDescription = stringResource(R.string.back) + ) + } + } + ) + } + ) { paddingValues -> Column( + modifier = Modifier + .padding(paddingValues) + .padding(horizontal = 12.dp, vertical = 14.dp) + .verticalScroll(state = rememberScrollState()), verticalArrangement = Arrangement.spacedBy(12.dp) ) { + val viewModifier: SettingsViewModel = viewModel() + + var showThemeDialog by remember { mutableStateOf(false) } + + if (showThemeDialog) { + ThemeDialog( + onDismissRequest = { showThemeDialog = false }, + onConfirm = { Prefs.theme.set(it.ordinal) } + ) + } + + ThemeSetting(onClick = { showThemeDialog = true }) + + PreferenceItem( + title = { Text(stringResource(R.string.use_black)) }, + description = { Text(stringResource(R.string.use_black_description)) }, + preference = Prefs.useBlack + ) + + Divider() + OutlinedTextField( modifier = Modifier .fillMaxWidth() - .padding(horizontal = 8.dp), - value = Prefs.packageName.get(), - onValueChange = Prefs.packageName::set, - label = { Text(stringResource(R.string.package_name)) }, - placeholder = { Text("com.aliucord") }, + .padding(horizontal = 10.dp), + value = Prefs.appName.get(), + onValueChange = Prefs.appName::set, + label = { Text(stringResource(R.string.app_name_setting)) }, + placeholder = { Text("Aliucord") }, singleLine = true ) PreferenceItem( - title = { Text(stringResource(R.string.debuggable)) }, - description = { Text(stringResource(R.string.debuggable_description)) }, - preference = Prefs.debuggable + title = { Text(stringResource(R.string.replace_bg)) }, + preference = Prefs.replaceBg ) + + val devModeOn = Prefs.devMode.get() + + PreferenceItem( + title = { Text(stringResource(R.string.developer_mode)) }, + preference = Prefs.devMode + ) + + AnimatedVisibility( + visible = devModeOn, + exit = shrinkVertically() + ) { + Column( + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + OutlinedTextField( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 8.dp), + value = Prefs.packageName.get(), + onValueChange = Prefs.packageName::set, + label = { Text(stringResource(R.string.package_name)) }, + placeholder = { Text("com.aliucord") }, + singleLine = true + ) + + PreferenceItem( + title = { Text(stringResource(R.string.debuggable)) }, + description = { Text(stringResource(R.string.debuggable_description)) }, + preference = Prefs.debuggable + ) + } + } + + Button( + modifier = Modifier + .fillMaxWidth() + .padding(8.dp), + onClick = viewModifier::clearCacheDir + ) { + Text(stringResource(R.string.clear_files_cache), textAlign = TextAlign.Center) + } } } - - Button( - modifier = Modifier - .fillMaxWidth() - .padding(8.dp), - onClick = viewModifier::clearCacheDir - ) { - Text(stringResource(R.string.clear_files_cache), textAlign = TextAlign.Center) - } } diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/screen/StoreScreen.kt b/app/src/main/kotlin/com/aliucord/manager/ui/screen/StoreScreen.kt index 8780924..0a03f59 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/screen/StoreScreen.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/screen/StoreScreen.kt @@ -8,13 +8,16 @@ package com.aliucord.manager.ui.screen import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.NavigateBefore import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import com.aliucord.manager.R import com.aliucord.manager.util.httpClient -import com.ramcosta.composedestinations.annotation.Destination import io.ktor.client.call.* import io.ktor.client.request.* import kotlinx.coroutines.Dispatchers @@ -32,33 +35,53 @@ private data class Tree( ) } -@Destination +@OptIn(ExperimentalMaterial3Api::class) @Composable -fun StoreScreen() { +fun StoreScreen(onClickBack: () -> Unit) { val plugins = remember { mutableStateListOf() } - // TODO: Display cards with option to install plugin - Column { - if (plugins.isEmpty()) { - Box( - modifier = Modifier.fillMaxSize().padding(12.dp), - contentAlignment = Alignment.Center, - content = { CircularProgressIndicator() } - ) - - LaunchedEffect(Unit) { - launch(Dispatchers.IO) { - val content = httpClient.get("https://api.github.com/repos/DiamondMiner88/AllACPlugins/git/trees/master") { - header("accept", "application/json") - }.body() - - plugins.addAll(content.tree.filter { it.mode == "160000" }) + Scaffold( + topBar = { + MediumTopAppBar( + title = { Text(stringResource(R.string.store)) }, + navigationIcon = { + IconButton(onClick = onClickBack) { + Icon( + imageVector = Icons.Default.NavigateBefore, + contentDescription = stringResource(R.string.back) + ) + } } - } - } else { - LazyColumn { - items(plugins) { item -> - Text("SHA: ${item.sha}", style = MaterialTheme.typography.labelSmall) + ) + } + ) { paddingValues -> + Column( + modifier = Modifier + .padding(paddingValues) + ) { + if (plugins.isEmpty()) { + Box( + modifier = Modifier + .fillMaxSize() + .padding(12.dp), + contentAlignment = Alignment.Center, + content = { CircularProgressIndicator() } + ) + + LaunchedEffect(Unit) { + launch(Dispatchers.IO) { + val content = httpClient.get("https://api.github.com/repos/DiamondMiner88/AllACPlugins/git/trees/master") { + header("accept", "application/json") + }.body() + + plugins.addAll(content.tree.filter { it.mode == "160000" }) + } + } + } else { + LazyColumn { + items(plugins) { item -> + Text("SHA: ${item.sha}", style = MaterialTheme.typography.labelSmall) + } } } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 901785e..50ee3b4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -48,4 +48,5 @@ Changelog Media App Name Package Name + Clear