Skip to content

Commit 3d11e21

Browse files
authored
feat: Support YT Music change header option (#264)
1 parent 08da2d8 commit 3d11e21

6 files changed

Lines changed: 187 additions & 110 deletions

File tree

app/src/main/java/app/morphe/manager/ui/screen/home/ExpertModeDialog.kt

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ fun ExpertModeDialog(
6363
onProceed: () -> Unit,
6464
allowIncompatible: Boolean = false
6565
) {
66-
var selectedPatchForOptions by remember { mutableStateOf<Pair<Int, PatchInfo>?>(null) }
66+
var selectedPatchForOptions by remember { mutableStateOf<Triple<Int, PatchInfo, Boolean>?>(null) }
6767
var searchQuery by remember { mutableStateOf("") }
6868
var searchVisible by remember { mutableStateOf(false) }
6969
var showMultipleSourcesWarning by remember { mutableStateOf(false) }
@@ -294,7 +294,7 @@ fun ExpertModeDialog(
294294
isEnabled = isEnabled,
295295
onToggle = { togglePatch(bundle.uid, patch.name) },
296296
onConfigureOptions = {
297-
if (!patch.options.isNullOrEmpty()) selectedPatchForOptions = bundle.uid to patch
297+
if (!patch.options.isNullOrEmpty()) selectedPatchForOptions = Triple(bundle.uid, patch, bundle.uid == 0)
298298
},
299299
hasOptions = !patch.options.isNullOrEmpty()
300300
)
@@ -415,7 +415,7 @@ fun ExpertModeDialog(
415415
isEnabled = isEnabled,
416416
onToggle = { togglePatch(bundle.uid, patch.name) },
417417
onConfigureOptions = {
418-
if (!patch.options.isNullOrEmpty()) selectedPatchForOptions = bundle.uid to patch
418+
if (!patch.options.isNullOrEmpty()) selectedPatchForOptions = Triple(bundle.uid, patch, bundle.uid == 0)
419419
},
420420
hasOptions = !patch.options.isNullOrEmpty()
421421
)
@@ -456,9 +456,10 @@ fun ExpertModeDialog(
456456
}
457457

458458
// Options dialog
459-
selectedPatchForOptions?.let { (bundleUid, patch) ->
459+
selectedPatchForOptions?.let { (bundleUid, patch, isDefaultBundle) ->
460460
PatchOptionsDialog(
461461
patch = patch,
462+
isDefaultBundle = isDefaultBundle,
462463
values = options[bundleUid]?.get(patch.name),
463464
onValueChange = { key, value ->
464465
onOptionChange(bundleUid, patch.name, key, value)
@@ -676,11 +677,15 @@ private fun EmptyStateContent(
676677
@Composable
677678
private fun PatchOptionsDialog(
678679
patch: PatchInfo,
680+
isDefaultBundle: Boolean,
679681
values: Map<String, Any?>?,
680682
onValueChange: (String, Any?) -> Unit,
681683
onReset: () -> Unit,
682684
onDismiss: () -> Unit
683685
) {
686+
// Derive the target package from the patch's compatible packages list
687+
val packageName = patch.compatiblePackages?.firstOrNull()?.packageName.orEmpty()
688+
684689
var showColorPicker by remember { mutableStateOf<Pair<String, String>?>(null) }
685690

686691
MorpheDialog(
@@ -756,6 +761,8 @@ private fun PatchOptionsDialog(
756761
description = option.description,
757762
value = value?.toString() ?: "",
758763
presets = option.presets,
764+
packageName = packageName,
765+
isDefaultBundle = isDefaultBundle,
759766
onValueChange = { onValueChange(key, it) }
760767
)
761768
}
@@ -788,6 +795,8 @@ private fun PatchOptionsDialog(
788795
title = option.title,
789796
description = option.description,
790797
value = value?.toString() ?: "",
798+
packageName = packageName,
799+
isDefaultBundle = isDefaultBundle,
791800
// required = option.required,
792801
onValueChange = { onValueChange(key, it) }
793802
)
@@ -1017,6 +1026,8 @@ private fun PathInputOption(
10171026
title: String,
10181027
description: String,
10191028
value: String,
1029+
packageName: String,
1030+
isDefaultBundle: Boolean,
10201031
// required: Boolean,
10211032
onValueChange: (String) -> Unit
10221033
) {
@@ -1063,8 +1074,8 @@ private fun PathInputOption(
10631074
onFolderPickerClick = { folderPicker() }
10641075
)
10651076

1066-
// Create Icon button
1067-
if (isIconField) {
1077+
// Create Icon button (only for the default Morphe bundle)
1078+
if (isIconField && isDefaultBundle) {
10681079
MorpheDialogOutlinedButton(
10691080
text = stringResource(R.string.adaptive_icon_create),
10701081
onClick = { showIconCreator = true },
@@ -1073,8 +1084,8 @@ private fun PathInputOption(
10731084
)
10741085
}
10751086

1076-
// Create Header button
1077-
if (isHeaderField) {
1087+
// Create Header button (only for the default Morphe bundle)
1088+
if (isHeaderField && isDefaultBundle) {
10781089
MorpheDialogOutlinedButton(
10791090
text = stringResource(R.string.header_creator_create),
10801091
onClick = { showHeaderCreator = true },
@@ -1100,6 +1111,7 @@ private fun PathInputOption(
11001111
// Icon creator dialog
11011112
if (showIconCreator) {
11021113
AdaptiveIconCreatorDialog(
1114+
packageName = packageName,
11031115
onDismiss = { showIconCreator = false },
11041116
onIconCreated = { path ->
11051117
onValueChange(path)
@@ -1111,6 +1123,7 @@ private fun PathInputOption(
11111123
// Header creator dialog
11121124
if (showHeaderCreator) {
11131125
HeaderCreatorDialog(
1126+
packageName = packageName,
11141127
onDismiss = { showHeaderCreator = false },
11151128
onHeaderCreated = { path ->
11161129
onValueChange(path)
@@ -1130,6 +1143,8 @@ private fun PathWithPresetsOption(
11301143
description: String,
11311144
value: String,
11321145
presets: Map<String, *>,
1146+
packageName: String,
1147+
isDefaultBundle: Boolean,
11331148
onValueChange: (String) -> Unit
11341149
) {
11351150
var showIconCreator by remember { mutableStateOf(false) }
@@ -1185,8 +1200,8 @@ private fun PathWithPresetsOption(
11851200
onFolderPickerClick = { folderPicker() }
11861201
)
11871202

1188-
// Create Icon button
1189-
if (isIconField) {
1203+
// Create Icon button (only for the default Morphe bundle)
1204+
if (isIconField && isDefaultBundle) {
11901205
MorpheDialogOutlinedButton(
11911206
text = stringResource(R.string.adaptive_icon_create),
11921207
onClick = { showIconCreator = true },
@@ -1195,8 +1210,8 @@ private fun PathWithPresetsOption(
11951210
)
11961211
}
11971212

1198-
// Create Header button
1199-
if (isHeaderField) {
1213+
// Create Header button (only for the default Morphe bundle)
1214+
if (isHeaderField && isDefaultBundle) {
12001215
MorpheDialogOutlinedButton(
12011216
text = stringResource(R.string.header_creator_create),
12021217
onClick = { showHeaderCreator = true },
@@ -1224,6 +1239,7 @@ private fun PathWithPresetsOption(
12241239
// Icon creator dialog
12251240
if (showIconCreator) {
12261241
AdaptiveIconCreatorDialog(
1242+
packageName = packageName,
12271243
onDismiss = { showIconCreator = false },
12281244
onIconCreated = { path ->
12291245
onValueChange(path)
@@ -1235,6 +1251,7 @@ private fun PathWithPresetsOption(
12351251
// Header creator dialog
12361252
if (showHeaderCreator) {
12371253
HeaderCreatorDialog(
1254+
packageName = packageName,
12381255
onDismiss = { showHeaderCreator = false },
12391256
onHeaderCreated = { path ->
12401257
onValueChange(path)

app/src/main/java/app/morphe/manager/ui/screen/settings/advanced/PatchOptionsDialogs.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
/*
2+
* Copyright 2026 Morphe.
3+
* https://github.com/MorpheApp/morphe-manager
4+
*/
5+
16
package app.morphe.manager.ui.screen.settings.advanced
27

38
import androidx.compose.foundation.layout.*
@@ -389,6 +394,7 @@ fun CustomBrandingDialog(
389394
// Icon creator dialog
390395
if (showIconCreator) {
391396
AdaptiveIconCreatorDialog(
397+
packageName = packageName,
392398
onDismiss = { showIconCreator = false },
393399
onIconCreated = { path ->
394400
iconPath = path
@@ -405,6 +411,7 @@ fun CustomBrandingDialog(
405411
fun CustomHeaderDialog(
406412
patchOptionsPrefs: PatchOptionsPreferencesManager,
407413
patchOptionsViewModel: PatchOptionsViewModel,
414+
packageName: String,
408415
onDismiss: () -> Unit
409416
) {
410417
val context = LocalContext.current
@@ -502,6 +509,7 @@ fun CustomHeaderDialog(
502509
// Header creator dialog
503510
if (showHeaderCreator) {
504511
HeaderCreatorDialog(
512+
packageName = packageName,
505513
onDismiss = { showHeaderCreator = false },
506514
onHeaderCreated = { path ->
507515
headerPath = path

app/src/main/java/app/morphe/manager/ui/screen/settings/advanced/PatchOptionsSection.kt

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
/*
2+
* Copyright 2026 Morphe.
3+
* https://github.com/MorpheApp/morphe-manager
4+
*/
5+
16
package app.morphe.manager.ui.screen.settings.advanced
27

38
import android.annotation.SuppressLint
@@ -49,7 +54,7 @@ fun PatchOptionsSection(
4954

5055
var showThemeDialog by remember { mutableStateOf<String?>(null) }
5156
var showBrandingDialog by remember { mutableStateOf<String?>(null) }
52-
var showHeaderDialog by remember { mutableStateOf(false) }
57+
var showHeaderDialog by remember { mutableStateOf<String?>(null) }
5358

5459
// Collect patch options from ViewModel
5560
val youtubePatches by patchOptionsViewModel.youtubePatches.collectAsState()
@@ -186,7 +191,7 @@ fun PatchOptionsSection(
186191
patchOptionsViewModel = patchOptionsViewModel,
187192
onThemeClick = { showThemeDialog = KnownApp.YOUTUBE },
188193
onBrandingClick = { showBrandingDialog = KnownApp.YOUTUBE },
189-
onHeaderClick = { showHeaderDialog = true }
194+
onHeaderClick = { showHeaderDialog = KnownApp.YOUTUBE }
190195
)
191196
}
192197

@@ -218,7 +223,7 @@ fun PatchOptionsSection(
218223
patchOptionsViewModel = patchOptionsViewModel,
219224
onThemeClick = { showThemeDialog = KnownApp.YOUTUBE_MUSIC },
220225
onBrandingClick = { showBrandingDialog = KnownApp.YOUTUBE_MUSIC },
221-
onHeaderClick = null // No header for YouTube Music
226+
onHeaderClick = { showHeaderDialog = KnownApp.YOUTUBE_MUSIC }
222227
)
223228
}
224229
}
@@ -245,12 +250,13 @@ fun PatchOptionsSection(
245250
)
246251
}
247252

248-
// Header Dialog (YouTube only)
249-
if (showHeaderDialog) {
253+
// Header Dialog
254+
showHeaderDialog?.let { packageName ->
250255
CustomHeaderDialog(
251256
patchOptionsPrefs = patchOptionsPrefs,
252257
patchOptionsViewModel = patchOptionsViewModel,
253-
onDismiss = { showHeaderDialog = false }
258+
packageName = packageName,
259+
onDismiss = { showHeaderDialog = null }
254260
)
255261
}
256262
}
@@ -272,7 +278,7 @@ private fun AppPatchOptionsCard(
272278
// Get available patches for this app type
273279
val hasTheme = patchOptionsViewModel.getThemeOptions(packageName) != null
274280
val hasBranding = patchOptionsViewModel.getBrandingOptions(packageName) != null
275-
val hasHeader = packageName == KnownApp.YOUTUBE && patchOptionsViewModel.getHeaderOptions() != null
281+
val hasHeader = patchOptionsViewModel.getHeaderOptions() != null
276282

277283
Column {
278284
// Header

app/src/main/java/app/morphe/manager/ui/screen/shared/AdaptiveIconCreatorDialog.kt

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
/*
2+
* Copyright 2026 Morphe.
3+
* https://github.com/MorpheApp/morphe-manager
4+
*/
5+
16
package app.morphe.manager.ui.screen.shared
27

38
import android.annotation.SuppressLint
@@ -52,7 +57,13 @@ import kotlin.math.abs
5257
private object AdaptiveIconConfig {
5358
// Folder structure
5459
const val BRANDING_FOLDER_NAME = "morphe_branding"
55-
const val ICONS_FOLDER_NAME = "morphe_icons"
60+
const val YOUTUBE_ICONS_FOLDER_NAME = "morphe_icons_youtube"
61+
const val YTM_ICONS_FOLDER_NAME = "morphe_icons_music"
62+
63+
fun iconFolderName(packageName: String) = when (packageName) {
64+
KnownApp.YOUTUBE_MUSIC -> YTM_ICONS_FOLDER_NAME
65+
else -> YOUTUBE_ICONS_FOLDER_NAME
66+
}
5667

5768
// File names
5869
const val BACKGROUND_FILE_NAME = "morphe_adaptive_background_custom.png"
@@ -102,6 +113,7 @@ private object AdaptiveIconConfig {
102113
*/
103114
@Composable
104115
fun AdaptiveIconCreatorDialog(
116+
packageName: String,
105117
onDismiss: () -> Unit,
106118
onIconCreated: (String) -> Unit
107119
) {
@@ -159,6 +171,7 @@ fun AdaptiveIconCreatorDialog(
159171
val success = createAdaptiveIcons(
160172
context = context,
161173
baseUri = it,
174+
packageName = packageName,
162175
foregroundBitmap = foregroundBitmap!!,
163176
backgroundColor = backgroundColor,
164177
scale = scale,
@@ -200,7 +213,11 @@ fun AdaptiveIconCreatorDialog(
200213
exit = fadeOut() + shrinkVertically()
201214
) {
202215
InfoBadge(
203-
text = stringResource(R.string.adaptive_icon_folder_explanation),
216+
text = stringResource(
217+
R.string.adaptive_icon_folder_explanation,
218+
AdaptiveIconConfig.BRANDING_FOLDER_NAME,
219+
AdaptiveIconConfig.iconFolderName(packageName)
220+
),
204221
style = InfoBadgeStyle.Primary,
205222
icon = Icons.Outlined.Info
206223
)
@@ -580,6 +597,7 @@ private fun SafeZoneLegendItem(
580597
private suspend fun createAdaptiveIcons(
581598
context: Context,
582599
baseUri: Uri,
600+
packageName: String,
583601
foregroundBitmap: Bitmap,
584602
backgroundColor: String,
585603
scale: Float,
@@ -591,7 +609,7 @@ private suspend fun createAdaptiveIcons(
591609
val basePath = baseUri.toFilePath()
592610
val baseDir = File(basePath)
593611

594-
// Create directory structure: morphe_branding/morphe_icons
612+
// Create directory structure: morphe_branding/morphe_icons_youtube or morphe_icons_music
595613
val brandingDir = File(baseDir, AdaptiveIconConfig.BRANDING_FOLDER_NAME)
596614
if (!brandingDir.exists()) brandingDir.mkdirs()
597615

@@ -601,7 +619,7 @@ private suspend fun createAdaptiveIcons(
601619
nomediaFile.createNewFile()
602620
}
603621

604-
val iconsDir = File(brandingDir, AdaptiveIconConfig.ICONS_FOLDER_NAME)
622+
val iconsDir = File(brandingDir, AdaptiveIconConfig.iconFolderName(packageName))
605623
if (!iconsDir.exists()) iconsDir.mkdirs()
606624

607625
// Get preview density for offset calculations

0 commit comments

Comments
 (0)