fix: Liquid Glass month title updates via @Observable store (visibleMonth)
The system NavigationStack toolbar title would not refresh on a plain @State change (title kept disappearing on iPhone). Moved visibleMonth into the @Observable CalendarStore so the toolbar's read is tracked with @Observable's fine-grained observation and refreshes on month change. Reverted the @State/.id workaround. Real system glass bar retained. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -51,6 +51,10 @@ class CalendarStore {
|
|||||||
var events: [CalEvent] = []
|
var events: [CalEvent] = []
|
||||||
var viewType: CalViewType = .month
|
var viewType: CalViewType = .month
|
||||||
var currentDate: Date = .now
|
var currentDate: Date = .now
|
||||||
|
// The month currently scrolled into view (month view). Lives in the store so
|
||||||
|
// the Liquid-Glass navigation title — read in the system toolbar — updates
|
||||||
|
// via @Observable tracking (a plain @State did not refresh the toolbar).
|
||||||
|
var visibleMonth: Date = .now
|
||||||
var isLoading = false
|
var isLoading = false
|
||||||
var isCachingBackground = false
|
var isCachingBackground = false
|
||||||
var lastError: String? = nil
|
var lastError: String? = nil
|
||||||
|
|||||||
@@ -27,21 +27,16 @@ struct CalendarHostView: View {
|
|||||||
@State private var store = CalendarStore()
|
@State private var store = CalendarStore()
|
||||||
@State private var editorContext: CalEditorContext? = nil
|
@State private var editorContext: CalEditorContext? = nil
|
||||||
@State private var selectedEvent: CalEvent? = nil
|
@State private var selectedEvent: CalEvent? = nil
|
||||||
@State private var visibleMonth: Date = .now
|
|
||||||
@State private var showFilter = false
|
@State private var showFilter = false
|
||||||
@State private var didApplyDefaultView = false
|
@State private var didApplyDefaultView = false
|
||||||
@State private var groups: [CalGroup] = []
|
@State private var groups: [CalGroup] = []
|
||||||
// Drives the Liquid-Glass navigation-bar title. Kept in @State and updated
|
|
||||||
// via onChange so the system toolbar reliably refreshes on month change
|
|
||||||
// (reading the computed title directly in the toolbar did not update).
|
|
||||||
@State private var navTitle = ""
|
|
||||||
|
|
||||||
private var titleString: String {
|
private var titleString: String {
|
||||||
if store.viewType == .month {
|
if store.viewType == .month {
|
||||||
let f = DateFormatter()
|
let f = DateFormatter()
|
||||||
f.locale = L10n.locale(appLang)
|
f.locale = L10n.locale(appLang)
|
||||||
f.dateFormat = "LLLL yyyy"
|
f.dateFormat = "LLLL yyyy"
|
||||||
return f.string(from: visibleMonth).capitalized(with: L10n.locale(appLang))
|
return f.string(from: store.visibleMonth).capitalized(with: L10n.locale(appLang))
|
||||||
}
|
}
|
||||||
return store.titleForCurrentView(language: appLang)
|
return store.titleForCurrentView(language: appLang)
|
||||||
}
|
}
|
||||||
@@ -89,7 +84,7 @@ struct CalendarHostView: View {
|
|||||||
.onChange(of: store.currentDate) { _, _ in Task { await onNavigate() } }
|
.onChange(of: store.currentDate) { _, _ in Task { await onNavigate() } }
|
||||||
.onChange(of: store.viewType) { _, _ in Task { await onNavigate() } }
|
.onChange(of: store.viewType) { _, _ in Task { await onNavigate() } }
|
||||||
.onChange(of: cacheMonths) { _, _ in Task { await recache() } }
|
.onChange(of: cacheMonths) { _, _ in Task { await recache() } }
|
||||||
.onChange(of: visibleMonth) { _, new in Task { await ensureLoaded(around: new) } }
|
.onChange(of: store.visibleMonth) { _, new in Task { await ensureLoaded(around: new) } }
|
||||||
.onChange(of: scenePhase) { _, phase in if phase == .active { Task { await SettingsSync.pull(api: api) } } }
|
.onChange(of: scenePhase) { _, phase in if phase == .active { Task { await SettingsSync.pull(api: api) } } }
|
||||||
.onReceive(NotificationCenter.default.publisher(for: .banishedCalendarsChanged)) { _ in
|
.onReceive(NotificationCenter.default.publisher(for: .banishedCalendarsChanged)) { _ in
|
||||||
store.syncBanishedFromDefaults()
|
store.syncBanishedFromDefaults()
|
||||||
@@ -130,11 +125,12 @@ struct CalendarHostView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
ToolbarItem(placement: .principal) {
|
ToolbarItem(placement: .principal) {
|
||||||
Text(navTitle)
|
// Reads store.visibleMonth (@Observable) so the system
|
||||||
|
// toolbar refreshes on month change.
|
||||||
|
Text(titleString)
|
||||||
.font(.headline)
|
.font(.headline)
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
.minimumScaleFactor(0.7)
|
.minimumScaleFactor(0.7)
|
||||||
.id(navTitle) // force re-creation so the bar refreshes
|
|
||||||
}
|
}
|
||||||
ToolbarItem(placement: .navigationBarTrailing) {
|
ToolbarItem(placement: .navigationBarTrailing) {
|
||||||
HStack(spacing: 8) {
|
HStack(spacing: 8) {
|
||||||
@@ -150,12 +146,10 @@ struct CalendarHostView: View {
|
|||||||
.overlay(alignment: .bottomTrailing) { glassFAB }
|
.overlay(alignment: .bottomTrailing) { glassFAB }
|
||||||
.modifier(calendarSheets)
|
.modifier(calendarSheets)
|
||||||
.task { await startup() }
|
.task { await startup() }
|
||||||
.task { navTitle = titleString }
|
|
||||||
.onChange(of: titleString) { _, new in navTitle = new }
|
|
||||||
.onChange(of: store.currentDate) { _, _ in Task { await onNavigate() } }
|
.onChange(of: store.currentDate) { _, _ in Task { await onNavigate() } }
|
||||||
.onChange(of: store.viewType) { _, _ in Task { await onNavigate() } }
|
.onChange(of: store.viewType) { _, _ in Task { await onNavigate() } }
|
||||||
.onChange(of: cacheMonths) { _, _ in Task { await recache() } }
|
.onChange(of: cacheMonths) { _, _ in Task { await recache() } }
|
||||||
.onChange(of: visibleMonth) { _, new in Task { await ensureLoaded(around: new) } }
|
.onChange(of: store.visibleMonth) { _, new in Task { await ensureLoaded(around: new) } }
|
||||||
.onChange(of: scenePhase) { _, phase in if phase == .active { Task { await SettingsSync.pull(api: api) } } }
|
.onChange(of: scenePhase) { _, phase in if phase == .active { Task { await SettingsSync.pull(api: api) } } }
|
||||||
.onReceive(NotificationCenter.default.publisher(for: .banishedCalendarsChanged)) { _ in
|
.onReceive(NotificationCenter.default.publisher(for: .banishedCalendarsChanged)) { _ in
|
||||||
store.syncBanishedFromDefaults()
|
store.syncBanishedFromDefaults()
|
||||||
@@ -332,8 +326,7 @@ struct CalendarHostView: View {
|
|||||||
onShowDay: { day in
|
onShowDay: { day in
|
||||||
store.currentDate = day
|
store.currentDate = day
|
||||||
store.viewType = .day
|
store.viewType = .day
|
||||||
},
|
})
|
||||||
visibleMonth: $visibleMonth)
|
|
||||||
case .week:
|
case .week:
|
||||||
WeekView(store: store,
|
WeekView(store: store,
|
||||||
onEventTap: { selectedEvent = $0 },
|
onEventTap: { selectedEvent = $0 },
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ struct MonthView: View {
|
|||||||
let onCreateEvent: (Date) -> Void
|
let onCreateEvent: (Date) -> Void
|
||||||
let onShowWeek: (Date) -> Void
|
let onShowWeek: (Date) -> Void
|
||||||
let onShowDay: (Date) -> Void
|
let onShowDay: (Date) -> Void
|
||||||
@Binding var visibleMonth: Date
|
|
||||||
|
|
||||||
@AppStorage("appLanguage") private var appLang = "system"
|
@AppStorage("appLanguage") private var appLang = "system"
|
||||||
@AppStorage("monthDividerColor") private var dividerHex = "#7090c0"
|
@AppStorage("monthDividerColor") private var dividerHex = "#7090c0"
|
||||||
@@ -117,8 +116,8 @@ struct MonthView: View {
|
|||||||
private func publishVisibleMonth(from week: Date?) {
|
private func publishVisibleMonth(from week: Date?) {
|
||||||
guard let w = week else { return }
|
guard let w = week else { return }
|
||||||
let month = cal.date(from: cal.dateComponents([.year, .month], from: w)) ?? w
|
let month = cal.date(from: cal.dateComponents([.year, .month], from: w)) ?? w
|
||||||
if visibleMonth != month {
|
if store.visibleMonth != month {
|
||||||
visibleMonth = month
|
store.visibleMonth = month
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user