fix: widget iOS 17 deployment target + register FloatingTabState

- Widget extension: 16.0 → 17.0 (containerBackground/widget API require 17)
- xcodegen re-run picks up FloatingTabState.swift (was on disk but
  not registered in project.pbxproj after previous xcodegen run)
This commit is contained in:
kutesir
2026-05-28 20:35:44 +03:00
parent 5e232f2d81
commit dc99a3f1f0
9 changed files with 74 additions and 8 deletions

View File

@@ -26,6 +26,7 @@
BEAFF968632A34C70B11C5AC /* MatrixView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D44530A77DF12A17E52AAF34 /* MatrixView.swift */; }; BEAFF968632A34C70B11C5AC /* MatrixView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D44530A77DF12A17E52AAF34 /* MatrixView.swift */; };
C1B3FC29820FCAF069D2C6FE /* KisaniCalWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 825C519927190A5CDB3CD4A8 /* KisaniCalWidget.swift */; }; C1B3FC29820FCAF069D2C6FE /* KisaniCalWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 825C519927190A5CDB3CD4A8 /* KisaniCalWidget.swift */; };
C79C33BE2802E81AA00175CC /* TodayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C786EBC7DF879D64EB28165E /* TodayView.swift */; }; C79C33BE2802E81AA00175CC /* TodayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C786EBC7DF879D64EB28165E /* TodayView.swift */; };
CBE7295BF5ADE08FE93AFAAF /* FloatingTabState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9100804DB1E61EA882CC54DA /* FloatingTabState.swift */; };
CF130E143FC6FFE2FE853FB6 /* WidgetDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3364C849866EAAA5FEB46A1 /* WidgetDataStore.swift */; }; CF130E143FC6FFE2FE853FB6 /* WidgetDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3364C849866EAAA5FEB46A1 /* WidgetDataStore.swift */; };
D591A72235A53D4038FBC2B4 /* KisaniCalApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 001F67ADC72FBAEC03EB7E01 /* KisaniCalApp.swift */; }; D591A72235A53D4038FBC2B4 /* KisaniCalApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 001F67ADC72FBAEC03EB7E01 /* KisaniCalApp.swift */; };
EF03EC9F974B14D100DD5528 /* NotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93D045FE3DEB1D22D908A29F /* NotificationManager.swift */; }; EF03EC9F974B14D100DD5528 /* NotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93D045FE3DEB1D22D908A29F /* NotificationManager.swift */; };
@@ -70,6 +71,7 @@
768D4E314252E2A90A06CCF2 /* AddExerciseSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddExerciseSheet.swift; sourceTree = "<group>"; }; 768D4E314252E2A90A06CCF2 /* AddExerciseSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddExerciseSheet.swift; sourceTree = "<group>"; };
825C519927190A5CDB3CD4A8 /* KisaniCalWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KisaniCalWidget.swift; sourceTree = "<group>"; }; 825C519927190A5CDB3CD4A8 /* KisaniCalWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KisaniCalWidget.swift; sourceTree = "<group>"; };
8934E4F21B90C9650F71D0A2 /* WidgetBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetBridge.swift; sourceTree = "<group>"; }; 8934E4F21B90C9650F71D0A2 /* WidgetBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetBridge.swift; sourceTree = "<group>"; };
9100804DB1E61EA882CC54DA /* FloatingTabState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingTabState.swift; sourceTree = "<group>"; };
93D045FE3DEB1D22D908A29F /* NotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationManager.swift; sourceTree = "<group>"; }; 93D045FE3DEB1D22D908A29F /* NotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationManager.swift; sourceTree = "<group>"; };
9BD1F84B4F45B12CB9B9F1B2 /* KisaniCal.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = KisaniCal.app; sourceTree = BUILT_PRODUCTS_DIR; }; 9BD1F84B4F45B12CB9B9F1B2 /* KisaniCal.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = KisaniCal.app; sourceTree = BUILT_PRODUCTS_DIR; };
A3364C849866EAAA5FEB46A1 /* WidgetDataStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetDataStore.swift; sourceTree = "<group>"; }; A3364C849866EAAA5FEB46A1 /* WidgetDataStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetDataStore.swift; sourceTree = "<group>"; };
@@ -95,6 +97,7 @@
21B93C269F283F11B415B18C /* Components */ = { 21B93C269F283F11B415B18C /* Components */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
9100804DB1E61EA882CC54DA /* FloatingTabState.swift */,
23A4491BFA50721082024756 /* SharedComponents.swift */, 23A4491BFA50721082024756 /* SharedComponents.swift */,
); );
path = Components; path = Components;
@@ -292,6 +295,7 @@
13E4B9854F595394FC9D5912 /* ContentView.swift in Sources */, 13E4B9854F595394FC9D5912 /* ContentView.swift in Sources */,
A9FF93259AE8FF0ABF69D71A /* DesignTokens.swift in Sources */, A9FF93259AE8FF0ABF69D71A /* DesignTokens.swift in Sources */,
1A22EE21460821170E44B1DF /* ExerciseModels.swift in Sources */, 1A22EE21460821170E44B1DF /* ExerciseModels.swift in Sources */,
CBE7295BF5ADE08FE93AFAAF /* FloatingTabState.swift in Sources */,
D591A72235A53D4038FBC2B4 /* KisaniCalApp.swift in Sources */, D591A72235A53D4038FBC2B4 /* KisaniCalApp.swift in Sources */,
BEAFF968632A34C70B11C5AC /* MatrixView.swift in Sources */, BEAFF968632A34C70B11C5AC /* MatrixView.swift in Sources */,
EF03EC9F974B14D100DD5528 /* NotificationManager.swift in Sources */, EF03EC9F974B14D100DD5528 /* NotificationManager.swift in Sources */,
@@ -420,7 +424,7 @@
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_NSExtensionPointIdentifier = "com.apple.widgetkit-extension"; INFOPLIST_KEY_NSExtensionPointIdentifier = "com.apple.widgetkit-extension";
IPHONEOS_DEPLOYMENT_TARGET = 16.0; IPHONEOS_DEPLOYMENT_TARGET = 17.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.kutesir.KisaniCal.widget; PRODUCT_BUNDLE_IDENTIFIER = com.kutesir.KisaniCal.widget;
SDKROOT = iphoneos; SDKROOT = iphoneos;
@@ -437,7 +441,7 @@
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_NSExtensionPointIdentifier = "com.apple.widgetkit-extension"; INFOPLIST_KEY_NSExtensionPointIdentifier = "com.apple.widgetkit-extension";
IPHONEOS_DEPLOYMENT_TARGET = 16.0; IPHONEOS_DEPLOYMENT_TARGET = 17.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.kutesir.KisaniCal.widget; PRODUCT_BUNDLE_IDENTIFIER = com.kutesir.KisaniCal.widget;
SDKROOT = iphoneos; SDKROOT = iphoneos;

View File

@@ -0,0 +1,52 @@
import SwiftUI
@MainActor
final class FloatingTabState: ObservableObject {
@Published private(set) var isCollapsed = false
private var lastY: CGFloat = 0
func report(scrollY: CGFloat) {
let delta = scrollY - lastY
guard abs(delta) > 1.5 else { return }
let shouldCollapse = delta < 0 // content Y decreases user scrolling down
lastY = scrollY
guard shouldCollapse != isCollapsed else { return }
withAnimation(KisaniSpring.snappy) {
isCollapsed = shouldCollapse
}
}
func resetOffset() {
lastY = 0
}
}
// MARK: Scroll tracker
private struct ScrollYKey: PreferenceKey {
static let defaultValue: CGFloat = 0
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) { value = nextValue() }
}
private struct TabBarScrollModifier: ViewModifier {
@EnvironmentObject private var tabState: FloatingTabState
func body(content: Content) -> some View {
content
.background(
GeometryReader { geo in
Color.clear
.preference(key: ScrollYKey.self, value: geo.frame(in: .global).minY)
}
)
.onPreferenceChange(ScrollYKey.self) { y in
tabState.report(scrollY: y)
}
}
}
extension View {
func trackScrollForTabBar() -> some View {
modifier(TabBarScrollModifier())
}
}

View File

@@ -11,6 +11,7 @@ struct ContentView: View {
@AppStorage("hasOnboarded") private var hasOnboarded = false @AppStorage("hasOnboarded") private var hasOnboarded = false
@State private var showOnboarding = false @State private var showOnboarding = false
@State private var selectedTab = 0 @State private var selectedTab = 0
@StateObject private var tabState = FloatingTabState()
@State private var iPadSelection: Int? = 0 @State private var iPadSelection: Int? = 0
@State private var calIconImage: Image = ContentView.buildCalIcon() @State private var calIconImage: Image = ContentView.buildCalIcon()
@@ -135,16 +136,19 @@ struct ContentView: View {
.tabBarInset() .tabBarInset()
} }
.tint(AppColors.accent) .tint(AppColors.accent)
.onChange(of: selectedTab) { _ in tabState.resetOffset() }
KisaniTabBar( KisaniTabBar(
selection: $selectedTab, selection: $selectedTab,
calIcon: calIconImage, calIcon: calIconImage,
showMatrix: showMatrixTab, showMatrix: showMatrixTab,
showWorkout: showWorkoutTab showWorkout: showWorkoutTab,
isCollapsed: tabState.isCollapsed
) )
.padding(.horizontal, 24) .padding(.horizontal, 24)
.padding(.bottom, 8) .padding(.bottom, 8)
} }
.environmentObject(tabState)
.onAppear { UITabBar.appearance().isHidden = true } .onAppear { UITabBar.appearance().isHidden = true }
} }
@@ -191,6 +195,7 @@ struct KisaniTabBar: View {
let calIcon: Image let calIcon: Image
var showMatrix: Bool var showMatrix: Bool
var showWorkout: Bool var showWorkout: Bool
var isCollapsed: Bool = false
@Environment(\.colorScheme) private var cs @Environment(\.colorScheme) private var cs
private struct Entry: Identifiable { private struct Entry: Identifiable {
@@ -224,15 +229,15 @@ struct KisaniTabBar: View {
} }
if let img = entry.custom { if let img = entry.custom {
img img
.font(.system(size: 20)) .font(.system(size: isCollapsed ? 17 : 20))
.foregroundColor(selection == entry.id ? AppColors.accent : AppColors.text(cs)) .foregroundColor(selection == entry.id ? AppColors.accent : AppColors.text(cs))
} else if let icon = entry.sfIcon { } else if let icon = entry.sfIcon {
Image(systemName: icon) Image(systemName: icon)
.font(.system(size: 17, weight: selection == entry.id ? .semibold : .regular)) .font(.system(size: isCollapsed ? 15 : 17, weight: selection == entry.id ? .semibold : .regular))
.foregroundColor(selection == entry.id ? AppColors.accent : AppColors.text(cs)) .foregroundColor(selection == entry.id ? AppColors.accent : AppColors.text(cs))
} }
} }
.frame(maxWidth: .infinity).frame(height: 50) .frame(maxWidth: .infinity).frame(height: isCollapsed ? 38 : 50)
} }
.buttonStyle(PressButtonStyle(scale: 0.88)) .buttonStyle(PressButtonStyle(scale: 0.88))
} }
@@ -241,8 +246,9 @@ struct KisaniTabBar: View {
.background( .background(
RoundedRectangle(cornerRadius: 28) RoundedRectangle(cornerRadius: 28)
.fill(AppColors.surface(cs)) .fill(AppColors.surface(cs))
.shadow(color: .black.opacity(0.10), radius: 20, x: 0, y: 6) .shadow(color: .black.opacity(isCollapsed ? 0.06 : 0.10), radius: 20, x: 0, y: 6)
.shadow(color: .black.opacity(0.04), radius: 4, x: 0, y: 1) .shadow(color: .black.opacity(0.04), radius: 4, x: 0, y: 1)
) )
.animation(KisaniSpring.snappy, value: isCollapsed)
} }
} }

View File

@@ -128,6 +128,7 @@ struct QuadrantCard: View {
// Scrollable task list // Scrollable task list
ScrollView(showsIndicators: false) { ScrollView(showsIndicators: false) {
VStack(alignment: .leading, spacing: 0) { VStack(alignment: .leading, spacing: 0) {
Color.clear.frame(height: 0).trackScrollForTabBar()
ForEach(tasks) { task in ForEach(tasks) { task in
HStack(alignment: .top, spacing: 6) { HStack(alignment: .top, spacing: 6) {
RoundedRectangle(cornerRadius: 3) RoundedRectangle(cornerRadius: 3)

View File

@@ -44,6 +44,7 @@ struct SettingsView: View {
AppColors.background(cs).ignoresSafeArea() AppColors.background(cs).ignoresSafeArea()
ScrollView(showsIndicators: false) { ScrollView(showsIndicators: false) {
VStack(spacing: 0) { VStack(spacing: 0) {
Color.clear.frame(height: 0).trackScrollForTabBar()
Text("Settings") Text("Settings")
.font(AppFonts.sans(15, weight: .semibold)) .font(AppFonts.sans(15, weight: .semibold))

View File

@@ -18,6 +18,7 @@ struct TodayView: View {
ScrollView(showsIndicators: false) { ScrollView(showsIndicators: false) {
VStack(alignment: .leading, spacing: 0) { VStack(alignment: .leading, spacing: 0) {
Color.clear.frame(height: 0).trackScrollForTabBar()
// Header // Header
HStack { HStack {

View File

@@ -710,6 +710,7 @@ struct ManageWorkoutsSheet: View {
ScrollView(showsIndicators: false) { ScrollView(showsIndicators: false) {
VStack(alignment: .leading, spacing: 20) { VStack(alignment: .leading, spacing: 20) {
Color.clear.frame(height: 0).trackScrollForTabBar()
VStack(alignment: .leading, spacing: 8) { VStack(alignment: .leading, spacing: 8) {
Text("PROGRAMS").font(AppFonts.mono(9, weight: .bold)).foregroundColor(AppColors.text3(cs)) Text("PROGRAMS").font(AppFonts.mono(9, weight: .bold)).foregroundColor(AppColors.text3(cs))

View File

@@ -39,7 +39,7 @@ targets:
KisaniCalWidget: KisaniCalWidget:
type: app-extension type: app-extension
platform: iOS platform: iOS
deploymentTarget: "16.0" deploymentTarget: "17.0"
sources: sources:
- path: KisaniCalWidget - path: KisaniCalWidget
- path: Shared - path: Shared