Compare commits

..
8 Commits
Author SHA1 Message Date
meoware.exe af28dda074 Merge pull request #89 from meowarex/dev
Fix offline Shader + Lyric Seeking
2026-08-19 16:50:59 +10:00
meowarex bbd0876324 Merge pull request #87 from meowarex/dev
Refactor Backdrop <3
2026-08-14 16:55:38 +00:00
meoware.exe 5e150d10e3 Merge pull request #86 from meowarex/dev
New Shader Defaults <3
2026-08-15 00:23:45 +10:00
meoware.exe 225ebe3413 Merge pull request #85 from meowarex/dev
Update License <3
2026-08-14 21:08:56 +10:00
meowarex b175890e9d Merge pull request #84 from meowarex/dev
Forgot to bump .version <3
2026-08-14 06:38:52 +00:00
meoware.exe 4531e6e259 Merge pull request #83 from meowarex/dev
Kawarp Shader <3
2026-08-14 16:23:25 +10:00
meoware.exe a7fa8f7d30 Merge pull request #81 from meowarex/dev
Forgot .version (again)..
2026-08-13 01:08:17 +10:00
meoware.exe 2cb0e1a67e Merge pull request #80 from meowarex/dev
Revert to Legacy Sliders <3
2026-08-13 00:53:30 +10:00
8 changed files with 47 additions and 143 deletions
+1 -1
View File
@@ -1 +1 @@
1.1.6 1.1.5
+2 -2
View File
@@ -31,8 +31,8 @@ android {
defaultConfig { defaultConfig {
minSdk = 24 minSdk = 24
targetSdk = 36 targetSdk = 36
versionCode = 68 versionCode = 67
versionName = "1.1.6" versionName = "1.1.5"
vectorDrawables { vectorDrawables {
useSupportLibrary = true useSupportLibrary = true
@@ -39,15 +39,11 @@ class RadiantLyricsGithubService(
/** /**
* Fetches manager self-update releases. * Fetches manager self-update releases.
*/ */
suspend fun getManagerReleases(force: Boolean = false): ApiResponse<List<GithubRelease>> = suspend fun getManagerReleases(): ApiResponse<List<GithubRelease>> =
http.request { http.request {
url("https://api.github.com/repos/${BuildConfig.PATCHES_REPO_OWNER}/${BuildConfig.PATCHES_REPO_NAME}/releases") url("https://api.github.com/repos/${BuildConfig.PATCHES_REPO_OWNER}/${BuildConfig.PATCHES_REPO_NAME}/releases")
if (force) {
header(HttpHeaders.CacheControl, "no-cache")
} else {
header(HttpHeaders.CacheControl, "public, max-age=60, s-maxage=60") header(HttpHeaders.CacheControl, "public, max-age=60, s-maxage=60")
} }
}
/** /**
* Fetches the contributors list from GitHub for the repo. * Fetches the contributors list from GitHub for the repo.
@@ -70,19 +66,11 @@ class RadiantLyricsGithubService(
/** /**
* Fetches a page of commits (paginated). Used by the Home screen's commit list. * Fetches a page of commits (paginated). Used by the Home screen's commit list.
*/ */
suspend fun getCommits( suspend fun getCommits(page: Int, perPage: Int = 30): ApiResponse<List<com.meowarex.rlmobile.network.models.GithubCommit>> =
page: Int,
perPage: Int = 30,
force: Boolean = false,
): ApiResponse<List<com.meowarex.rlmobile.network.models.GithubCommit>> =
http.request { http.request {
url("https://api.github.com/repos/${BuildConfig.PATCHES_REPO_OWNER}/${BuildConfig.PATCHES_REPO_NAME}/commits?per_page=$perPage&page=${page + 1}") url("https://api.github.com/repos/${BuildConfig.PATCHES_REPO_OWNER}/${BuildConfig.PATCHES_REPO_NAME}/commits?per_page=$perPage&page=${page + 1}")
if (force) {
header(HttpHeaders.CacheControl, "no-cache")
} else {
header(HttpHeaders.CacheControl, "public, max-age=120, s-maxage=120") header(HttpHeaders.CacheControl, "public, max-age=120, s-maxage=120")
} }
}
companion object { companion object {
const val REPO_OWNER = BuildConfig.PATCHES_REPO_OWNER const val REPO_OWNER = BuildConfig.PATCHES_REPO_OWNER
@@ -7,8 +7,6 @@ import com.meowarex.rlmobile.network.services.RadiantLyricsGithubService
class CommitsPagingSource( class CommitsPagingSource(
private val github: RadiantLyricsGithubService, private val github: RadiantLyricsGithubService,
/** Bypasses the http response cache */
private val force: Boolean = false,
) : PagingSource<Int, GithubCommit>() { ) : PagingSource<Int, GithubCommit>() {
private val seenShas = mutableSetOf<String>() private val seenShas = mutableSetOf<String>()
private val seenTitles = mutableSetOf<String>() private val seenTitles = mutableSetOf<String>()
@@ -21,8 +19,7 @@ class CommitsPagingSource(
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, GithubCommit> { override suspend fun load(params: LoadParams<Int>): LoadResult<Int, GithubCommit> {
val page = params.key ?: 0 val page = params.key ?: 0
// Only the first page skips the cache return when (val r = github.getCommits(page)) {
return when (val r = github.getCommits(page, force = force && page == 0)) {
is ApiResponse.Success -> LoadResult.Page( is ApiResponse.Success -> LoadResult.Page(
data = r.data.filter { commit -> data = r.data.filter { commit ->
val title = commit.commit.message.lineSequence().first().trim() val title = commit.commit.message.lineSequence().first().trim()
@@ -15,7 +15,6 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.graphics.painter.BitmapPainter import androidx.compose.ui.graphics.painter.BitmapPainter
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
@@ -58,8 +57,6 @@ class HomeScreen : Screen, Parcelable {
val activity = LocalContext.current as ComponentActivity val activity = LocalContext.current as ComponentActivity
val updater = koinViewModel<UpdaterViewModel>(viewModelStoreOwner = activity) val updater = koinViewModel<UpdaterViewModel>(viewModelStoreOwner = activity)
val managerUpdateAvailable = updater.targetVersion != null val managerUpdateAvailable = updater.targetVersion != null
val refreshing = model.refreshing
val refreshAngle = rememberRefreshAngle()
LifecycleResumeEffect(Unit) { LifecycleResumeEffect(Unit) {
model.refresh(delay = true) model.refresh(delay = true)
@@ -71,19 +68,10 @@ class HomeScreen : Screen, Parcelable {
TopAppBar( TopAppBar(
title = { Text(stringResource(R.string.navigation_home)) }, title = { Text(stringResource(R.string.navigation_home)) },
actions = { actions = {
IconButton( IconButton(onClick = { model.refresh() }) {
onClick = {
model.refresh(force = true)
updater.checkForUpdates(force = true)
},
enabled = !refreshing,
) {
Icon( Icon(
painterResource(R.drawable.ic_refresh), painterResource(R.drawable.ic_refresh),
contentDescription = stringResource(R.string.navigation_refresh), contentDescription = stringResource(R.string.navigation_refresh),
modifier = Modifier.graphicsLayer {
rotationZ = if (refreshing) refreshAngle() else 0f
},
) )
} }
if (managerUpdateAvailable) { if (managerUpdateAvailable) {
@@ -34,9 +34,6 @@ import com.meowarex.rlmobile.ui.util.TidalVersion
import com.meowarex.rlmobile.ui.widgets.managerupdate.VersionDelta import com.meowarex.rlmobile.ui.widgets.managerupdate.VersionDelta
import com.meowarex.rlmobile.util.* import com.meowarex.rlmobile.util.*
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
@@ -56,22 +53,9 @@ class HomeModel(
var managerUpdateDeltas by mutableStateOf<List<VersionDelta>?>(null) var managerUpdateDeltas by mutableStateOf<List<VersionDelta>?>(null)
private set private set
/** Whether a user-initiated refresh is currently in flight */ val commits = Pager(PagingConfig(pageSize = 30)) {
var refreshing by mutableStateOf(false) CommitsPagingSource(github)
private set }.flow.cachedIn(screenModelScope)
// Bumped by every forced refresh, which restarts the pager with a fresh (cache-skipping) source.
// Without this the commit list would stay pinned to whatever it loaded on process start.
private val commitsGeneration = MutableStateFlow(0)
@OptIn(ExperimentalCoroutinesApi::class)
val commits = commitsGeneration
.flatMapLatest { generation ->
Pager(PagingConfig(pageSize = 30)) {
CommitsPagingSource(github, force = generation > 0)
}.flow
}
.cachedIn(screenModelScope)
private val refreshingLock = Mutex() private val refreshingLock = Mutex()
private var remoteDataJson: RLBuildInfo? = null private var remoteDataJson: RLBuildInfo? = null
@@ -180,10 +164,7 @@ class HomeModel(
) )
} }
/** fun refresh(delay: Boolean = false) = screenModelScope.launchIO {
* @param delay Waits a moment before starting
*/
fun refresh(delay: Boolean = false, force: Boolean = false) = screenModelScope.launchIO {
if (refreshingLock.isLocked) return@launchIO if (refreshingLock.isLocked) return@launchIO
if (delay) { if (delay) {
delay(250) delay(250)
@@ -191,16 +172,13 @@ class HomeModel(
} }
refreshingLock.withLock { refreshingLock.withLock {
if (force) mainThread { refreshing = true }
try {
val pkg = fetchInstalled() val pkg = fetchInstalled()
// Skip the remote fetch entirely when offline // Skip the remote fetch entirely when offline
val online = application.isOnline() val online = application.isOnline()
if (online && (force || remoteDataJson == null)) { if (online) {
fetchRemoteData(force) val remote = async(Dispatchers.IO) { if (remoteDataJson == null) fetchRemoteData() }
remote.await()
} }
// Offline the reload would only replace the visible list with a load error
if (force && online) commitsGeneration.update { it + 1 }
val install = pkg?.toInstallData() val install = pkg?.toInstallData()
val latest = remoteDataJson?.tidalVersionCode val latest = remoteDataJson?.tidalVersionCode
@@ -215,9 +193,6 @@ class HomeModel(
) )
maybeCheckManagerUpdate(pkg) maybeCheckManagerUpdate(pkg)
} }
} finally {
if (force) mainThread { refreshing = false }
}
} }
} }
@@ -308,9 +283,9 @@ class HomeModel(
paths.hasCachedSmaliPatches(info.data.patchesVersion) paths.hasCachedSmaliPatches(info.data.patchesVersion)
} }
private suspend fun fetchRemoteData(force: Boolean = false) { private suspend fun fetchRemoteData() {
val release = try { val release = try {
github.getLatestRelease(force).fold( github.getLatestRelease().fold(
success = { it }, success = { it },
fail = { Log.w(BuildConfig.TAG, "Failed to fetch latest release", it); return }, fail = { Log.w(BuildConfig.TAG, "Failed to fetch latest release", it); return },
) )
@@ -324,7 +299,7 @@ class HomeModel(
?.browserDownloadUrl ?.browserDownloadUrl
?: return ?: return
github.getBuildInfo(dataJsonUrl, force).fold( github.getBuildInfo(dataJsonUrl).fold(
success = { remoteDataJson = it }, success = { remoteDataJson = it },
fail = { Log.w(BuildConfig.TAG, "Failed to fetch build info", it) }, fail = { Log.w(BuildConfig.TAG, "Failed to fetch build info", it) },
) )
@@ -3,11 +3,6 @@ package com.meowarex.rlmobile.ui.screens.home
import android.os.Parcelable import android.os.Parcelable
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.infiniteRepeatable
import androidx.compose.animation.core.rememberInfiniteTransition
import androidx.compose.animation.core.tween
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.basicMarquee import androidx.compose.foundation.basicMarquee
import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource
@@ -19,7 +14,6 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.graphics.painter.BitmapPainter import androidx.compose.ui.graphics.painter.BitmapPainter
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
@@ -76,8 +70,6 @@ class HomeScreen : Screen, Parcelable {
val activity = LocalContext.current as ComponentActivity val activity = LocalContext.current as ComponentActivity
val updater = koinViewModel<UpdaterViewModel>(viewModelStoreOwner = activity) val updater = koinViewModel<UpdaterViewModel>(viewModelStoreOwner = activity)
val managerUpdateAvailable = updater.targetVersion != null val managerUpdateAvailable = updater.targetVersion != null
val refreshing = model.refreshing
val refreshAngle = rememberRefreshAngle()
LifecycleResumeEffect(Unit) { LifecycleResumeEffect(Unit) {
model.refresh(delay = true) model.refresh(delay = true)
@@ -94,14 +86,7 @@ class HomeScreen : Screen, Parcelable {
icon = painterResource(R.drawable.ic_refresh), icon = painterResource(R.drawable.ic_refresh),
contentDescription = stringResource(R.string.navigation_refresh), contentDescription = stringResource(R.string.navigation_refresh),
subtle = true, subtle = true,
enabled = !refreshing, onClick = { model.refresh() },
onClick = {
model.refresh(force = true)
updater.checkForUpdates(force = true)
},
modifier = Modifier.graphicsLayer {
rotationZ = if (refreshing) refreshAngle() else 0f
},
) )
if (managerUpdateAvailable) { if (managerUpdateAvailable) {
RadiantIconButton( RadiantIconButton(
@@ -379,18 +364,3 @@ private fun HeroContainer(
Column(modifier = Modifier.fillMaxWidth(), content = content) Column(modifier = Modifier.fillMaxWidth(), content = content)
} }
} }
/**
* Refresh Spinner thing
*/
@Composable
internal fun rememberRefreshAngle(): () -> Float {
val transition = rememberInfiniteTransition(label = "RefreshSpin")
val angle by transition.animateFloat(
initialValue = 0f,
targetValue = 360f,
animationSpec = infiniteRepeatable(tween(900, easing = LinearEasing)),
label = "RefreshSpinAngle",
)
return { angle }
}
@@ -37,29 +37,17 @@ class UpdaterViewModel(
field = MutableStateFlow(null) field = MutableStateFlow(null)
val isWorking: StateFlow<Boolean> val isWorking: StateFlow<Boolean>
field = MutableStateFlow(false) field = MutableStateFlow(false)
val isChecking: StateFlow<Boolean>
field = MutableStateFlow(false)
private var targetApkUrl: String? = null private var targetApkUrl: String? = null
init { init {
checkForUpdates() viewModelScope.launchIO {
}
/**
* Re-runs the update check
*/
fun checkForUpdates(force: Boolean = false) = viewModelScope.launchIO {
if (!isChecking.compareAndSet(expect = false, update = true))
return@launchIO
try { try {
fetchInfo(force) fetchInfo()
} catch (t: Throwable) { } catch (t: Throwable) {
Log.e(BuildConfig.TAG, "Failed to check for updates!", t) Log.e(BuildConfig.TAG, "Failed to check for updates!", t)
mainThread { application.showToast(R.string.updater_check_fail) } mainThread { application.showToast(R.string.updater_check_fail) }
} finally { }
isChecking.value = false
} }
} }
@@ -154,14 +142,14 @@ class UpdaterViewModel(
* then finds the latest release based on the largest semantic version extracted from the tag name (`v1.0.0`), * then finds the latest release based on the largest semantic version extracted from the tag name (`v1.0.0`),
* and populates the state to show to the user. * and populates the state to show to the user.
*/ */
private suspend fun fetchInfo(force: Boolean = false) { private suspend fun fetchInfo() {
Log.d(BuildConfig.TAG, "Checking for updates...") Log.d(BuildConfig.TAG, "Checking for updates...")
val currentVersion = SemVer.parseOrNull(BuildConfig.VERSION_NAME) val currentVersion = SemVer.parseOrNull(BuildConfig.VERSION_NAME)
?: throw Error("Failed to parse current app version") ?: throw Error("Failed to parse current app version")
// Fetch releases from GitHub (60s local cache) // Fetch releases from GitHub (60s local cache)
val releases = github.getManagerReleases(force).getOrThrow() val releases = github.getManagerReleases().getOrThrow()
// Find the latest release by parsed version // Find the latest release by parsed version
val (version, release, apkUrl) = releases val (version, release, apkUrl) = releases
@@ -183,14 +171,12 @@ class UpdaterViewModel(
return return
} }
Log.d(BuildConfig.TAG, "Found an update! $version $apkUrl") Log.d(BuildConfig.TAG, "Found an update! $targetVersion $targetApkUrl")
val newVersion = version.toString()
mainThread { mainThread {
val alreadyKnown = targetVersion == newVersion
targetReleaseUrl = release.htmlUrl targetReleaseUrl = release.htmlUrl
targetVersion = newVersion targetVersion = version.toString()
targetApkUrl = apkUrl targetApkUrl = apkUrl
if (!alreadyKnown) showDialog = true showDialog = true
} }
} }