icon: Icon-Composer-App-Icon + macOS-Build grün
- AppIcon.icon (Icon Composer, blaues W) als App-Icon integriert. In project.yml als Target-Source mit type:file → XcodeGen erkennt .icon nativ als wrapper.icon. Alter forest-grüner Platzhalter (AppIcon.appiconset) entfernt. actool baut Icon für iOS + macOS. - macOS-Build repariert (war pre-existing rot seit β-3/β-5/β-6): iOS-only SwiftUI-Modifier mit #if os(iOS) gegated (textInputAutocapitalization, keyboardType, navigationBarDrawer, tabViewBottomAccessory, .buttonStyle(.glass)); .topBarTrailing → cross-platform .primaryAction; .bottomBar-Toolbar gekapselt; iOS-only Extensions mit platformFilter:iOS an den embed-Deps. - Verifiziert: iOS-Sim + macOS BUILD SUCCEEDED. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
fbd758d96e
commit
edc60056ea
15 changed files with 173 additions and 90 deletions
24
AppIcon.icon/Assets/wordeck-logo 2.svg
Normal file
24
AppIcon.icon/Assets/wordeck-logo 2.svg
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
<svg width="1024" height="1024" viewBox="0 0 1024 1024" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M321.374 512V870.102L130.387 512V153.898L321.374 512Z" stroke="url(#paint0_linear_1454_2099)" stroke-width="57.2962"/>
|
||||
<path d="M703.341 512V870.102L512.354 512V153.898L703.341 512Z" stroke="url(#paint1_linear_1454_2099)" stroke-width="57.2962"/>
|
||||
<path d="M703.333 512V870.102L894.32 512V153.898L703.333 512Z" stroke="url(#paint2_linear_1454_2099)" stroke-width="57.2962"/>
|
||||
<path d="M321.366 512V870.102L512.354 512V153.898L321.366 512Z" stroke="url(#paint3_linear_1454_2099)" stroke-width="57.2962"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_1454_2099" x1="225.88" y1="153.898" x2="225.88" y2="870.102" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#29BBFF"/>
|
||||
<stop offset="1" stop-color="#0C597C"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_1454_2099" x1="607.847" y1="153.898" x2="607.847" y2="870.102" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#29BBFF"/>
|
||||
<stop offset="1" stop-color="#0C597C"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint2_linear_1454_2099" x1="798.827" y1="153.898" x2="798.827" y2="870.102" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#29BBFF"/>
|
||||
<stop offset="1" stop-color="#0C597C"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint3_linear_1454_2099" x1="416.86" y1="153.898" x2="416.86" y2="870.102" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#29BBFF"/>
|
||||
<stop offset="1" stop-color="#0C597C"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
47
AppIcon.icon/icon.json
Normal file
47
AppIcon.icon/icon.json
Normal file
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ struct PublicDeckView: View {
|
|||
#endif
|
||||
.toolbar {
|
||||
if detail != nil {
|
||||
ToolbarItem(placement: .topBarTrailing) {
|
||||
ToolbarItem(placement: .primaryAction) {
|
||||
moderationMenu
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 45 KiB |
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue