Compare commits

..

No commits in common. "main" and "v1.1.0" have entirely different histories.
main ... v1.1.0

157 changed files with 1633 additions and 851 deletions

View file

@ -26,7 +26,7 @@ Goon Manager makes installing and managing Discord mods simple and safe. It's bu
<p><strong>Download & run</strong> — get a released APK from Releases and install with:</p>
Get newest apk from [release list](https://github.com/OTKUSteyler/GoonManager/releases/tag/v1.1.3) and install it.
Get newest apk from [release list](https://github.com/OTKUSteyler/GoonManager/releases/tag/V1.0) and install it.
<p><strong>Build from source</strong> — clone and assemble:</p>
@ -127,5 +127,3 @@ Goon Manager is open source under the OSL License. See `LICENSE` for details.
<p align="center">
<strong>Made with ❤️ for the GoonCord community — keep it playful, keep it safe.</strong>
</p>
![OTKUSteyler](https://count.getloli.com/@OTKUSteyler?name=OTKUSteyler&theme=miku&padding=7&offset=0&align=top&scale=1&pixelated=1&darkmode=1)

View file

@ -20,13 +20,13 @@ val gitHasHasLocalChanges = providers.execIgnoreCode("git", "status", "-s").isNo
android {
namespace = "dev.shiggy.manager"
compileSdk = 37
compileSdk = 36
defaultConfig {
minSdk = 28
targetSdk = 37
versionCode = 10_12_01
versionName = "1.1.2"
targetSdk = 36
versionCode = 10_10_00
versionName = "1.1.0"
vectorDrawables {
useSupportLibrary = true
}

View file

@ -1,30 +0,0 @@
# Download LSPatch jar and convert it to an aar
# Jars cannot be used as a dependency without breaking R8 optimization
LSPATCH_URL="https://github.com/JingMatrix/LSPatch/releases/latest/download/lspatch.jar"
LSPATCH_FILE_NAME="lspatch"
function cleanup {
rm -rf $LSPATCH_FILE_NAME.jar META-INF AndroidManifest.xml classes.jar R.txt
}
cleanup
# download lspatch
curl -sSL -o $LSPATCH_FILE_NAME.jar $LSPATCH_URL
# prepare aar contents
unzip -q $LSPATCH_FILE_NAME.jar "META-INF/**"
mv $LSPATCH_FILE_NAME.jar classes.jar
touch R.txt
cat >AndroidManifest.xml <<EOF
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.lsposed.lspatch">
<uses-sdk android:minSdkVersion="28" android:targetSdkVersion="36" />
</manifest>
EOF
# add everything to aar
rm -f $LSPATCH_FILE_NAME.aar
zip -rq $LSPATCH_FILE_NAME.aar R.txt AndroidManifest.xml META-INF classes.jar
cleanup

Binary file not shown.

View file

@ -18,11 +18,11 @@
<application
android:name="com.aliucord.manager.ManagerApplication"
android:icon="@drawable/ic_launcher"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:largeHeap="true"
android:requestLegacyExternalStorage="true"
android:roundIcon="@drawable/ic_launcher_round"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.AppCompat.NoActionBar"
>

View file

@ -24,7 +24,7 @@ import com.aliucord.manager.ui.screens.home.HomeScreen
import com.aliucord.manager.ui.theme.ManagerTheme
import com.aliucord.manager.ui.widgets.updater.UpdaterDialog
import com.aliucord.manager.util.back
import dev.goon.manager.BuildConfig
import dev.shiggy.manager.BuildConfig
import org.koin.android.ext.android.inject
class MainActivity : ComponentActivity() {
@ -42,7 +42,6 @@ class MainActivity : ComponentActivity() {
ManagerTheme(
theme = preferences.theme,
dynamicColor = preferences.dynamicColor,
amoled = preferences.amoled,
) {
if (BuildConfig.RELEASE) {
UpdaterDialog()

View file

@ -17,7 +17,7 @@ import com.aliucord.manager.ui.screens.log.LogScreenModel
import com.aliucord.manager.ui.screens.logs.LogsListScreenModel
import com.aliucord.manager.ui.screens.patching.PatchingScreenModel
import com.aliucord.manager.ui.screens.patchopts.PatchOptionsModel
import com.aliucord.manager.ui.screens.plugins.PluginsModel
import com.aliucord.manager.ui.screens.settings.SettingsModel
import com.aliucord.manager.ui.widgets.updater.UpdaterViewModel
import com.aliucord.manager.installers.root.RootInstaller
@ -57,7 +57,7 @@ class ManagerApplication : Application() {
// UI Models
modules(module {
factory { HomeModel(get<Application>(), get<AliucordGithubService>(), get<AliucordMavenService>(), get<ShiggyGithubService>(), get<RNATrackerService>(), get<kotlinx.serialization.json.Json>(), get<com.aliucord.manager.manager.PreferencesManager>()) }
factoryOf(::PluginsModel)
factoryOf(::AboutModel)
factoryOf(::PatchingScreenModel)
factory { SettingsModel(get<Application>(), get<PathManager>(), get<PreferencesManager>(), get<InstallerManager>()) }

View file

@ -1,7 +1,7 @@
package com.aliucord.manager.di
import android.app.Application
import dev.goon.manager.BuildConfig
import dev.shiggy.manager.BuildConfig
import io.ktor.client.HttpClient
import io.ktor.client.call.HttpClientCall
import io.ktor.client.engine.okhttp.OkHttp

View file

@ -1,7 +1,7 @@
package com.aliucord.manager.installers
import android.content.Context
import dev.goon.manager.R
import dev.shiggy.manager.R
import kotlinx.parcelize.Parcelize
@Parcelize

View file

@ -4,7 +4,7 @@ import android.app.Application
import android.content.pm.PackageInstaller
import android.content.pm.PackageManager
import android.util.Log
import dev.goon.manager.BuildConfig
import dev.shiggy.manager.BuildConfig
import com.aliucord.manager.installers.Installer
import kotlinx.coroutines.suspendCancellableCoroutine
import java.io.File

View file

@ -4,7 +4,7 @@ import android.content.Context
import android.content.pm.PackageInstaller
import android.os.Parcelable
import com.aliucord.manager.installers.InstallerResult
import dev.goon.manager.R
import dev.shiggy.manager.R
import kotlinx.parcelize.Parcelize
/**

View file

@ -7,7 +7,7 @@ import android.util.Log
import android.widget.Toast
import com.aliucord.manager.installers.InstallerResult
import com.aliucord.manager.installers.UnknownInstallerError
import dev.goon.manager.BuildConfig
import dev.shiggy.manager.BuildConfig
/**
* This class is used as a callback receiver for [PackageInstaller] events,

View file

@ -5,7 +5,7 @@ import android.content.pm.PackageInstaller
import com.aliucord.manager.installers.InstallerResult
import com.aliucord.manager.installers.UnknownInstallerError
import com.aliucord.manager.util.showToast
import dev.goon.manager.R
import dev.shiggy.manager.R
/**
* This receiver is meant to be registered dynamically in combination with [PMIntentReceiver] in order to

View file

@ -10,7 +10,7 @@ import com.aliucord.manager.installers.InstallerResult
import com.aliucord.manager.installers.pm.PMUtils
import com.aliucord.manager.util.showToast
import dev.rikka.tools.refine.Refine
import dev.goon.manager.R
import dev.shiggy.manager.R
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.suspendCancellableCoroutine

View file

@ -8,7 +8,7 @@ import android.os.Bundle
import android.util.Log
import com.aliucord.manager.installers.shizuku.ShizukuInstallerError
import com.aliucord.manager.installers.InstallerResult
import dev.goon.manager.BuildConfig
import dev.shiggy.manager.BuildConfig
class ShizukuIntentReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {

View file

@ -11,7 +11,7 @@ import androidx.core.content.getSystemService
import com.aliucord.manager.ui.screens.patchopts.PatchOptions
import com.aliucord.manager.util.IS_PROBABLY_EMULATOR
import com.aliucord.manager.util.isPlayProtectEnabled
import dev.goon.manager.BuildConfig
import dev.shiggy.manager.BuildConfig
import kotlinx.serialization.*
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromStream

View file

@ -11,12 +11,28 @@ import java.io.File
class PathManager(
private val context: Context,
) {
/**
* The Aliucord folder in which plugins/settings/themes are stored.
* Standard path: `~/Aliucord`
*/
val aliucordDir = Environment.getExternalStorageDirectory().resolve("Aliucord")
/**
* The Shiggy folder where shared Shiggy files are stored.
* Standard path: `~/Shiggy`
*/
val shiggyDir = Environment.getExternalStorageDirectory().resolve("Shiggy")
/**
* The directory in external storage in which plugins are stored by Aliucord.
*/
val pluginsDir = aliucordDir.resolve("plugins")
/**
* The settings file in which Aliucord's core uses.
*/
val coreSettingsFile = aliucordDir.resolve("settings/Aliucord.json")
/**
* Global keystore used for signing APKs.
*/

View file

@ -8,7 +8,6 @@ import com.aliucord.manager.ui.theme.Theme
@Stable
class PreferencesManager(preferences: SharedPreferences) : BasePreferenceManager(preferences) {
var theme by enumPreference("theme", Theme.System)
var amoled by booleanPreference("amoled", false)
var dynamicColor by booleanPreference("dynamic_color", true)
var devMode by booleanPreference("dev_mode", false)
var installer by enumPreference<InstallerSetting>("installer", InstallerSetting.PM)

View file

@ -9,8 +9,8 @@ import androidx.core.content.getSystemService
import androidx.core.net.toUri
import com.aliucord.manager.manager.download.IDownloadManager.ProgressListener
import com.aliucord.manager.manager.download.IDownloadManager.Result
import dev.goon.manager.BuildConfig
import dev.goon.manager.R
import dev.shiggy.manager.BuildConfig
import dev.shiggy.manager.R
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import java.io.File

View file

@ -9,20 +9,18 @@ import androidx.core.content.getSystemService
import com.aliucord.manager.manager.download.IDownloadManager.Result
import com.aliucord.manager.patcher.util.InsufficientStorageException
import com.aliucord.manager.util.IS_PROBABLY_EMULATOR
import dev.goon.manager.R
import dev.shiggy.manager.R
import io.ktor.client.HttpClient
import io.ktor.client.request.header
import io.ktor.client.request.prepareGet
import io.ktor.client.statement.bodyAsChannel
import io.ktor.http.HttpHeaders
import io.ktor.http.contentLength
import io.ktor.http.isSuccess
import io.ktor.utils.io.readAvailable
import kotlinx.coroutines.CancellationException
import java.io.File
import java.io.IOException
import java.net.SocketTimeoutException
import java.net.URI
/**
* Handle downloading remote urls to a path with Ktor.
@ -41,108 +39,55 @@ class KtorDownloadManager(
.apply { exists() || createNewFile() }
try {
val downloadUrl = rewriteRawGithubUrl(url)
val httpStmt = http.prepareGet(url) {
header(HttpHeaders.CacheControl, "no-cache, no-store")
// Disable compression due to bug on emulators
// This header cannot be set with Android's DownloadManager
if (IS_PROBABLY_EMULATOR) {
header(HttpHeaders.AcceptEncoding, null)
}
}
httpStmt.execute { resp ->
val channel = resp.bodyAsChannel()
val total = resp.contentLength() ?: 0
var retrieved = 0L
val buf = ByteArray(1024 * 1024 * 1)
var bufLen: Int
tmpOut.outputStream().use { stream ->
// Preallocate space for this file
if (total > 0 && Build.VERSION.SDK_INT >= 26) {
val storageManager = application.getSystemService<StorageManager>()!!
// Try the CDN first (jsDelivr), then an alternate raw proxy (raw.githack.com), and finally
// fall back to the original URL if all CDN attempts fail.
val rawGithack = try {
when {
// direct raw.githubusercontent.com -> raw.githack.com
url.contains("raw.githubusercontent.com", ignoreCase = true) ->
url.replace("raw.githubusercontent.com", "raw.githack.com")
// github.com/.../raw/... -> convert to raw.githack.com/ORG/REPO/BRANCH/path
url.contains("/raw/", ignoreCase = true) && url.contains("github.com", ignoreCase = true) -> {
try {
val u = URI(url)
val segs = u.path.trimStart('/').split('/', limit = 6)
if (segs.size >= 5 && segs[2].equals("raw", ignoreCase = true)) {
"https://raw.githack.com/${segs[0]}/${segs[1]}/${segs.subList(3, segs.size).joinToString("/")}"
} else url
} catch (_: Throwable) {
url
}
}
else -> url
}
} catch (_: Throwable) {
url
}
val attempts = listOf(downloadUrl, rawGithack, url).distinct()
var lastException: Throwable? = null
var downloaded = false
for (attemptUrl in attempts) {
val httpStmt = http.prepareGet(attemptUrl) {
header(HttpHeaders.CacheControl, "no-cache, no-store")
// Disable compression due to bug on emulators
// This header cannot be set with Android's DownloadManager
if (IS_PROBABLY_EMULATOR) {
header(HttpHeaders.AcceptEncoding, null)
}
}
try {
httpStmt.execute { resp ->
if (!resp.status.isSuccess()) {
throw IOException("HTTP error ${resp.status.value} for $attemptUrl")
}
val channel = resp.bodyAsChannel()
val total = resp.contentLength() ?: 0
var retrieved = 0L
val buf = ByteArray(1024 * 1024 * 1)
var bufLen: Int
tmpOut.outputStream().use { stream ->
// Preallocate space for this file
if (total > 0 && Build.VERSION.SDK_INT >= 26) {
val storageManager = application.getSystemService<StorageManager>()!!
try {
storageManager.allocateBytes(stream.fd, total)
} catch (e: IOException) {
throw InsufficientStorageException(e.message)
}
}
while (!channel.isClosedForRead) {
bufLen = channel.readAvailable(buf)
if (bufLen <= 0) break
stream.write(buf, 0, bufLen)
stream.flush()
retrieved += bufLen
if (total > 0) {
if (retrieved > total)
throw IOException("Total bytes received exceeds header total!")
onProgressUpdate?.onUpdate(retrieved / total.toFloat())
} else {
onProgressUpdate?.onUpdate(null)
}
}
storageManager.allocateBytes(stream.fd, total)
} catch (e: IOException) {
throw InsufficientStorageException(e.message)
}
}
// If we reach here, the attempt succeeded
downloaded = true
break
} catch (e: CancellationException) {
// Propagate cancellations immediately
throw e
} catch (t: Throwable) {
// Record the last exception and try the next URL (if any)
lastException = t
}
}
while (!channel.isClosedForRead) {
bufLen = channel.readAvailable(buf)
if (bufLen <= 0) break
if (!downloaded) {
// No attempt succeeded, throw the last failure to be handled by outer catch blocks
throw lastException ?: IOException("Failed to download resource from $url")
stream.write(buf, 0, bufLen)
stream.flush()
retrieved += bufLen
if (total > 0) {
if (retrieved > total)
throw IOException("Total bytes received exceeds header total!")
onProgressUpdate?.onUpdate(retrieved / total.toFloat())
} else {
onProgressUpdate?.onUpdate(null)
}
}
}
}
} catch (_: CancellationException) {
tmpOut.delete()
@ -162,17 +107,6 @@ class KtorDownloadManager(
return Result.Success(out)
}
private fun rewriteRawGithubUrl(url: String): String {
val regex = Regex("^https?://raw\\.githubusercontent\\.com/([^/]+)/([^/]+)/([^/]+)/(.*)$", RegexOption.IGNORE_CASE)
val match = regex.matchEntire(url)
return if (match != null) {
val (org, repo, branch, path) = match.destructured
"https://cdn.jsdelivr.net/gh/$org/$repo@$branch/$path"
} else {
url
}
}
/**
* Wrapper around an exception that occurred from invoking Ktor
*/

View file

@ -0,0 +1,23 @@
package com.aliucord.manager.network.models
import androidx.compose.runtime.Immutable
import com.aliucord.manager.util.serialization.ImmutableListSerializer
import kotlinx.collections.immutable.ImmutableList
import kotlinx.serialization.Serializable
@Immutable
@Serializable
data class Contributor(
val username: String,
val avatarUrl: String,
val commits: Int,
@Serializable(with = ImmutableListSerializer::class)
val repositories: ImmutableList<Repository>,
) {
@Immutable
@Serializable
data class Repository(
val name: String,
val commits: Int,
)
}

View file

@ -1,18 +0,0 @@
package com.aliucord.manager.network.models
import kotlinx.serialization.Serializable
@Serializable
data class Developer(
val username: String,
val avatarUrl: String,
val commits: Int,
val repositories: List<Repository>,
val role: String? = null
) {
@Serializable
data class Repository(
val name: String,
val commits: Int
)
}

View file

@ -2,7 +2,7 @@ package com.aliucord.manager.network.services
import android.util.Log
import com.aliucord.manager.network.utils.*
import dev.goon.manager.BuildConfig
import dev.shiggy.manager.BuildConfig
import io.ktor.client.HttpClient
import io.ktor.client.request.HttpRequestBuilder
import io.ktor.client.request.request

View file

@ -26,8 +26,8 @@ class ShiggyGithubService(
}
companion object {
const val ORG = "OTKUSteyler"
const val MANAGER_REPO = "GoonManager"
const val XPOSED_REPO = "GoonXposed"
const val ORG = "kmmiio99o"
const val MANAGER_REPO = "ShiggyManager"
const val XPOSED_REPO = "ShiggyXposed"
}
}

View file

@ -59,7 +59,7 @@ data class SemVer(
?: throw IllegalArgumentException("Invalid semver string $version")
fun parseOrNull(version: String): SemVer? {
val parts = version.removePrefix("v").removePrefix("V").split(".")
val parts = version.removePrefix("v").split(".")
if (parts.size != 3)
return null

View file

@ -6,9 +6,6 @@ import kotlinx.serialization.Serializable
/**
* Data stored inside patched APKs as ~~"aliucord.json"~~ "shiggy.json" in order to preserve install-time information about Aliucord and the Manager.
* Metadata about an Aliucord installation embedded in the APK.
* @param options The patch options used for this installation
* @param goonXposedVersion The version of GoonXposed used for this installation
*/
@Serializable
data class InstallMetadata(
@ -50,5 +47,5 @@ data class InstallMetadata(
/**
* Version of ShiggyXposed embedded into the APK.
*/
val goonXposedVersion: SemVer,
val shiggyXposedVersion: SemVer?,
)

View file

@ -9,8 +9,8 @@ import com.aliucord.manager.patcher.steps.StepGroup
import com.aliucord.manager.patcher.steps.base.DownloadStep
import com.aliucord.manager.patcher.steps.base.Step
import com.aliucord.manager.ui.util.InstallNotifications
import dev.goon.manager.BuildConfig
import dev.goon.manager.R
import dev.shiggy.manager.BuildConfig
import dev.shiggy.manager.R
import kotlinx.collections.immutable.ImmutableList
import kotlinx.coroutines.delay
import org.koin.core.component.KoinComponent

View file

@ -3,7 +3,7 @@ package com.aliucord.manager.patcher.steps
import android.os.Parcelable
import androidx.annotation.StringRes
import androidx.compose.runtime.Immutable
import dev.goon.manager.R
import dev.shiggy.manager.R
import kotlinx.parcelize.Parcelize
/**

View file

@ -10,7 +10,7 @@ import com.aliucord.manager.manager.download.KtorDownloadManager
import com.aliucord.manager.patcher.StepRunner
import com.aliucord.manager.patcher.steps.StepGroup
import com.aliucord.manager.util.*
import dev.goon.manager.R
import dev.shiggy.manager.R
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.koin.core.component.KoinComponent

View file

@ -9,7 +9,7 @@ import com.aliucord.manager.patcher.StepRunner
import com.aliucord.manager.patcher.steps.StepGroup
import com.aliucord.manager.patcher.steps.base.Step
import com.aliucord.manager.patcher.util.InsufficientStorageException
import dev.goon.manager.R
import dev.shiggy.manager.R
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import java.io.File

View file

@ -10,7 +10,7 @@ import com.aliucord.manager.patcher.steps.base.DownloadStep
import com.aliucord.manager.patcher.steps.base.IDexProvider
import com.aliucord.manager.patcher.steps.patch.ReorganizeDexStep
import com.github.diamondminer88.zip.ZipReader
import dev.goon.manager.R
import dev.shiggy.manager.R
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject

View file

@ -3,7 +3,7 @@ package com.aliucord.manager.patcher.steps.download
import com.aliucord.manager.manager.PathManager
import com.aliucord.manager.network.services.RNATrackerService.Companion.BASE_URL
import com.aliucord.manager.patcher.steps.base.DownloadDiscordRNAStep
import dev.goon.manager.R
import dev.shiggy.manager.R
import org.koin.core.component.inject
class DownloadDiscordRNABaseStep : DownloadDiscordRNAStep() {

View file

@ -3,7 +3,7 @@ package com.aliucord.manager.patcher.steps.download
import com.aliucord.manager.manager.PathManager
import com.aliucord.manager.network.services.RNATrackerService.Companion.BASE_URL
import com.aliucord.manager.patcher.steps.base.DownloadDiscordRNAStep
import dev.goon.manager.R
import dev.shiggy.manager.R
import org.koin.core.component.inject
class DownloadDiscordRNALangStep : DownloadDiscordRNAStep() {

View file

@ -4,7 +4,7 @@ import android.os.Build
import com.aliucord.manager.manager.PathManager
import com.aliucord.manager.network.services.RNATrackerService.Companion.BASE_URL
import com.aliucord.manager.patcher.steps.base.DownloadDiscordRNAStep
import dev.goon.manager.R
import dev.shiggy.manager.R
import org.koin.core.component.inject
class DownloadDiscordRNALibStep : DownloadDiscordRNAStep() {

View file

@ -3,7 +3,7 @@ package com.aliucord.manager.patcher.steps.download
import com.aliucord.manager.manager.PathManager
import com.aliucord.manager.network.services.RNATrackerService.Companion.BASE_URL
import com.aliucord.manager.patcher.steps.base.DownloadDiscordRNAStep
import dev.goon.manager.R
import dev.shiggy.manager.R
import org.koin.core.component.inject
class DownloadDiscordRNAResourcesStep : DownloadDiscordRNAStep() {

View file

@ -3,8 +3,8 @@ package com.aliucord.manager.patcher.steps.download
import androidx.compose.runtime.Stable
import com.aliucord.manager.manager.PathManager
import com.aliucord.manager.patcher.steps.base.DownloadStep
import dev.goon.manager.BuildConfig
import dev.goon.manager.R
import dev.shiggy.manager.BuildConfig
import dev.shiggy.manager.R
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject

View file

@ -11,8 +11,8 @@ import com.aliucord.manager.patcher.steps.base.*
import com.aliucord.manager.patcher.steps.patch.ReorganizeDexStep
import com.aliucord.manager.patcher.steps.prepare.FetchInfoStep
import com.aliucord.manager.ui.components.dialogs.CustomComponentVersionPicker
import dev.goon.manager.BuildConfig
import dev.goon.manager.R
import dev.shiggy.manager.BuildConfig
import dev.shiggy.manager.R
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject

View file

@ -5,7 +5,7 @@ import com.aliucord.manager.manager.PathManager
import com.aliucord.manager.patcher.steps.base.DownloadStep
import com.aliucord.manager.patcher.steps.base.IDexProvider
import com.aliucord.manager.patcher.steps.patch.ReorganizeDexStep
import dev.goon.manager.R
import dev.shiggy.manager.R
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject

View file

@ -11,8 +11,8 @@ import com.aliucord.manager.patcher.steps.base.DownloadStep
import com.aliucord.manager.patcher.steps.base.StepState
import com.aliucord.manager.patcher.steps.prepare.FetchInfoStep
import com.aliucord.manager.ui.components.dialogs.CustomComponentVersionPicker
import dev.goon.manager.BuildConfig
import dev.goon.manager.R
import dev.shiggy.manager.BuildConfig
import dev.shiggy.manager.R
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject

View file

@ -6,7 +6,7 @@ import com.aliucord.manager.network.utils.SemVer
import com.aliucord.manager.network.utils.getOrThrow
import com.aliucord.manager.patcher.StepRunner
import com.aliucord.manager.patcher.steps.base.DownloadStep
import dev.goon.manager.R
import dev.shiggy.manager.R
import org.koin.core.component.inject
class DownloadShiggyXposedStep : DownloadStep() {
@ -22,7 +22,7 @@ class DownloadShiggyXposedStep : DownloadStep() {
override suspend fun execute(container: StepRunner) {
val latestRelease = shiggyGithubService.getLatestXposedRelease().getOrThrow()
container.log("Latest GoonXposed release is ${latestRelease.name}")
container.log("Latest ShiggyXposed release is ${latestRelease.name}")
targetVersion = SemVer.parse(latestRelease.name)
targetUrl = latestRelease.assets.first { it.name == "app-release.apk" }.browserDownloadUrl

View file

@ -7,7 +7,7 @@ import com.aliucord.manager.patcher.steps.StepGroup
import com.aliucord.manager.patcher.steps.base.Step
import com.aliucord.manager.patcher.steps.download.CopyDependenciesStep
import com.github.diamondminer88.zip.*
import dev.goon.manager.R
import dev.shiggy.manager.R
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject

View file

@ -5,7 +5,7 @@ import com.aliucord.manager.manager.PreferencesManager
import com.aliucord.manager.patcher.StepRunner
import com.aliucord.manager.patcher.steps.StepGroup
import com.aliucord.manager.patcher.steps.base.Step
import dev.goon.manager.R
import dev.shiggy.manager.R
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject

View file

@ -6,18 +6,17 @@ import com.aliucord.manager.patcher.steps.base.Step
import com.aliucord.manager.patcher.steps.download.CopyDependenciesStep
import com.aliucord.manager.patcher.steps.download.DownloadShiggyXposedStep
import com.aliucord.manager.patcher.util.Signer
import dev.goon.manager.R
import dev.shiggy.manager.R
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.lsposed.lspatch.share.LSPConfig
import org.lsposed.patch.LSPatch
import org.lsposed.patch.util.Logger
import java.io.File
import java.util.*
class InjectGoonXposedStep : Step() {
class InjectShiggyXposedStep : Step() {
override val group: StepGroup = StepGroup.Install
override val localizedName: Int = R.string.patch_step_inject_goon
override val localizedName: Int = R.string.patch_step_inject_shiggy
suspend fun patch(
container: StepRunner,
@ -83,15 +82,7 @@ class InjectGoonXposedStep : Step() {
// FilenameUtils.getBaseName(apkFileName),
// LSPConfig.instance.VERSION_CODE)
// )
// Note: LSPatch uses Locale.getDefault() which can produce locale-specific numerals
// (e.g., Arabic numerals on Arabic locales). We must use Locale.US to ensure
// consistent filename matching regardless of device locale.
val patchedApkName = String.format(
Locale.US,
"%s-%d-lspatched.apk",
baseName,
LSPConfig.instance.VERSION_CODE
)
val patchedApkName = "${baseName}-${LSPConfig.instance.VERSION_CODE}-lspatched.apk"
val patchedApk = File(tempDir, patchedApkName)
if (patchedApk.exists()) {

View file

@ -14,7 +14,7 @@ import com.aliucord.manager.ui.screens.patchopts.PatchOptions
import com.aliucord.manager.ui.util.InstallNotifications
import com.aliucord.manager.util.isPackageInstalled
import com.aliucord.manager.util.isPlayProtectEnabled
import dev.goon.manager.R
import dev.shiggy.manager.R
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject

View file

@ -5,7 +5,7 @@ import com.aliucord.manager.patcher.steps.StepGroup
import com.aliucord.manager.patcher.steps.base.Step
import com.aliucord.manager.patcher.steps.download.CopyDependenciesStep
import com.aliucord.manager.patcher.util.Signer
import dev.goon.manager.R
import dev.shiggy.manager.R
import org.koin.core.component.KoinComponent
/**

View file

@ -8,7 +8,7 @@ import com.aliucord.manager.patcher.steps.base.Step
import com.aliucord.manager.patcher.steps.download.CopyDependenciesStep
import com.aliucord.manager.patcher.steps.download.DownloadAliuhookStep
import com.github.diamondminer88.zip.*
import dev.goon.manager.R
import dev.shiggy.manager.R
import org.koin.core.component.KoinComponent
/**

View file

@ -30,7 +30,7 @@ import com.aliucord.manager.ui.screens.patchopts.PatchOptions.IconReplacement
import com.aliucord.manager.util.getResBytes
import com.github.diamondminer88.zip.ZipWriter
import com.google.devrel.gmscore.tools.apk.arsc.*
import dev.goon.manager.R
import dev.shiggy.manager.R
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import java.io.ByteArrayOutputStream

View file

@ -8,7 +8,7 @@ import com.aliucord.manager.patcher.util.ManifestPatcher
import com.aliucord.manager.ui.screens.patchopts.PatchOptions
import com.github.diamondminer88.zip.ZipReader
import com.github.diamondminer88.zip.ZipWriter
import dev.goon.manager.R
import dev.shiggy.manager.R
/**
* Patch the APK's AndroidManifest.xml

View file

@ -7,7 +7,7 @@ import com.aliucord.manager.patcher.steps.base.IDexProvider
import com.aliucord.manager.patcher.steps.base.Step
import com.aliucord.manager.patcher.steps.download.CopyDependenciesStep
import com.github.diamondminer88.zip.*
import dev.goon.manager.R
import dev.shiggy.manager.R
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject

View file

@ -8,8 +8,8 @@ import com.aliucord.manager.patcher.steps.base.Step
import com.aliucord.manager.patcher.steps.download.*
import com.aliucord.manager.ui.screens.patchopts.PatchOptions
import com.github.diamondminer88.zip.ZipWriter
import dev.goon.manager.BuildConfig
import dev.goon.manager.R
import dev.shiggy.manager.BuildConfig
import dev.shiggy.manager.R
import kotlinx.serialization.json.Json
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
@ -31,7 +31,7 @@ class SaveMetadataStep(private val options: PatchOptions) : Step(), KoinComponen
val aliuhook = container.getStepOrNull<DownloadAliuhookStep>()
val injector = container.getStepOrNull<DownloadInjectorStep>()
val patches = container.getStepOrNull<DownloadPatchesStep>()
val goonXposed = container.getStepOrNull<DownloadShiggyXposedStep>()
val shiggyXposed = container.getStepOrNull<DownloadShiggyXposedStep>()
val metadata = InstallMetadata(
options = options,
@ -41,12 +41,12 @@ class SaveMetadataStep(private val options: PatchOptions) : Step(), KoinComponen
injectorVersion = injector?.targetVersion,
patchesVersion = patches?.targetVersion,
lspatchVersion = LSPConfig.instance.VERSION_CODE,
goonXposedVersion = goonXposed?.targetVersion ?: SemVer.parse("0.0.0"), // Provide a default if null
shiggyXposedVersion = shiggyXposed?.targetVersion,
)
container.log("Writing serialized install metadata to APK")
ZipWriter(apk, /* append = */ true).use {
it.writeEntry("goon.json", json.encodeToString<InstallMetadata>(metadata))
it.writeEntry("shiggy.json", json.encodeToString<InstallMetadata>(metadata))
}
}
}

View file

@ -17,7 +17,7 @@ import com.github.diamondminer88.zip.ZipReader
import com.github.difflib.DiffUtils
import com.github.difflib.UnifiedDiffUtils
import com.github.difflib.patch.Patch
import dev.goon.manager.R
import dev.shiggy.manager.R
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import java.io.FileNotFoundException

View file

@ -10,7 +10,7 @@ import com.aliucord.manager.patcher.steps.base.Step
import com.aliucord.manager.patcher.steps.base.StepState
import com.aliucord.manager.ui.screens.patchopts.PatchOptions
import com.aliucord.manager.util.*
import dev.goon.manager.R
import dev.shiggy.manager.R
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject

View file

@ -7,7 +7,7 @@ import com.aliucord.manager.patcher.steps.StepGroup
import com.aliucord.manager.patcher.steps.base.Step
import com.aliucord.manager.ui.screens.patchopts.PatchOptions
import com.aliucord.manager.ui.screens.patchopts.VersionPreference
import dev.goon.manager.R
import dev.shiggy.manager.R
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import kotlin.properties.Delegates

View file

@ -7,7 +7,7 @@ import com.aliucord.manager.network.utils.getOrThrow
import com.aliucord.manager.patcher.StepRunner
import com.aliucord.manager.patcher.steps.StepGroup
import com.aliucord.manager.patcher.steps.base.Step
import dev.goon.manager.R
import dev.shiggy.manager.R
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject

View file

@ -6,7 +6,7 @@ import com.aliucord.manager.patcher.StepRunner
import com.aliucord.manager.patcher.steps.StepGroup
import com.aliucord.manager.patcher.steps.base.Step
import com.aliucord.manager.patcher.steps.base.StepState
import dev.goon.manager.R
import dev.shiggy.manager.R
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject

View file

@ -252,8 +252,6 @@ object ManifestPatcher {
extractNativeLibs ?: false
private var shouldAddMetadata =
addManagerMetadata ?: false
private var shouldAddCleartext = true
override fun attr(
ns: String?,
@ -271,12 +269,6 @@ object ManifestPatcher {
shouldAddExtractNativeLibs = false
if (name == DEBUGGABLE)
shouldAddDebuggable = false
if (name == "usesCleartextTraffic") {
shouldAddCleartext = false
super.attr(ns, name, 0x010104ec, TYPE_INT_BOOLEAN, 1)
return
}
super.attr(
ns,
name,
@ -433,15 +425,7 @@ object ManifestPatcher {
0
)
}
if (shouldAddCleartext) {
super.attr(
ANDROID_NAMESPACE,
"usesCleartextTraffic",
0x010104ec,
TYPE_INT_BOOLEAN,
1
)
}
super.end()
}
}

View file

@ -7,7 +7,7 @@ package com.aliucord.manager.patcher.util
import com.aliucord.manager.manager.PathManager
import com.android.apksig.ApkSigner
import dev.goon.manager.BuildConfig
import dev.shiggy.manager.BuildConfig
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo
import org.bouncycastle.cert.X509v3CertificateBuilder

View file

@ -8,7 +8,7 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import cafe.adriel.voyager.navigator.LocalNavigator
import com.aliucord.manager.util.back
import dev.goon.manager.R
import dev.shiggy.manager.R
/**
* Standalone back button for interacting with the current navigator.

View file

@ -17,13 +17,13 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.unit.dp
import coil3.compose.SubcomposeAsyncImage
import com.aliucord.manager.network.models.Developer
import com.aliucord.manager.network.models.Contributor
import com.valentinilk.shimmer.shimmer
import dev.goon.manager.R
import dev.shiggy.manager.R
@Composable
fun ContributorCommitsItem(
user: Developer,
user: Contributor,
modifier: Modifier = Modifier,
) {
val uriHandler = LocalUriHandler.current

View file

@ -19,7 +19,7 @@ import androidx.core.graphics.applyCanvas
import androidx.core.graphics.createBitmap
import androidx.core.graphics.drawable.toDrawable
import com.aliucord.manager.ui.screens.patchopts.PatchOptions.IconReplacement
import dev.goon.manager.R
import dev.shiggy.manager.R
// Sizing information is obtained from here:
// https://medium.com/google-design/designing-adaptive-icons-515af294c783

View file

@ -8,7 +8,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import dev.goon.manager.R
import dev.shiggy.manager.R
@Composable
fun LoadFailure(modifier: Modifier = Modifier) {

View file

@ -12,7 +12,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import dev.goon.manager.R
import dev.shiggy.manager.R
@Composable
fun ProjectHeader(

View file

@ -9,7 +9,7 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.aliucord.manager.ui.util.mirrorVertically
import dev.goon.manager.R
import dev.shiggy.manager.R
@Composable
fun ResetToDefaultButton(

View file

@ -16,7 +16,7 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.DialogProperties
import com.aliucord.manager.network.utils.SemVer
import dev.goon.manager.R
import dev.shiggy.manager.R
@Composable
fun CustomComponentVersionPicker(

View file

@ -8,7 +8,7 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import dev.goon.manager.R
import dev.shiggy.manager.R
@Composable
fun InstallerAbortDialog(

View file

@ -13,7 +13,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.DialogProperties
import dev.goon.manager.R
import dev.shiggy.manager.R
@Composable
fun NetworkWarningDialog(

View file

@ -17,7 +17,7 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.DialogProperties
import com.aliucord.manager.ui.theme.customColors
import dev.goon.manager.R
import dev.shiggy.manager.R
@Composable
fun PlayProtectDialog(

View file

@ -19,8 +19,8 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.DialogProperties
import androidx.core.net.toUri
import com.google.accompanist.permissions.*
import dev.goon.manager.BuildConfig
import dev.goon.manager.R
import dev.shiggy.manager.BuildConfig
import dev.shiggy.manager.R
@Composable
@OptIn(ExperimentalPermissionsApi::class)

View file

@ -16,8 +16,8 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.DialogProperties
import androidx.core.net.toUri
import dev.goon.manager.BuildConfig
import dev.goon.manager.R
import dev.shiggy.manager.BuildConfig
import dev.shiggy.manager.R
@Composable
fun UnknownSourcesPermissionDialog() {

View file

@ -11,7 +11,7 @@ import androidx.compose.ui.tooling.preview.*
import com.aliucord.manager.ui.screens.patching.components.TextBanner
import com.aliucord.manager.ui.theme.ManagerTheme
import com.aliucord.manager.ui.theme.customColors
import dev.goon.manager.R
import dev.shiggy.manager.R
@Composable
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)

View file

@ -0,0 +1,20 @@
package com.aliucord.manager.ui.previews.dialogs
import android.content.res.Configuration
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import com.aliucord.manager.ui.screens.plugins.components.dialogs.UninstallPluginDialog
import com.aliucord.manager.ui.theme.ManagerTheme
@Composable
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
private fun UninstallPluginDialogPreview() {
ManagerTheme {
UninstallPluginDialog(
pluginName = "FakeNitro",
onConfirm = {},
onDismiss = {},
)
}
}

View file

@ -14,7 +14,7 @@ import com.aliucord.manager.ui.screens.iconopts.IconOptionsMode
import com.aliucord.manager.ui.screens.iconopts.IconOptionsScreenContent
import com.aliucord.manager.ui.screens.patchopts.PatchOptions.IconReplacement
import com.aliucord.manager.ui.theme.ManagerTheme
import dev.goon.manager.R
import dev.shiggy.manager.R
import dev.zt64.compose.pipette.HsvColor
import java.io.ByteArrayOutputStream

View file

@ -0,0 +1,52 @@
package com.aliucord.manager.ui.previews.screens.about
import android.content.res.Configuration
import androidx.compose.runtime.*
import androidx.compose.ui.tooling.preview.*
import com.aliucord.manager.network.models.Contributor
import com.aliucord.manager.ui.screens.about.AboutScreenContent
import com.aliucord.manager.ui.screens.about.AboutScreenState
import com.aliucord.manager.ui.theme.ManagerTheme
import com.aliucord.manager.ui.util.emptyImmutableList
import com.aliucord.manager.util.serialization.ImmutableListSerializer
import kotlinx.collections.immutable.*
import kotlinx.serialization.json.Json
// This preview has scrollable content that cannot be properly viewed from an IDE preview
@Composable
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
private fun AboutScreenLoadedPreview(
@PreviewParameter(ContributorsProvider::class) contributors: ImmutableList<Contributor>,
) {
ManagerTheme {
AboutScreenContent(
state = remember { mutableStateOf(AboutScreenState.Loaded(contributors)) },
)
}
}
private class ContributorsProvider : PreviewParameterProvider<ImmutableList<Contributor>> {
private val realDataRaw =
"[{\"username\":\"maisymoe\",\"avatarUrl\":\"https://avatars.githubusercontent.com/u/61095277?v=4\",\"repositories\":]"
private val realData =
Json.decodeFromString(ImmutableListSerializer(Contributor.serializer()), realDataRaw)
override val values =
sequenceOf(
emptyImmutableList<Contributor>(),
persistentListOf(
Contributor(
username =
"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz",
avatarUrl = "UNUSED",
commits = Int.MAX_VALUE,
repositories =
(realData[0].repositories + realData[0].repositories)
.toImmutableList(),
)
),
realData,
)
}

View file

@ -34,6 +34,7 @@ private fun HomeScreenLoadedPreview(
onUpdate = {},
onOpenApp = {},
onOpenAppInfo = {},
onOpenPlugins = {},
onUninstall = {},
)
}

View file

@ -0,0 +1,29 @@
package com.aliucord.manager.ui.previews.screens.plugins
import android.content.res.Configuration
import androidx.compose.runtime.*
import androidx.compose.ui.tooling.preview.Preview
import com.aliucord.manager.ui.screens.plugins.PluginsScreenContent
import com.aliucord.manager.ui.theme.ManagerTheme
import com.aliucord.manager.ui.util.emptyImmutableList
// This preview has interactable content that cannot be tested from an IDE preview
@Composable
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
private fun PluginsScreenFailedPreview() {
val filterState = remember { mutableStateOf("") }
ManagerTheme {
PluginsScreenContent(
searchText = filterState,
setSearchText = filterState::value::set,
isError = true,
plugins = emptyImmutableList(),
onPluginUninstall = {},
onPluginChangelog = {},
onPluginToggle = { name, enabled -> },
)
}
}

View file

@ -0,0 +1,92 @@
package com.aliucord.manager.ui.previews.screens.plugins
import android.content.res.Configuration
import androidx.compose.runtime.*
import androidx.compose.ui.tooling.preview.Preview
import com.aliucord.manager.ui.screens.plugins.PluginsScreenContent
import com.aliucord.manager.ui.screens.plugins.model.PluginItem
import com.aliucord.manager.ui.screens.plugins.model.PluginManifest
import com.aliucord.manager.ui.theme.ManagerTheme
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import java.util.UUID
// This preview has scrollable/interactable content that cannot be tested from an IDE preview
@Composable
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
private fun PluginsScreenLoadedPreview() {
val filterState = remember { mutableStateOf("test") }
ManagerTheme {
PluginsScreenContent(
searchText = filterState,
setSearchText = filterState::value::set,
isError = false,
plugins = plugins,
onPluginUninstall = {},
onPluginChangelog = {},
onPluginToggle = { name, enabled -> },
)
}
}
private val plugins: ImmutableList<PluginItem> = persistentListOf(
PluginItem(
path = UUID.randomUUID().toString(),
manifest = PluginManifest(
name = "CloseDMs",
authors = persistentListOf(
PluginManifest.Author(name = "Diamond", id = 0L),
),
description = "Shortcut to close DMs in the DM context menu.",
version = "1.0.0",
updateUrl = "",
changelog = "",
changelogMedia = null,
)
),
PluginItem(
path = UUID.randomUUID().toString(),
manifest = PluginManifest(
name = "ConfigurableStickerSizes",
authors = persistentListOf(
PluginManifest.Author(name = "rushii", id = 0L, hyperlink = false),
),
description = "Makes sticker sizes configurable.",
version = "1.1.5",
updateUrl = "",
changelog = "",
changelogMedia = null,
)
),
PluginItem(
path = UUID.randomUUID().toString(),
manifest = PluginManifest(
name = "AudioPlayer",
authors = persistentListOf(
PluginManifest.Author(name = "rushii", id = 0L, hyperlink = false),
),
description = "Makes audio files playable.",
version = "0.0.1",
updateUrl = "",
changelog = "",
changelogMedia = null,
)
),
PluginItem(
path = UUID.randomUUID().toString(),
manifest = PluginManifest(
name = "TypingIndicators",
authors = persistentListOf(
PluginManifest.Author(name = "rushii", id = 0L, hyperlink = false),
),
description = "Adds typing indicators to channels that people are currently typing in.",
version = "1.1.0",
updateUrl = "",
changelog = "",
changelogMedia = null,
)
),
)

View file

@ -0,0 +1,29 @@
package com.aliucord.manager.ui.previews.screens.plugins
import android.content.res.Configuration
import androidx.compose.runtime.*
import androidx.compose.ui.tooling.preview.Preview
import com.aliucord.manager.ui.screens.plugins.PluginsScreenContent
import com.aliucord.manager.ui.theme.ManagerTheme
import com.aliucord.manager.ui.util.emptyImmutableList
// This preview has interactable content that cannot be tested from an IDE preview
@Composable
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
private fun PluginsScreenNonePreview() {
val filterState = remember { mutableStateOf("") }
ManagerTheme {
PluginsScreenContent(
searchText = filterState,
setSearchText = filterState::value::set,
isError = false,
plugins = emptyImmutableList(),
onPluginUninstall = {},
onPluginChangelog = {},
onPluginToggle = { name, enabled -> },
)
}
}

View file

@ -2,67 +2,56 @@ package com.aliucord.manager.ui.screens.about
import cafe.adriel.voyager.core.model.StateScreenModel
import cafe.adriel.voyager.core.model.screenModelScope
import com.aliucord.manager.network.models.Developer
import com.aliucord.manager.network.models.Contributor
import com.aliucord.manager.network.services.HttpService
import com.aliucord.manager.network.utils.fold
import com.aliucord.manager.ui.util.toUnsafeImmutable
import com.aliucord.manager.util.launchIO
import io.ktor.client.request.url
import kotlinx.serialization.Serializable
import kotlinx.serialization.SerialName
import kotlinx.collections.immutable.persistentListOf
class AboutModel(
private val http: HttpService,
) : StateScreenModel<AboutScreenState>(AboutScreenState.Loading) {
init {
fetchDevelopers()
fetchContributors()
}
fun fetchDevelopers() = screenModelScope.launchIO {
fun fetchContributors() = screenModelScope.launchIO {
mutableState.value = AboutScreenState.Loading
val allDevelopers = persistentListOf(
Developer(
username = "OTKUSteyler",
avatarUrl = "https://github.com/OTKUSteyler.png",
commits = 0,
repositories = emptyList(),
role = "GoonCord - Creator"
),
Developer(
username = "kmmiio99o",
avatarUrl = "https://github.com/kmmiio99o.png",
commits = 0,
repositories = emptyList(),
role = "ShiggyCord - Creator"
),
Developer(
// Use a curated static list of GitHub usernames (profiles + static avatar URLs).
// This avoids pulling from a specific repo and lets us include people
// who are not contributors to any particular repository.
val staticContributors = persistentListOf(
Contributor(
username = "rushiiMachine",
avatarUrl = "https://github.com/rushiiMachine.png",
commits = 0,
repositories = emptyList(),
role = "Manager - Creator"
repositories = persistentListOf<com.aliucord.manager.network.models.Contributor.Repository>()
),
Developer(
Contributor(
username = "maisymoe",
avatarUrl = "https://github.com/maisymoe.png",
commits = 0,
repositories = emptyList(),
role = "Vendetta - Creator"
repositories = persistentListOf<com.aliucord.manager.network.models.Contributor.Repository>()
),
Developer(
Contributor(
username = "kmmiio99o",
avatarUrl = "https://github.com/kmmiio99o.png",
commits = 0,
repositories = persistentListOf<com.aliucord.manager.network.models.Contributor.Repository>()
),
Contributor(
username = "pylixonly",
avatarUrl = "https://github.com/pylixonly.png",
commits = 0,
repositories = emptyList(),
role = "Bunny - Creator"
),
Developer(
username = "jonatanktk",
avatarUrl = "https://github.com/jonatanktk.png",
commits = 0,
repositories = emptyList(),
role = "Random Person there"
),
repositories = persistentListOf<com.aliucord.manager.network.models.Contributor.Repository>()
)
)
mutableState.value = AboutScreenState.Loaded(allDevelopers.toUnsafeImmutable())
mutableState.value = AboutScreenState.Loaded(staticContributors.toUnsafeImmutable())
}
}

View file

@ -10,7 +10,6 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
@ -21,16 +20,15 @@ import androidx.compose.ui.unit.dp
import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.koin.koinScreenModel
import com.aliucord.manager.ui.components.*
import com.aliucord.manager.ui.screens.about.components.RegularContributorListItem
import com.aliucord.manager.ui.screens.about.components.LeadContributor
import com.aliucord.manager.ui.util.paddings.*
import dev.goon.manager.R
import dev.shiggy.manager.R
import kotlinx.parcelize.IgnoredOnParcel
import kotlinx.parcelize.Parcelize
@Parcelize
class AboutScreen : Screen, Parcelable {
@IgnoredOnParcel
override val key = "About"
@IgnoredOnParcel override val key = "About"
@Composable
override fun Content() {
@ -43,93 +41,108 @@ class AboutScreen : Screen, Parcelable {
@Composable
fun AboutScreenContent(state: State<AboutScreenState>) {
Scaffold(
topBar = {
TopAppBar(
title = { Text(stringResource(R.string.navigation_about_manager)) },
navigationIcon = { BackButton() },
)
}
topBar = {
TopAppBar(
title = { Text(stringResource(R.string.navigation_about_manager)) },
navigationIcon = { BackButton() },
)
}
) { paddingValues ->
LazyColumn(
verticalArrangement = Arrangement.spacedBy(2.dp),
horizontalAlignment = Alignment.CenterHorizontally,
contentPadding = paddingValues
.exclude(PaddingValuesSides.Horizontal + PaddingValuesSides.Top)
.add(PaddingValues(vertical = 16.dp)),
modifier = Modifier
.padding(paddingValues.exclude(PaddingValuesSides.Bottom))
verticalArrangement = Arrangement.spacedBy(2.dp),
horizontalAlignment = Alignment.CenterHorizontally,
contentPadding =
paddingValues
.exclude(PaddingValuesSides.Horizontal + PaddingValuesSides.Top)
.add(PaddingValues(vertical = 16.dp)),
modifier =
Modifier.padding(paddingValues.exclude(PaddingValuesSides.Bottom))
.padding(horizontal = 14.dp),
) {
item(key = "MANAGER_CREDIT_DISCLAIMER_BANNER") {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.padding(horizontal = 16.dp, vertical = 8.dp)
.fillMaxWidth()
.border(
width = 1.dp,
color = MaterialTheme.colorScheme.outline.copy(alpha = 0.3f),
shape = MaterialTheme.shapes.large
)
.clip(MaterialTheme.shapes.large)
.background(MaterialTheme.colorScheme.surfaceContainerLow)
contentAlignment = Alignment.Center,
modifier =
Modifier.padding(bottom = 24.dp)
.border(
width = 2.dp,
color = MaterialTheme.colorScheme.surfaceVariant,
shape = MaterialTheme.shapes.medium
)
.clip(MaterialTheme.shapes.medium)
.background(MaterialTheme.colorScheme.surfaceContainerHigh)
) {
Text(
text = stringResource(R.string.about_manager_credit_disclaimer_notice),
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.padding(horizontal = 20.dp, vertical = 16.dp)
)
}
}
item(key = "PROJECT_HEADER") {
Box(modifier = Modifier.padding(horizontal = 16.dp)) {
ProjectHeader()
}
}
item(key = "TEAM_SPACER") {
Spacer(modifier = Modifier.height(32.dp))
}
when (val currentState = state.value) {
AboutScreenState.Loading -> {
item(key = "LOADING") {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 48.dp)
) {
CircularProgressIndicator()
}
}
}
AboutScreenState.Failure -> {
item(key = "LOAD_FAILURE") {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 48.dp)
) {
LoadFailure()
}
}
}
is AboutScreenState.Loaded -> {
itemsIndexed(
items = currentState.contributors,
key = { index, developer -> developer.username }
) { index, developer ->
RegularContributorListItem(
contributor = developer,
isLast = index == currentState.contributors.lastIndex
Row(
horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(horizontal = 20.dp, vertical = 14.dp)
) {
Text(
text =
stringResource(
R.string.about_manager_credit_disclaimer_notice
),
style = MaterialTheme.typography.labelMedium,
)
}
}
}
item(key = "PROJECT_HEADER") { ProjectHeader() }
item(key = "HEADER_DIVIDER") {
TextDivider(
text = stringResource(R.string.contributors_lead),
modifier = Modifier.padding(top = 18.dp, bottom = 20.dp),
)
}
item(key = "MAIN_CONTRIBUTORS") {
Row(
horizontalArrangement = Arrangement.SpaceEvenly,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth(),
) {
LeadContributor("C0C0B01", stringResource(R.string.lead_role_kettu))
LeadContributor("kmmiio99o", stringResource(R.string.lead_role_shiggycord))
LeadContributor("pylixonly", stringResource(R.string.lead_role_bunny))
}
}
item(key = "CONTRIBUTORS_DIVIDER") {
TextDivider(
text = stringResource(R.string.contributors),
modifier = Modifier.padding(top = 16.dp, bottom = 6.dp)
)
}
when (val state = state.value) {
AboutScreenState.Loading ->
item(key = "CONTRIBUTIONS_LOADING") {
Box(
contentAlignment = Alignment.Center,
content = { CircularProgressIndicator() },
modifier = Modifier.fillMaxWidth().padding(vertical = 38.dp),
)
}
AboutScreenState.Failure ->
item(key = "LOAD_FAILURE") {
LoadFailure(modifier = Modifier.fillMaxSize().padding(vertical = 38.dp))
}
is AboutScreenState.Loaded -> {
// Render the two requested contributors as LeadContributor entries
item(key = "SELECTED_CONTRIBUTORS") {
Row(
horizontalArrangement = Arrangement.SpaceEvenly,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth(),
) {
LeadContributor("maisymoe", "Creator - Vendetta")
LeadContributor("rushiiMachine", "Manager - Creator")
}
}
}
}
}
}
}

View file

@ -1,10 +1,10 @@
package com.aliucord.manager.ui.screens.about
import com.aliucord.manager.network.models.Developer
import com.aliucord.manager.network.models.Contributor
import kotlinx.collections.immutable.ImmutableList
sealed interface AboutScreenState {
data object Loading : AboutScreenState
data object Failure : AboutScreenState
data class Loaded(val contributors: ImmutableList<Developer>) : AboutScreenState
data class Loaded(val contributors: ImmutableList<Contributor>) : AboutScreenState
}

View file

@ -1,96 +0,0 @@
package com.aliucord.manager.ui.screens.about.components
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
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.layout.ContentScale
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import coil3.compose.SubcomposeAsyncImage
import com.aliucord.manager.network.models.Developer
import com.valentinilk.shimmer.shimmer
@Composable
fun RegularContributorListItem(
contributor: Developer,
isLast: Boolean = false,
modifier: Modifier = Modifier
) {
val uriHandler = LocalUriHandler.current
Column(modifier = modifier.fillMaxWidth()) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.clickable(
onClick = { uriHandler.openUri("https://github.com/${contributor.username}") }
)
.padding(horizontal = 24.dp, vertical = 16.dp)
) {
SubcomposeAsyncImage(
model = contributor.avatarUrl,
contentDescription = contributor.username,
contentScale = ContentScale.Crop,
loading = {
Surface(
color = MaterialTheme.colorScheme.surfaceVariant,
modifier = Modifier
.size(56.dp)
.clip(CircleShape)
.shimmer()
) {}
},
error = {
Surface(
color = MaterialTheme.colorScheme.surfaceVariant,
modifier = Modifier
.size(56.dp)
.clip(CircleShape)
) {}
},
modifier = Modifier
.size(56.dp)
.clip(CircleShape)
)
Spacer(modifier = Modifier.width(20.dp))
Column(
modifier = Modifier.weight(1f)
) {
Text(
text = contributor.username,
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.onSurface,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
Spacer(modifier = Modifier.height(4.dp))
contributor.role?.let { role ->
Text(
text = role,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
}
if (!isLast) {
HorizontalDivider(
color = MaterialTheme.colorScheme.outline.copy(alpha = 0.1f),
thickness = 1.dp,
modifier = Modifier.padding(start = 100.dp, end = 24.dp)
)
}
}
}

View file

@ -1,96 +1,94 @@
package com.aliucord.manager.ui.screens.about.components
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
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
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import coil3.compose.SubcomposeAsyncImage
import com.aliucord.manager.network.models.Developer
import com.valentinilk.shimmer.shimmer
@Composable
fun LeadContributorItem(
contributor: Developer,
isLast: Boolean = false,
modifier: Modifier = Modifier
fun LeadContributor(
name: String,
roles: String,
username: String = name,
modifier: Modifier = Modifier,
) {
val uriHandler = LocalUriHandler.current
Column(modifier = modifier.fillMaxWidth()) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.clickable(
onClick = { uriHandler.openUri("https://github.com/${contributor.username}") }
)
.padding(horizontal = 24.dp, vertical = 16.dp)
) {
SubcomposeAsyncImage(
model = contributor.avatarUrl,
contentDescription = contributor.username,
contentScale = ContentScale.Crop,
loading = {
Surface(
color = MaterialTheme.colorScheme.surfaceVariant,
modifier = Modifier
.size(56.dp)
.clip(CircleShape)
.shimmer()
) {}
},
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(16.dp),
modifier =
modifier.clickable(
onClick = {
uriHandler.openUri("https://github.com/$username")
},
indication = ripple(bounded = false, radius = 90.dp),
interactionSource = remember(::MutableInteractionSource)
)
.widthIn(min = 100.dp)
) {
SubcomposeAsyncImage(
model = "https://github.com/$username.png",
contentDescription = username,
error = {
Surface(
color = MaterialTheme.colorScheme.surfaceVariant,
modifier = Modifier
.size(56.dp)
.clip(CircleShape)
) {}
},
modifier = Modifier
.size(56.dp)
.clip(CircleShape)
)
Spacer(modifier = Modifier.width(20.dp))
Column(
modifier = Modifier.weight(1f)
) {
Text(
text = contributor.username,
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.onSurface,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
Spacer(modifier = Modifier.height(4.dp))
contributor.role?.let { role ->
Text(
text = role,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
content = {},
tonalElevation = 2.dp,
modifier = Modifier.fillMaxSize().shimmer(),
)
}
}
}
},
modifier = Modifier.size(80.dp).clip(CircleShape),
)
if (!isLast) {
HorizontalDivider(
color = MaterialTheme.colorScheme.outline.copy(alpha = 0.1f),
thickness = 1.dp,
modifier = Modifier.padding(start = 100.dp, end = 24.dp)
)
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(text = name, style = MaterialTheme.typography.titleMedium.copy(fontSize = 18.sp))
val roleParts = roles.split(Regex("\\s*-\\s*"), limit = 2)
if (roleParts.size == 2) {
// Render left and right parts on two separate lines, removing the dash
Text(
text = roleParts[0],
style =
MaterialTheme.typography.titleSmall.copy(
color =
MaterialTheme.colorScheme.onSurface.copy(
alpha = 0.6f
)
)
)
Text(
text = roleParts[1],
style =
MaterialTheme.typography.bodySmall.copy(
color =
MaterialTheme.colorScheme.onSurface.copy(
alpha = 0.6f
)
)
)
} else {
Text(
text = roles,
style =
MaterialTheme.typography.titleSmall.copy(
color =
MaterialTheme.colorScheme.onSurface.copy(
alpha = 0.6f
)
)
)
}
}
}
}

View file

@ -24,8 +24,8 @@ import com.aliucord.manager.ui.util.DiscordVersion
import com.aliucord.manager.ui.util.toUnsafeImmutable
import com.aliucord.manager.util.*
import com.github.diamondminer88.zip.ZipReader
import dev.goon.manager.BuildConfig
import dev.goon.manager.R
import dev.shiggy.manager.BuildConfig
import dev.shiggy.manager.R
import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
@ -51,7 +51,7 @@ class HomeModel(
private val refreshingLock = Mutex()
private var remoteDataJson: BuildInfo? = null
private var trackerIndexJson: RNATrackerIndex? = null
private var latestGoonXposedVersion: SemVer? = null
private var latestShiggyXposedVersion: SemVer? = null
private var latestAliuhookVersion: SemVer? = null
init {
@ -122,7 +122,7 @@ class HomeModel(
val metadata = try {
val applicationInfo = application.packageManager.getApplicationInfo(packageName, 0)
val metadataFile = ZipReader(applicationInfo.publicSourceDir)
.use { it.openEntry("goon.json")?.read() }
.use { it.openEntry("shiggy.json")?.read() }
@OptIn(ExperimentalSerializationApi::class)
metadataFile?.let { json.decodeFromStream<InstallMetadata>(it.inputStream()) }
@ -146,60 +146,27 @@ class HomeModel(
try {
val packageManager = application.packageManager
val aliucordInstallations = packages.mapNotNull { pkg ->
try {
@Suppress("DEPRECATION")
val versionCode = pkg.versionCode
val versionName = pkg.versionName ?: return@mapNotNull null
val applicationInfo = pkg.applicationInfo ?: return@mapNotNull null
// `longVersionCode` is unnecessary since Discord doesn't use `versionCodeMajor`
@Suppress("DEPRECATION")
val versionCode = pkg.versionCode
val versionName = pkg.versionName ?: return@mapNotNull null
val applicationInfo = pkg.applicationInfo ?: return@mapNotNull null
val name = try {
packageManager.getApplicationLabel(applicationInfo).toString()
} catch (e: Exception) {
Log.w(BuildConfig.TAG, "Failed to get label for ${pkg.packageName}", e)
null
} ?: return@mapNotNull null
val icon = try {
packageManager.getApplicationIcon(applicationInfo)
.toBitmap()
.asImageBitmap()
.let(::BitmapPainter)
} catch (e: Exception) {
Log.w(BuildConfig.TAG, "Failed to get icon for ${pkg.packageName}", e)
null
} ?: return@mapNotNull null
// Defensive: check for any other possible nulls or bad data
if (pkg.packageName.isNullOrEmpty()) {
Log.w(BuildConfig.TAG, "Package name is null or empty for a package")
return@mapNotNull null
}
InstallData(
name = name,
packageName = pkg.packageName,
isUpToDate = try {
isInstallationUpToDate(pkg)
} catch (e: Exception) {
Log.w(BuildConfig.TAG, "Failed to check up-to-date for ${pkg.packageName}", e)
false
},
icon = icon,
version = try {
DiscordVersion.Existing(
type = DiscordVersion.parseVersionType(versionCode),
name = versionName.split("-")[0].trim(),
code = versionCode,
)
} catch (e: Exception) {
Log.w(BuildConfig.TAG, "Failed to parse version for ${pkg.packageName}", e)
null
} ?: return@mapNotNull null,
)
} catch (e: Exception) {
Log.w(BuildConfig.TAG, "Failed to process package ${pkg.packageName}", e)
null
}
InstallData(
name = packageManager.getApplicationLabel(applicationInfo).toString(),
packageName = pkg.packageName,
isUpToDate = isInstallationUpToDate(pkg),
icon = packageManager
.getApplicationIcon(applicationInfo)
.toBitmap()
.asImageBitmap()
.let(::BitmapPainter),
version = DiscordVersion.Existing(
type = DiscordVersion.parseVersionType(versionCode),
name = versionName.split("-")[0].trim(),
code = versionCode,
),
)
}
mainThread {
@ -236,7 +203,7 @@ class HomeModel(
private suspend fun fetchRemoteData() {
listOf(
// // These aren't needed by Goon
// // These aren't needed by Shiggy
// screenModelScope.launch(Dispatchers.IO) {
// github.getBuildData().fold(
// success = { remoteDataJson = it },
@ -257,18 +224,14 @@ class HomeModel(
},
screenModelScope.launch(Dispatchers.IO) {
wtGithub.getLatestXposedRelease().fold(
success = {
val versionName = it.name
val truncatedVersion = versionName.split(".").take(3).joinToString(".")
latestGoonXposedVersion = SemVer.parse(truncatedVersion)
},
fail = { Log.w(BuildConfig.TAG, "Failed to fetch latest GoonXposed version", it) },
success = { latestShiggyXposedVersion = SemVer.parse(it.name) },
fail = { Log.w(BuildConfig.TAG, "Failed to fetch latest ShiggyXposed version", it) },
)
},
).joinAll()
if (trackerIndexJson == null /* remoteDataJson == null || latestAliuhookVersion == null */ || latestGoonXposedVersion == null) {
if (trackerIndexJson == null /* remoteDataJson == null || latestAliuhookVersion == null */ || latestShiggyXposedVersion == null) {
mainThread { application.showToast(R.string.home_network_fail) }
}
}
@ -307,7 +270,7 @@ class HomeModel(
return application.packageManager
.getInstalledPackages(PackageManager.GET_META_DATA)
.filter {
// Goon doesn't have "legacy installer" whatsoever
// Shiggy doesn't have "legacy installer" whatsoever
return@filter it.applicationInfo?.metaData?.containsKey("isShiggy") == true
// // Packages installed via the legacy Installer do not have the metadata marker
@ -318,11 +281,11 @@ class HomeModel(
}
/**
* Checks whether the current Goon installation is up-to-date.
* Checks whether the current Shiggy installation is up-to-date.
*
* Currently mirrors the behavior of Bunny Manager by directly comparing against
* the latest available Discord and GoonXposed release. This is a temporary approach and will remain
* in place until Goon adds support for version pinning.
* the latest available Discord and ShiggyXposed release. This is a temporary approach and will remain
* in place until Shiggy adds support for version pinning.
*/
private fun isInstallationUpToDate(pkg: PackageInfo): Boolean? {
val trackerData = trackerIndexJson ?: return null
@ -334,7 +297,7 @@ class HomeModel(
// Try to parse install metadata. If none present, install was made via legacy installer.
val apkPath = pkg.applicationInfo?.publicSourceDir ?: return false
val installMetadata = try {
val metadataFile = ZipReader(apkPath).use { it.openEntry("goon.json")?.read() }
val metadataFile = ZipReader(apkPath).use { it.openEntry("shiggy.json")?.read() }
?: return false
@OptIn(ExperimentalSerializationApi::class)
@ -351,6 +314,6 @@ class HomeModel(
VersionPreference.Custom -> return true
}
return latestByPref == versionCode && installMetadata.goonXposedVersion == latestGoonXposedVersion
return latestByPref == versionCode && installMetadata.shiggyXposedVersion == latestShiggyXposedVersion
}
}

View file

@ -30,10 +30,11 @@ import com.aliucord.manager.ui.components.LoadFailure
import com.aliucord.manager.ui.components.ProjectHeader
import com.aliucord.manager.ui.screens.home.components.*
import com.aliucord.manager.ui.screens.patchopts.PatchOptionsScreen
import com.aliucord.manager.ui.screens.plugins.PluginsScreen
import com.aliucord.manager.ui.util.paddings.PaddingValuesSides
import com.aliucord.manager.ui.util.paddings.exclude
import com.aliucord.manager.util.*
import dev.goon.manager.R
import dev.shiggy.manager.R
import kotlinx.parcelize.IgnoredOnParcel
import kotlinx.parcelize.Parcelize
@ -77,6 +78,7 @@ class HomeScreen : Screen, Parcelable {
},
onOpenApp = model::openApp,
onOpenAppInfo = model::openAppInfo,
onOpenPlugins = { navigator.push(PluginsScreen()) }, // TODO: install-specific plugins
onUninstall = model::uninstallApp,
)
@ -127,6 +129,7 @@ fun HomeScreenLoadedContent(
onUpdate: (packageName: String) -> Unit,
onOpenApp: (packageName: String) -> Unit,
onOpenAppInfo: (packageName: String) -> Unit,
onOpenPlugins: (packageName: String) -> Unit,
onUninstall: (packageName: String) -> Unit,
) {
LazyColumn(
@ -160,6 +163,7 @@ fun HomeScreenLoadedContent(
onUpdate = { onUpdate(item.packageName) },
onOpenApp = { onOpenApp(item.packageName) },
onOpenInfo = { onOpenAppInfo(item.packageName) },
onOpenPlugins = { onOpenPlugins(item.packageName) },
onUninstall = { onUninstall(item.packageName) },
modifier = Modifier.fillMaxWidth()
)

View file

@ -8,7 +8,7 @@ import cafe.adriel.voyager.navigator.LocalNavigator
import com.aliucord.manager.ui.screens.about.AboutScreen
import com.aliucord.manager.ui.screens.logs.LogsListScreen
import com.aliucord.manager.ui.screens.settings.SettingsScreen
import dev.goon.manager.R
import dev.shiggy.manager.R
@Composable
fun HomeAppBar() {

View file

@ -14,7 +14,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.aliucord.manager.ui.util.thenIf
import com.valentinilk.shimmer.*
import dev.goon.manager.R
import dev.shiggy.manager.R
private val shimmerTheme = defaultShimmerTheme.copy(
shimmerWidth = 150.dp,

View file

@ -18,7 +18,7 @@ import androidx.compose.ui.unit.dp
import com.aliucord.manager.ui.components.SegmentedButton
import com.aliucord.manager.ui.components.VersionDisplay
import com.aliucord.manager.ui.screens.home.InstallData
import dev.goon.manager.R
import dev.shiggy.manager.R
@Composable
fun InstalledItemCard(
@ -26,6 +26,7 @@ fun InstalledItemCard(
onUpdate: () -> Unit,
onOpenApp: () -> Unit,
onOpenInfo: () -> Unit,
onOpenPlugins: () -> Unit,
onUninstall: () -> Unit,
modifier: Modifier = Modifier,
) {
@ -88,6 +89,13 @@ fun InstalledItemCard(
horizontalArrangement = Arrangement.spacedBy(2.dp),
modifier = Modifier.clip(MaterialTheme.shapes.large),
) {
// // Plugins cannot be managed externally yet in Shiggy
// SegmentedButton(
// icon = painterResource(R.drawable.ic_extension),
// text = stringResource(R.string.plugins_title),
// onClick = onOpenPlugins,
// )
// If the up-to-date status cannot be determined, assume it is up-to-date
if (data.isUpToDate != false)
SegmentedButton(
@ -118,7 +126,7 @@ fun InstalledItemCard(
)
SegmentedButton(
icon = painterResource(R.drawable.ic_delete_forever),
text = stringResource(R.string.action_uninstall),
text = stringResource(R.string.action_uninstall), // Assuming R.string.action_uninstall exists or will be added
iconColor = MaterialTheme.colorScheme.error,
textColor = MaterialTheme.colorScheme.error,
onClick = onUninstall,

View file

@ -10,8 +10,8 @@ import cafe.adriel.voyager.core.model.ScreenModel
import cafe.adriel.voyager.core.model.screenModelScope
import com.aliucord.manager.ui.screens.patchopts.PatchOptions.IconReplacement
import com.aliucord.manager.util.*
import dev.goon.manager.BuildConfig
import dev.goon.manager.R
import dev.shiggy.manager.BuildConfig
import dev.shiggy.manager.R
import dev.zt64.compose.pipette.HsvColor
import java.io.IOException

View file

@ -41,7 +41,7 @@ import com.aliucord.manager.ui.screens.patchopts.PatchOptionsScreen
import com.aliucord.manager.ui.util.ColorSaver
import com.aliucord.manager.ui.util.throttledState
import com.aliucord.manager.util.back
import dev.goon.manager.R
import dev.shiggy.manager.R
import dev.zt64.compose.pipette.CircularColorPicker
import dev.zt64.compose.pipette.HsvColor
import kotlinx.parcelize.IgnoredOnParcel

View file

@ -7,7 +7,7 @@ import androidx.compose.ui.res.stringResource
import cafe.adriel.voyager.navigator.LocalNavigator
import com.aliucord.manager.ui.components.BackButton
import com.aliucord.manager.ui.screens.settings.SettingsScreen
import dev.goon.manager.R
import dev.shiggy.manager.R
@Composable
fun IconOptionsAppBar() {

View file

@ -11,7 +11,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import dev.goon.manager.R
import dev.shiggy.manager.R
import java.util.Locale
import kotlin.math.roundToInt

View file

@ -18,7 +18,7 @@ import com.aliucord.manager.ui.components.Label
import com.aliucord.manager.ui.screens.log.components.LogAppBar
import com.aliucord.manager.ui.screens.log.components.LogTextArea
import com.aliucord.manager.util.back
import dev.goon.manager.R
import dev.shiggy.manager.R
import kotlinx.parcelize.IgnoredOnParcel
import kotlinx.parcelize.Parcelize
import org.koin.core.parameter.parametersOf

View file

@ -10,8 +10,8 @@ import cafe.adriel.voyager.core.model.screenModelScope
import com.aliucord.manager.manager.InstallLogData
import com.aliucord.manager.manager.InstallLogManager
import com.aliucord.manager.util.*
import dev.goon.manager.BuildConfig
import dev.goon.manager.R
import dev.shiggy.manager.BuildConfig
import dev.shiggy.manager.R
class LogScreenModel(
private val installId: String,

View file

@ -5,7 +5,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import com.aliucord.manager.ui.components.BackButton
import dev.goon.manager.R
import dev.shiggy.manager.R
@Composable
fun LogAppBar(

View file

@ -8,7 +8,7 @@ import cafe.adriel.voyager.core.model.ScreenModel
import cafe.adriel.voyager.core.model.screenModelScope
import com.aliucord.manager.manager.InstallLogManager
import com.aliucord.manager.util.*
import dev.goon.manager.R
import dev.shiggy.manager.R
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList

View file

@ -21,7 +21,7 @@ import com.aliucord.manager.patcher.steps.base.StepState
import com.aliucord.manager.ui.screens.logs.LogEntry
import com.aliucord.manager.ui.screens.patching.components.StepStateIcon
import com.aliucord.manager.ui.screens.patching.components.TimeElapsed
import dev.goon.manager.R
import dev.shiggy.manager.R
@Composable
fun LogEntryCard(

View file

@ -7,7 +7,7 @@ import androidx.compose.ui.res.stringResource
import cafe.adriel.voyager.navigator.LocalNavigator
import com.aliucord.manager.ui.components.BackButton
import com.aliucord.manager.ui.screens.settings.SettingsScreen
import dev.goon.manager.R
import dev.shiggy.manager.R
@Composable
fun LogsListAppBar(

View file

@ -9,7 +9,7 @@ import androidx.compose.ui.draw.alpha
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import dev.goon.manager.R
import dev.shiggy.manager.R
@Composable
fun LogsNone(modifier: Modifier = Modifier) {

View file

@ -8,7 +8,7 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import dev.goon.manager.R
import dev.shiggy.manager.R
@Composable
fun DeleteLogsDialog(

View file

@ -40,7 +40,7 @@ import com.aliucord.manager.ui.util.spacedByLastAtBottom
import com.aliucord.manager.ui.util.thenIf
import com.aliucord.manager.util.back
import com.aliucord.manager.util.isIgnoringBatteryOptimizations
import dev.goon.manager.R
import dev.shiggy.manager.R
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.flow.filter
import kotlinx.parcelize.IgnoredOnParcel

Some files were not shown because too many files have changed in this diff Show more