mirror of
https://github.com/meowarex/rl-mobile.git
synced 2026-06-17 21:13:11 +10:00
@@ -48,6 +48,8 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
mv ./dist/app-release.apk ./dist/rl-manager.apk
|
mv ./dist/app-release.apk ./dist/rl-manager.apk
|
||||||
cp patches/data.json ./dist/data.json
|
cp patches/data.json ./dist/data.json
|
||||||
|
# Point each release's data.json at its own assets so historical releases stay self-contained.
|
||||||
|
sed -i "s|releases/download/latest/|releases/download/v${{ needs.Version.outputs.version }}/|g" ./dist/data.json
|
||||||
cd patches && zip -r ../dist/patches.zip . -x "data.json" && cd ..
|
cd patches && zip -r ../dist/patches.zip . -x "data.json" && cd ..
|
||||||
|
|
||||||
tidal_src=$(find tidal-apk -maxdepth 1 \( -name "*.apk" -o -name "*.apkm" \) | head -1)
|
tidal_src=$(find tidal-apk -maxdepth 1 \( -name "*.apk" -o -name "*.apkm" \) | head -1)
|
||||||
@@ -67,10 +69,10 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Publish release
|
- name: Publish release
|
||||||
uses: marvinpinto/action-automatic-releases@latest
|
uses: softprops/action-gh-release@v2
|
||||||
with:
|
with:
|
||||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
tag_name: v${{ needs.Version.outputs.version }}
|
||||||
automatic_release_tag: latest
|
name: v${{ needs.Version.outputs.version }}
|
||||||
prerelease: false
|
prerelease: false
|
||||||
title: v${{ needs.Version.outputs.version }}
|
make_latest: "true"
|
||||||
files: ./dist/**
|
files: ./dist/**
|
||||||
|
|||||||
+6
@@ -30,6 +30,12 @@ private fun ComponentOptionsScreenPreview(
|
|||||||
selected = parameters.selected,
|
selected = parameters.selected,
|
||||||
onSelectComponent = {},
|
onSelectComponent = {},
|
||||||
onDeleteComponent = {},
|
onDeleteComponent = {},
|
||||||
|
onImportFromUri = {},
|
||||||
|
releasesExpanded = false,
|
||||||
|
releasesState = com.meowarex.rlmobile.ui.screens.componentopts.ComponentOptionsModel.ReleasesState.Idle,
|
||||||
|
onToggleReleases = {},
|
||||||
|
onImportRelease = {},
|
||||||
|
importingReleaseTag = null,
|
||||||
onBackPressed = {},
|
onBackPressed = {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+168
@@ -1,26 +1,44 @@
|
|||||||
package com.meowarex.rlmobile.ui.screens.componentopts
|
package com.meowarex.rlmobile.ui.screens.componentopts
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
|
import android.net.Uri
|
||||||
|
import android.provider.OpenableColumns
|
||||||
|
import android.util.Log
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import cafe.adriel.voyager.core.model.screenModelScope
|
import cafe.adriel.voyager.core.model.screenModelScope
|
||||||
|
import com.meowarex.rlmobile.BuildConfig
|
||||||
import com.meowarex.rlmobile.R
|
import com.meowarex.rlmobile.R
|
||||||
import com.meowarex.rlmobile.manager.PathManager
|
import com.meowarex.rlmobile.manager.PathManager
|
||||||
|
import com.meowarex.rlmobile.manager.download.KtorDownloadManager
|
||||||
|
import com.meowarex.rlmobile.network.models.GithubRelease
|
||||||
|
import com.meowarex.rlmobile.network.services.RadiantLyricsGithubService
|
||||||
import com.meowarex.rlmobile.network.utils.SemVer
|
import com.meowarex.rlmobile.network.utils.SemVer
|
||||||
|
import com.meowarex.rlmobile.network.utils.fold
|
||||||
import com.meowarex.rlmobile.ui.util.ScreenModelWithResult
|
import com.meowarex.rlmobile.ui.util.ScreenModelWithResult
|
||||||
import com.meowarex.rlmobile.ui.util.ScreenResultKey
|
import com.meowarex.rlmobile.ui.util.ScreenResultKey
|
||||||
import com.meowarex.rlmobile.util.*
|
import com.meowarex.rlmobile.util.*
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import java.io.File
|
||||||
import kotlin.time.Instant
|
import kotlin.time.Instant
|
||||||
|
|
||||||
class ComponentOptionsModel(
|
class ComponentOptionsModel(
|
||||||
screenResultKey: ScreenResultKey,
|
screenResultKey: ScreenResultKey,
|
||||||
private val paths: PathManager,
|
private val paths: PathManager,
|
||||||
private val context: Application,
|
private val context: Application,
|
||||||
|
private val github: RadiantLyricsGithubService,
|
||||||
|
private val downloader: KtorDownloadManager,
|
||||||
) : ScreenModelWithResult<PatchComponent?>(screenResultKey) {
|
) : ScreenModelWithResult<PatchComponent?>(screenResultKey) {
|
||||||
val components = mutableStateListOf<PatchComponent>()
|
val components = mutableStateListOf<PatchComponent>()
|
||||||
var selected by mutableStateOf<PatchComponent?>(null)
|
var selected by mutableStateOf<PatchComponent?>(null)
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
var releasesExpanded by mutableStateOf(false)
|
||||||
|
private set
|
||||||
|
var releasesState by mutableStateOf<ReleasesState>(ReleasesState.Idle)
|
||||||
|
private set
|
||||||
|
var importingReleaseTag by mutableStateOf<String?>(null)
|
||||||
|
private set
|
||||||
|
|
||||||
fun selectComponent(component: PatchComponent?) {
|
fun selectComponent(component: PatchComponent?) {
|
||||||
selected = component
|
selected = component
|
||||||
}
|
}
|
||||||
@@ -64,7 +82,157 @@ class ComponentOptionsModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun importFromUri(uri: Uri, type: PatchComponent.Type) = screenModelScope.launchIO {
|
||||||
|
try {
|
||||||
|
val targetDir = when (type) {
|
||||||
|
PatchComponent.Type.TidalApk -> paths.customTidalApksDir
|
||||||
|
PatchComponent.Type.Patches -> paths.customPatchesDir
|
||||||
|
}
|
||||||
|
val ext = when (type) {
|
||||||
|
PatchComponent.Type.TidalApk -> "apk"
|
||||||
|
PatchComponent.Type.Patches -> "zip"
|
||||||
|
}
|
||||||
|
targetDir.mkdirs()
|
||||||
|
|
||||||
|
val tempFile = targetDir.resolve("import-${System.currentTimeMillis()}.tmp")
|
||||||
|
context.contentResolver.openInputStream(uri)?.use { input ->
|
||||||
|
tempFile.outputStream().use { output -> input.copyTo(output) }
|
||||||
|
} ?: throw IllegalStateException("Could not open input stream for $uri")
|
||||||
|
|
||||||
|
val sourceDisplayName = queryDisplayName(uri)
|
||||||
|
val version = when (type) {
|
||||||
|
PatchComponent.Type.TidalApk -> readApkVersion(tempFile)
|
||||||
|
?: extractVersionFromName(sourceDisplayName)
|
||||||
|
?: FALLBACK_VERSION
|
||||||
|
PatchComponent.Type.Patches -> extractVersionFromName(sourceDisplayName)
|
||||||
|
?: FALLBACK_VERSION
|
||||||
|
}
|
||||||
|
|
||||||
|
val finalName = "${System.currentTimeMillis()}_$version.$ext"
|
||||||
|
val finalFile = targetDir.resolve(finalName)
|
||||||
|
if (!tempFile.renameTo(finalFile)) {
|
||||||
|
tempFile.copyTo(finalFile, overwrite = true)
|
||||||
|
tempFile.delete()
|
||||||
|
}
|
||||||
|
|
||||||
|
refreshComponents(type)
|
||||||
|
mainThread { context.showToast(R.string.intent_import_component_success, finalName) }
|
||||||
|
} catch (t: Throwable) {
|
||||||
|
Log.e(BuildConfig.TAG, "Failed to import custom component from $uri", t)
|
||||||
|
mainThread { context.showToast(R.string.intent_import_component_failure) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun queryDisplayName(uri: Uri): String? = try {
|
||||||
|
context.contentResolver.query(uri, arrayOf(OpenableColumns.DISPLAY_NAME), null, null, null)
|
||||||
|
?.use { cursor ->
|
||||||
|
if (cursor.moveToFirst()) cursor.getString(0) else null
|
||||||
|
}
|
||||||
|
} catch (_: Throwable) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun readApkVersion(apkFile: File): String? = try {
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
context.packageManager.getPackageArchiveInfo(apkFile.absolutePath, 0)
|
||||||
|
?.versionName
|
||||||
|
?.let { SemVer.parseOrNull(it) }
|
||||||
|
?.toString()
|
||||||
|
} catch (_: Throwable) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun extractVersionFromName(name: String?): String? {
|
||||||
|
if (name == null) return null
|
||||||
|
return """(\d+\.\d+\.\d+)""".toRegex().find(name)?.value
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toggleReleasesExpanded(type: PatchComponent.Type) {
|
||||||
|
releasesExpanded = !releasesExpanded
|
||||||
|
if (releasesExpanded && releasesState is ReleasesState.Idle) {
|
||||||
|
loadReleases(type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadReleases(type: PatchComponent.Type) = screenModelScope.launchIO {
|
||||||
|
releasesState = ReleasesState.Loading
|
||||||
|
github.getManagerReleases().fold(
|
||||||
|
success = { all ->
|
||||||
|
val assetName = assetNameFor(type)
|
||||||
|
val filtered = all.filter { release ->
|
||||||
|
release.assets.any { it.name == assetName }
|
||||||
|
}
|
||||||
|
releasesState = ReleasesState.Loaded(filtered)
|
||||||
|
},
|
||||||
|
fail = {
|
||||||
|
Log.w(BuildConfig.TAG, "Failed to load GitHub releases", it)
|
||||||
|
releasesState = ReleasesState.Failed
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun importFromRelease(release: GithubRelease, type: PatchComponent.Type) = screenModelScope.launchIO {
|
||||||
|
val assetName = assetNameFor(type)
|
||||||
|
val asset = release.assets.find { it.name == assetName } ?: run {
|
||||||
|
mainThread { context.showToast(R.string.intent_import_component_failure) }
|
||||||
|
return@launchIO
|
||||||
|
}
|
||||||
|
|
||||||
|
val targetDir = when (type) {
|
||||||
|
PatchComponent.Type.TidalApk -> paths.customTidalApksDir
|
||||||
|
PatchComponent.Type.Patches -> paths.customPatchesDir
|
||||||
|
}
|
||||||
|
targetDir.mkdirs()
|
||||||
|
|
||||||
|
importingReleaseTag = release.tagName
|
||||||
|
try {
|
||||||
|
val tempFile = targetDir.resolve("release-${System.currentTimeMillis()}.tmp")
|
||||||
|
val result = downloader.download(asset.browserDownloadUrl, tempFile)
|
||||||
|
if (result !is com.meowarex.rlmobile.manager.download.IDownloadManager.Result.Success) {
|
||||||
|
tempFile.delete()
|
||||||
|
mainThread { context.showToast(R.string.intent_import_component_failure) }
|
||||||
|
return@launchIO
|
||||||
|
}
|
||||||
|
|
||||||
|
val ext = when (type) {
|
||||||
|
PatchComponent.Type.TidalApk -> "apk"
|
||||||
|
PatchComponent.Type.Patches -> "zip"
|
||||||
|
}
|
||||||
|
val version = SemVer.parseOrNull(release.tagName.removePrefix("v"))?.toString()
|
||||||
|
?: FALLBACK_VERSION
|
||||||
|
val finalName = "${System.currentTimeMillis()}_$version.$ext"
|
||||||
|
val finalFile = targetDir.resolve(finalName)
|
||||||
|
if (!tempFile.renameTo(finalFile)) {
|
||||||
|
tempFile.copyTo(finalFile, overwrite = true)
|
||||||
|
tempFile.delete()
|
||||||
|
}
|
||||||
|
refreshComponents(type)
|
||||||
|
mainThread { context.showToast(R.string.intent_import_component_success, finalName) }
|
||||||
|
} catch (t: Throwable) {
|
||||||
|
Log.e(BuildConfig.TAG, "Failed to import release ${release.tagName}", t)
|
||||||
|
mainThread { context.showToast(R.string.intent_import_component_failure) }
|
||||||
|
} finally {
|
||||||
|
importingReleaseTag = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun assetNameFor(type: PatchComponent.Type) = when (type) {
|
||||||
|
PatchComponent.Type.TidalApk -> "tidal-stock.apk"
|
||||||
|
PatchComponent.Type.Patches -> "patches.zip"
|
||||||
|
}
|
||||||
|
|
||||||
override fun onDispose() {
|
override fun onDispose() {
|
||||||
screenModelScope.launch { setResult(selected) }
|
screenModelScope.launch { setResult(selected) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sealed interface ReleasesState {
|
||||||
|
data object Idle : ReleasesState
|
||||||
|
data object Loading : ReleasesState
|
||||||
|
data class Loaded(val releases: List<GithubRelease>) : ReleasesState
|
||||||
|
data object Failed : ReleasesState
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val FALLBACK_VERSION = "0.0.0"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+232
@@ -1,13 +1,20 @@
|
|||||||
package com.meowarex.rlmobile.ui.screens.componentopts
|
package com.meowarex.rlmobile.ui.screens.componentopts
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
|
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.layout.*
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import cafe.adriel.voyager.koin.koinScreenModel
|
import cafe.adriel.voyager.koin.koinScreenModel
|
||||||
@@ -61,6 +68,12 @@ class ComponentOptionsScreen(
|
|||||||
selected = model.selected,
|
selected = model.selected,
|
||||||
onSelectComponent = model::selectComponent,
|
onSelectComponent = model::selectComponent,
|
||||||
onDeleteComponent = model::deleteComponent,
|
onDeleteComponent = model::deleteComponent,
|
||||||
|
onImportFromUri = { uri -> model.importFromUri(uri, componentType) },
|
||||||
|
releasesExpanded = model.releasesExpanded,
|
||||||
|
releasesState = model.releasesState,
|
||||||
|
onToggleReleases = { model.toggleReleasesExpanded(componentType) },
|
||||||
|
onImportRelease = { release -> model.importFromRelease(release, componentType) },
|
||||||
|
importingReleaseTag = model.importingReleaseTag,
|
||||||
onBackPressed = { navigator.back(null) },
|
onBackPressed = { navigator.back(null) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -73,8 +86,22 @@ fun ComponentOptionsScreenContent(
|
|||||||
selected: PatchComponent?,
|
selected: PatchComponent?,
|
||||||
onSelectComponent: (PatchComponent?) -> Unit,
|
onSelectComponent: (PatchComponent?) -> Unit,
|
||||||
onDeleteComponent: (PatchComponent) -> Unit,
|
onDeleteComponent: (PatchComponent) -> Unit,
|
||||||
|
onImportFromUri: (Uri) -> Unit,
|
||||||
|
releasesExpanded: Boolean,
|
||||||
|
releasesState: ComponentOptionsModel.ReleasesState,
|
||||||
|
onToggleReleases: () -> Unit,
|
||||||
|
onImportRelease: (com.meowarex.rlmobile.network.models.GithubRelease) -> Unit,
|
||||||
|
importingReleaseTag: String?,
|
||||||
onBackPressed: () -> Unit,
|
onBackPressed: () -> Unit,
|
||||||
) {
|
) {
|
||||||
|
val mime = when (componentType) {
|
||||||
|
PatchComponent.Type.TidalApk -> "application/vnd.android.package-archive"
|
||||||
|
PatchComponent.Type.Patches -> "application/zip"
|
||||||
|
}
|
||||||
|
val filePicker = rememberLauncherForActivityResult(
|
||||||
|
ActivityResultContracts.OpenDocument(),
|
||||||
|
) { uri -> uri?.let(onImportFromUri) }
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = { ComponentOptionsAppBar(componentType = componentType) },
|
topBar = { ComponentOptionsAppBar(componentType = componentType) },
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
@@ -98,6 +125,16 @@ fun ComponentOptionsScreenContent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
item(key = "RELEASES_ACCORDION") {
|
||||||
|
ReleasesAccordion(
|
||||||
|
expanded = releasesExpanded,
|
||||||
|
state = releasesState,
|
||||||
|
importingTag = importingReleaseTag,
|
||||||
|
onToggle = onToggleReleases,
|
||||||
|
onImport = onImportRelease,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
items(
|
items(
|
||||||
items = components,
|
items = components,
|
||||||
contentType = { "COMPONENT" },
|
contentType = { "COMPONENT" },
|
||||||
@@ -112,6 +149,10 @@ fun ComponentOptionsScreenContent(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
item(key = "BROWSE") {
|
||||||
|
BrowseImportCard(onClick = { filePicker.launch(arrayOf(mime)) })
|
||||||
|
}
|
||||||
|
|
||||||
item("EXIT_BTN") {
|
item("EXIT_BTN") {
|
||||||
Row(
|
Row(
|
||||||
horizontalArrangement = Arrangement.End,
|
horizontalArrangement = Arrangement.End,
|
||||||
@@ -129,3 +170,194 @@ fun ComponentOptionsScreenContent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ReleasesAccordion(
|
||||||
|
expanded: Boolean,
|
||||||
|
state: ComponentOptionsModel.ReleasesState,
|
||||||
|
importingTag: String?,
|
||||||
|
onToggle: () -> Unit,
|
||||||
|
onImport: (com.meowarex.rlmobile.network.models.GithubRelease) -> Unit,
|
||||||
|
) {
|
||||||
|
Surface(
|
||||||
|
color = MaterialTheme.colorScheme.surfaceContainerHigh,
|
||||||
|
shape = RoundedCornerShape(16.dp),
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
) {
|
||||||
|
Column {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(14.dp),
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clickable(onClick = onToggle)
|
||||||
|
.padding(horizontal = 16.dp, vertical = 14.dp),
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(R.drawable.ic_account_github_white_24dp),
|
||||||
|
contentDescription = null,
|
||||||
|
tint = MaterialTheme.colorScheme.primary,
|
||||||
|
)
|
||||||
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.componentopts_releases_title),
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.componentopts_releases_desc),
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = LocalContentColor.current.copy(alpha = 0.65f),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(
|
||||||
|
if (expanded) R.drawable.ic_arrow_up_small else R.drawable.ic_arrow_down_small
|
||||||
|
),
|
||||||
|
contentDescription = null,
|
||||||
|
tint = LocalContentColor.current.copy(alpha = 0.7f),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (expanded) {
|
||||||
|
HorizontalDivider(
|
||||||
|
color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.35f),
|
||||||
|
)
|
||||||
|
ReleasesContent(
|
||||||
|
state = state,
|
||||||
|
importingTag = importingTag,
|
||||||
|
onImport = onImport,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ReleasesContent(
|
||||||
|
state: ComponentOptionsModel.ReleasesState,
|
||||||
|
importingTag: String?,
|
||||||
|
onImport: (com.meowarex.rlmobile.network.models.GithubRelease) -> Unit,
|
||||||
|
) {
|
||||||
|
when (state) {
|
||||||
|
is ComponentOptionsModel.ReleasesState.Idle,
|
||||||
|
ComponentOptionsModel.ReleasesState.Loading -> Box(
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(20.dp),
|
||||||
|
) { CircularProgressIndicator(modifier = Modifier.size(24.dp)) }
|
||||||
|
|
||||||
|
ComponentOptionsModel.ReleasesState.Failed -> Text(
|
||||||
|
text = stringResource(R.string.network_load_fail),
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.error,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(20.dp),
|
||||||
|
)
|
||||||
|
|
||||||
|
is ComponentOptionsModel.ReleasesState.Loaded -> {
|
||||||
|
if (state.releases.isEmpty()) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.componentopts_releases_empty),
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = LocalContentColor.current.copy(alpha = 0.65f),
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(20.dp),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Column {
|
||||||
|
state.releases.forEachIndexed { index, release ->
|
||||||
|
ReleaseRow(
|
||||||
|
release = release,
|
||||||
|
importing = importingTag == release.tagName,
|
||||||
|
anyImporting = importingTag != null,
|
||||||
|
onImport = { onImport(release) },
|
||||||
|
)
|
||||||
|
if (index != state.releases.lastIndex) {
|
||||||
|
HorizontalDivider(
|
||||||
|
color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.2f),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ReleaseRow(
|
||||||
|
release: com.meowarex.rlmobile.network.models.GithubRelease,
|
||||||
|
importing: Boolean,
|
||||||
|
anyImporting: Boolean,
|
||||||
|
onImport: () -> Unit,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||||
|
) {
|
||||||
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
|
Text(
|
||||||
|
text = release.tagName,
|
||||||
|
style = MaterialTheme.typography.titleSmall,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = release.name ?: release.tagName,
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = LocalContentColor.current.copy(alpha = 0.55f),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
FilledTonalButton(
|
||||||
|
onClick = onImport,
|
||||||
|
enabled = !anyImporting,
|
||||||
|
) {
|
||||||
|
if (importing) {
|
||||||
|
CircularProgressIndicator(
|
||||||
|
strokeWidth = 2.dp,
|
||||||
|
modifier = Modifier.size(16.dp),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Text(stringResource(R.string.action_install))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun BrowseImportCard(onClick: () -> Unit) {
|
||||||
|
Surface(
|
||||||
|
color = MaterialTheme.colorScheme.surfaceContainerHigh,
|
||||||
|
shape = RoundedCornerShape(16.dp),
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clickable(onClick = onClick),
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(14.dp),
|
||||||
|
modifier = Modifier.padding(horizontal = 16.dp, vertical = 14.dp),
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(R.drawable.ic_add),
|
||||||
|
contentDescription = null,
|
||||||
|
tint = MaterialTheme.colorScheme.primary,
|
||||||
|
)
|
||||||
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.componentopts_browse_title),
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.componentopts_browse_desc),
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = LocalContentColor.current.copy(alpha = 0.65f),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import cafe.adriel.voyager.navigator.LocalNavigator
|
|||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
import com.meowarex.rlmobile.R
|
import com.meowarex.rlmobile.R
|
||||||
import com.meowarex.rlmobile.ui.components.SegmentedButton
|
import com.meowarex.rlmobile.ui.components.SegmentedButton
|
||||||
|
import com.meowarex.rlmobile.ui.components.Tag
|
||||||
import com.meowarex.rlmobile.ui.screens.about.AboutScreen
|
import com.meowarex.rlmobile.ui.screens.about.AboutScreen
|
||||||
import com.meowarex.rlmobile.ui.screens.home.components.CommitList
|
import com.meowarex.rlmobile.ui.screens.home.components.CommitList
|
||||||
import com.meowarex.rlmobile.ui.screens.logs.LogsListScreen
|
import com.meowarex.rlmobile.ui.screens.logs.LogsListScreen
|
||||||
@@ -194,8 +195,8 @@ private fun ColumnScope.HomeContent(
|
|||||||
val tidalBehind = install != null && install.tidalUpToDate == false
|
val tidalBehind = install != null && install.tidalUpToDate == false
|
||||||
AnimatedVisibility(visible = patchesBehind || tidalBehind) {
|
AnimatedVisibility(visible = patchesBehind || tidalBehind) {
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) {
|
Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) {
|
||||||
if (patchesBehind) UpdateTag(text = "New Patches!")
|
if (patchesBehind) Tag(text = "New Patches!")
|
||||||
if (tidalBehind) UpdateTag(text = "TIDAL Update!")
|
if (tidalBehind) Tag(text = "TIDAL Update!")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,20 +241,5 @@ private fun ColumnScope.HomeContent(
|
|||||||
|
|
||||||
ElevatedCard(modifier = Modifier.fillMaxSize()) {
|
ElevatedCard(modifier = Modifier.fillMaxSize()) {
|
||||||
CommitList(commits = commits.collectAsLazyPagingItems())
|
CommitList(commits = commits.collectAsLazyPagingItems())
|
||||||
'}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun UpdateTag(text: String) {
|
|
||||||
Surface(
|
|
||||||
color = MaterialTheme.colorScheme.primaryContainer,
|
|
||||||
shape = RoundedCornerShape(6.dp),
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = text,
|
|
||||||
style = MaterialTheme.typography.labelMedium,
|
|
||||||
color = MaterialTheme.colorScheme.onPrimaryContainer,
|
|
||||||
modifier = Modifier.padding(horizontal = 8.dp, vertical = 3.dp),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,4 +21,4 @@
|
|||||||
android:pathData="M5.209,1.737C5.313,1.473 5.687,1.473 5.791,1.737L6.157,2.667C6.189,2.747 6.253,2.811 6.333,2.843L7.263,3.209C7.527,3.313 7.527,3.687 7.263,3.791L6.333,4.157C6.253,4.189 6.189,4.253 6.157,4.333L5.791,5.263C5.687,5.527 5.313,5.527 5.209,5.263L4.843,4.333C4.811,4.253 4.747,4.189 4.667,4.157L3.737,3.791C3.473,3.687 3.473,3.313 3.737,3.209L4.667,2.843C4.747,2.811 4.811,2.747 4.843,2.667L5.209,1.737Z" />
|
android:pathData="M5.209,1.737C5.313,1.473 5.687,1.473 5.791,1.737L6.157,2.667C6.189,2.747 6.253,2.811 6.333,2.843L7.263,3.209C7.527,3.313 7.527,3.687 7.263,3.791L6.333,4.157C6.253,4.189 6.189,4.253 6.157,4.333L5.791,5.263C5.687,5.527 5.313,5.527 5.209,5.263L4.843,4.333C4.811,4.253 4.747,4.189 4.667,4.157L3.737,3.791C3.473,3.687 3.473,3.313 3.737,3.209L4.667,2.843C4.747,2.811 4.811,2.747 4.843,2.667L5.209,1.737Z" />
|
||||||
</group>
|
</group>
|
||||||
</vector>
|
</vector>
|
||||||
aight
|
|
||||||
|
|||||||
@@ -71,7 +71,7 @@
|
|||||||
<string name="permissions_notifs_title">Notifications</string>
|
<string name="permissions_notifs_title">Notifications</string>
|
||||||
<string name="permissions_battery_title">Background Battery</string>
|
<string name="permissions_battery_title">Background Battery</string>
|
||||||
<string name="permissions_battery_desc">Ensures the installation process does not get automatically cancelled if the app is minimized.</string>
|
<string name="permissions_battery_desc">Ensures the installation process does not get automatically cancelled if the app is minimized.</string>
|
||||||
<string name="permissions_notifs_desc">Used only to show the download progress if Radiant Lyrics Manager is minimized during installation.</string>
|
<string name="permissions_notifs_desc">Used to notify you when updates are available!</string>
|
||||||
|
|
||||||
<string name="permissions_root_denied">Failed to obtain root permissions</string>
|
<string name="permissions_root_denied">Failed to obtain root permissions</string>
|
||||||
<string name="permissions_shizuku_denied">Failed to obtain Shizuku permissions</string>
|
<string name="permissions_shizuku_denied">Failed to obtain Shizuku permissions</string>
|
||||||
@@ -278,6 +278,11 @@
|
|||||||
<string name="componentopts_screen_desc">Select a custom build that was imported by Manager.</string>
|
<string name="componentopts_screen_desc">Select a custom build that was imported by Manager.</string>
|
||||||
<string name="componentopts_selected_none">Latest</string>
|
<string name="componentopts_selected_none">Latest</string>
|
||||||
<string name="componentopts_deleted">Successfully deleted component!</string>
|
<string name="componentopts_deleted">Successfully deleted component!</string>
|
||||||
|
<string name="componentopts_browse_title">Browse files…</string>
|
||||||
|
<string name="componentopts_browse_desc">Import a file from your device</string>
|
||||||
|
<string name="componentopts_releases_title">GitHub Releases</string>
|
||||||
|
<string name="componentopts_releases_desc">Pick a historical release from the repo</string>
|
||||||
|
<string name="componentopts_releases_empty">No releases with this component found</string>
|
||||||
|
|
||||||
<string name="log_title">Log</string>
|
<string name="log_title">Log</string>
|
||||||
<string name="log_section_install_info">Installation Info</string>
|
<string name="log_section_install_info">Installation Info</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user