Improve ui (#23)

Co-authored-by: wingio <wingio@users.noreply.github.com>
Co-authored-by: Diamond <33725716+DiamondMiner88@users.noreply.github.com>
This commit is contained in:
Wing 2022-10-10 19:51:32 -04:00 • committed by GitHub
parent 15610c8809
commit 5a7698c049
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 877 additions and 614 deletions

View file

@ -60,6 +60,7 @@ android {
kotlinOptions {
jvmTarget = "11"
freeCompilerArgs += "-Xcontext-receivers"
}
buildFeatures.compose = true
@ -67,7 +68,6 @@ android {
}
dependencies {
implementation(fileTree("./libs"))
// AndroidX
implementation("androidx.core:core-ktx:1.9.0")
@ -104,7 +104,6 @@ dependencies {
implementation("com.github.X1nto:Taxi:1.2.0")
implementation("org.bouncycastle:bcpkix-jdk15on:1.70")
implementation("com.android:zipflinger:8.0.0-alpha01")
// implementation("com.github.Aliucord:libzip:1.0.0")
implementation("com.aliucord:axml:1.0.1")
implementation("com.android.tools.build:apksig:7.4.0-beta01")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.0")

Binary file not shown.

View file

@ -17,11 +17,11 @@ import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import com.aliucord.manager.domain.manager.PreferencesManager
import com.aliucord.manager.ui.dialog.StoragePermissionsDialog
import com.aliucord.manager.ui.navigation.AppDestination
import com.aliucord.manager.ui.navigation.HomeDestination
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
import com.xinto.taxi.*
import org.koin.android.ext.android.inject
val aliucordDir = Environment.getExternalStorageDirectory().resolve("Aliucord")
@ -41,9 +41,15 @@ class MainActivity : ComponentActivity() {
isDynamicColor = preferences.dynamicColor
) {
val navigator = rememberBackstackNavigator<AppDestination>(AppDestination.Home)
val homeRootNavigator = rememberNavigator(HomeDestination.HOME)
BackHandler {
if (!navigator.pop()) finish()
if (!navigator.pop()) {
if (homeRootNavigator.currentDestination == HomeDestination.HOME)
finish()
else
homeRootNavigator.replace(HomeDestination.HOME)
}
}
StoragePermissionsDialog()
@ -55,20 +61,21 @@ class MainActivity : ComponentActivity() {
) { destination ->
when (destination) {
is AppDestination.Home -> MainRootScreen(
onClickInstall = { navigator.push(AppDestination.Install(it)) },
onClickAbout = { navigator.push(AppDestination.About) },
onClickSettings = { navigator.push(AppDestination.Settings) }
mainRootNavigator = homeRootNavigator,
onInstallClick = { navigator.push(AppDestination.Install(it)) },
onAboutClick = { navigator.push(AppDestination.About) }
)
is AppDestination.Install -> InstallerScreen(
installData = destination.installData,
onClickBack = navigator::pop
)
is AppDestination.Settings -> SettingsScreen(
onClickBack = navigator::pop
onBackClick = navigator::pop
)
is AppDestination.About -> AboutScreen(
onClickBack = navigator::pop
onBackClick = navigator::pop
)
else -> {}
}
}
}

View file

@ -11,7 +11,7 @@ import org.koin.dsl.module
import java.net.Inet4Address
import java.net.InetAddress
val httpModule = module {
val httpModule = module {
fun provideJson() = Json {
ignoreUnknownKeys = true
}

View file

@ -1,7 +1,6 @@
package com.aliucord.manager.di
import com.aliucord.manager.ui.viewmodel.*
import org.koin.androidx.compose.get
import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.androidx.viewmodel.dsl.viewModelOf
import org.koin.dsl.module
@ -10,6 +9,6 @@ val viewModelModule = module {
viewModelOf(::HomeViewModel)
viewModelOf(::PluginsViewModel)
viewModelOf(::AboutViewModel)
viewModel { params -> InstallViewModel(get(), get(), get(), get(), params.get())}
viewModel { params -> InstallViewModel(get(), get(), get(), get(), params.get()) }
viewModelOf(::SettingsViewModel)
}

View file

@ -5,7 +5,6 @@ import android.content.*
import androidx.core.content.getSystemService
import androidx.core.net.toUri
import java.io.File
import java.net.URL
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine

View file

@ -1,10 +1,7 @@
package com.aliucord.manager.domain.repository
import com.aliucord.manager.network.dto.GithubUser
import com.aliucord.manager.network.dto.Version
import com.aliucord.manager.network.service.GithubService
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
class GithubRepository(
private val service: GithubService

View file

@ -18,16 +18,19 @@ class InstallService : Service() {
startActivity(confirmationIntent)
}
PackageInstaller.STATUS_SUCCESS -> Toast.makeText(
this,
"Successfully installed Aliucord!",
Toast.LENGTH_LONG
this,
"Successfully installed Aliucord!",
Toast.LENGTH_LONG
).show()
PackageInstaller.STATUS_FAILURE_ABORTED -> Toast.makeText(
this,
"Aborted installation",
Toast.LENGTH_LONG
).show()
else -> {
Log.i(BuildConfig.TAG, "Installation failed: error code $statusCode")
Toast.makeText(this, "Failed: Error $statusCode", Toast.LENGTH_LONG).show()

View file

@ -100,6 +100,7 @@ object ManifestPatcher {
)
}
}
else -> visitor
}
}

View file

@ -1,16 +0,0 @@
/*
* Copyright (c) 2022 Juby210 & zt
* Licensed under the Open Software License version 3.0
*/
package com.aliucord.manager.installer.util
import com.aliucord.libzip.Zip
import java.io.InputStream
fun Zip.writeEntry(entryName: String, stream: InputStream) = writeEntry(entryName, stream.readBytes())
fun Zip.writeEntry(entryName: String, bytes: ByteArray) {
openEntry(entryName)
writeEntry(bytes, bytes.size.toLong())
closeEntry()
}

View file

@ -18,6 +18,7 @@ data class Commit(
) {
@Serializable
data class Author(@SerialName("login") val name: String)
@Serializable
data class Commit(val message: String)
}

View file

@ -0,0 +1,90 @@
/*
* Copyright (c) 2022 Juby210 & zt
* Licensed under the Open Software License version 3.0
*/
package com.aliucord.manager.ui.component
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage
import com.aliucord.manager.R
import com.aliucord.manager.network.dto.GithubUser
fun LazyListScope.contributors(contributors: List<GithubUser>) {
item {
Text(
text = stringResource(R.string.contributors),
style = MaterialTheme.typography.titleLarge,
color = MaterialTheme.colorScheme.primary,
modifier = Modifier
.padding(start = 16.dp, bottom = 16.dp)
)
}
if (contributors.isNotEmpty()) {
items(contributors) { user ->
ContributorEntry(user = user)
}
} else {
item {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
contentAlignment = Alignment.Center,
content = { CircularProgressIndicator() }
)
}
}
}
@Composable
fun ContributorEntry(user: GithubUser) {
val uriHandler = LocalUriHandler.current
Row(
modifier = Modifier
.clickable {
uriHandler.openUri("https://github.com/${user.name}")
}
.padding(horizontal = 16.dp, vertical = 10.dp)
.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalAlignment = Alignment.CenterVertically
) {
AsyncImage(
model = "https://github.com/${user.name}.png",
contentDescription = user.name,
Modifier
.size(45.dp)
.clip(CircleShape)
)
Column {
Text(
text = user.name,
style = MaterialTheme.typography.bodyLarge
)
Text(
text = stringResource(R.string.contributions, user.contributions),
style = MaterialTheme.typography.bodyMedium.copy(
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
)
)
}
}
}

View file

@ -1,73 +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.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.grid.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage
import com.aliucord.manager.R
import com.aliucord.manager.network.dto.GithubUser
@Composable
fun ContributorsCard(contributors: List<GithubUser>) {
ElevatedCard {
Column(
modifier = Modifier
.padding(16.dp)
.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
Text(
text = stringResource(R.string.contributors),
style = MaterialTheme.typography.titleLarge,
color = MaterialTheme.colorScheme.primary
)
if (contributors.isNotEmpty()) {
LazyVerticalGrid(
columns = GridCells.Adaptive(48.dp),
verticalArrangement = Arrangement.spacedBy(4.dp),
horizontalArrangement = Arrangement.spacedBy(4.dp),
userScrollEnabled = false
) {
items(contributors) { user -> ContributorEntry(user.name) }
}
} else {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(12.dp),
contentAlignment = Alignment.Center,
content = { CircularProgressIndicator() }
)
}
}
}
}
@Composable
fun ContributorEntry(name: String) {
val uriHandler = LocalUriHandler.current
AsyncImage(
modifier = Modifier
.fillMaxSize()
.clip(CircleShape)
.clickable { uriHandler.openUri("https://github.com/$name") },
model = "https://github.com/$name.png",
contentDescription = name
)
}

View file

@ -0,0 +1,152 @@
package com.aliucord.manager.ui.component.home
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalUriHandler
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.compose.ui.unit.sp
import androidx.paging.LoadState
import androidx.paging.PagingData
import androidx.paging.compose.collectAsLazyPagingItems
import androidx.paging.compose.items
import coil.compose.AsyncImage
import com.aliucord.manager.R
import com.aliucord.manager.network.dto.Commit
import kotlinx.coroutines.flow.Flow
@Composable
fun CommitList(
commits: Flow<PagingData<Commit>>
) {
ElevatedCard(
modifier = Modifier.padding(bottom = 16.dp)
) {
Column(
modifier = Modifier
.weight(1f, true)
.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
val lazyPagingItems = commits.collectAsLazyPagingItems()
Text(
modifier = Modifier.padding(20.dp, 20.dp, 20.dp),
text = stringResource(R.string.commits),
style = MaterialTheme.typography.titleLarge.copy(fontSize = 23.sp),
color = MaterialTheme.colorScheme.primary
)
LazyColumn(
modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(10.dp)
) {
if (lazyPagingItems.loadState.refresh == LoadState.Loading) {
item {
Text(
modifier = Modifier
.fillMaxWidth()
.wrapContentWidth(Alignment.CenterHorizontally),
text = stringResource(R.string.paging_initial_load)
)
}
}
items(lazyPagingItems) { commitData ->
if (commitData == null) return@items
CommitItem(commitData)
}
if (lazyPagingItems.loadState.append == LoadState.Loading) {
item {
CircularProgressIndicator(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.wrapContentWidth(Alignment.CenterHorizontally)
)
}
}
}
}
}
}
@Composable
fun CommitItem(
commit: Commit
) {
val uriHandler = LocalUriHandler.current
Column(
modifier = Modifier
.fillMaxWidth()
.clickable {
uriHandler.openUri(commit.htmlUrl)
}
.padding(horizontal = 20.dp, vertical = 5.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
commit.commit.message.split("\n").first(),
style = MaterialTheme.typography.bodyMedium
)
if (commit.author != null) Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
AsyncImage(
modifier = Modifier
.size(20.dp)
.clip(CircleShape),
model = "https://github.com/${commit.author.name}.png",
contentDescription = commit.author.name
)
Text(
buildAnnotatedString {
withStyle(
SpanStyle(
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.onSurface
)
) {
append(commit.author.name)
}
withStyle(
SpanStyle(
color = MaterialTheme.colorScheme.onSurface.copy(
alpha = 0.6f
)
)
) {
append(" authored ")
}
withStyle(
SpanStyle(
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.primary
)
) {
append(commit.sha.substring(0, 7))
}
},
style = MaterialTheme.typography.bodySmall
)
}
}
}

View file

@ -0,0 +1,121 @@
package com.aliucord.manager.ui.component.home
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.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
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.compose.ui.unit.sp
import com.aliucord.manager.R
@Composable
fun InfoCard(
packageName: String,
supportedVersion: String,
currentVersion: String,
onDownloadClick: () -> Unit,
onUninstallClick: () -> Unit,
onLaunchClick: () -> Unit
) {
ElevatedCard {
Column(
modifier = Modifier
.padding(20.dp)
.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
text = "Aliucord${packageName.let { if (it != "com.aliucord") " ($it)" else "" }}",
style = MaterialTheme.typography.titleLarge.copy(fontSize = 23.sp),
color = MaterialTheme.colorScheme.primary
)
Text(
buildAnnotatedString {
append("Supported version: ")
withStyle(SpanStyle(fontWeight = FontWeight.Bold)) {
append(supportedVersion)
}
append("\nInstalled version: ")
withStyle(SpanStyle(fontWeight = FontWeight.Bold)) {
append(currentVersion)
}
},
style = MaterialTheme.typography.bodyMedium
)
Row(
modifier = Modifier
.fillMaxWidth()
.padding(top = 10.dp),
horizontalArrangement = Arrangement.spacedBy(16.dp)
) {
val (icon, description) = when (currentVersion) {
"-" -> Icons.Default.Download to R.string.install
supportedVersion -> Icons.Default.Refresh to R.string.reinstall
else -> Icons.Default.Update to R.string.update
}
FilledTonalIconButton(
modifier = Modifier
.weight(1f)
.heightIn(min = 50.dp),
onClick = onDownloadClick,
shape = ShapeDefaults.Large
) {
Row(
horizontalArrangement = Arrangement.spacedBy(5.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = icon,
contentDescription = stringResource(description)
)
if (currentVersion == "-") Text(
stringResource(description),
style = MaterialTheme.typography.labelLarge
)
}
}
if (currentVersion != "-") {
FilledTonalIconButton(
modifier = Modifier
.weight(1f)
.heightIn(min = 50.dp),
onClick = onUninstallClick,
shape = ShapeDefaults.Large
) {
Icon(
imageVector = Icons.Default.Delete,
contentDescription = stringResource(R.string.uninstall)
)
}
FilledTonalIconButton(
modifier = Modifier
.weight(1f)
.heightIn(min = 50.dp),
onClick = onLaunchClick,
shape = ShapeDefaults.Large
) {
Icon(
imageVector = Icons.Default.Launch,
contentDescription = stringResource(R.string.launch)
)
}
}
}
}
}
}

View file

@ -106,6 +106,7 @@ fun Changelog(
modifier = Modifier.padding(top = 16.dp, bottom = 6.dp)
)
}
'*' -> {
LinkText(
modifier = Modifier.padding(bottom = 2.dp),
@ -123,6 +124,7 @@ fun Changelog(
}
)
}
else -> {
when {
line.endsWith("marginTop}") -> {
@ -135,7 +137,8 @@ fun Changelog(
modifier = Modifier.padding(top = 16.dp, bottom = 6.dp)
)
}
line.all { c -> c == '=' } -> { } // Discord ignores =======
line.all { c -> c == '=' } -> {} // Discord ignores =======
else -> {
LinkText(
annotatedString = buildAnnotatedString {

View file

@ -14,6 +14,7 @@ import androidx.compose.material.icons.filled.History
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.platform.LocalUriHandler
@ -21,9 +22,11 @@ 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.text.style.TextDecoration
import androidx.compose.ui.unit.dp
import com.aliucord.manager.R
import com.aliucord.manager.domain.model.Plugin
import com.aliucord.manager.ui.util.joinToAnnotatedString
@Composable
fun PluginCard(
@ -31,81 +34,86 @@ fun PluginCard(
onClickDelete: () -> Unit,
onClickShowChangelog: () -> Unit
) {
val uriHandler = LocalUriHandler.current
var isEnabled by rememberSaveable { mutableStateOf(true) }
ElevatedCard {
// Header
Row(
modifier = Modifier
.clickable { isEnabled = !isEnabled }
.padding(start = 16.dp, end = 16.dp, top = 16.dp, bottom = 14.dp),
verticalAlignment = Alignment.CenterVertically
) {
Column {
// Name
Text(
buildAnnotatedString {
withStyle(SpanStyle(fontWeight = FontWeight.Bold)) {
append(plugin.manifest.name)
}
append(" v${plugin.manifest.version}")
}
)
// Authors
val authors = buildAnnotatedString {
withStyle(SpanStyle(color = MaterialTheme.colorScheme.primary)) {
plugin.manifest.authors.joinToAnnotatedString(
prefix = {
withStyle(SpanStyle(color = MaterialTheme.colorScheme.secondary)) {
append("By ")
}
}
) { author ->
val start = this@joinToAnnotatedString.length
withStyle(
SpanStyle(
textDecoration = TextDecoration.Underline
)
) {
append(author.name)
}
addStringAnnotation("authorId", author.id.toString(), start, start + author.name.length)
}
}
}
ClickableText(
text = authors,
style = MaterialTheme.typography.labelMedium,
onClick = { offset ->
authors.getStringAnnotations("authorId", offset, offset)
.firstOrNull()
?.let { (id) -> uriHandler.openUri("https://discord.com/users/$id") }
}
)
}
Spacer(Modifier.weight(1f, true))
// Toggle Switch
Switch(
checked = isEnabled,
onCheckedChange = { isEnabled = it }
)
}
Divider(modifier = Modifier
.alpha(0.3f)
.padding(horizontal = 16.dp))
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
) {
val uriHandler = LocalUriHandler.current
var isEnabled by rememberSaveable { mutableStateOf(true) }
// Header
Row(
modifier = Modifier.clickable { isEnabled = !isEnabled }
) {
Column {
// Name
Text(
buildAnnotatedString {
withStyle(SpanStyle(fontWeight = FontWeight.Bold)) {
append(plugin.manifest.name)
}
append(" v${plugin.manifest.version}")
}
)
// Authors
val authors = buildAnnotatedString {
withStyle(SpanStyle(color = MaterialTheme.colorScheme.secondary)) {
append("By ")
}
val iterator = plugin.manifest.authors.iterator()
iterator.forEach { author ->
withStyle(SpanStyle(color = MaterialTheme.colorScheme.primary)) {
val start = this@buildAnnotatedString.length
append(author.name)
addStringAnnotation("authorId", author.id.toString(), start, start + author.name.length)
}
if (iterator.hasNext()) {
withStyle(SpanStyle(color = MaterialTheme.colorScheme.secondary)) {
append(", ")
}
}
}
}
ClickableText(
text = authors,
style = MaterialTheme.typography.labelMedium,
onClick = { offset ->
authors.getStringAnnotations("authorId", offset, offset)
.firstOrNull()
?.let { (id) -> uriHandler.openUri("https://discord.com/users/$id") }
}
)
}
Spacer(Modifier.weight(1f, true))
// Toggle Switch
Switch(
checked = isEnabled,
onCheckedChange = { isEnabled = it }
)
}
Divider(modifier = Modifier.alpha(0.3f))
// Description
Text(
modifier = Modifier
.heightIn(max = 150.dp, min = 40.dp)
.padding(top = 10.dp, bottom = 20.dp),
.padding(bottom = 20.dp),
text = plugin.manifest.description,
style = MaterialTheme.typography.bodyMedium
)

View file

@ -0,0 +1,19 @@
package com.aliucord.manager.ui.component.settings
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
@Composable
fun SettingsHeader(
text: String
) {
Text(
text = text,
style = MaterialTheme.typography.titleSmall,
modifier = Modifier.padding(18.dp, 24.dp, 18.dp, 10.dp)
)
}

View file

@ -0,0 +1,60 @@
package com.aliucord.manager.ui.component.settings
import androidx.compose.foundation.layout.*
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ProvideTextStyle
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@Composable
fun SettingsItem(
modifier: Modifier = Modifier,
icon: @Composable (() -> Unit) = { },
text: @Composable () -> Unit,
secondaryText: @Composable (() -> Unit) = { },
trailing: @Composable (() -> Unit) = { },
) {
Row(
modifier = modifier
.heightIn(min = 64.dp)
.fillMaxWidth()
.padding(horizontal = 18.dp, vertical = 4.dp),
horizontalArrangement = Arrangement.spacedBy(14.dp),
verticalAlignment = Alignment.CenterVertically
) {
Box(
modifier = Modifier.padding(end = 5.dp)
) {
icon()
}
Column(
verticalArrangement = Arrangement.spacedBy(2.dp),
modifier = Modifier.weight(0.95f, true)
) {
ProvideTextStyle(
MaterialTheme.typography.titleLarge.copy(
fontWeight = FontWeight.Normal,
fontSize = 18.sp
)
) {
text()
}
ProvideTextStyle(
MaterialTheme.typography.bodyMedium.copy(
color = MaterialTheme.colorScheme.onSurface.copy(0.6f)
)
) {
secondaryText()
}
}
Spacer(Modifier.weight(0.05f, true))
trailing()
}
}

View file

@ -0,0 +1,34 @@
package com.aliucord.manager.ui.component.settings
import androidx.compose.foundation.clickable
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
@Composable
fun SettingsSwitch(
label: String,
secondaryLabel: String? = null,
disabled: Boolean = false,
icon: @Composable () -> Unit = {},
pref: Boolean,
onPrefChange: (Boolean) -> Unit,
) {
SettingsItem(
modifier = Modifier.clickable(enabled = !disabled) { onPrefChange(!pref) },
text = { Text(text = label, softWrap = true) },
icon = icon,
secondaryText = {
secondaryLabel?.let {
Text(text = it)
}
}
) {
Switch(
checked = pref,
enabled = !disabled,
onCheckedChange = { onPrefChange(!pref) }
)
}
}

View file

@ -0,0 +1,27 @@
package com.aliucord.manager.ui.component.settings
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SettingsTextField(
label: String,
disabled: Boolean = false,
pref: String,
onPrefChange: (String) -> Unit,
) {
Box(modifier = Modifier.padding(horizontal = 18.dp, vertical = 10.dp)) {
OutlinedTextField(
modifier = Modifier.fillMaxWidth(),
value = pref,
onValueChange = onPrefChange,
enabled = !disabled,
label = { Text(label) },
singleLine = true
)
}
}

View file

@ -2,10 +2,8 @@ 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.material.icons.filled.*
import androidx.compose.material.icons.outlined.*
import androidx.compose.ui.graphics.vector.ImageVector
import com.aliucord.manager.R
import com.aliucord.manager.ui.screen.InstallData
@ -35,5 +33,6 @@ enum class HomeDestination(
@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)
PLUGINS(Icons.Default.Extension, Icons.Outlined.Extension, R.string.plugins),
SETTINGS(Icons.Default.Settings, Icons.Outlined.Settings, R.string.settings)
}

View file

@ -5,12 +5,17 @@
package com.aliucord.manager.ui.screen
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.NavigateBefore
import androidx.compose.material.icons.filled.*
import androidx.compose.material.ripple.rememberRipple
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.draw.clip
@ -18,9 +23,10 @@ import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import coil.compose.AsyncImage
import com.aliucord.manager.R
import com.aliucord.manager.ui.component.ContributorsCard
import com.aliucord.manager.ui.component.contributors
import com.aliucord.manager.ui.viewmodel.AboutViewModel
import org.koin.androidx.compose.getViewModel
@ -28,16 +34,18 @@ import org.koin.androidx.compose.getViewModel
@Composable
fun AboutScreen(
viewModel: AboutViewModel = getViewModel(),
onClickBack: () -> Unit
onBackClick: () -> Unit
) {
val uriHandler = LocalUriHandler.current
Scaffold(
topBar = {
TopAppBar(
title = { Text(stringResource(R.string.about)) },
navigationIcon = {
IconButton(onClick = onClickBack) {
IconButton(onClick = onBackClick) {
Icon(
imageVector = Icons.Default.NavigateBefore,
imageVector = Icons.Default.ArrowBack,
contentDescription = stringResource(R.string.back)
)
}
@ -45,80 +53,120 @@ fun AboutScreen(
)
}
) { paddingValues ->
Column(
LazyColumn(
modifier = Modifier
.padding(paddingValues)
.padding(horizontal = 12.dp, vertical = 14.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
val uriHandler = LocalUriHandler.current
ElevatedCard {
item {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.padding(16.dp)
.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(8.dp)
.fillMaxWidth()
.padding(vertical = 16.dp)
) {
Text(
text = stringResource(R.string.team),
style = MaterialTheme.typography.titleLarge,
color = MaterialTheme.colorScheme.primary
AsyncImage(
model = "https://github.com/Aliucord.png",
contentDescription = "Aliucord",
modifier = Modifier.size(71.dp)
)
Column(
verticalArrangement = Arrangement.spacedBy(16.dp)
Text(
text = "Aliucord",
style = MaterialTheme.typography.titleMedium.copy(
fontSize = 26.sp
)
)
Text(
text = stringResource(R.string.aliucord_description),
style = MaterialTheme.typography.titleSmall.copy(
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
)
)
Row(
horizontalArrangement = Arrangement.Center,
modifier = Modifier.fillMaxWidth()
) {
UserEntry("Juby210", "Fox")
UserEntry("Vendicated", "the ven")
TextButton(onClick = { uriHandler.openUri("https://github.com/Aliucord") }) {
Icon(
painter = painterResource(R.drawable.ic_account_github_white_24dp),
contentDescription = stringResource(R.string.github)
)
Spacer(modifier = Modifier.width(ButtonDefaults.IconSpacing))
Text(text = stringResource(id = R.string.github))
}
TextButton(onClick = { uriHandler.openUri("https://aliucord.com") }) {
Icon(
imageVector = Icons.Filled.Link,
contentDescription = stringResource(R.string.website)
)
Spacer(modifier = Modifier.width(ButtonDefaults.IconSpacing))
Text(text = stringResource(id = R.string.website))
}
}
}
}
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 = stringResource(R.string.github)
)
Spacer(Modifier.width(ButtonDefaults.IconSpacing))
Text(stringResource(R.string.github))
item {
Row(
horizontalArrangement = Arrangement.SpaceEvenly,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 16.dp)
) {
UserEntry("Vendicated", "the ven")
UserEntry("Juby210", "Fox")
UserEntry("rushii", "explod", "DiamondMiner88")
}
}
contributors(viewModel.contributors)
}
}
}
@Composable
private fun UserEntry(name: String, roles: String) {
Column {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
private fun UserEntry(name: String, roles: String, username: String = name) {
val uriHandler = LocalUriHandler.current
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(16.dp),
modifier = Modifier
.clickable(
onClick = { uriHandler.openUri("https://github.com/$username") },
indication = rememberRipple(bounded = false, radius = 90.dp),
interactionSource = remember { MutableInteractionSource() }
)
.widthIn(min = 100.dp)
) {
AsyncImage(
modifier = Modifier
.size(71.dp)
.clip(CircleShape),
model = "https://github.com/$username.png",
contentDescription = username
)
Column(
horizontalAlignment = Alignment.CenterHorizontally
) {
AsyncImage(
modifier = Modifier
.size(54.dp)
.clip(CircleShape),
model = "https://github.com/$name.png",
contentDescription = name
Text(
text = name,
style = MaterialTheme.typography.titleMedium.copy(
fontSize = 18.sp
)
)
Column {
Text(
text = name,
style = MaterialTheme.typography.titleLarge
Text(
text = roles,
style = MaterialTheme.typography.titleSmall.copy(
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
)
Text(roles)
}
)
}
}
}

View file

@ -1,41 +1,33 @@
package com.aliucord.manager.ui.screen
import androidx.compose.animation.*
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Info
import androidx.compose.material.icons.filled.Settings
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
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.dp
import com.aliucord.manager.BuildConfig
import com.aliucord.manager.R
import com.aliucord.manager.ui.navigation.HomeDestination
import com.xinto.taxi.RegularNavigator
import com.xinto.taxi.Taxi
import com.xinto.taxi.rememberNavigator
@OptIn(ExperimentalMaterial3Api::class, ExperimentalAnimationApi::class)
@Composable
fun MainRootScreen(
onClickInstall: (InstallData) -> Unit,
onClickAbout: () -> Unit,
onClickSettings: () -> Unit
mainRootNavigator: RegularNavigator<HomeDestination>,
onInstallClick: (InstallData) -> Unit,
onAboutClick: () -> Unit
) {
val mainRootNavigator = rememberNavigator(HomeDestination.HOME)
Scaffold(
topBar = {
TopAppBar(
title = {
Text(
stringResource(mainRootNavigator.currentDestination.label),
modifier = Modifier.padding(horizontal = 4.dp)
)
},
title = { Text(stringResource(mainRootNavigator.currentDestination.label)) },
actions = {
val localUriHandler = LocalUriHandler.current
@ -50,19 +42,12 @@ fun MainRootScreen(
)
}
IconButton(onClick = onClickAbout) {
IconButton(onClick = onAboutClick) {
Icon(
imageVector = Icons.Default.Info,
contentDescription = stringResource(R.string.about)
)
}
IconButton(onClick = onClickSettings) {
Icon(
imageVector = Icons.Default.Settings,
contentDescription = stringResource(R.string.settings)
)
}
}
)
},
@ -94,15 +79,19 @@ fun MainRootScreen(
}
) { paddingValues ->
Taxi(
modifier = Modifier.padding(paddingValues),
modifier = Modifier
.padding(paddingValues)
.fillMaxSize(),
navigator = mainRootNavigator,
transitionSpec = { fadeIn() with fadeOut() }
) { destination ->
when (destination) {
HomeDestination.HOME -> HomeScreen(
onClickInstall = onClickInstall
onClickInstall = onInstallClick
)
HomeDestination.PLUGINS -> PluginsScreen()
HomeDestination.SETTINGS -> SettingsScreen()
}
}
}

View file

@ -8,30 +8,17 @@ 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.*
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.stringResource
import androidx.compose.ui.text.*
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.paging.LoadState
import androidx.paging.compose.collectAsLazyPagingItems
import androidx.paging.compose.items
import com.aliucord.manager.R
import com.aliucord.manager.ui.component.home.CommitList
import com.aliucord.manager.ui.component.home.InfoCard
import com.aliucord.manager.ui.dialog.DownloadMethod
import com.aliucord.manager.ui.dialog.InstallerDialog
import com.aliucord.manager.ui.viewmodel.HomeViewModel
import org.koin.androidx.compose.getViewModel
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun HomeScreen(
viewModel: HomeViewModel = getViewModel(),
@ -65,167 +52,29 @@ fun HomeScreen(
modifier = Modifier
.padding(horizontal = 16.dp)
.padding(top = 8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
ElevatedCard {
Column(
modifier = Modifier
.padding(16.dp)
.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Column {
Text(
text = "Aliucord${viewModel.preferences.packageName.let { if (it != "com.aliucord") " ($it)" else "" }}",
style = MaterialTheme.typography.titleLarge,
color = MaterialTheme.colorScheme.primary
)
Text(
buildAnnotatedString {
append("Supported version: ")
withStyle(SpanStyle(fontWeight = FontWeight.Bold)) {
append(viewModel.supportedVersion)
}
append("\nInstalled version: ")
withStyle(SpanStyle(fontWeight = FontWeight.Bold)) {
append(viewModel.installedVersion)
}
}
)
}
Column(
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
val (icon, description) = when (viewModel.installedVersion) {
"-" -> Icons.Default.Download to R.string.install
viewModel.supportedVersion -> Icons.Default.Update to R.string.reinstall
else -> Icons.Default.Update to R.string.update
}
Row(
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
Button(
modifier = Modifier.weight(1f, true),
onClick = {
if (viewModel.preferences.devMode) {
showOptionsDialog = true
} else {
onClickInstall(
InstallData(
DownloadMethod.DOWNLOAD,
)
)
}
}
) {
Icon(
modifier = Modifier.padding(8.dp),
imageVector = icon,
contentDescription = stringResource(description),
tint = MaterialTheme.colorScheme.onPrimary
)
Text(stringResource(description))
}
}
if (viewModel.installedVersion != "-") {
Row(
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
CompositionLocalProvider(
LocalContentColor provides MaterialTheme.colorScheme.secondaryContainer
) {
FilledTonalButton(
modifier = Modifier.weight(1f),
onClick = viewModel::uninstallAliucord
) {
Icon(
modifier = Modifier.padding(8.dp),
imageVector = Icons.Default.Delete,
contentDescription = stringResource(R.string.uninstall)
)
}
FilledTonalButton(
modifier = Modifier.weight(1f),
onClick = viewModel::launchAliucord
) {
Icon(
modifier = Modifier.padding(8.dp),
imageVector = Icons.Default.Launch,
contentDescription = stringResource(R.string.launch)
)
}
}
}
}
}
}
}
ElevatedCard(
modifier = Modifier.padding(bottom = 16.dp)
) {
Column(
modifier = Modifier
.weight(1f, true)
.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
val lazyPagingItems = viewModel.commits.collectAsLazyPagingItems()
Text(
modifier = Modifier.padding(16.dp, 16.dp, 16.dp),
text = stringResource(R.string.commits),
style = MaterialTheme.typography.titleLarge,
color = MaterialTheme.colorScheme.primary
)
LazyColumn(
modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(4.dp)
) {
if (lazyPagingItems.loadState.refresh == LoadState.Loading) {
item {
Text(
modifier = Modifier
.fillMaxWidth()
.wrapContentWidth(Alignment.CenterHorizontally),
text = stringResource(R.string.paging_initial_load)
)
}
}
items(lazyPagingItems) { commitData ->
if (commitData == null) return@items
val uriHandler = LocalUriHandler.current
ListItem(
modifier = Modifier.clickable { uriHandler.openUri(commitData.htmlUrl) },
overlineText = { Text(commitData.sha.substring(0, 7)) },
headlineText = { Text("${commitData.commit.message.split("\n").first()} - ${commitData.author?.name ?: "UNKNOWN"}") }
InfoCard(
packageName = viewModel.preferences.packageName,
supportedVersion = viewModel.supportedVersion,
currentVersion = viewModel.installedVersion,
onDownloadClick = {
if (viewModel.preferences.devMode) {
showOptionsDialog = true
} else {
onClickInstall(
InstallData(
DownloadMethod.DOWNLOAD,
)
}
if (lazyPagingItems.loadState.append == LoadState.Loading) {
item {
CircularProgressIndicator(
modifier = Modifier
.fillMaxWidth()
.wrapContentWidth(Alignment.CenterHorizontally)
)
}
}
)
}
}
}
},
onLaunchClick = viewModel::launchAliucord,
onUninstallClick = viewModel::uninstallAliucord
)
CommitList(
commits = viewModel.commits
)
}
}

View file

@ -35,19 +35,19 @@ data class InstallData(
@Composable
fun InstallerScreen(
installData: InstallData,
onClickBack: () -> Unit,
onBackClick: () -> Unit,
viewModel: InstallViewModel = getViewModel(parameters = { parametersOf(installData) })
) {
val navigateMain by viewModel.returnToHome.collectAsState(initial = false)
if (navigateMain) onClickBack()
if (navigateMain) onBackClick()
Scaffold(
topBar = {
TopAppBar(
title = { Text("Installer") },
navigationIcon = {
IconButton(onClick = onClickBack) {
IconButton(onClick = onBackClick) {
Icon(
imageVector = Icons.Default.NavigateBefore,
contentDescription = stringResource(R.string.back)

View file

@ -84,7 +84,7 @@ fun PluginsScreen(
modifier = Modifier
.fillMaxSize()
.padding(horizontal = 16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
verticalArrangement = Arrangement.spacedBy(14.dp)
) {
val search = viewModel.search
@ -119,8 +119,8 @@ fun PluginsScreen(
)
LazyColumn(
contentPadding = PaddingValues(bottom = 8.dp, top = 6.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
contentPadding = PaddingValues(bottom = 15.dp, top = 6.dp),
verticalArrangement = Arrangement.spacedBy(14.dp)
) {
items(
viewModel.plugins.filter { plugin ->

View file

@ -15,186 +15,126 @@ import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.aliucord.manager.R
import com.aliucord.manager.ui.component.settings.*
import com.aliucord.manager.ui.theme.Theme
import com.aliucord.manager.ui.viewmodel.SettingsViewModel
import org.koin.androidx.compose.getViewModel
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SettingsScreen(
viewModel: SettingsViewModel = getViewModel(),
onClickBack: () -> Unit
viewModel: SettingsViewModel = getViewModel()
) {
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
Column(
modifier = Modifier
.verticalScroll(state = rememberScrollState())
) {
val preferences = viewModel.preferences
Scaffold(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
TopAppBar(
title = { Text(stringResource(R.string.settings)) },
navigationIcon = {
IconButton(onClick = onClickBack) {
Icon(
imageVector = Icons.Default.NavigateBefore,
contentDescription = stringResource(R.string.back)
)
}
},
scrollBehavior = scrollBehavior
if (viewModel.showThemeDialog) {
ThemeDialog(
currentTheme = preferences.theme,
onDismissRequest = viewModel::hideThemeDialog,
onConfirm = viewModel::setTheme
)
}
) { paddingValues ->
Column(
modifier = Modifier
.padding(paddingValues)
.padding(horizontal = 12.dp, vertical = 14.dp)
.verticalScroll(state = rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(12.dp)
SettingsHeader(stringResource(R.string.appearance))
SettingsItem(
modifier = Modifier.clickable(onClick = viewModel::showThemeDialog),
icon = { Icon(Icons.Default.Style, null) },
text = { Text(stringResource(R.string.theme)) }
) {
val preferences = viewModel.preferences
if (viewModel.showThemeDialog) {
ThemeDialog(
currentTheme = preferences.theme,
onDismissRequest = viewModel::hideThemeDialog,
onConfirm = viewModel::setTheme
)
FilledTonalButton(onClick = viewModel::showThemeDialog) {
Text(preferences.theme.displayName)
}
}
GroupHeader(stringResource(R.string.appearance))
SettingsSwitch(
label = stringResource(R.string.dynamic_color),
pref = preferences.dynamicColor,
icon = { Icon(Icons.Default.Palette, null) }
) {
preferences.dynamicColor = it
}
ListItem(
modifier = Modifier.clickable(onClick = viewModel::showThemeDialog),
headlineText = { Text(stringResource(R.string.theme)) },
supportingText = { Text(stringResource(R.string.theme_setting_description)) },
leadingContent = { Icon(Icons.Default.Style, null) },
trailingContent = {
FilledTonalButton(onClick = viewModel::showThemeDialog) {
Text(preferences.theme.displayName)
}
}
)
SettingsHeader(stringResource(R.string.advanced))
SwitchSetting(
checked = preferences.dynamicColor,
title = { Text(stringResource(R.string.dynamic_color)) },
onCheckedChange = { preferences.dynamicColor = it },
icon = { Icon(Icons.Default.Palette, null) }
)
SettingsSwitch(
label = stringResource(R.string.replace_bg),
secondaryLabel = stringResource(R.string.replace_bg_description),
pref = preferences.replaceIcon,
icon = { Icon(Icons.Default.AppShortcut, null) }
) {
preferences.replaceIcon = it
}
GroupHeader(stringResource(R.string.advanced))
Spacer(modifier = Modifier.height(8.dp))
SwitchSetting(
checked = preferences.replaceIcon,
title = { Text(stringResource(R.string.replace_bg)) },
onCheckedChange = { preferences.replaceIcon = it },
icon = { Icon(Icons.Default.AppShortcut, null) }
)
SettingsTextField(
label = stringResource(R.string.app_name_setting),
pref = preferences.appName,
onPrefChange = viewModel::setAppName
)
OutlinedTextField(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 10.dp),
value = preferences.appName,
onValueChange = viewModel::setAppName,
label = { Text(stringResource(R.string.app_name_setting)) },
placeholder = { Text("Aliucord") },
singleLine = true
)
SettingsSwitch(
label = stringResource(R.string.developer_options),
pref = preferences.devMode,
icon = { Icon(Icons.Default.Code, null) }
) {
preferences.devMode = it
}
SwitchSetting(
checked = preferences.devMode,
title = { Text(stringResource(R.string.developer_options)) },
onCheckedChange = { preferences.devMode = it },
icon = { Icon(Icons.Default.Code, null) }
)
AnimatedVisibility(
visible = preferences.devMode,
enter = expandVertically(),
exit = shrinkVertically()
AnimatedVisibility(
visible = preferences.devMode,
enter = expandVertically(),
exit = shrinkVertically()
) {
Column(
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
Column(
verticalArrangement = Arrangement.spacedBy(12.dp)
SettingsTextField(
label = stringResource(R.string.package_name),
pref = preferences.packageName,
onPrefChange = viewModel::setPackageName
)
SettingsTextField(
label = stringResource(R.string.version),
pref = preferences.version,
onPrefChange = viewModel::setVersion
)
SettingsSwitch(
label = stringResource(R.string.debuggable),
secondaryLabel = stringResource(R.string.debuggable_description),
pref = preferences.debuggable,
icon = { Icon(Icons.Default.BugReport, null) }
) {
OutlinedTextField(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 10.dp),
value = preferences.packageName,
onValueChange = viewModel::setPackageName,
label = { Text(stringResource(R.string.package_name)) },
placeholder = { Text("com.aliucord") },
singleLine = true
)
OutlinedTextField(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 10.dp),
value = preferences.version,
onValueChange = viewModel::setVersion,
label = { Text(stringResource(R.string.version)) },
singleLine = true
)
SwitchSetting(
checked = preferences.debuggable,
title = { Text(stringResource(R.string.debuggable)) },
description = { Text(stringResource(R.string.debuggable_description)) },
onCheckedChange = { preferences.debuggable = it },
icon = { Icon(Icons.Default.BugReport, null) }
)
preferences.debuggable = it
}
}
}
Button(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
onClick = viewModel::clearCacheDir
) {
Text(
text = stringResource(R.string.clear_files_cache),
textAlign = TextAlign.Center
)
}
Button(
modifier = Modifier
.fillMaxWidth()
.padding(18.dp),
shape = ShapeDefaults.Large,
onClick = viewModel::clearCacheDir
) {
Text(
text = stringResource(R.string.clear_cache),
textAlign = TextAlign.Center
)
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SwitchSetting(
enabled: Boolean = true,
checked: Boolean,
icon: @Composable (() -> Unit)? = null,
title: @Composable () -> Unit,
description: @Composable (() -> Unit)? = null,
onCheckedChange: (Boolean) -> Unit
) {
ListItem(
modifier = Modifier.clickable { onCheckedChange(!checked) },
leadingContent = icon,
headlineText = title,
supportingText = description,
trailingContent = {
Switch(
enabled = enabled,
checked = checked,
onCheckedChange = onCheckedChange
)
}
)
}
@Composable
fun ThemeDialog(
currentTheme: Theme,
@ -248,23 +188,3 @@ fun ThemeDialog(
}
)
}
@Composable
fun GroupHeader(
title: String,
color: Color = MaterialTheme.colorScheme.primary
) {
Box(
Modifier
.padding(start = 12.dp)
.fillMaxWidth(),
contentAlignment = Alignment.CenterStart
) {
Text(
text = title,
color = color,
fontSize = LocalTextStyle.current.fontSize.times(0.95f),
fontWeight = FontWeight.SemiBold
)
}
}

View file

@ -0,0 +1,23 @@
package com.aliucord.manager.ui.util
import androidx.compose.ui.text.AnnotatedString
context(AnnotatedString.Builder)
inline fun <T> Iterable<T>.joinToAnnotatedString(
separator: AnnotatedString.Builder.() -> Unit = { append(", ") },
prefix: AnnotatedString.Builder.() -> Unit = {},
postfix: AnnotatedString.Builder.() -> Unit = {},
transform: (AnnotatedString.Builder.(T) -> Unit) = {
when (it) {
is Char -> append(it)
else -> append(it.toString())
}
}
) {
prefix(this@Builder)
for ((count, element) in this@joinToAnnotatedString.withIndex()) {
if (count + 1 > 1) separator(this@Builder)
transform(this@Builder, element)
}
postfix(this@Builder)
}

View file

@ -3,8 +3,8 @@ package com.aliucord.manager.ui.viewmodel
import androidx.compose.runtime.mutableStateListOf
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.aliucord.manager.network.dto.GithubUser
import com.aliucord.manager.domain.repository.GithubRepository
import com.aliucord.manager.network.dto.GithubUser
import kotlinx.coroutines.launch
class AboutViewModel(

View file

@ -1,10 +1,11 @@
<resources>
<string name="app_name">Aliucord Manager</string>
<string name="clear_files_cache">Clear files cache</string>
<string name="clear_cache">Clear cache</string>
<string name="commits">Commits</string>
<string name="dex_location">Dex location</string>
<string name="open_settings">Open settings</string>
<string name="replace_bg">Replace app icon with Aliucord variant</string>
<string name="replace_bg">Replace app icon</string>
<string name="replace_bg_description">Uses the Aliucord variant as opposed to Discord\'s own icon</string>
<string name="settings">Settings</string>
<string name="show_menu">Show menu</string>
<string name="theme">Theme</string>
@ -28,7 +29,9 @@
<string name="contributors">Contributors</string>
<string name="search_plugins">Search Plugins</string>
<string name="github">GitHub</string>
<string name="website">Website</string>
<string name="installer">Installer</string>
<string name="contributions">%o contributions</string>
<string name="theme_setting_description">Manager appearance</string>
<string name="no_plugins_installed">No plugins installed!</string>
<string name="paging_initial_load">Loading…</string>
@ -51,4 +54,5 @@
<string name="advanced">Advanced</string>
<string name="permissions_grant">Grant Permissions</string>
<string name="permissions_grant_body">In order for Aliucord Manager to work properly, you need to grant file permissions. Since shared data in stored in ~/Aliucord, permissions are required in order for manager to function.</string>
<string name="aliucord_description">A modification for the Discord Android App</string>
</resources>