diff --git a/AppIcon.icon/Assets/wordeck-logo 2.svg b/AppIcon.icon/Assets/wordeck-logo 2.svg new file mode 100644 index 0000000..33a6008 --- /dev/null +++ b/AppIcon.icon/Assets/wordeck-logo 2.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AppIcon.icon/icon.json b/AppIcon.icon/icon.json new file mode 100644 index 0000000..4d61967 --- /dev/null +++ b/AppIcon.icon/icon.json @@ -0,0 +1,47 @@ +{ + "fill" : "automatic", + "groups" : [ + { + "layers" : [ + + ], + "shadow" : { + "kind" : "neutral", + "opacity" : 0.5 + }, + "translucency" : { + "enabled" : true, + "value" : 0.5 + } + }, + { + "layers" : [ + { + "image-name" : "wordeck-logo 2.svg", + "name" : "wordeck-logo 2", + "position" : { + "scale" : 0.9, + "translation-in-points" : [ + 0, + 20 + ] + } + } + ], + "shadow" : { + "kind" : "neutral", + "opacity" : 0.5 + }, + "translucency" : { + "enabled" : true, + "value" : 0.5 + } + } + ], + "supported-platforms" : { + "circles" : [ + "watchOS" + ], + "squares" : "shared" + } +} \ No newline at end of file diff --git a/Sources/App/RootView.swift b/Sources/App/RootView.swift index a175a68..8988a34 100644 --- a/Sources/App/RootView.swift +++ b/Sources/App/RootView.swift @@ -167,27 +167,35 @@ private extension View { /// Streifen über der TabBar auf Entdecken/Account). @ViewBuilder func decksCreateAccessory(visible: Bool, onTap: @escaping () -> Void) -> some View { - if #available(iOS 26.0, *), visible { - tabViewBottomAccessory { - DeckCreateAccessoryPill(action: onTap) + // `tabViewBottomAccessory` + `.buttonStyle(.glass)` sind iOS-26-only + // und auf macOS gar nicht verfügbar → ganz ausklammern. + #if os(iOS) + if #available(iOS 26.0, *), visible { + tabViewBottomAccessory { + DeckCreateAccessoryPill(action: onTap) + } + } else { + self } - } else { + #else self - } + #endif } } -@available(iOS 26.0, *) -private struct DeckCreateAccessoryPill: View { - let action: () -> Void +#if os(iOS) + @available(iOS 26.0, *) + private struct DeckCreateAccessoryPill: View { + let action: () -> Void - var body: some View { - Button(action: action) { - Label("Neues Deck", systemImage: "plus") - .font(.subheadline.weight(.semibold)) + var body: some View { + Button(action: action) { + Label("Neues Deck", systemImage: "plus") + .font(.subheadline.weight(.semibold)) + } + .buttonStyle(.glass) + .tint(WordeckTheme.primary) + .accessibilityLabel("Neues Deck erstellen") } - .buttonStyle(.glass) - .tint(WordeckTheme.primary) - .accessibilityLabel("Neues Deck erstellen") } -} +#endif diff --git a/Sources/Features/Decks/DeckListView.swift b/Sources/Features/Decks/DeckListView.swift index 9d289f2..dde1986 100644 --- a/Sources/Features/Decks/DeckListView.swift +++ b/Sources/Features/Decks/DeckListView.swift @@ -56,7 +56,9 @@ struct DeckListView: View { pendingShares = PendingShareStore.readAll() }) } + #if os(iOS) .toolbar { toolbar } + #endif .refreshable { await store?.refresh() } @@ -309,27 +311,30 @@ struct DeckListView: View { .frame(maxWidth: .infinity, maxHeight: .infinity) } - @ToolbarContentBuilder - private var toolbar: some ToolbarContent { - // Auf iOS 26 übernimmt das `.tabViewBottomAccessory` aus RootView die - // „Neues Deck"-Pille. Doppelten „+"-Button im Liquid-Glass-Layout - // vermeiden — bottomBar-Button nur auf iOS < 26 zeigen. - if #unavailable(iOS 26.0) { - ToolbarItemGroup(placement: .bottomBar) { - Button { - authGate.require(reason: "deck-create-toolbar") { - showCreate = true + // `.bottomBar`-Placement existiert nur auf iOS; auf macOS gibt es keine + // Bottom-Toolbar — die „Neues Deck"-Aktion ist dort über das Detail-UI + // erreichbar. Auf iOS 26 übernimmt zudem `.tabViewBottomAccessory` aus + // RootView die Pille, deshalb der `#unavailable(iOS 26.0)`-Gate. + #if os(iOS) + @ToolbarContentBuilder + private var toolbar: some ToolbarContent { + if #unavailable(iOS 26.0) { + ToolbarItemGroup(placement: .bottomBar) { + Button { + authGate.require(reason: "deck-create-toolbar") { + showCreate = true + } + } label: { + Label("Deck hinzufügen", systemImage: "plus") + .labelStyle(.iconOnly) + .foregroundStyle(WordeckTheme.primary) } - } label: { - Label("Deck hinzufügen", systemImage: "plus") - .labelStyle(.iconOnly) - .foregroundStyle(WordeckTheme.primary) + .accessibilityLabel("Deck hinzufügen") + Spacer() } - .accessibilityLabel("Deck hinzufügen") - Spacer() } } - } + #endif } // swiftlint:enable type_body_length diff --git a/Sources/Features/Editor/CSVImportFormSections.swift b/Sources/Features/Editor/CSVImportFormSections.swift index d33a909..32f5a99 100644 --- a/Sources/Features/Editor/CSVImportFormSections.swift +++ b/Sources/Features/Editor/CSVImportFormSections.swift @@ -27,7 +27,9 @@ struct CSVImportFormSections: View { if !rows.isEmpty { Section("Deck-Name") { TextField("Deck-Name", text: $deckName) + #if os(iOS) .textInputAutocapitalization(.sentences) + #endif } Section { diff --git a/Sources/Features/Editor/CardEditorView.swift b/Sources/Features/Editor/CardEditorView.swift index 4fcbdbe..0f99cda 100644 --- a/Sources/Features/Editor/CardEditorView.swift +++ b/Sources/Features/Editor/CardEditorView.swift @@ -140,8 +140,10 @@ struct CardEditorView: View { ) .lineLimit(3 ... 8) .autocorrectionDisabled() - .textInputAutocapitalization(.sentences) - .monospaced() + #if os(iOS) + .textInputAutocapitalization(.sentences) + #endif + .monospaced() } Section { let count = Cloze.subIndexCount(clozeText) diff --git a/Sources/Features/Editor/DeckEditorView.swift b/Sources/Features/Editor/DeckEditorView.swift index 1ae7e70..22757e3 100644 --- a/Sources/Features/Editor/DeckEditorView.swift +++ b/Sources/Features/Editor/DeckEditorView.swift @@ -371,7 +371,9 @@ private struct ManualFormSections: View { var body: some View { Section("Name") { TextField("Deck-Name", text: $name) + #if os(iOS) .textInputAutocapitalization(.sentences) + #endif } Section("Beschreibung") { @@ -450,7 +452,9 @@ private struct AITextFormSections: View { axis: .vertical ) .lineLimit(3 ... 6) - .textInputAutocapitalization(.sentences) + #if os(iOS) + .textInputAutocapitalization(.sentences) + #endif } header: { Text("Thema") } footer: { @@ -484,9 +488,13 @@ private struct AISharedSections: View { Section { TextField("https://…", text: $url) + #if os(iOS) .textInputAutocapitalization(.never) + #endif .autocorrectionDisabled(true) + #if os(iOS) .keyboardType(.URL) + #endif } header: { Text("Zusätzliche URL (optional)") } footer: { diff --git a/Sources/Features/Marketplace/BrowseView.swift b/Sources/Features/Marketplace/BrowseView.swift index 5b6472a..82e4481 100644 --- a/Sources/Features/Marketplace/BrowseView.swift +++ b/Sources/Features/Marketplace/BrowseView.swift @@ -20,21 +20,25 @@ struct BrowseView: View { #if os(iOS) .navigationBarTitleDisplayMode(.inline) #endif - .searchable( - text: $queryText, - placement: .navigationBarDrawer(displayMode: .always), - prompt: "Decks suchen" - ) - .onSubmit(of: .search) { - store?.browseQuery = queryText - Task { await store?.browse() } - } - .task { - if store == nil { - store = MarketplaceStore(auth: auth) - await store?.browse() - } + #if os(iOS) + .searchable( + text: $queryText, + placement: .navigationBarDrawer(displayMode: .always), + prompt: "Decks suchen" + ) + #else + .searchable(text: $queryText, prompt: "Decks suchen") + #endif + .onSubmit(of: .search) { + store?.browseQuery = queryText + Task { await store?.browse() } + } + .task { + if store == nil { + store = MarketplaceStore(auth: auth) + await store?.browse() } + } } @ViewBuilder diff --git a/Sources/Features/Marketplace/MarketplacePublishView.swift b/Sources/Features/Marketplace/MarketplacePublishView.swift index 729d172..911f84a 100644 --- a/Sources/Features/Marketplace/MarketplacePublishView.swift +++ b/Sources/Features/Marketplace/MarketplacePublishView.swift @@ -166,7 +166,9 @@ struct MarketplacePublishView: View { private var authorSection: some View { Section { TextField("Author-Slug (URL)", text: $authorSlug) + #if os(iOS) .textInputAutocapitalization(.never) + #endif .autocorrectionDisabled(true) TextField("Anzeigename", text: $authorDisplayName) TextField("Bio (optional)", text: $authorBio, axis: .vertical) @@ -182,10 +184,14 @@ struct MarketplacePublishView: View { private var deckMetadataSection: some View { Section { TextField("Slug (URL)", text: $slug) + #if os(iOS) .textInputAutocapitalization(.never) + #endif .autocorrectionDisabled(true) TextField("Titel", text: $title) + #if os(iOS) .textInputAutocapitalization(.sentences) + #endif TextField("Beschreibung", text: $deckDescription, axis: .vertical) .lineLimit(2 ... 6) Picker("Sprache", selection: $language) { @@ -230,9 +236,13 @@ struct MarketplacePublishView: View { private var versionSection: some View { Section { TextField("SemVer (z.B. 1.0.0)", text: $semver) + #if os(iOS) .textInputAutocapitalization(.never) + #endif .autocorrectionDisabled(true) + #if os(iOS) .keyboardType(.numbersAndPunctuation) + #endif TextField("Changelog (optional)", text: $changelog, axis: .vertical) .lineLimit(2 ... 4) } header: { diff --git a/Sources/Features/Marketplace/PublicDeckView.swift b/Sources/Features/Marketplace/PublicDeckView.swift index 9cbcc83..9576c8e 100644 --- a/Sources/Features/Marketplace/PublicDeckView.swift +++ b/Sources/Features/Marketplace/PublicDeckView.swift @@ -36,7 +36,7 @@ struct PublicDeckView: View { #endif .toolbar { if detail != nil { - ToolbarItem(placement: .topBarTrailing) { + ToolbarItem(placement: .primaryAction) { moderationMenu } } diff --git a/Sources/Features/Marketplace/ReportDeckSheet.swift b/Sources/Features/Marketplace/ReportDeckSheet.swift index 026c7be..34e6d95 100644 --- a/Sources/Features/Marketplace/ReportDeckSheet.swift +++ b/Sources/Features/Marketplace/ReportDeckSheet.swift @@ -33,7 +33,9 @@ struct ReportDeckSheet: View { Section { TextField("Optional: Details", text: $message, axis: .vertical) .lineLimit(3 ... 6) + #if os(iOS) .textInputAutocapitalization(.sentences) + #endif } header: { Text("Beschreibung") } footer: { diff --git a/Sources/Features/Study/StudySessionView.swift b/Sources/Features/Study/StudySessionView.swift index 7bc299a..269dee7 100644 --- a/Sources/Features/Study/StudySessionView.swift +++ b/Sources/Features/Study/StudySessionView.swift @@ -26,7 +26,7 @@ struct StudySessionView: View { .navigationBarTitleDisplayMode(.inline) #endif .toolbar { - ToolbarItem(placement: .topBarTrailing) { + ToolbarItem(placement: .primaryAction) { if let session, case .studying = session.phase { Text("\(session.remaining)") .font(.subheadline.weight(.semibold)) diff --git a/Sources/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-1024.png b/Sources/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-1024.png deleted file mode 100644 index 76f939e..0000000 Binary files a/Sources/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-1024.png and /dev/null differ diff --git a/Sources/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json b/Sources/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index 59e99f9..0000000 --- a/Sources/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "images" : [ - { - "filename" : "AppIcon-1024.png", - "idiom" : "universal", - "platform" : "ios", - "size" : "1024x1024" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "dark" - } - ], - "filename" : "AppIcon-1024.png", - "idiom" : "universal", - "platform" : "ios", - "size" : "1024x1024" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "tinted" - } - ], - "filename" : "AppIcon-1024.png", - "idiom" : "universal", - "platform" : "ios", - "size" : "1024x1024" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/project.yml b/project.yml index 6aab5ad..059420f 100644 --- a/project.yml +++ b/project.yml @@ -47,14 +47,23 @@ targets: product: ManaEventSync - package: ManaSwiftUI product: ManaAuthUI + # Beide Extensions sind iOS-only (supportedDestinations: [iOS]). + # platformFilter verhindert, dass der macOS-Build versucht, die + # iOS-.appex einzubetten („embedded content built for iOS" Fehler). - target: WordeckWidgetExtension embed: true + platformFilter: iOS - target: WordeckShareExtension embed: true + platformFilter: iOS sources: - path: Sources/App - path: Sources/Features - path: Sources/Core + # Icon Composer App-Icon (Xcode 26). Als einzelne Datei referenziert, + # damit XcodeGen nicht in das .icon-Bundle hineinrekursiert. + - path: AppIcon.icon + type: file - path: Sources/Resources excludes: - "Info.plist"