Event widget: upcoming-queue auto-select + cleaner config & preview
- Add "Upcoming queue" toggle to the Event Countdown widget. When on, it auto-tracks the Nth nearest upcoming event (Nearest / 2nd … 5th, new AutoSelectRank enum). When off, track a picked or fully custom event. - parameterSummary now shows only the relevant fields per mode (toggle + auto-select, or event/name/date), so the date input only appears for custom events. - Replace the dense 380-day placeholder with a ~90-day window so the gallery preview shows a legible dot grid. (The interactive task-checkbox widget already exists as "My Tasks".) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -80,7 +80,7 @@
|
|||||||
0506183945D16EC443A69651 /* LiveActivityManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveActivityManager.swift; sourceTree = "<group>"; };
|
0506183945D16EC443A69651 /* LiveActivityManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveActivityManager.swift; sourceTree = "<group>"; };
|
||||||
0513EB8C7F6B6853B9E93E09 /* TaskLiveActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskLiveActivity.swift; sourceTree = "<group>"; };
|
0513EB8C7F6B6853B9E93E09 /* TaskLiveActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskLiveActivity.swift; sourceTree = "<group>"; };
|
||||||
0C96EC00F6B021DBA3CC1A79 /* ProfileSetupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileSetupView.swift; sourceTree = "<group>"; };
|
0C96EC00F6B021DBA3CC1A79 /* ProfileSetupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileSetupView.swift; sourceTree = "<group>"; };
|
||||||
0D6D6740721F01A74E404EB9 /* KisaniCalWidgets.appex */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = "wrapper.app-extension"; path = KisaniCalWidgets.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
0D6D6740721F01A74E404EB9 /* KisaniCalWidgets.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = KisaniCalWidgets.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
106EEF572C6F8990408329F0 /* OnboardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingView.swift; sourceTree = "<group>"; };
|
106EEF572C6F8990408329F0 /* OnboardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingView.swift; sourceTree = "<group>"; };
|
||||||
1AA7498EFED7692022F3E7E1 /* KisaniCal.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = KisaniCal.entitlements; sourceTree = "<group>"; };
|
1AA7498EFED7692022F3E7E1 /* KisaniCal.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = KisaniCal.entitlements; sourceTree = "<group>"; };
|
||||||
208F82348DEBD9FF7B0DCF17 /* MyTasksWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyTasksWidget.swift; sourceTree = "<group>"; };
|
208F82348DEBD9FF7B0DCF17 /* MyTasksWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyTasksWidget.swift; sourceTree = "<group>"; };
|
||||||
@@ -104,7 +104,7 @@
|
|||||||
9100804DB1E61EA882CC54DA /* FloatingTabState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingTabState.swift; sourceTree = "<group>"; };
|
9100804DB1E61EA882CC54DA /* FloatingTabState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingTabState.swift; sourceTree = "<group>"; };
|
||||||
92824ED40ECD41EFD4F78BEC /* ISSUES.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = ISSUES.md; sourceTree = "<group>"; };
|
92824ED40ECD41EFD4F78BEC /* ISSUES.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = ISSUES.md; 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; explicitFileType = wrapper.application; includeInIndex = 0; path = KisaniCal.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
ADF6CCD95A587E26E30F5712 /* CalendarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarView.swift; sourceTree = "<group>"; };
|
ADF6CCD95A587E26E30F5712 /* CalendarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarView.swift; sourceTree = "<group>"; };
|
||||||
AF905C574F34B4EE51A8D21E /* AppGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppGroup.swift; sourceTree = "<group>"; };
|
AF905C574F34B4EE51A8D21E /* AppGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppGroup.swift; sourceTree = "<group>"; };
|
||||||
BC5E179A9189B0A8C3F856F6 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
|
BC5E179A9189B0A8C3F856F6 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
|
||||||
@@ -289,7 +289,7 @@
|
|||||||
isa = PBXProject;
|
isa = PBXProject;
|
||||||
attributes = {
|
attributes = {
|
||||||
BuildIndependentTargetsInParallel = YES;
|
BuildIndependentTargetsInParallel = YES;
|
||||||
LastUpgradeCheck = 1500;
|
LastUpgradeCheck = 2620;
|
||||||
TargetAttributes = {
|
TargetAttributes = {
|
||||||
28B43516AD88946E21D9BFE0 = {
|
28B43516AD88946E21D9BFE0 = {
|
||||||
DevelopmentTeam = K8BLMMR883;
|
DevelopmentTeam = K8BLMMR883;
|
||||||
@@ -405,6 +405,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = KisaniCal/KisaniCal.entitlements;
|
CODE_SIGN_ENTITLEMENTS = KisaniCal/KisaniCal.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 2;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = "Kisani Cal";
|
INFOPLIST_KEY_CFBundleDisplayName = "Kisani Cal";
|
||||||
@@ -416,13 +417,14 @@
|
|||||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||||
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
|
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
|
||||||
INFOPLIST_KEY_UIStatusBarStyle = UIStatusBarStyleDefault;
|
INFOPLIST_KEY_UIStatusBarStyle = UIStatusBarStyleDefault;
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
|
MARKETING_VERSION = 2;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.kutesir.KisaniCal;
|
PRODUCT_BUNDLE_IDENTIFIER = com.kutesir.KisaniCal;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SWIFT_VERSION = 5.9;
|
SWIFT_VERSION = 5.9;
|
||||||
@@ -469,6 +471,7 @@
|
|||||||
DEVELOPMENT_TEAM = K8BLMMR883;
|
DEVELOPMENT_TEAM = K8BLMMR883;
|
||||||
ENABLE_NS_ASSERTIONS = NO;
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||||
GCC_NO_COMMON_BLOCKS = YES;
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
@@ -483,6 +486,7 @@
|
|||||||
MTL_FAST_MATH = YES;
|
MTL_FAST_MATH = YES;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
|
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||||
SWIFT_COMPILATION_MODE = wholemodule;
|
SWIFT_COMPILATION_MODE = wholemodule;
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
@@ -495,10 +499,16 @@
|
|||||||
APPLICATION_EXTENSION_API_ONLY = YES;
|
APPLICATION_EXTENSION_API_ONLY = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = KisaniCalWidgets/KisaniCalWidgets.entitlements;
|
CODE_SIGN_ENTITLEMENTS = KisaniCalWidgets/KisaniCalWidgets.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 2;
|
||||||
GENERATE_INFOPLIST_FILE = NO;
|
GENERATE_INFOPLIST_FILE = NO;
|
||||||
INFOPLIST_FILE = KisaniCalWidgets/Info.plist;
|
INFOPLIST_FILE = KisaniCalWidgets/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
"@executable_path/../../Frameworks",
|
||||||
|
);
|
||||||
|
MARKETING_VERSION = 2;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.kutesir.KisaniCal.KisaniCalWidgets;
|
PRODUCT_BUNDLE_IDENTIFIER = com.kutesir.KisaniCal.KisaniCalWidgets;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SWIFT_VERSION = 5.9;
|
SWIFT_VERSION = 5.9;
|
||||||
@@ -545,6 +555,7 @@
|
|||||||
DEVELOPMENT_TEAM = K8BLMMR883;
|
DEVELOPMENT_TEAM = K8BLMMR883;
|
||||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
ENABLE_TESTABILITY = YES;
|
ENABLE_TESTABILITY = YES;
|
||||||
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||||
GCC_DYNAMIC_NO_PIC = NO;
|
GCC_DYNAMIC_NO_PIC = NO;
|
||||||
GCC_NO_COMMON_BLOCKS = YES;
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
@@ -566,6 +577,7 @@
|
|||||||
ONLY_ACTIVE_ARCH = YES;
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
|
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
@@ -578,10 +590,16 @@
|
|||||||
APPLICATION_EXTENSION_API_ONLY = YES;
|
APPLICATION_EXTENSION_API_ONLY = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = KisaniCalWidgets/KisaniCalWidgets.entitlements;
|
CODE_SIGN_ENTITLEMENTS = KisaniCalWidgets/KisaniCalWidgets.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 2;
|
||||||
GENERATE_INFOPLIST_FILE = NO;
|
GENERATE_INFOPLIST_FILE = NO;
|
||||||
INFOPLIST_FILE = KisaniCalWidgets/Info.plist;
|
INFOPLIST_FILE = KisaniCalWidgets/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
"@executable_path/../../Frameworks",
|
||||||
|
);
|
||||||
|
MARKETING_VERSION = 2;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.kutesir.KisaniCal.KisaniCalWidgets;
|
PRODUCT_BUNDLE_IDENTIFIER = com.kutesir.KisaniCal.KisaniCalWidgets;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SWIFT_VERSION = 5.9;
|
SWIFT_VERSION = 5.9;
|
||||||
@@ -597,6 +615,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = KisaniCal/KisaniCal.entitlements;
|
CODE_SIGN_ENTITLEMENTS = KisaniCal/KisaniCal.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 2;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = "Kisani Cal";
|
INFOPLIST_KEY_CFBundleDisplayName = "Kisani Cal";
|
||||||
@@ -608,13 +627,14 @@
|
|||||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||||
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
|
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
|
||||||
INFOPLIST_KEY_UIStatusBarStyle = UIStatusBarStyleDefault;
|
INFOPLIST_KEY_UIStatusBarStyle = UIStatusBarStyleDefault;
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
|
MARKETING_VERSION = 2;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.kutesir.KisaniCal;
|
PRODUCT_BUNDLE_IDENTIFIER = com.kutesir.KisaniCal;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SWIFT_VERSION = 5.9;
|
SWIFT_VERSION = 5.9;
|
||||||
|
|||||||
Binary file not shown.
@@ -1,11 +1,10 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "1500"
|
LastUpgradeVersion = "2620"
|
||||||
version = "1.7">
|
version = "1.3">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
buildImplicitDependencies = "YES"
|
buildImplicitDependencies = "YES">
|
||||||
runPostActionsOnFailure = "NO">
|
|
||||||
<BuildActionEntries>
|
<BuildActionEntries>
|
||||||
<BuildActionEntry
|
<BuildActionEntry
|
||||||
buildForTesting = "YES"
|
buildForTesting = "YES"
|
||||||
@@ -27,8 +26,7 @@
|
|||||||
buildConfiguration = "Debug"
|
buildConfiguration = "Debug"
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||||
onlyGenerateCoverageForSpecifiedTargets = "NO">
|
|
||||||
<MacroExpansion>
|
<MacroExpansion>
|
||||||
<BuildableReference
|
<BuildableReference
|
||||||
BuildableIdentifier = "primary"
|
BuildableIdentifier = "primary"
|
||||||
@@ -40,8 +38,6 @@
|
|||||||
</MacroExpansion>
|
</MacroExpansion>
|
||||||
<Testables>
|
<Testables>
|
||||||
</Testables>
|
</Testables>
|
||||||
<CommandLineArguments>
|
|
||||||
</CommandLineArguments>
|
|
||||||
</TestAction>
|
</TestAction>
|
||||||
<LaunchAction
|
<LaunchAction
|
||||||
buildConfiguration = "Debug"
|
buildConfiguration = "Debug"
|
||||||
@@ -63,8 +59,6 @@
|
|||||||
ReferencedContainer = "container:KisaniCal.xcodeproj">
|
ReferencedContainer = "container:KisaniCal.xcodeproj">
|
||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
</BuildableProductRunnable>
|
</BuildableProductRunnable>
|
||||||
<CommandLineArguments>
|
|
||||||
</CommandLineArguments>
|
|
||||||
</LaunchAction>
|
</LaunchAction>
|
||||||
<ProfileAction
|
<ProfileAction
|
||||||
buildConfiguration = "Release"
|
buildConfiguration = "Release"
|
||||||
@@ -82,8 +76,6 @@
|
|||||||
ReferencedContainer = "container:KisaniCal.xcodeproj">
|
ReferencedContainer = "container:KisaniCal.xcodeproj">
|
||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
</BuildableProductRunnable>
|
</BuildableProductRunnable>
|
||||||
<CommandLineArguments>
|
|
||||||
</CommandLineArguments>
|
|
||||||
</ProfileAction>
|
</ProfileAction>
|
||||||
<AnalyzeAction
|
<AnalyzeAction
|
||||||
buildConfiguration = "Debug">
|
buildConfiguration = "Debug">
|
||||||
|
|||||||
@@ -10,12 +10,32 @@ import AppIntents
|
|||||||
// persisted per-instance by WidgetKit, the widget is fully self-contained and does
|
// persisted per-instance by WidgetKit, the widget is fully self-contained and does
|
||||||
// not depend on App Group data sharing.
|
// not depend on App Group data sharing.
|
||||||
|
|
||||||
|
// Which upcoming event to auto-track when the queue is on.
|
||||||
|
enum AutoSelectRank: Int, AppEnum {
|
||||||
|
case first = 1, second, third, fourth, fifth
|
||||||
|
|
||||||
|
static var typeDisplayRepresentation: TypeDisplayRepresentation = "Auto Select"
|
||||||
|
static var caseDisplayRepresentations: [AutoSelectRank: DisplayRepresentation] = [
|
||||||
|
.first: "Nearest to finish",
|
||||||
|
.second: "2nd nearest to finish",
|
||||||
|
.third: "3rd nearest to finish",
|
||||||
|
.fourth: "4th nearest to finish",
|
||||||
|
.fifth: "5th nearest to finish",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
struct EventConfigIntent: WidgetConfigurationIntent {
|
struct EventConfigIntent: WidgetConfigurationIntent {
|
||||||
static var title: LocalizedStringResource = "Event Countdown"
|
static var title: LocalizedStringResource = "Event Countdown"
|
||||||
static var description = IntentDescription("Count down to any upcoming event with a dot grid.")
|
static var description = IntentDescription("Count down to an event with a dot grid.")
|
||||||
|
|
||||||
|
// When on, the widget auto-tracks one of your upcoming events. When off, you
|
||||||
|
// track a specific event you pick, or a fully custom name + date.
|
||||||
|
@Parameter(title: "Upcoming queue", default: true)
|
||||||
|
var upcomingQueue: Bool
|
||||||
|
|
||||||
|
@Parameter(title: "Auto select", default: .first)
|
||||||
|
var autoSelect: AutoSelectRank
|
||||||
|
|
||||||
// Pick one of your existing events (tasks with a due date). When set, it
|
|
||||||
// overrides the manual name/date fields below.
|
|
||||||
@Parameter(title: "Track event")
|
@Parameter(title: "Track event")
|
||||||
var event: EventEntity?
|
var event: EventEntity?
|
||||||
|
|
||||||
@@ -27,6 +47,25 @@ struct EventConfigIntent: WidgetConfigurationIntent {
|
|||||||
|
|
||||||
@Parameter(title: "Counting from", default: Date.now)
|
@Parameter(title: "Counting from", default: Date.now)
|
||||||
var startDate: Date
|
var startDate: Date
|
||||||
|
|
||||||
|
// Show only the relevant fields depending on the toggle — like a clean,
|
||||||
|
// single-purpose config screen instead of every field at once.
|
||||||
|
static var parameterSummary: some ParameterSummary {
|
||||||
|
When(\.$upcomingQueue, .equalTo, true) {
|
||||||
|
Summary("Track upcoming event") {
|
||||||
|
\.$upcomingQueue
|
||||||
|
\.$autoSelect
|
||||||
|
}
|
||||||
|
} otherwise: {
|
||||||
|
Summary("Track a custom event") {
|
||||||
|
\.$upcomingQueue
|
||||||
|
\.$event
|
||||||
|
\.$eventName
|
||||||
|
\.$targetDate
|
||||||
|
\.$startDate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Event picker (reads your tasks from the App Group)
|
// MARK: - Event picker (reads your tasks from the App Group)
|
||||||
@@ -141,10 +180,11 @@ struct EventEntry: TimelineEntry {
|
|||||||
|
|
||||||
struct EventProvider: AppIntentTimelineProvider {
|
struct EventProvider: AppIntentTimelineProvider {
|
||||||
func placeholder(in context: Context) -> EventEntry {
|
func placeholder(in context: Context) -> EventEntry {
|
||||||
|
// ~90-day window, ~60% elapsed → a clear, legible dot grid in the gallery.
|
||||||
EventEntry(date: .now,
|
EventEntry(date: .now,
|
||||||
eventName: "Mum's Birthday",
|
eventName: "IELTS",
|
||||||
start: Date().addingTimeInterval(-86400 * 320),
|
start: Date().addingTimeInterval(-86400 * 54),
|
||||||
target: Date().addingTimeInterval(86400 * 60))
|
target: Date().addingTimeInterval(86400 * 36))
|
||||||
}
|
}
|
||||||
|
|
||||||
func snapshot(for configuration: EventConfigIntent, in context: Context) async -> EventEntry {
|
func snapshot(for configuration: EventConfigIntent, in context: Context) async -> EventEntry {
|
||||||
@@ -158,19 +198,43 @@ struct EventProvider: AppIntentTimelineProvider {
|
|||||||
return Timeline(entries: [entry], policy: .after(nextMidnight))
|
return Timeline(entries: [entry], policy: .after(nextMidnight))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Upcoming events (incomplete tasks with a future due date), nearest first.
|
||||||
|
private func upcomingEvents() -> [EventEntity] {
|
||||||
|
loadWidgetTasks()
|
||||||
|
.filter { !$0.isComplete && ($0.dueDate ?? .distantPast) > Date() }
|
||||||
|
.sorted { ($0.dueDate ?? .distantFuture) < ($1.dueDate ?? .distantFuture) }
|
||||||
|
.map { EventEntity(id: $0.id.uuidString, title: $0.title, dueDate: $0.dueDate) }
|
||||||
|
}
|
||||||
|
|
||||||
private func entry(for config: EventConfigIntent) -> EventEntry {
|
private func entry(for config: EventConfigIntent) -> EventEntry {
|
||||||
let name: String
|
var name: String
|
||||||
let target: Date
|
var target: Date
|
||||||
if let ev = config.event, let due = ev.dueDate {
|
|
||||||
// A picked event supplies its own name + date.
|
if config.upcomingQueue {
|
||||||
|
// Auto-track the Nth nearest upcoming event.
|
||||||
|
let upcoming = upcomingEvents()
|
||||||
|
let idx = max(0, config.autoSelect.rawValue - 1)
|
||||||
|
if idx < upcoming.count, let due = upcoming[idx].dueDate {
|
||||||
|
name = upcoming[idx].title
|
||||||
|
target = due
|
||||||
|
} else {
|
||||||
|
name = "No upcoming event"
|
||||||
|
target = Date().addingTimeInterval(86400)
|
||||||
|
}
|
||||||
|
} else if let ev = config.event, let due = ev.dueDate {
|
||||||
|
// A specifically picked event supplies its own name + date.
|
||||||
name = ev.title
|
name = ev.title
|
||||||
target = due
|
target = due
|
||||||
} else {
|
} else {
|
||||||
|
// Fully custom event.
|
||||||
name = config.eventName.isEmpty ? "My Event" : config.eventName
|
name = config.eventName.isEmpty ? "My Event" : config.eventName
|
||||||
target = config.targetDate
|
target = config.targetDate
|
||||||
}
|
}
|
||||||
// Never let the start sit after the target.
|
|
||||||
let start = min(config.startDate, target)
|
// Counting anchor: custom mode honors the picker; auto mode counts from
|
||||||
|
// "now" so the grid fills toward the event. Never start after the target.
|
||||||
|
let anchor = config.upcomingQueue ? Date() : config.startDate
|
||||||
|
let start = min(anchor, target)
|
||||||
let mode = UserDefaults.kisani.integer(forKey: countdownUnitKey)
|
let mode = UserDefaults.kisani.integer(forKey: countdownUnitKey)
|
||||||
return EventEntry(date: .now, eventName: name, start: start, target: target, unitMode: mode)
|
return EventEntry(date: .now, eventName: name, start: start, target: target, unitMode: mode)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user