mirror of
https://github.com/sudosylabs/vnidrop.git
synced 2026-08-05 02:29:55 +02:00
Replaces every stringly-typed localization key in the Apple app with compile-time-checked accessors generated from localization/strings.json. A mistyped key is now a build error instead of a silent fallback to the raw key at runtime. The runtime path is unchanged: plain keys are String.LocalizationValue constants resolved with String(localized:) and Apple's String Catalog still does the lookup; keys with arguments become typed, named functions applying args through String(format:). Generator: new renderSwiftAccessors emits apple/VniDrop/Generated/L10n.swift, wired into generate. Renamed generic arg1/arg2 tokens on four keys to semantic names (receiver, transferName, deviceId) and updated their context notes; positional output is unchanged so .xcstrings (bar the 4 comments) and the Android XML regenerate identical. Migration: every key-carrying value flipped to String.LocalizationValue end to end, resolved only at the leaf. Zero key literals and zero LocalizedStringKey remain in app or test code. macOS build passes; iOS test run pending.
67 lines
1.8 KiB
Swift
67 lines
1.8 KiB
Swift
import SwiftUI
|
|
|
|
/// Presents modal content in a native sheet. On phones it uses medium/large
|
|
/// detents with a grabber; on wider layouts the sheet is form-sized. Content is
|
|
/// wrapped in a `NavigationStack` so it gets a native title bar + Close button.
|
|
struct AdaptiveDrawer<DrawerContent: View>: ViewModifier {
|
|
@Binding var isPresented: Bool
|
|
let windowClass: WindowClass
|
|
let onDismiss: () -> Void
|
|
@ViewBuilder let drawerContent: () -> DrawerContent
|
|
|
|
func body(content: Content) -> some View {
|
|
content.sheet(
|
|
isPresented: Binding(get: { isPresented }, set: { if !$0 { onDismiss() } })
|
|
) {
|
|
SheetChrome(onClose: onDismiss) { drawerContent() }
|
|
.modifier(PhoneDetents(enabled: windowClass == .phone))
|
|
}
|
|
}
|
|
}
|
|
|
|
private struct PhoneDetents: ViewModifier {
|
|
let enabled: Bool
|
|
func body(content: Content) -> some View {
|
|
if enabled {
|
|
content
|
|
.presentationDetents([.medium, .large])
|
|
.presentationDragIndicator(.visible)
|
|
} else {
|
|
content.frame(minWidth: 460, minHeight: 480)
|
|
}
|
|
}
|
|
}
|
|
|
|
private struct SheetChrome<Content: View>: View {
|
|
let onClose: () -> Void
|
|
@ViewBuilder let content: () -> Content
|
|
|
|
var body: some View {
|
|
NavigationStack {
|
|
ScrollView { content().padding(.top, 4) }
|
|
.toolbar {
|
|
ToolbarItem(placement: .cancellationAction) {
|
|
Button(String(localized: L10n.Button.close), action: onClose)
|
|
}
|
|
}
|
|
#if os(iOS)
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
extension View {
|
|
func adaptiveDrawer<DrawerContent: View>(
|
|
isPresented: Binding<Bool>,
|
|
windowClass: WindowClass,
|
|
onDismiss: @escaping () -> Void,
|
|
@ViewBuilder content: @escaping () -> DrawerContent
|
|
) -> some View {
|
|
modifier(AdaptiveDrawer(
|
|
isPresented: isPresented, windowClass: windowClass,
|
|
onDismiss: onDismiss, drawerContent: content
|
|
))
|
|
}
|
|
}
|