Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
25a7ab4
potential apkm fix
prateek-who Jan 31, 2026
f92d1c9
Update PatchCommand.kt
prateek-who Jan 31, 2026
227bc14
Update .gitignore
prateek-who Jan 31, 2026
b2e63be
Update PatchCommand.kt
prateek-who Feb 1, 2026
ee63a14
feat: GUI Update
prateek-who Feb 6, 2026
d4ae84e
Removed hush hush svgs
prateek-who Feb 6, 2026
3c9c11a
Simplified version fixes + .apkm support fixed
prateek-who Feb 6, 2026
5199e5d
Minor UI updates
prateek-who Feb 6, 2026
73151ae
apkmirror link builder
prateek-who Feb 7, 2026
67a8aab
fix build
LisoUseInAIKyrios Feb 7, 2026
d30d4a5
Add IDE launcher preset
LisoUseInAIKyrios Feb 7, 2026
b6de86b
Use web-search api
LisoUseInAIKyrios Feb 7, 2026
afb8846
Follow redirects in non simple mode
LisoUseInAIKyrios Feb 7, 2026
3bfc223
Minor UI updates
prateek-who Feb 8, 2026
930d4b3
Connected devices update
prateek-who Feb 8, 2026
783dd7a
Patch Screen UI Improvements
prateek-who Feb 9, 2026
fdba2c6
--riplibs update
prateek-who Feb 9, 2026
ffa14f9
Minor release and windows fixes
prateek-who Feb 10, 2026
512f6f5
Patching Engine Fix
prateek-who Feb 11, 2026
bf224f6
Minor Fixes
prateek-who Feb 11, 2026
46a4577
Use non zero Java exit code if patching fails
prateek-who Feb 12, 2026
7640a80
Minor Fixes
prateek-who Feb 12, 2026
4477106
Merge remote-tracking branch 'origin/dev' into gui-update
LisoUseInAIKyrios Feb 15, 2026
5fb0e1a
Minor Fixes and new theme
prateek-who Feb 15, 2026
b8d3109
Better Offline support + Minor fixes
prateek-who Feb 19, 2026
7927c7e
Merge branch 'dev' into gui-update
prateek-who Feb 19, 2026
34eb844
Update PatchesViewModel.kt
prateek-who Feb 19, 2026
3648b94
Merge branch 'dev' into gui-update
prateek-who Feb 22, 2026
f3f80fc
conflict fix proper
prateek-who Feb 22, 2026
2cbb805
this for sure is the fix
prateek-who Feb 22, 2026
3ac83f3
Merge branch 'dev' into gui-update
prateek-who Feb 28, 2026
3100d85
partial fix
prateek-who Feb 28, 2026
a445ce6
final fix?
prateek-who Feb 28, 2026
c3b2311
final fix actually?
prateek-who Feb 28, 2026
bf8058b
Extremely minor update
prateek-who Mar 8, 2026
633fa97
revert the aapt2 libraries exclude change
prateek-who Mar 9, 2026
258ee86
backup before remote reset
prateek-who Mar 11, 2026
7eed1c6
Major Update
prateek-who Mar 13, 2026
aed56c2
Minor UI Update
prateek-who Mar 16, 2026
bcea7dd
Major Update
prateek-who Mar 17, 2026
bdc6d5b
Major Fix Update
prateek-who Mar 21, 2026
408395d
Minor UI Fixes
prateek-who Mar 22, 2026
b73a3dd
added ability to patch xapk
prateek-who Mar 23, 2026
1b123a5
apks support added
prateek-who Mar 26, 2026
9f56cb6
Minor UI fixes
prateek-who Mar 28, 2026
70a840e
Options and minor fixes
prateek-who Mar 30, 2026
c04a7a6
Minor theme fixes
prateek-who Mar 31, 2026
8cae648
Delete CLAUDE.md
prateek-who Mar 31, 2026
9873385
Merge branch 'dev' into gui-overhaul
prateek-who Apr 1, 2026
3616a97
Final FIX HOPEFULLY
prateek-who Apr 1, 2026
2b99b01
Fixed image selection fix for patch screen's patch options
prateek-who Apr 1, 2026
02738c1
refactor
LisoUseInAIKyrios Apr 2, 2026
8e86362
Merge remote-tracking branch 'origin/dev' into gui-overhaul
LisoUseInAIKyrios Apr 2, 2026
6acaba2
Windows fixes + Minor UI tweaks
prateek-who Apr 3, 2026
74a34d3
App names are read from patch files + minor mac UI fixes
prateek-who Apr 3, 2026
8dc0fb7
Major UX and Minor UI fixes
prateek-who Apr 6, 2026
74f87a6
Minor third party patches fix.
prateek-who Apr 8, 2026
afa7432
Potential fix FOR THAT ANNOYING TITLE BAR INSETS
prateek-who Apr 9, 2026
740740d
Matcha theme disabled until it can be made better in the future.
prateek-who Apr 11, 2026
4854ea0
Custom Keystore update
prateek-who Apr 12, 2026
006c244
Remove app name from windows title inset
prateek-who Apr 12, 2026
63ff9f8
Delete cat2333s.json
prateek-who Apr 12, 2026
5731a9c
UI Fixes and SettingsDialogue Fixes
prateek-who Apr 12, 2026
381c1fe
App version warnings unification and fixes
prateek-who Apr 12, 2026
be37cdd
JNA exclusion fix
prateek-who Apr 13, 2026
4d581f3
ci: Use JDK 17
LisoUseInAIKyrios Apr 13, 2026
8b57cf8
ci: Don't use cache
LisoUseInAIKyrios Apr 13, 2026
2d222c2
ci: Rearrange repos? Why does this not work on GitHub
LisoUseInAIKyrios Apr 13, 2026
300f912
refactor: Use shared constants
LisoUseInAIKyrios Apr 13, 2026
4e65148
ci: Restore cache since build is now working
LisoUseInAIKyrios Apr 13, 2026
cdc7d3b
Cleanup
LisoUseInAIKyrios Apr 13, 2026
3db6645
Patch name now shows the source too along with the version.
prateek-who Apr 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .github/workflows/build_pull_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,19 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]

steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Set up JDK 17
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'

- name: Cache Gradle
uses: burrunan/gradle-cache-action@v1

Expand All @@ -35,4 +42,3 @@ jobs:
with:
archive: false
path: build/libs/morphe-cli-*-all.jar

1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ build/

# Local configuration file (sdk path, etc)
local.properties
old_build.gradle.kts

# Log/OS Files
*.log
Expand Down
8 changes: 7 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ group = "app.morphe"
kotlin {
jvmToolchain {
languageVersion.set(JavaLanguageVersion.of(17))
vendor.set(JvmVendorSpec.ADOPTIUM)
vendor.set(JvmVendorSpec.JETBRAINS)
}
compilerOptions {
jvmTarget.set(JvmTarget.JVM_17)
Expand Down Expand Up @@ -100,6 +100,10 @@ dependencies {
implementation(libs.voyager.koin)
implementation(libs.voyager.transitions)

// -- JNA (Windows DWM title bar tinting) -------------------------------
implementation(libs.jna)
implementation(libs.jna.platform)

// -- APK Parsing (GUI) -------------------------------------------------
implementation(libs.apk.parser)

Expand Down Expand Up @@ -154,6 +158,8 @@ tasks {
exclude(dependency("io.insert-koin:.*"))
// Coroutines Swing provides Dispatchers.Main via ServiceLoader
exclude(dependency("org.jetbrains.kotlinx:kotlinx-coroutines-swing"))
// JNA uses reflection + native loading for DWM title bar tinting
exclude(dependency("net.java.dev.jna:.*"))
}

mergeServiceFiles()
Expand Down
7 changes: 7 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ voyager = "1.1.0-beta03"
coroutines = "1.10.2"
kotlinx-serialization = "1.9.0"

# JNA (Windows DWM title bar tinting)
jna = "5.14.0"

# APK
apk-parser = "2.6.10"

Expand Down Expand Up @@ -65,6 +68,10 @@ kotlinx-coroutines-swing = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-
# Serialization
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" }

# JNA (Windows DWM title bar tinting)
jna = { module = "net.java.dev.jna:jna", version.ref = "jna" }
jna-platform = { module = "net.java.dev.jna:jna-platform", version.ref = "jna" }

# APK
apk-parser = { module = "net.dongliu:apk-parser", version.ref = "apk-parser" }

Expand Down
2 changes: 1 addition & 1 deletion settings.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
pluginManagement {
repositories {
gradlePluginPortal()
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
google()
mavenCentral()
gradlePluginPortal()
}
}

Expand Down
3 changes: 2 additions & 1 deletion src/main/kotlin/app/morphe/cli/command/PatchCommand.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import app.morphe.cli.command.model.withUpdatedBundle
import app.morphe.engine.PatchEngine
import app.morphe.engine.PatchEngine.Config.Companion.DEFAULT_KEYSTORE_ALIAS
import app.morphe.engine.PatchEngine.Config.Companion.DEFAULT_KEYSTORE_PASSWORD
import app.morphe.engine.PatchEngine.Config.Companion.DEFAULT_SIGNER_NAME
import app.morphe.engine.PatchEngine.Config.Companion.LEGACY_KEYSTORE_ALIAS
import app.morphe.engine.PatchEngine.Config.Companion.LEGACY_KEYSTORE_PASSWORD
import app.morphe.engine.UpdateChecker
Expand Down Expand Up @@ -219,7 +220,7 @@ internal object PatchCommand : Callable<Int> {
description = ["The name of the signer to sign the patched APK file with."],
showDefaultValue = ALWAYS,
)
private var signer = "Morphe"
private var signer = DEFAULT_SIGNER_NAME

@CommandLine.Option(
names = ["-t", "--temporary-files-path"],
Expand Down
26 changes: 16 additions & 10 deletions src/main/kotlin/app/morphe/engine/PatchEngine.kt
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ object PatchEngine {
val forceCompatibility: Boolean = false,
val patchOptions: Map<String, Map<String, Any?>> = emptyMap(),
val unsigned: Boolean = false,
val signerName: String = "Morphe",
val signerName: String = DEFAULT_SIGNER_NAME,
val keystoreDetails: ApkUtils.KeyStoreDetails? = null,
val architecturesToKeep: Set<CpuArchitecture> = emptySet(),
val aaptBinaryPath: File? = null,
Expand All @@ -60,6 +60,7 @@ object PatchEngine {
companion object {
internal const val DEFAULT_KEYSTORE_ALIAS = "Morphe"
internal const val DEFAULT_KEYSTORE_PASSWORD = "Morphe"
internal const val DEFAULT_SIGNER_NAME = "Morphe"
internal const val LEGACY_KEYSTORE_ALIAS = "Morphe Key"
internal const val LEGACY_KEYSTORE_PASSWORD = ""
}
Expand Down Expand Up @@ -222,7 +223,19 @@ object PatchEngine {
// 7. Sign APK (unless unsigned)
val tempOutput = File(tempDir, config.outputApk.name)
if (!config.unsigned) {
onProgress("Signing APK...")
val keystoreDetails = config.keystoreDetails ?: ApkUtils.KeyStoreDetails(
File(tempDir, "morphe.keystore"),
null,
Config.DEFAULT_KEYSTORE_ALIAS,
Config.DEFAULT_KEYSTORE_PASSWORD,
)

if (config.keystoreDetails != null) {
onProgress("Signing APK with custom keystore: ${keystoreDetails.keyStore.name}")
} else {
onProgress("Signing APK...")
}

try {
fun signApk(details: ApkUtils.KeyStoreDetails) {
ApkUtils.signApk(
Expand All @@ -233,16 +246,9 @@ object PatchEngine {
)
}

val keystoreDetails = config.keystoreDetails ?: ApkUtils.KeyStoreDetails(
File(tempDir, "morphe.keystore"),
null,
Config.DEFAULT_KEYSTORE_ALIAS,
Config.DEFAULT_KEYSTORE_PASSWORD,
)

try {
signApk(keystoreDetails)
} catch (e: Exception){
} catch (e: Exception) {
// Retry with legacy keystore defaults.
if (config.keystoreDetails == null && keystoreDetails.keyStore.exists()) {
logger.info("Using legacy keystore credentials")
Expand Down
130 changes: 108 additions & 22 deletions src/main/kotlin/app/morphe/gui/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,32 @@
package app.morphe.gui

import androidx.compose.animation.Crossfade
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.window.WindowDraggableArea
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.unit.dp
import app.morphe.gui.ui.components.LocalFrameWindowScope
import app.morphe.gui.ui.components.LottieAnimation
import app.morphe.gui.ui.components.SakuraPetals
import app.morphe.gui.util.applyTitleBarTint
import cafe.adriel.voyager.navigator.Navigator
import cafe.adriel.voyager.transitions.SlideTransition
import app.morphe.gui.data.repository.ConfigRepository
import app.morphe.gui.data.repository.PatchRepository
import app.morphe.gui.util.PatchService
import app.morphe.gui.data.repository.PatchSourceManager
import app.morphe.gui.di.appModule
import kotlinx.coroutines.launch
import org.koin.compose.KoinApplication
import org.koin.compose.koinInject
import app.morphe.gui.ui.screens.home.HomeScreen
import app.morphe.gui.ui.screens.quick.QuickPatchContent
import app.morphe.gui.ui.screens.quick.QuickPatchViewModel
import app.morphe.gui.util.PatchService
import app.morphe.gui.ui.theme.LocalThemeState
import app.morphe.gui.ui.theme.MorpheTheme
import app.morphe.gui.ui.theme.ThemePreference
Expand All @@ -42,31 +52,35 @@ val LocalModeState = staticCompositionLocalOf<ModeState> {
}

@Composable
fun App(initialSimplifiedMode: Boolean = true) {
fun App(
initialSimplifiedMode: Boolean = true
) {
LaunchedEffect(Unit) {
Logger.init()
}

KoinApplication(application = {
modules(appModule)
}) {
AppContent(initialSimplifiedMode)
AppContent(initialSimplifiedMode = initialSimplifiedMode)
}
}

@Composable
private fun AppContent(initialSimplifiedMode: Boolean) {
private fun AppContent(
initialSimplifiedMode: Boolean
) {
val configRepository: ConfigRepository = koinInject()
val patchRepository: PatchRepository = koinInject()
val patchService: PatchService = koinInject()
val patchSourceManager: PatchSourceManager = koinInject()
val scope = rememberCoroutineScope()

var themePreference by remember { mutableStateOf(ThemePreference.SYSTEM) }
var isSimplifiedMode by remember { mutableStateOf(initialSimplifiedMode) }
var isLoading by remember { mutableStateOf(true) }

// Load config on startup
// Initialize PatchSourceManager and load config on startup
LaunchedEffect(Unit) {
patchSourceManager.initialize()
val config = configRepository.loadConfig()
themePreference = config.getThemePreference()
isSimplifiedMode = config.useSimplifiedMode
Expand Down Expand Up @@ -114,25 +128,97 @@ private fun AppContent(initialSimplifiedMode: Boolean) {
LocalThemeState provides themeState,
LocalModeState provides modeState
) {
// Tint the OS title bar (Windows DWM caption color, macOS traffic
// light contrast) to match the active theme's surface color.
val titleBarColor = MaterialTheme.colorScheme.surface
val frameScope = LocalFrameWindowScope.current
LaunchedEffect(titleBarColor, frameScope) {
frameScope?.window?.let { applyTitleBarTint(it, titleBarColor) }
}

// macOS only: render a 28dp colored band at the very top of the
// window, sitting underneath the (now-transparent) OS title bar.
// The traffic lights overlay this band at their default position.
// Wrapped in WindowDraggableArea so the band acts as a drag region.
val isMac = remember {
System.getProperty("os.name")?.lowercase()?.contains("mac") == true
}

Surface(modifier = Modifier.fillMaxSize()) {
if (!isLoading) {
// Create QuickPatchViewModel outside Crossfade so it persists across mode switches.
// Otherwise every expert→simplified switch creates a new VM that re-fetches from GitHub.
val quickViewModel = remember {
QuickPatchViewModel(patchRepository, patchService, configRepository)
Column(modifier = Modifier.fillMaxSize()) {
if (isMac && frameScope != null) {
with(frameScope) {
WindowDraggableArea {
Box(
modifier = Modifier
.fillMaxWidth()
.height(16.dp)
.background(titleBarColor)
)
}
}
}

Crossfade(targetState = isSimplifiedMode) { simplified ->
if (simplified) {
// Quick/Simplified mode
QuickPatchContent(quickViewModel)
} else {
// Full mode
Navigator(HomeScreen()) { navigator ->
SlideTransition(navigator)
Box(modifier = Modifier.fillMaxWidth().weight(1f)) {
if (!isLoading) {
val patchService: PatchService = koinInject()
val quickViewModel = remember {
QuickPatchViewModel(patchSourceManager, patchService, configRepository)
}

Crossfade(targetState = isSimplifiedMode) { simplified ->
if (simplified) {
QuickPatchContent(quickViewModel)
} else {
Navigator(HomeScreen()) { navigator ->
SlideTransition(navigator)
}
}
}
}

// Falling petals — on top of everything (Sakura)
SakuraPetals(
enabled = themePreference == ThemePreference.SAKURA
)

// Matcha cat — top-right corner
if (themePreference == ThemePreference.MATCHA) {
val catJson = remember {
try {
object {}.javaClass.getResourceAsStream("/cat2333s.json")
?.bufferedReader()?.readText()
} catch (e: Exception) {
null
}
}
catJson?.let { json ->
// 1080px canvas, rendered at 350dp (1dp ≈ 3.086 canvas px).
// Ears ~y385 → 125dp, bar bottom ~y576 → 187dp.
// Body shrunk to 85% so it hides behind bar.
// Clip from 120dp to 192dp (72dp visible) — ears to just past bar.
val renderSize = 350.dp
val clipTop = 120.dp // just above ears
val clipHeight = 72.dp // ears → just past bar bottom
Box(
modifier = Modifier
.align(Alignment.TopEnd)
.padding(top = 24.dp, end = 16.dp)
.requiredWidth(renderSize)
.requiredHeight(clipHeight)
.clipToBounds()
) {
LottieAnimation(
jsonString = json,
modifier = Modifier
.requiredSize(renderSize)
.offset(y = -clipTop),
alpha = 0.28f
)
}
}
}
}
}
}
}
Expand Down
Loading
Loading