feat: about page

This commit is contained in:
二刺螈 2024-11-12 22:59:55 +08:00
parent 3a8b8858c3
commit 39fd831853
12 changed files with 455 additions and 235 deletions

View File

@ -59,7 +59,7 @@ android {
applicationId = "li.songe.gkd"
versionCode = 48
versionName = "v1.9.1"
versionName = "1.9.1"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
@ -153,6 +153,7 @@ android {
freeCompilerArgs += "-opt-in=kotlinx.serialization.ExperimentalSerializationApi"
freeCompilerArgs += "-opt-in=androidx.compose.material3.ExperimentalMaterial3Api"
freeCompilerArgs += "-opt-in=androidx.compose.foundation.ExperimentalFoundationApi"
freeCompilerArgs += "-opt-in=androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi"
}
dependenciesInfo.includeInApk = false
packagingOptions.resources.excludes += setOf(
@ -196,6 +197,9 @@ dependencies {
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.compose.ui)
implementation(libs.compose.ui.graphics)
implementation(libs.compose.animation)
implementation(libs.compose.animation.graphics)
implementation(libs.compose.icons)
implementation(libs.compose.preview)
debugImplementation(libs.compose.tooling)

View File

@ -1,42 +1,188 @@
package li.songe.gkd.ui
import androidx.compose.animation.graphics.res.animatedVectorResource
import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
import androidx.compose.animation.graphics.vector.AnimatedImageVector
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Share
import androidx.compose.material.icons.outlined.Info
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Card
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.compose.viewModel
import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.annotation.RootGraph
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.update
import li.songe.gkd.META
import li.songe.gkd.MainActivity
import li.songe.gkd.ui.component.RotatingLoadingIcon
import li.songe.gkd.ui.component.SettingItem
import li.songe.gkd.ui.component.TextMenu
import li.songe.gkd.ui.component.TextSwitch
import li.songe.gkd.ui.component.waitResult
import li.songe.gkd.ui.style.EmptyHeight
import li.songe.gkd.ui.style.itemPadding
import li.songe.gkd.ui.style.titleItemPadding
import li.songe.gkd.util.ISSUES_URL
import li.songe.gkd.util.LocalNavController
import li.songe.gkd.util.ProfileTransitions
import li.songe.gkd.util.REPOSITORY_URL
import li.songe.gkd.util.SafeR
import li.songe.gkd.util.UpdateChannelOption
import li.songe.gkd.util.buildLogFile
import li.songe.gkd.util.checkUpdate
import li.songe.gkd.util.findOption
import li.songe.gkd.util.format
import li.songe.gkd.util.launchAsFn
import li.songe.gkd.util.launchTry
import li.songe.gkd.util.openUri
import li.songe.gkd.util.saveFileToDownloads
import li.songe.gkd.util.shareFile
import li.songe.gkd.util.storeFlow
import li.songe.gkd.util.throttle
import li.songe.gkd.util.toast
@Destination<RootGraph>(style = ProfileTransitions::class)
@Composable
fun AboutPage() {
val navController = LocalNavController.current
val context = LocalContext.current
val context = LocalContext.current as MainActivity
val vm = viewModel<AboutVm>()
val store by storeFlow.collectAsState()
vm.uploadOptions.ShowDialog()
var showInfoDlg by remember { mutableStateOf(false) }
if (showInfoDlg) {
AlertDialog(
onDismissRequest = { showInfoDlg = false },
title = { Text(text = "版本信息") },
text = {
Column(
verticalArrangement = Arrangement.spacedBy(12.dp),
) {
Column {
Text(text = "构建渠道")
Text(text = META.channel)
}
Column {
Text(text = "版本代码")
Text(text = META.versionCode.toString())
}
Column {
Text(text = "版本名称")
Text(text = META.versionName)
}
Column {
Text(text = "代码记录")
Text(
modifier = Modifier.clickable { context.openUri(META.commitUrl) },
text = META.commitId.substring(0, 16),
color = MaterialTheme.colorScheme.primary,
style = LocalTextStyle.current.copy(textDecoration = TextDecoration.Underline),
)
}
Column {
Text(text = "提交时间")
Text(text = META.commitTime.format("yyyy-MM-dd HH:mm:ss ZZ"))
}
}
},
confirmButton = {
TextButton(onClick = {
showInfoDlg = false
}) {
Text(text = "关闭")
}
},
)
}
var showShareLogDlg by remember { mutableStateOf(false) }
if (showShareLogDlg) {
Dialog(onDismissRequest = { showShareLogDlg = false }) {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
shape = RoundedCornerShape(16.dp),
) {
val modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
Text(
text = "分享到其他应用", modifier = Modifier
.clickable(onClick = throttle {
showShareLogDlg = false
context.mainVm.viewModelScope.launchTry(Dispatchers.IO) {
val logZipFile = buildLogFile()
context.shareFile(logZipFile, "分享日志文件")
}
})
.then(modifier)
)
Text(
text = "保存到下载", modifier = Modifier
.clickable(onClick = throttle {
showShareLogDlg = false
context.mainVm.viewModelScope.launchTry(Dispatchers.IO) {
val logZipFile = buildLogFile()
context.saveFileToDownloads(logZipFile)
}
})
.then(modifier)
)
Text(
text = "生成链接(需科学上网)",
modifier = Modifier
.clickable(onClick = throttle {
showShareLogDlg = false
vm.uploadOptions.startTask(getFile = { buildLogFile() })
})
.then(modifier)
)
}
}
}
val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior()
Scaffold(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
@ -53,7 +199,17 @@ fun AboutPage() {
)
}
},
title = { Text(text = "关于") }
title = { Text(text = "关于") },
actions = {
IconButton(onClick = throttle(fn = {
showInfoDlg = true
})) {
Icon(
imageVector = Icons.Outlined.Info,
contentDescription = null,
)
}
}
)
}
) { contentPadding ->
@ -63,6 +219,25 @@ fun AboutPage() {
.verticalScroll(rememberScrollState())
.padding(contentPadding),
) {
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
AnimatedLogoIcon(
modifier = Modifier
.clickable(
indication = null,
interactionSource = remember { MutableInteractionSource() },
onClick = throttle { toast("你干嘛~ 哎呦~") }
)
.fillMaxWidth(0.33f)
.aspectRatio(1f)
)
Text(text = META.appName, style = MaterialTheme.typography.titleMedium)
Text(text = META.versionName, style = MaterialTheme.typography.bodyMedium)
Spacer(modifier = Modifier.height(32.dp))
}
Column(
modifier = Modifier
.clickable {
@ -81,86 +256,139 @@ fun AboutPage() {
color = MaterialTheme.colorScheme.primary,
)
}
Column(
modifier = Modifier
.fillMaxWidth()
.itemPadding()
) {
Text(
text = "版本代码",
style = MaterialTheme.typography.bodyLarge,
)
Text(
text = META.versionCode.toString(),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
Column(
modifier = Modifier
.fillMaxWidth()
.itemPadding()
) {
Text(
text = "版本名称",
style = MaterialTheme.typography.bodyLarge,
)
Text(
text = META.versionName,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
Column(
modifier = Modifier
.clickable {
context.openUri(META.commitUrl)
context.openUri(ISSUES_URL)
}
.fillMaxWidth()
.itemPadding()
) {
Text(
text = "代码记录",
text = "问题反馈",
style = MaterialTheme.typography.bodyLarge,
)
Text(
text = META.commitId,
text = ISSUES_URL,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.primary,
)
}
Column(
modifier = Modifier
.fillMaxWidth()
.itemPadding()
) {
if (META.updateEnabled) {
val checkUpdating by context.mainVm.updateStatus.checkUpdatingFlow.collectAsState()
Text(
text = "提交时间",
style = MaterialTheme.typography.bodyLarge,
text = "更新",
modifier = Modifier.titleItemPadding(),
style = MaterialTheme.typography.titleSmall,
color = MaterialTheme.colorScheme.primary,
)
Text(
text = META.commitTime.format("yyyy-MM-dd HH:mm:ss ZZ"),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
TextSwitch(
title = "自动更新",
subtitle = "自动检查更新",
checked = store.autoCheckAppUpdate,
onCheckedChange = {
storeFlow.value = store.copy(
autoCheckAppUpdate = it
)
}
)
TextMenu(
title = "更新渠道",
option = UpdateChannelOption.allSubObject.findOption(store.updateChannel)
) {
if (context.mainVm.updateStatus.checkUpdatingFlow.value) return@TextMenu
if (it.value == UpdateChannelOption.Beta.value) {
context.mainVm.viewModelScope.launchTry {
context.mainVm.dialogFlow.waitResult(
title = "版本渠道",
text = "测试版本渠道更新快\n但不稳定可能存在较多BUG\n请谨慎使用",
)
storeFlow.update { s -> s.copy(updateChannel = it.value) }
}
} else {
storeFlow.update { s -> s.copy(updateChannel = it.value) }
}
}
Row(
modifier = Modifier
.clickable(
onClick = throttle(fn = context.mainVm.viewModelScope.launchAsFn {
if (context.mainVm.updateStatus.checkUpdatingFlow.value) return@launchAsFn
val newVersion = context.mainVm.updateStatus.checkUpdate()
if (newVersion == null) {
toast("暂无更新")
}
})
)
.fillMaxWidth()
.itemPadding(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = "检查更新",
style = MaterialTheme.typography.bodyLarge,
)
RotatingLoadingIcon(loading = checkUpdating)
}
}
Column(
modifier = Modifier
.fillMaxWidth()
.itemPadding()
) {
Text(
text = "构建渠道",
style = MaterialTheme.typography.bodyLarge,
)
Text(
text = META.channel,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
Text(
text = "日志",
modifier = Modifier.titleItemPadding(),
style = MaterialTheme.typography.titleSmall,
color = MaterialTheme.colorScheme.primary,
)
TextSwitch(
title = "保存日志",
subtitle = "保存7天日志便于反馈问题",
checked = store.log2FileSwitch,
onCheckedChange = {
storeFlow.value = store.copy(
log2FileSwitch = it
)
})
if (store.log2FileSwitch) {
SettingItem(
title = "导出日志",
imageVector = Icons.Default.Share,
onClick = {
showShareLogDlg = true
}
)
}
Spacer(modifier = Modifier.height(EmptyHeight))
}
}
}
@Composable
private fun AnimatedLogoIcon(
modifier: Modifier = Modifier
) {
val context = LocalContext.current as MainActivity
val enableDarkTheme by context.mainVm.enableDarkThemeFlow.collectAsState()
val darkTheme = enableDarkTheme ?: isSystemInDarkTheme()
var atEnd by remember { mutableStateOf(false) }
val animation = AnimatedImageVector.animatedVectorResource(id = SafeR.ic_logo_animation)
val painter = rememberAnimatedVectorPainter(
animation,
atEnd
)
LaunchedEffect(Unit) {
while (true) {
atEnd = !atEnd
delay(animation.totalDuration.toLong())
}
}
val colorRid = if (darkTheme) SafeR.better_white else SafeR.better_black
Icon(
modifier = modifier,
painter = painter,
contentDescription = null,
tint = colorResource(colorRid),
)
}

View File

@ -0,0 +1,9 @@
package li.songe.gkd.ui
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import li.songe.gkd.ui.component.UploadOptions
class AboutVm : ViewModel() {
val uploadOptions = UploadOptions(viewModelScope)
}

View File

@ -15,16 +15,13 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.automirrored.outlined.HelpOutline
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.filled.Share
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Card
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LocalTextStyle
@ -51,7 +48,6 @@ import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.compose.viewModel
import com.blankj.utilcode.util.LogUtils
@ -83,13 +79,9 @@ import li.songe.gkd.ui.style.itemPadding
import li.songe.gkd.ui.style.titleItemPadding
import li.songe.gkd.util.LocalNavController
import li.songe.gkd.util.ProfileTransitions
import li.songe.gkd.util.buildLogFile
import li.songe.gkd.util.launchAsFn
import li.songe.gkd.util.launchTry
import li.songe.gkd.util.openUri
import li.songe.gkd.util.privacyStoreFlow
import li.songe.gkd.util.saveFileToDownloads
import li.songe.gkd.util.shareFile
import li.songe.gkd.util.storeFlow
import li.songe.gkd.util.throttle
import li.songe.gkd.util.toast
@ -104,8 +96,6 @@ fun AdvancedPage() {
val store by storeFlow.collectAsState()
val snapshotCount by vm.snapshotCountFlow.collectAsState()
vm.uploadOptions.ShowDialog()
var showEditPortDlg by remember {
mutableStateOf(false)
}
@ -165,55 +155,6 @@ fun AdvancedPage() {
})
}
var showShareLogDlg by remember {
mutableStateOf(false)
}
if (showShareLogDlg) {
Dialog(onDismissRequest = { showShareLogDlg = false }) {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
shape = RoundedCornerShape(16.dp),
) {
val modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
Text(
text = "分享到其他应用", modifier = Modifier
.clickable(onClick = throttle {
showShareLogDlg = false
vm.viewModelScope.launchTry(Dispatchers.IO) {
val logZipFile = buildLogFile()
context.shareFile(logZipFile, "分享日志文件")
}
})
.then(modifier)
)
Text(
text = "保存到下载", modifier = Modifier
.clickable(onClick = throttle {
showShareLogDlg = false
vm.viewModelScope.launchTry(Dispatchers.IO) {
val logZipFile = buildLogFile()
context.saveFileToDownloads(logZipFile)
}
})
.then(modifier)
)
Text(
text = "生成链接(需科学上网)",
modifier = Modifier
.clickable(onClick = throttle {
showShareLogDlg = false
vm.uploadOptions.startTask(getFile = { buildLogFile() })
})
.then(modifier)
)
}
}
}
var showEditCookieDlg by remember { mutableStateOf(false) }
if (showEditCookieDlg) {
val privacyStore by privacyStoreFlow.collectAsState()
@ -540,44 +481,6 @@ fun AdvancedPage() {
}
)
Text(
text = "日志",
modifier = Modifier.titleItemPadding(),
style = MaterialTheme.typography.titleSmall,
color = MaterialTheme.colorScheme.primary,
)
TextSwitch(
title = "保存日志",
subtitle = "保存7天日志,帮助定位BUG",
checked = store.log2FileSwitch,
onCheckedChange = {
storeFlow.value = store.copy(
log2FileSwitch = it
)
if (!it) {
context.mainVm.viewModelScope.launchTry(Dispatchers.IO) {
val logFiles = LogUtils.getLogFiles()
if (logFiles.isNotEmpty()) {
logFiles.forEach { f ->
f.delete()
}
toast("已删除全部日志")
}
}
}
})
if (store.log2FileSwitch) {
SettingItem(
title = "导出日志",
imageVector = Icons.Default.Share,
onClick = {
showShareLogDlg = true
}
)
}
Text(
text = "其它",
modifier = Modifier.titleItemPadding(),

View File

@ -5,11 +5,8 @@ import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.stateIn
import li.songe.gkd.db.DbSet
import li.songe.gkd.ui.component.UploadOptions
class AdvancedVm : ViewModel() {
val snapshotCountFlow =
DbSet.snapshotDao.count().stateIn(viewModelScope, SharingStarted.Eagerly, 0)
val uploadOptions = UploadOptions(viewModelScope)
}

View File

@ -33,34 +33,24 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.style.TextAlign
import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.compose.viewModel
import com.ramcosta.composedestinations.generated.destinations.AboutPageDestination
import com.ramcosta.composedestinations.generated.destinations.AdvancedPageDestination
import com.ramcosta.composedestinations.utils.toDestinationsNavigator
import kotlinx.coroutines.flow.update
import li.songe.gkd.META
import li.songe.gkd.MainActivity
import li.songe.gkd.ui.component.RotatingLoadingIcon
import li.songe.gkd.ui.component.SettingItem
import li.songe.gkd.ui.component.TextMenu
import li.songe.gkd.ui.component.TextSwitch
import li.songe.gkd.ui.component.updateDialogOptions
import li.songe.gkd.ui.component.waitResult
import li.songe.gkd.ui.style.EmptyHeight
import li.songe.gkd.ui.style.itemPadding
import li.songe.gkd.ui.style.titleItemPadding
import li.songe.gkd.ui.theme.supportDynamicColor
import li.songe.gkd.util.DarkThemeOption
import li.songe.gkd.util.LocalNavController
import li.songe.gkd.util.UpdateChannelOption
import li.songe.gkd.util.checkUpdate
import li.songe.gkd.util.findOption
import li.songe.gkd.util.launchAsFn
import li.songe.gkd.util.launchTry
import li.songe.gkd.util.storeFlow
import li.songe.gkd.util.throttle
import li.songe.gkd.util.toast
val settingsNav = BottomNavItem(
label = "设置", icon = Icons.Outlined.Settings
@ -80,7 +70,6 @@ fun useSettingsPage(): ScaffoldExt {
mutableStateOf(false)
}
val checkUpdating by context.mainVm.updateStatus.checkUpdatingFlow.collectAsState()
if (showToastInputDlg) {
var value by remember {
@ -297,66 +286,6 @@ fun useSettingsPage(): ScaffoldExt {
})
}
if (META.updateEnabled) {
Text(
text = "更新",
modifier = Modifier.titleItemPadding(),
style = MaterialTheme.typography.titleSmall,
color = MaterialTheme.colorScheme.primary,
)
TextSwitch(
title = "自动更新",
subtitle = "打开应用时检测新版本",
checked = store.autoCheckAppUpdate,
onCheckedChange = {
storeFlow.value = store.copy(
autoCheckAppUpdate = it
)
}
)
TextMenu(
title = "更新渠道",
option = UpdateChannelOption.allSubObject.findOption(store.updateChannel)
) {
if (it.value == UpdateChannelOption.Beta.value) {
vm.viewModelScope.launchTry {
context.mainVm.dialogFlow.waitResult(
title = "版本渠道",
text = "测试版本渠道更新快\n但不稳定可能存在较多BUG\n请谨慎使用",
)
storeFlow.update { s -> s.copy(updateChannel = it.value) }
}
} else {
storeFlow.update { s -> s.copy(updateChannel = it.value) }
}
}
Row(
modifier = Modifier
.clickable(
onClick = throttle(fn = context.mainVm.viewModelScope.launchAsFn {
if (context.mainVm.updateStatus.checkUpdatingFlow.value) return@launchAsFn
val newVersion = context.mainVm.updateStatus.checkUpdate()
if (newVersion == null) {
toast("暂无更新")
}
})
)
.fillMaxWidth()
.itemPadding(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = "检查更新",
style = MaterialTheme.typography.bodyLarge,
)
RotatingLoadingIcon(loading = checkUpdating)
}
}
Text(
text = "其它",
modifier = Modifier.titleItemPadding(),

View File

@ -9,6 +9,7 @@ const val SERVER_SCRIPT_URL =
"https://registry.npmmirror.com/@gkd-kit/config/latest/files/dist/server.js"
const val REPOSITORY_URL = "https://github.com/gkd-kit/gkd"
const val ISSUES_URL = "${REPOSITORY_URL}/issues"
const val HOME_PAGE_URL = "https://gkd.li"

View File

@ -12,4 +12,7 @@ object SafeR {
val ic_page_info: Int = R.drawable.ic_page_info
val ic_flash_on: Int = R.drawable.ic_flash_on
val ic_flash_off: Int = R.drawable.ic_flash_off
val ic_logo_animation: Int = R.drawable.ic_logo_animation
val better_black: Int = R.color.better_black
val better_white: Int = R.color.better_white
}

View File

@ -0,0 +1,141 @@
<?xml version="1.0" encoding="utf-8"?>
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt">
<aapt:attr name="android:drawable">
<vector
android:width="64dp"
android:height="64dp"
android:viewportWidth="64"
android:viewportHeight="64">
<group android:name="path1">
<path
android:fillColor="#FFF"
android:pathData="M19,17.485l8.485,-8.485l2.816,2.841l-8.485,8.485z" />
</group>
<group android:name="path2">
<path
android:fillColor="#FFF"
android:pathData="M35.816,9l8.485,8.485l-2.816,2.841l-8.485,-8.485z" />
</group>
<group android:name="path3">
<path
android:fillColor="#FFF"
android:pathData="M23.08,21.999l8.466,-8.504l8.466,8.504l-8.466,8.504z" />
</group>
<group android:name="path4">
<path
android:fillColor="#FFF"
android:pathData="M8,55C8.726,50.581 10.104,46.395 12.775,42.977C14.285,41.046 16.133,39.452 17.847,37.729C18.107,37.468 18.465,37.332 19,37C20.272,43.004 20.091,48.871 19.751,54.875C15.872,55 11.994,55 8,55Z" />
</group>
<path
android:name="path5"
android:fillColor="#FFF"
android:pathData="M21.912,55C21.867,52.521 22.113,50.034 21.993,47.565C21.833,44.288 21.429,41.019 21.039,37.758C20.922,36.775 21.013,36.135 22.054,35.748C27.61,33.682 33.218,33.423 38.918,35.103C39.826,35.37 40.103,35.763 39.967,36.755C39.717,38.583 39.642,40.44 39.586,42.288C39.461,46.44 39.392,50.593 39.297,54.873C33.543,55 27.792,55 21.912,55Z" />
<group android:name="path6">
<path
android:fillColor="#FFF"
android:pathData="M42,55C42.06,48.791 42.24,42.582 42.432,36C50.149,40.036 53.985,46.652 56,54.895C51.379,55 46.75,55 42,55Z" />
</group>
</vector>
</aapt:attr>
<target android:name="path1">
<aapt:attr name="android:animation">
<set>
<objectAnimator
android:duration="1000"
android:interpolator="@android:interpolator/accelerate_decelerate"
android:propertyName="translateX"
android:repeatCount="infinite"
android:repeatMode="reverse"
android:valueFrom="0"
android:valueTo="-0.7071"
android:valueType="floatType" />
<objectAnimator
android:duration="1000"
android:interpolator="@android:interpolator/accelerate_decelerate"
android:propertyName="translateY"
android:repeatCount="infinite"
android:repeatMode="reverse"
android:valueFrom="0"
android:valueTo="-0.7071"
android:valueType="floatType" />
</set>
</aapt:attr>
</target>
<target android:name="path2">
<aapt:attr name="android:animation">
<set>
<objectAnimator
android:duration="1000"
android:interpolator="@android:interpolator/accelerate_decelerate"
android:propertyName="translateX"
android:repeatCount="infinite"
android:repeatMode="reverse"
android:valueFrom="0"
android:valueTo="0.7071"
android:valueType="floatType" />
<objectAnimator
android:duration="1000"
android:interpolator="@android:interpolator/accelerate_decelerate"
android:propertyName="translateY"
android:repeatCount="infinite"
android:repeatMode="reverse"
android:valueFrom="0"
android:valueTo="-0.7071"
android:valueType="floatType" />
</set>
</aapt:attr>
</target>
<target android:name="path3">
<aapt:attr name="android:animation">
<objectAnimator
android:duration="1000"
android:interpolator="@android:interpolator/accelerate_decelerate"
android:propertyName="translateY"
android:repeatCount="infinite"
android:repeatMode="reverse"
android:valueFrom="0"
android:valueTo="-1.13137"
android:valueType="floatType" />
</aapt:attr>
</target>
<target android:name="path4">
<aapt:attr name="android:animation">
<objectAnimator
android:duration="1000"
android:interpolator="@android:interpolator/accelerate_decelerate"
android:propertyName="translateX"
android:repeatCount="infinite"
android:repeatMode="reverse"
android:valueFrom="0"
android:valueTo="1.5"
android:valueType="floatType" />
</aapt:attr>
</target>
<target android:name="path5">
<aapt:attr name="android:animation">
<objectAnimator
android:duration="1000"
android:interpolator="@android:interpolator/accelerate_decelerate"
android:propertyName="fillAlpha"
android:repeatCount="infinite"
android:repeatMode="reverse"
android:valueFrom="0.8"
android:valueTo="1"
android:valueType="floatType" />
</aapt:attr>
</target>
<target android:name="path6">
<aapt:attr name="android:animation">
<objectAnimator
android:duration="1000"
android:interpolator="@android:interpolator/accelerate_decelerate"
android:propertyName="translateX"
android:repeatCount="infinite"
android:repeatMode="reverse"
android:valueFrom="-0.7"
android:valueTo="-2.2"
android:valueType="floatType" />
</aapt:attr>
</target>
</animated-vector>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background_tint">#FCFCFC</color>
<color name="ic_launcher_foreground_tint">#111</color>
<color name="ic_launcher_background_tint">@color/better_white</color>
<color name="ic_launcher_foreground_tint">@color/better_black</color>
</resources>

View File

@ -1,5 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background_tint">#111</color>
<color name="ic_launcher_foreground_tint">#FCFCFC</color>
<color name="better_white">#FCFCFC</color>
<color name="better_black">#111111</color>
<color name="ic_launcher_background_tint">@color/better_black</color>
<color name="ic_launcher_foreground_tint">@color/better_white</color>
</resources>

View File

@ -26,6 +26,9 @@ ktor_client_content_negotiation = { module = "io.ktor:ktor-client-content-negoti
ktor_serialization_kotlinx_json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" }
android_gradle = { module = "com.android.tools.build:gradle", version.ref = "android" }
compose_ui = { module = "androidx.compose.ui:ui", version.ref = "compose" }
compose_ui_graphics = { module = "androidx.compose.ui:ui-graphics", version.ref = "compose" }
compose_animation = { module = "androidx.compose.animation:animation", version.ref = "compose" }
compose_animation_graphics = { module = "androidx.compose.animation:animation-graphics", version.ref = "compose" }
compose_preview = { module = "androidx.compose.ui:ui-tooling-preview", version.ref = "compose" }
compose_tooling = { module = "androidx.compose.ui:ui-tooling", version.ref = "compose" }
compose_junit4 = { module = "androidx.compose.ui:ui-test-junit4", version.ref = "compose" }