feat: redesign navigation and screen layout (#11)
* feat: redesign navigation and screen layout * chore: remove ksp
This commit is contained in:
parent
4a0027c638
commit
306ac35635
16 changed files with 709 additions and 587 deletions
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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>(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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Int, Commit>() {
|
||||
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Commit> {
|
||||
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<Int, Commit>) = 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)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<AppDestination>) {
|
||||
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) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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<AppDestination>) {
|
||||
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<Int, Commit>() {
|
||||
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Commit> {
|
||||
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<Int, Commit>) = 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)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<AppDestination>) {
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Tree.File>() }
|
||||
|
||||
// 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<Tree>()
|
||||
|
||||
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<Tree>()
|
||||
|
||||
plugins.addAll(content.tree.filter { it.mode == "160000" })
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LazyColumn {
|
||||
items(plugins) { item ->
|
||||
Text("SHA: ${item.sha}", style = MaterialTheme.typography.labelSmall)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,4 +48,5 @@
|
|||
<string name="changelog_media">Changelog Media</string>
|
||||
<string name="app_name_setting">App Name</string>
|
||||
<string name="package_name">Package Name</string>
|
||||
<string name="clear">Clear</string>
|
||||
</resources>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue