From 363e9d2107459ba0e5aedd9d492d494f3781e297 Mon Sep 17 00:00:00 2001 From: Joao Victor Sena Date: Mon, 27 Jul 2026 18:13:52 -0300 Subject: [PATCH 1/4] feat: name the OS-level entry points in one shared enum Android's intent action and iOS's UIApplicationShortcutItem type are bare strings crossing a platform boundary, so they are declared once in commonMain and each platform maps its own identifier onto them. Co-Authored-By: Claude Opus 5 (1M context) --- .../github/worn/domain/model/AppShortcut.kt | 22 +++++++++++ .../com/github/worn/model/AppShortcutTest.kt | 37 +++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 shared/src/commonMain/kotlin/com/github/worn/domain/model/AppShortcut.kt create mode 100644 shared/src/commonTest/kotlin/com/github/worn/model/AppShortcutTest.kt diff --git a/shared/src/commonMain/kotlin/com/github/worn/domain/model/AppShortcut.kt b/shared/src/commonMain/kotlin/com/github/worn/domain/model/AppShortcut.kt new file mode 100644 index 0000000..1180ac0 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/github/worn/domain/model/AppShortcut.kt @@ -0,0 +1,22 @@ +package com.github.worn.domain.model + +/** + * Actions the OS can launch straight from the app icon: Android App Shortcuts and iOS Home Screen + * Quick Actions. + * + * [id] is the iOS `UIApplicationShortcutItem.type` and the suffix of the Android intent action, so + * the two platforms cannot drift apart. + */ +enum class AppShortcut(val id: String) { + /** Opens the Wardrobe tab with the Add Item sheet already up. */ + ADD_ITEM("add_item"), + + /** Opens the Try It tab in its idle state. */ + TRY_IT("try_it"), + + ; + + companion object { + fun fromId(id: String?): AppShortcut? = entries.firstOrNull { it.id == id } + } +} diff --git a/shared/src/commonTest/kotlin/com/github/worn/model/AppShortcutTest.kt b/shared/src/commonTest/kotlin/com/github/worn/model/AppShortcutTest.kt new file mode 100644 index 0000000..cec0648 --- /dev/null +++ b/shared/src/commonTest/kotlin/com/github/worn/model/AppShortcutTest.kt @@ -0,0 +1,37 @@ +package com.github.worn.model + +import com.github.worn.domain.model.AppShortcut +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNull + +class AppShortcutTest { + + @Test + fun fromId_resolves_the_add_item_shortcut() { + assertEquals(AppShortcut.ADD_ITEM, AppShortcut.fromId("add_item")) + } + + @Test + fun fromId_resolves_the_try_it_shortcut() { + assertEquals(AppShortcut.TRY_IT, AppShortcut.fromId("try_it")) + } + + @Test + fun fromId_returns_null_for_an_unknown_id() { + assertNull(AppShortcut.fromId("outfits")) + } + + @Test + fun fromId_returns_null_for_a_missing_id() { + assertNull(AppShortcut.fromId(null)) + } + + /** The ids cross a platform boundary as bare strings, so a rename must not go unnoticed. */ + @Test + fun every_shortcut_round_trips_through_its_id() { + AppShortcut.entries.forEach { shortcut -> + assertEquals(shortcut, AppShortcut.fromId(shortcut.id)) + } + } +} From 1a47fea64ca93f14622f53c1df3f10a57fea5bf7 Mon Sep 17 00:00:00 2001 From: Joao Victor Sena Date: Mon, 27 Jul 2026 18:13:53 -0300 Subject: [PATCH 2/4] feat: add Add item and Try it launcher shortcuts on Android MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Static shortcuts, so they exist from install and localize through @string with no runtime code. The intents are explicit, so the custom actions are a payload for MainActivity to read rather than something the system has to resolve. ShortcutCommand is deliberately not a data class, for the same reason as SharedPhoto: identity equality re-fires LaunchedEffect when the same shortcut is tapped twice in a row. Try It needs only the tab switch, so App consumes it. Add Item is threaded down to WardrobeScreen, which consumes it once the sheet is open — the pager sets beyondViewportPageCount = 1, so an unconsumed command would reopen the sheet on every return to the tab. The shortcut drawables bake in the ink colour instead of the existing icons' placeholder black: a launcher does not tint a shortcut icon. Co-Authored-By: Claude Opus 5 (1M context) --- composeApp/src/main/AndroidManifest.xml | 5 +++ .../src/main/kotlin/com/github/worn/App.kt | 28 ++++++++++++++-- .../kotlin/com/github/worn/MainActivity.kt | 18 +++++++++-- .../github/worn/ui/screen/WardrobeScreen.kt | 15 +++++++++ .../github/worn/ui/util/ShortcutCommand.kt | 19 +++++++++++ .../res/drawable/ic_shortcut_add_item.xml | 14 ++++++++ .../main/res/drawable/ic_shortcut_try_it.xml | 14 ++++++++ .../src/main/res/values-pt-rBR/strings.xml | 6 ++++ composeApp/src/main/res/values/strings.xml | 6 ++++ composeApp/src/main/res/xml/shortcuts.xml | 32 +++++++++++++++++++ 10 files changed, 153 insertions(+), 4 deletions(-) create mode 100644 composeApp/src/main/kotlin/com/github/worn/ui/util/ShortcutCommand.kt create mode 100644 composeApp/src/main/res/drawable/ic_shortcut_add_item.xml create mode 100644 composeApp/src/main/res/drawable/ic_shortcut_try_it.xml create mode 100644 composeApp/src/main/res/xml/shortcuts.xml diff --git a/composeApp/src/main/AndroidManifest.xml b/composeApp/src/main/AndroidManifest.xml index a2d79cb..d0bd92a 100644 --- a/composeApp/src/main/AndroidManifest.xml +++ b/composeApp/src/main/AndroidManifest.xml @@ -32,6 +32,11 @@ + + + diff --git a/composeApp/src/main/kotlin/com/github/worn/App.kt b/composeApp/src/main/kotlin/com/github/worn/App.kt index c297606..8e742a1 100644 --- a/composeApp/src/main/kotlin/com/github/worn/App.kt +++ b/composeApp/src/main/kotlin/com/github/worn/App.kt @@ -16,6 +16,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.testTagsAsResourceId import androidx.window.core.layout.WindowWidthSizeClass +import com.github.worn.domain.model.AppShortcut import com.github.worn.ui.components.Tab import com.github.worn.ui.components.WornBottomBar import com.github.worn.ui.screen.GapsScreen @@ -26,6 +27,7 @@ import com.github.worn.ui.screen.WardrobeScreen import com.github.worn.ui.theme.WornColors import com.github.worn.ui.theme.WornTheme import com.github.worn.ui.util.SharedPhoto +import com.github.worn.ui.util.ShortcutCommand import kotlinx.coroutines.launch private val tabs = Tab.entries.toList() @@ -33,7 +35,12 @@ private val tabs = Tab.entries.toList() @OptIn(ExperimentalComposeUiApi::class) @Suppress("FunctionNaming") @Composable -fun App(sharedPhoto: SharedPhoto? = null, onSharedPhotoConsumed: () -> Unit = {}) { +fun App( + sharedPhoto: SharedPhoto? = null, + onSharedPhotoConsumed: () -> Unit = {}, + shortcut: ShortcutCommand? = null, + onShortcutConsumed: () -> Unit = {}, +) { WornTheme { val pagerState = rememberPagerState(pageCount = { tabs.size }) val scope = rememberCoroutineScope() @@ -52,6 +59,19 @@ fun App(sharedPhoto: SharedPhoto? = null, onSharedPhotoConsumed: () -> Unit = {} if (sharedPhoto != null) onTabSelected(Tab.TRY_IT) } + LaunchedEffect(shortcut) { + when (shortcut?.shortcut) { + // The tab switch is the whole job, so this one is done here. + AppShortcut.TRY_IT -> { + onTabSelected(Tab.TRY_IT) + onShortcutConsumed() + } + // WardrobeScreen consumes this once the sheet is actually open. + AppShortcut.ADD_ITEM -> onTabSelected(Tab.WARDROBE) + null -> Unit + } + } + Box( modifier = Modifier .fillMaxSize() @@ -64,7 +84,11 @@ fun App(sharedPhoto: SharedPhoto? = null, onSharedPhotoConsumed: () -> Unit = {} modifier = Modifier.fillMaxSize(), ) { page -> when (tabs[page]) { - Tab.WARDROBE -> WardrobeScreen(onTabSelected = onTabSelected) + Tab.WARDROBE -> WardrobeScreen( + onTabSelected = onTabSelected, + openAddSheet = shortcut?.takeIf { it.shortcut == AppShortcut.ADD_ITEM }, + onAddSheetOpened = onShortcutConsumed, + ) Tab.OUTFITS -> OutfitsScreen(onTabSelected = onTabSelected) Tab.GAPS -> GapsScreen(onTabSelected = onTabSelected) Tab.TRY_IT -> TryItScreen( diff --git a/composeApp/src/main/kotlin/com/github/worn/MainActivity.kt b/composeApp/src/main/kotlin/com/github/worn/MainActivity.kt index 3fdf76b..9079f58 100644 --- a/composeApp/src/main/kotlin/com/github/worn/MainActivity.kt +++ b/composeApp/src/main/kotlin/com/github/worn/MainActivity.kt @@ -15,32 +15,46 @@ import androidx.core.content.IntentCompat import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.lifecycle.lifecycleScope import com.github.worn.ui.util.SharedPhoto +import com.github.worn.ui.util.ShortcutCommand import com.github.worn.ui.util.readImageBytes +import com.github.worn.ui.util.shortcutCommandFor import kotlinx.coroutines.launch class MainActivity : ComponentActivity() { private var sharedPhoto by mutableStateOf(null) + private var shortcut by mutableStateOf(null) override fun onCreate(savedInstanceState: Bundle?) { installSplashScreen() enableEdgeToEdge() super.onCreate(savedInstanceState) - handleShareIntent(intent) + handleIntent(intent) setContent { App( sharedPhoto = sharedPhoto, onSharedPhotoConsumed = { sharedPhoto = null }, + shortcut = shortcut, + onShortcutConsumed = { shortcut = null }, ) } } - /** singleTask launch mode routes a share into the running instance rather than a new one. */ + /** + * singleTask launch mode routes a share or a launcher shortcut into the running instance rather + * than a new one. + */ override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) setIntent(intent) + handleIntent(intent) + } + + private fun handleIntent(intent: Intent?) { + if (intent == null) return + shortcutCommandFor(intent.action)?.let { shortcut = it } handleShareIntent(intent) } diff --git a/composeApp/src/main/kotlin/com/github/worn/ui/screen/WardrobeScreen.kt b/composeApp/src/main/kotlin/com/github/worn/ui/screen/WardrobeScreen.kt index e0bb176..3e71449 100644 --- a/composeApp/src/main/kotlin/com/github/worn/ui/screen/WardrobeScreen.kt +++ b/composeApp/src/main/kotlin/com/github/worn/ui/screen/WardrobeScreen.kt @@ -74,6 +74,7 @@ import com.github.worn.ui.components.Tab import com.github.worn.ui.theme.WornColors import com.github.worn.ui.theme.WornDimens import com.github.worn.ui.theme.WornTheme +import com.github.worn.ui.util.ShortcutCommand import org.koin.compose.viewmodel.koinViewModel private val GRID_MIN_CELL_WIDTH = 160.dp @@ -82,6 +83,8 @@ private val GRID_GAP_EXPANDED = 16.dp @Composable fun WardrobeScreen( onTabSelected: (Tab) -> Unit = {}, + openAddSheet: ShortcutCommand? = null, + onAddSheetOpened: () -> Unit = {}, viewModel: WardrobeViewModel = koinViewModel(), ) { val state by viewModel.state.collectAsStateWithLifecycle() @@ -89,6 +92,18 @@ fun WardrobeScreen( var detailItem by remember { mutableStateOf(null) } var editItem by remember { mutableStateOf(null) } + /** + * [onAddSheetOpened] clears the shortcut so it is handled exactly once: the pager disposes this + * screen when it is more than a page away, and without this the sheet would reopen on every + * return to the tab. + */ + LaunchedEffect(openAddSheet) { + if (openAddSheet == null) return@LaunchedEffect + editItem = null + showAddSheet = true + onAddSheetOpened() + } + LaunchedEffect(Unit) { viewModel.effects.collect { effect -> when (effect) { diff --git a/composeApp/src/main/kotlin/com/github/worn/ui/util/ShortcutCommand.kt b/composeApp/src/main/kotlin/com/github/worn/ui/util/ShortcutCommand.kt new file mode 100644 index 0000000..d55f926 --- /dev/null +++ b/composeApp/src/main/kotlin/com/github/worn/ui/util/ShortcutCommand.kt @@ -0,0 +1,19 @@ +package com.github.worn.ui.util + +import com.github.worn.domain.model.AppShortcut + +private const val ACTION_PREFIX = "com.github.worn.action." + +/** + * One launcher shortcut tap, waiting to be routed. + * + * Deliberately not a data class: identity equality makes every tap a distinct value, so + * `LaunchedEffect` re-fires even when the same shortcut is tapped twice in a row. + */ +class ShortcutCommand(val shortcut: AppShortcut) + +/** The intent action declared for this shortcut in `res/xml/shortcuts.xml`. */ +fun AppShortcut.action(): String = ACTION_PREFIX + id + +fun shortcutCommandFor(action: String?): ShortcutCommand? = + AppShortcut.entries.firstOrNull { it.action() == action }?.let(::ShortcutCommand) diff --git a/composeApp/src/main/res/drawable/ic_shortcut_add_item.xml b/composeApp/src/main/res/drawable/ic_shortcut_add_item.xml new file mode 100644 index 0000000..bb1a6fa --- /dev/null +++ b/composeApp/src/main/res/drawable/ic_shortcut_add_item.xml @@ -0,0 +1,14 @@ + + + + diff --git a/composeApp/src/main/res/drawable/ic_shortcut_try_it.xml b/composeApp/src/main/res/drawable/ic_shortcut_try_it.xml new file mode 100644 index 0000000..ff92e9d --- /dev/null +++ b/composeApp/src/main/res/drawable/ic_shortcut_try_it.xml @@ -0,0 +1,14 @@ + + + + diff --git a/composeApp/src/main/res/values-pt-rBR/strings.xml b/composeApp/src/main/res/values-pt-rBR/strings.xml index 217fe12..83d8e8e 100644 --- a/composeApp/src/main/res/values-pt-rBR/strings.xml +++ b/composeApp/src/main/res/values-pt-rBR/strings.xml @@ -165,6 +165,12 @@ Ver em mim Não foi possível abrir essa foto + + Adicionar + Adicionar ao armário + Testar + Experimentar uma peça + Desbloquear funções de IA Adicione sua chave da API Claude nos Ajustes para habilitar. diff --git a/composeApp/src/main/res/values/strings.xml b/composeApp/src/main/res/values/strings.xml index 1866fdb..7915685 100644 --- a/composeApp/src/main/res/values/strings.xml +++ b/composeApp/src/main/res/values/strings.xml @@ -165,6 +165,12 @@ See it on me Couldn\'t open that photo + + Add item + Add to wardrobe + Try it + Try on an item + Unlock AI features Add your Claude API key in Settings to enable this. diff --git a/composeApp/src/main/res/xml/shortcuts.xml b/composeApp/src/main/res/xml/shortcuts.xml new file mode 100644 index 0000000..04dd21e --- /dev/null +++ b/composeApp/src/main/res/xml/shortcuts.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + From 8febcf21ac23e38553bd6ec1c1ac6347e62faaba Mon Sep 17 00:00:00 2001 From: Joao Victor Sena Date: Mon, 27 Jul 2026 18:13:53 -0300 Subject: [PATCH 3/4] feat: add Add item and Try it quick actions on iOS Registered at launch rather than declared in Info.plist so the titles come from Localizable.strings like every other user-facing string and the icons are SF Symbols like the rest of the app. They appear after the first launch, not at install, which is the accepted cost. SwiftUI has no hook for performActionFor, so a minimal app/scene delegate pair is attached. Both delivery paths are covered: willConnectTo on a cold launch and windowScene(_:performActionFor:) while running. A tap is marked handled by id instead of cleared on the inbox, because the @Published replay can land mid-update and SwiftUI forbids publishing changes from there. WardrobeScreen uses task(id:) rather than onChange so a command already set when the view first appears is not missed. Co-Authored-By: Claude Opus 5 (1M context) --- iosApp/iosApp/Screens/WardrobeScreen.swift | 10 +++ iosApp/iosApp/Services/QuickActions.swift | 87 +++++++++++++++++++ iosApp/iosApp/en.lproj/Localizable.strings | 6 ++ iosApp/iosApp/iOSApp.swift | 33 ++++++- iosApp/iosApp/pt-BR.lproj/Localizable.strings | 6 ++ 5 files changed, 141 insertions(+), 1 deletion(-) create mode 100644 iosApp/iosApp/Services/QuickActions.swift diff --git a/iosApp/iosApp/Screens/WardrobeScreen.swift b/iosApp/iosApp/Screens/WardrobeScreen.swift index 2790caa..df34c9f 100644 --- a/iosApp/iosApp/Screens/WardrobeScreen.swift +++ b/iosApp/iosApp/Screens/WardrobeScreen.swift @@ -8,6 +8,8 @@ struct WardrobeScreen: View { @State private var detailItem: ClothingItem? @State private var editItem: ClothingItem? var onTabSelected: (WornTab) -> Void = { _ in } + var openAddSheet: ShortcutCommand? + var onAddSheetOpened: () -> Void = {} var body: some View { WardrobeContent( @@ -62,6 +64,14 @@ struct WardrobeScreen: View { } ) } + // task(id:), not onChange: this also runs on first appearance, so a quick action that + // cold-launched the app is not missed. onAddSheetOpened clears it so it fires exactly once. + .task(id: openAddSheet) { + guard openAddSheet != nil else { return } + editItem = nil + showAddSheet = true + onAddSheetOpened() + } } } diff --git a/iosApp/iosApp/Services/QuickActions.swift b/iosApp/iosApp/Services/QuickActions.swift new file mode 100644 index 0000000..48fff55 --- /dev/null +++ b/iosApp/iosApp/Services/QuickActions.swift @@ -0,0 +1,87 @@ +import Shared +import SwiftUI + +/// One Home Screen quick-action tap, waiting to be routed. +/// +/// The `id` makes every tap distinct, so tapping the same action twice in a row still reads as a +/// change — the same reason `SharedPhoto` carries one. +struct ShortcutCommand: Equatable { + let id = UUID() + let shortcut: AppShortcut +} + +/// Receives quick-action taps from `SceneDelegate` and hands them to SwiftUI. +/// +/// SwiftUI has no hook for `performActionFor`, so the delegates below are the only way in. +@MainActor +final class QuickActionInbox: ObservableObject { + static let shared = QuickActionInbox() + + @Published var pending: ShortcutCommand? + + private init() {} + + func receive(_ item: UIApplicationShortcutItem) { + guard let shortcut = AppShortcut.companion.fromId(id: item.type) else { return } + pending = ShortcutCommand(shortcut: shortcut) + } + + /// Registered at launch rather than declared in `Info.plist` so the titles come from + /// `Localizable.strings` like every other user-facing string, and the icons are SF Symbols like + /// the rest of the app. The trade-off: the actions appear after the first launch, not at install. + static func register() { + UIApplication.shared.shortcutItems = [ + UIApplicationShortcutItem( + type: AppShortcut.addItem.id, + localizedTitle: String(localized: "shortcut_add_item_short"), + localizedSubtitle: String(localized: "shortcut_add_item_long"), + icon: UIApplicationShortcutIcon(systemImageName: "plus") + ), + UIApplicationShortcutItem( + type: AppShortcut.tryIt.id, + localizedTitle: String(localized: "shortcut_try_it_short"), + localizedSubtitle: String(localized: "shortcut_try_it_long"), + icon: UIApplicationShortcutIcon(systemImageName: "viewfinder") + ), + ] + } +} + +/// Exists only to attach `SceneDelegate`; SwiftUI still owns the window. +final class AppDelegate: NSObject, UIApplicationDelegate { + func application( + _ application: UIApplication, + configurationForConnecting connectingSceneSession: UISceneSession, + options: UIScene.ConnectionOptions + ) -> UISceneConfiguration { + let configuration = UISceneConfiguration( + name: connectingSceneSession.configuration.name, + sessionRole: connectingSceneSession.role + ) + configuration.delegateClass = SceneDelegate.self + return configuration + } +} + +final class SceneDelegate: NSObject, UIWindowSceneDelegate { + + /// Cold launch: the tap arrives here, not in `performActionFor`. + func scene( + _ scene: UIScene, + willConnectTo session: UISceneSession, + options connectionOptions: UIScene.ConnectionOptions + ) { + guard let item = connectionOptions.shortcutItem else { return } + QuickActionInbox.shared.receive(item) + } + + /// The app was already running. + func windowScene( + _ windowScene: UIWindowScene, + performActionFor shortcutItem: UIApplicationShortcutItem, + completionHandler: @escaping (Bool) -> Void + ) { + QuickActionInbox.shared.receive(shortcutItem) + completionHandler(true) + } +} diff --git a/iosApp/iosApp/en.lproj/Localizable.strings b/iosApp/iosApp/en.lproj/Localizable.strings index c4e78c5..dd79397 100644 --- a/iosApp/iosApp/en.lproj/Localizable.strings +++ b/iosApp/iosApp/en.lproj/Localizable.strings @@ -162,6 +162,12 @@ "share_choose_try_on" = "See it on me"; "share_photo_read_failed" = "Couldn't open that photo"; +/* App Shortcuts */ +"shortcut_add_item_short" = "Add item"; +"shortcut_add_item_long" = "Add to wardrobe"; +"shortcut_try_it_short" = "Try it"; +"shortcut_try_it_long" = "Try on an item"; + /* AI Locked Sheet */ "ai_locked_title" = "Unlock AI features"; "ai_locked_description" = "Add your Claude API key in Settings to enable this."; diff --git a/iosApp/iosApp/iOSApp.swift b/iosApp/iosApp/iOSApp.swift index f104a04..3b130db 100644 --- a/iosApp/iosApp/iOSApp.swift +++ b/iosApp/iosApp/iOSApp.swift @@ -3,19 +3,28 @@ import Shared @main struct iOSApp: App { + @UIApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate @Environment(\.scenePhase) private var scenePhase + @StateObject private var quickActions = QuickActionInbox.shared @State private var activeTab: WornTab = .wardrobe @State private var sharedPhoto: SharedPhoto? + @State private var openAddSheet: ShortcutCommand? + @State private var handledShortcutId: UUID? init() { KoinHelperKt.initKoin() + QuickActionInbox.register() } var body: some Scene { WindowGroup { VStack(spacing: 0) { TabView(selection: $activeTab) { - WardrobeScreen(onTabSelected: selectTab) + WardrobeScreen( + onTabSelected: selectTab, + openAddSheet: openAddSheet, + onAddSheetOpened: { openAddSheet = nil } + ) .tag(WornTab.wardrobe) OutfitsScreen(onTabSelected: selectTab) .tag(WornTab.outfits) @@ -35,6 +44,28 @@ struct iOSApp: App { // Covers the case where the extension wrote the file but could not launch us. if phase == .active { receiveSharedPhoto() } } + // onReceive, not onChange: on a cold launch the scene delegate fills the inbox before + // this view installs its observers, and @Published replays its current value to a new + // subscriber whereas onChange would only see later ones. + // + // The tap is marked handled here rather than cleared on the inbox, because that replay + // can land mid-update and SwiftUI forbids publishing changes from there. Every tap + // carries a fresh id, so a repeat of the same action still gets through. + .onReceive(quickActions.$pending) { command in + guard let command, command.id != handledShortcutId else { return } + handledShortcutId = command.id + perform(command) + } + } + } + + private func perform(_ command: ShortcutCommand) { + // A Kotlin enum bridges to Swift as a class, so it is compared rather than switched on. + if command.shortcut == .addItem { + selectTab(.wardrobe) + openAddSheet = command + } else if command.shortcut == .tryIt { + selectTab(.tryIt) } } diff --git a/iosApp/iosApp/pt-BR.lproj/Localizable.strings b/iosApp/iosApp/pt-BR.lproj/Localizable.strings index f5891ac..ee52155 100644 --- a/iosApp/iosApp/pt-BR.lproj/Localizable.strings +++ b/iosApp/iosApp/pt-BR.lproj/Localizable.strings @@ -162,6 +162,12 @@ "share_choose_try_on" = "Ver em mim"; "share_photo_read_failed" = "Não foi possível abrir essa foto"; +/* App Shortcuts */ +"shortcut_add_item_short" = "Adicionar"; +"shortcut_add_item_long" = "Adicionar ao armário"; +"shortcut_try_it_short" = "Testar"; +"shortcut_try_it_long" = "Experimentar uma peça"; + /* AI Locked Sheet */ "ai_locked_title" = "Desbloquear funções de IA"; "ai_locked_description" = "Adicione sua chave da API Claude nos Ajustes para habilitar."; From 65a6608d116c0cf28859153ec921c9e7a2ec2137 Mon Sep 17 00:00:00 2001 From: Joao Victor Sena Date: Mon, 27 Jul 2026 18:29:26 -0300 Subject: [PATCH 4/4] style: make the shortcut icons adaptive so the glyphs can be white A plain drawable is scaled onto a white badge by the launcher, which is why the glyphs had to carry dark ink. Going adaptive means the shortcut owns its background, so a white glyph stays legible whatever the launcher's theme, and a pinned shortcut matches the app icon instead of sitting on a white disc. The background reuses the launcher icon's green for exactly that reason. The monochrome layer opts into themed icons on Android 13+. Co-Authored-By: Claude Opus 5 (1M context) --- .../res/drawable/ic_shortcut_add_item.xml | 25 ++++++++----------- .../ic_shortcut_add_item_foreground.xml | 23 +++++++++++++++++ .../main/res/drawable/ic_shortcut_try_it.xml | 25 ++++++++----------- .../ic_shortcut_try_it_foreground.xml | 23 +++++++++++++++++ 4 files changed, 68 insertions(+), 28 deletions(-) create mode 100644 composeApp/src/main/res/drawable/ic_shortcut_add_item_foreground.xml create mode 100644 composeApp/src/main/res/drawable/ic_shortcut_try_it_foreground.xml diff --git a/composeApp/src/main/res/drawable/ic_shortcut_add_item.xml b/composeApp/src/main/res/drawable/ic_shortcut_add_item.xml index bb1a6fa..09413f8 100644 --- a/composeApp/src/main/res/drawable/ic_shortcut_add_item.xml +++ b/composeApp/src/main/res/drawable/ic_shortcut_add_item.xml @@ -1,14 +1,11 @@ - - - - + + + + + + + diff --git a/composeApp/src/main/res/drawable/ic_shortcut_add_item_foreground.xml b/composeApp/src/main/res/drawable/ic_shortcut_add_item_foreground.xml new file mode 100644 index 0000000..c70eacd --- /dev/null +++ b/composeApp/src/main/res/drawable/ic_shortcut_add_item_foreground.xml @@ -0,0 +1,23 @@ + + + + + + diff --git a/composeApp/src/main/res/drawable/ic_shortcut_try_it.xml b/composeApp/src/main/res/drawable/ic_shortcut_try_it.xml index ff92e9d..ffd20f0 100644 --- a/composeApp/src/main/res/drawable/ic_shortcut_try_it.xml +++ b/composeApp/src/main/res/drawable/ic_shortcut_try_it.xml @@ -1,14 +1,11 @@ - - - - + + + + + + + diff --git a/composeApp/src/main/res/drawable/ic_shortcut_try_it_foreground.xml b/composeApp/src/main/res/drawable/ic_shortcut_try_it_foreground.xml new file mode 100644 index 0000000..94f4731 --- /dev/null +++ b/composeApp/src/main/res/drawable/ic_shortcut_try_it_foreground.xml @@ -0,0 +1,23 @@ + + + + + +