diff --git a/android/app/src/main/kotlin/com/ccextractor/taskwarriorflutter/MainActivity.kt b/android/app/src/main/kotlin/com/ccextractor/taskwarriorflutter/MainActivity.kt index 3a8aba62..7132878c 100644 --- a/android/app/src/main/kotlin/com/ccextractor/taskwarriorflutter/MainActivity.kt +++ b/android/app/src/main/kotlin/com/ccextractor/taskwarriorflutter/MainActivity.kt @@ -11,7 +11,7 @@ import android.appwidget.AppWidgetManager import android.content.ComponentName class MainActivity: FlutterActivity() { - private val channel = "com.example.taskwarrior/widget" + private val channel = "com.ccextractor.taskwarriorflutter/widget" override fun configureFlutterEngine(flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index a7b8abbb..e2010f6c 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1,7 +1,6 @@ PODS: - connectivity_plus (0.0.1): - Flutter - - ReachabilitySwift - DKImagePickerController/Core (4.3.4): - DKImagePickerController/ImageDataManager - DKImagePickerController/Resource @@ -43,9 +42,9 @@ PODS: - Flutter (1.0.0) - flutter_local_notifications (0.0.1): - Flutter - - flutter_native_splash (0.0.1): + - flutter_native_splash (2.4.3): - Flutter - - flutter_native_timezone (0.0.1): + - flutter_timezone (0.0.1): - Flutter - home_widget (0.0.1): - Flutter @@ -54,15 +53,17 @@ PODS: - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS - - permission_handler_apple (9.0.4): + - permission_handler_apple (9.3.0): - Flutter - - ReachabilitySwift (5.0.0) - SDWebImage (5.19.0): - SDWebImage/Core (= 5.19.0) - SDWebImage/Core (5.19.0) - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS + - sqflite_darwin (0.0.4): + - Flutter + - FlutterMacOS - SwiftyGif (5.4.4) - url_launcher_ios (0.0.1): - Flutter @@ -75,19 +76,19 @@ DEPENDENCIES: - Flutter (from `Flutter`) - flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`) - flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`) - - flutter_native_timezone (from `.symlinks/plugins/flutter_native_timezone/ios`) + - flutter_timezone (from `.symlinks/plugins/flutter_timezone/ios`) - home_widget (from `.symlinks/plugins/home_widget/ios`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) + - sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) SPEC REPOS: trunk: - DKImagePickerController - DKPhotoGallery - - ReachabilitySwift - SDWebImage - SwiftyGif @@ -106,8 +107,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/flutter_local_notifications/ios" flutter_native_splash: :path: ".symlinks/plugins/flutter_native_splash/ios" - flutter_native_timezone: - :path: ".symlinks/plugins/flutter_native_timezone/ios" + flutter_timezone: + :path: ".symlinks/plugins/flutter_timezone/ios" home_widget: :path: ".symlinks/plugins/home_widget/ios" package_info_plus: @@ -118,30 +119,32 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/permission_handler_apple/ios" shared_preferences_foundation: :path: ".symlinks/plugins/shared_preferences_foundation/darwin" + sqflite_darwin: + :path: ".symlinks/plugins/sqflite_darwin/darwin" url_launcher_ios: :path: ".symlinks/plugins/url_launcher_ios/ios" SPEC CHECKSUMS: - connectivity_plus: bf0076dd84a130856aa636df1c71ccaff908fa1d + connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd DKImagePickerController: b512c28220a2b8ac7419f21c491fc8534b7601ac DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179 - file_picker: 15fd9539e4eb735dc54bae8c0534a7a9511a03de - file_picker_writable: 67959f5c516feb5121693a14eda63fcbe6cbb6dc - file_selector_ios: 8c25d700d625e1dcdd6599f2d927072f2254647b + file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be + file_picker_writable: 3a458aa8cdb7c1378cb3e8ec0543ead87a77d0be + file_selector_ios: f92e583d43608aebc2e4a18daac30b8902845502 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 - flutter_local_notifications: 0c0b1ae97e741e1521e4c1629a459d04b9aec743 - flutter_native_splash: 52501b97d1c0a5f898d687f1646226c1f93c56ef - flutter_native_timezone: 5f05b2de06c9776b4cc70e1839f03de178394d22 - home_widget: 0434835a4c9a75704264feff6be17ea40e0f0d57 - package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85 - path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 - permission_handler_apple: 44366e37eaf29454a1e7b1b7d736c2cceaeb17ce - ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825 + flutter_local_notifications: 395056b3175ba4f08480a7c5de30cd36d69827e4 + flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf + flutter_timezone: 7c838e17ffd4645d261e87037e5bebf6d38fe544 + home_widget: f169fc41fd807b4d46ab6615dc44d62adbf9f64f + package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499 + path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 + permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d SDWebImage: 981fd7e860af070920f249fd092420006014c3eb - shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126 + shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 + sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0 SwiftyGif: 93a1cc87bf3a51916001cf8f3d63835fb64c819f - url_launcher_ios: bbd758c6e7f9fd7b5b1d4cde34d2b95fcce5e812 + url_launcher_ios: 694010445543906933d732453a59da0a173ae33d PODFILE CHECKSUM: c4c93c5f6502fe2754f48404d3594bf779584011 -COCOAPODS: 1.14.3 +COCOAPODS: 1.16.2 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index dbf0250c..0578c84a 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -14,8 +14,21 @@ 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + AABC0EB72DA46F0C008D692D /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AABC0EB62DA46F0C008D692D /* WidgetKit.framework */; }; + AABC0EB92DA46F0C008D692D /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AABC0EB82DA46F0C008D692D /* SwiftUI.framework */; }; + AABC0EC62DA46F0E008D692D /* TaskWarriorWidgetsExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = AABC0EB52DA46F0C008D692D /* TaskWarriorWidgetsExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + AABC0EC42DA46F0E008D692D /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = AABC0EB42DA46F0C008D692D; + remoteInfo = TaskWarriorWidgetsExtension; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXCopyFilesBuildPhase section */ 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; @@ -27,6 +40,17 @@ name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; + AABC0EC72DA46F0E008D692D /* Embed Foundation Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 13; + files = ( + AABC0EC62DA46F0E008D692D /* TaskWarriorWidgetsExtension.appex in Embed Foundation Extensions */, + ); + name = "Embed Foundation Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ @@ -45,10 +69,29 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + AABC0EB52DA46F0C008D692D /* TaskWarriorWidgetsExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = TaskWarriorWidgetsExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + AABC0EB62DA46F0C008D692D /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; }; + AABC0EB82DA46F0C008D692D /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; }; + AABC0F812DA481A8008D692D /* RunnerDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = RunnerDebug.entitlements; sourceTree = ""; }; + AABC0F822DA481B0008D692D /* TaskWarriorWidgetsExtensionDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = TaskWarriorWidgetsExtensionDebug.entitlements; sourceTree = ""; }; CCEEA0AE8F1C59FFACAE45B3 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; DB57389FBBE4024E44956E34 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ +/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ + AABC0ECB2DA46F0E008D692D /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + Info.plist, + ); + target = AABC0EB42DA46F0C008D692D /* TaskWarriorWidgetsExtension */; + }; +/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ + +/* Begin PBXFileSystemSynchronizedRootGroup section */ + AABC0EBA2DA46F0C008D692D /* TaskWarriorWidgets */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (AABC0ECB2DA46F0E008D692D /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = TaskWarriorWidgets; sourceTree = ""; }; +/* End PBXFileSystemSynchronizedRootGroup section */ + /* Begin PBXFrameworksBuildPhase section */ 97C146EB1CF9000F007C117D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; @@ -58,6 +101,15 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + AABC0EB22DA46F0C008D692D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + AABC0EB92DA46F0C008D692D /* SwiftUI.framework in Frameworks */, + AABC0EB72DA46F0C008D692D /* WidgetKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -75,8 +127,10 @@ 97C146E51CF9000F007C117D = { isa = PBXGroup; children = ( + AABC0F822DA481B0008D692D /* TaskWarriorWidgetsExtensionDebug.entitlements */, 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, + AABC0EBA2DA46F0C008D692D /* TaskWarriorWidgets */, 97C146EF1CF9000F007C117D /* Products */, C3171261BB8F68A7E2CB70A5 /* Pods */, EC04A4346BDDFBE60E9077C7 /* Frameworks */, @@ -87,6 +141,7 @@ isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, + AABC0EB52DA46F0C008D692D /* TaskWarriorWidgetsExtension.appex */, ); name = Products; sourceTree = ""; @@ -94,6 +149,7 @@ 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( + AABC0F812DA481A8008D692D /* RunnerDebug.entitlements */, 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, @@ -113,7 +169,6 @@ DB57389FBBE4024E44956E34 /* Pods-Runner.release.xcconfig */, 162E9301614D923D4E360425 /* Pods-Runner.profile.xcconfig */, ); - name = Pods; path = Pods; sourceTree = ""; }; @@ -121,6 +176,8 @@ isa = PBXGroup; children = ( 089D9819C0366C673FE320BF /* Pods_Runner.framework */, + AABC0EB62DA46F0C008D692D /* WidgetKit.framework */, + AABC0EB82DA46F0C008D692D /* SwiftUI.framework */, ); name = Frameworks; sourceTree = ""; @@ -138,24 +195,48 @@ 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, + AABC0EC72DA46F0E008D692D /* Embed Foundation Extensions */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, E6257F2992FA3E40BE65E7BD /* [CP] Embed Pods Frameworks */, + 06185774F7A690D00A41F067 /* [CP] Copy Pods Resources */, ); buildRules = ( ); dependencies = ( + AABC0EC52DA46F0E008D692D /* PBXTargetDependency */, ); name = Runner; productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; }; + AABC0EB42DA46F0C008D692D /* TaskWarriorWidgetsExtension */ = { + isa = PBXNativeTarget; + buildConfigurationList = AABC0ECC2DA46F0E008D692D /* Build configuration list for PBXNativeTarget "TaskWarriorWidgetsExtension" */; + buildPhases = ( + AABC0EB12DA46F0C008D692D /* Sources */, + AABC0EB22DA46F0C008D692D /* Frameworks */, + AABC0EB32DA46F0C008D692D /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + fileSystemSynchronizedGroups = ( + AABC0EBA2DA46F0C008D692D /* TaskWarriorWidgets */, + ); + name = TaskWarriorWidgetsExtension; + productName = TaskWarriorWidgetsExtension; + productReference = AABC0EB52DA46F0C008D692D /* TaskWarriorWidgetsExtension.appex */; + productType = "com.apple.product-type.app-extension"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { + LastSwiftUpdateCheck = 1620; LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { @@ -163,6 +244,9 @@ CreatedOnToolsVersion = 7.3.1; LastSwiftMigration = 1100; }; + AABC0EB42DA46F0C008D692D = { + CreatedOnToolsVersion = 16.2; + }; }; }; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; @@ -179,6 +263,7 @@ projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, + AABC0EB42DA46F0C008D692D /* TaskWarriorWidgetsExtension */, ); }; /* End PBXProject section */ @@ -195,9 +280,33 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + AABC0EB32DA46F0C008D692D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 06185774F7A690D00A41F067 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; 2123C8066096B8DDE567BB91 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -280,8 +389,23 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + AABC0EB12DA46F0C008D692D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + AABC0EC52DA46F0E008D692D /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = AABC0EB42DA46F0C008D692D /* TaskWarriorWidgetsExtension */; + targetProxy = AABC0EC42DA46F0E008D692D /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; @@ -365,7 +489,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.taskwarrior; + PRODUCT_BUNDLE_IDENTIFIER = com.ccextractor.taskwarriorflutter; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -486,14 +610,16 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/RunnerDebug.entitlements; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = K3LKG5C683; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.taskwarrior; + PRODUCT_BUNDLE_IDENTIFIER = com.ccextractor.taskwarriorflutter; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -515,7 +641,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.taskwarrior; + PRODUCT_BUNDLE_IDENTIFIER = com.ccextractor.taskwarriorflutter; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -523,6 +649,125 @@ }; name = Release; }; + AABC0EC82DA46F0E008D692D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = TaskWarriorWidgetsExtensionDebug.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = K3LKG5C683; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = TaskWarriorWidgets/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = TaskWarriorWidgets; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 18.2; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.ccextractor.taskwarriorflutter.TaskWarriorWidgets; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + AABC0EC92DA46F0E008D692D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = TaskWarriorWidgets/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = TaskWarriorWidgets; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 18.2; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.ccextractor.taskwarriorflutter.TaskWarriorWidgets; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + AABC0ECA2DA46F0E008D692D /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = TaskWarriorWidgets/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = TaskWarriorWidgets; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 18.2; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.ccextractor.taskwarriorflutter.TaskWarriorWidgets; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Profile; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -546,6 +791,16 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + AABC0ECC2DA46F0E008D692D /* Build configuration list for PBXNativeTarget "TaskWarriorWidgetsExtension" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + AABC0EC82DA46F0E008D692D /* Debug */, + AABC0EC92DA46F0E008D692D /* Release */, + AABC0ECA2DA46F0E008D692D /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 5e31d3d3..c53e2b31 100644 --- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -48,6 +48,7 @@ ignoresPersistentStateOnLaunch = "NO" debugDocumentVersioning = "YES" debugServiceExtension = "internal" + enableGPUValidationMode = "1" allowLocationSimulation = "YES"> diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 090af1a3..a4c5bf96 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -1,16 +1,80 @@ import UIKit import Flutter +import WidgetKit -@UIApplicationMain +@main @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { GeneratedPluginRegistrant.register(with: self) - if #available(iOS 10.0, *) { - UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate - } + + // Register our widget UserDefaults plugin + let controller = window?.rootViewController as! FlutterViewController + let widgetChannel = FlutterMethodChannel( + name: "com.ccextractor.taskwarriorflutter/widget", + binaryMessenger: controller.binaryMessenger) + + widgetChannel.setMethodCallHandler { (call, result) in + if call.method == "saveToUserDefaults" { + guard let args = call.arguments as? [String: Any], + let key = args["key"] as? String, + let groupId = args["groupId"] as? String else { + result(FlutterError(code: "INVALID_ARGUMENTS", message: "Missing required arguments", details: nil)) + return + } + + guard let sharedDefaults = UserDefaults(suiteName: groupId) else { + result(FlutterError(code: "USERDEFAULTS_ERROR", message: "Could not access shared UserDefaults with groupId: \(groupId)", details: nil)) + return + } + + let isData = args["isData"] as? Bool ?? false + + if isData, let value = args["value"] as? FlutterStandardTypedData { + // Save binary data + sharedDefaults.set(value.data, forKey: key) + print("Successfully saved binary data for key: \(key) to group: \(groupId)") + } else if let value = args["value"] as? String { + // Save string value + sharedDefaults.set(value, forKey: key) + print("Successfully saved string for key: \(key) to group: \(groupId)") + } else { + result(FlutterError(code: "INVALID_VALUE", message: "Value must be either String or binary data", details: nil)) + return + } + + // Force synchronize to ensure data is written immediately + sharedDefaults.synchronize() + result(true) + } else if call.method == "updateWidget" { + #if os(iOS) + if #available(iOS 14.0, *) { + // Get the widget name to update + let args = call.arguments as? [String: Any] + let widgetName = args?["widgetName"] as? String + + // Request widget update + if widgetName != nil { + WidgetCenter.shared.reloadTimelines(ofKind: widgetName!) + print("iOS widget timeline reloaded for: \(widgetName!)") + } else { + WidgetCenter.shared.reloadAllTimelines() + print("iOS widget timelines reloaded") + } + result(true) + } else { + result(FlutterError(code: "VERSION_ERROR", message: "Widgets are only available on iOS 14.0 and above", details: nil)) + } + #else + result(FlutterError(code: "PLATFORM_ERROR", message: "Not available on this platform", details: nil)) + #endif + } else { + result(FlutterMethodNotImplemented) + } + } + return super.application(application, didFinishLaunchingWithOptions: launchOptions) } } diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index a878453f..72ecb775 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -1,58 +1,58 @@ - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleDisplayName - Taskwarrior - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - com.ccextractor.taskwarriorflutter - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - TaskWarrior - CFBundlePackageType - APPL - CFBundleShortVersionString - $(FLUTTER_BUILD_NAME) - CFBundleSignature - ???? - CFBundleVersion - $(FLUTTER_BUILD_NUMBER) - LSRequiresIPhoneOS - - UILaunchStoryboardName - LaunchScreen - UIMainStoryboardFile - Main - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - LSApplicationQueriesSchemes - - https://ccextractor.org/ - https://github.com/CCExtractor/taskwarrior-flutter - - UIViewControllerBasedStatusBarAppearance - - UIStatusBarHidden - - CADisableMinimumFrameDurationOnPhone - - UIApplicationSupportsIndirectInputEvents - - + + CADisableMinimumFrameDurationOnPhone + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Taskwarrior + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + TaskWarrior + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSApplicationQueriesSchemes + + https://ccextractor.org/ + https://github.com/CCExtractor/taskwarrior-flutter + + LSRequiresIPhoneOS + + UIApplicationSupportsIndirectInputEvents + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIStatusBarHidden + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + diff --git a/ios/Runner/RunnerDebug.entitlements b/ios/Runner/RunnerDebug.entitlements new file mode 100644 index 00000000..6385ba4d --- /dev/null +++ b/ios/Runner/RunnerDebug.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.application-groups + + group.taskwarrior + + + diff --git a/ios/TaskWarriorWidgets/AppIntent.swift b/ios/TaskWarriorWidgets/AppIntent.swift new file mode 100644 index 00000000..15628463 --- /dev/null +++ b/ios/TaskWarriorWidgets/AppIntent.swift @@ -0,0 +1,9 @@ +import WidgetKit +import AppIntents + +struct ConfigurationAppIntent: WidgetConfigurationIntent { + static var title: LocalizedStringResource { "Configuration" } + // static var description: IntentDescription { "This is an example widget." } + + // An example configurable parameter. +} diff --git a/ios/TaskWarriorWidgets/Assets.xcassets/AccentColor.colorset/Contents.json b/ios/TaskWarriorWidgets/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 00000000..eb878970 --- /dev/null +++ b/ios/TaskWarriorWidgets/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios/TaskWarriorWidgets/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/TaskWarriorWidgets/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..23058801 --- /dev/null +++ b/ios/TaskWarriorWidgets/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,35 @@ +{ + "images" : [ + { + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "tinted" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios/TaskWarriorWidgets/Assets.xcassets/Contents.json b/ios/TaskWarriorWidgets/Assets.xcassets/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/ios/TaskWarriorWidgets/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios/TaskWarriorWidgets/Assets.xcassets/WidgetBackground.colorset/Contents.json b/ios/TaskWarriorWidgets/Assets.xcassets/WidgetBackground.colorset/Contents.json new file mode 100644 index 00000000..eb878970 --- /dev/null +++ b/ios/TaskWarriorWidgets/Assets.xcassets/WidgetBackground.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios/TaskWarriorWidgets/ChartWidget.swift b/ios/TaskWarriorWidgets/ChartWidget.swift new file mode 100644 index 00000000..27288322 --- /dev/null +++ b/ios/TaskWarriorWidgets/ChartWidget.swift @@ -0,0 +1,199 @@ +import WidgetKit +import SwiftUI +import os.log + +struct ChartWidgetEntry: TimelineEntry { + let date: Date + let themeMode: String + let chart_image: String + let configuration: ConfigurationAppIntent +} + +struct ChartProvider: AppIntentTimelineProvider { + func placeholder(in context: Context) -> ChartWidgetEntry { + ChartWidgetEntry( + date: Date(), + themeMode: "light", + chart_image: "default_chart", + configuration: ConfigurationAppIntent() + ) + } + + func snapshot(for configuration: ConfigurationAppIntent, in context: Context) async -> ChartWidgetEntry { + ChartWidgetEntry( + date: Date(), + themeMode: "light", + chart_image: "default_chart", + configuration: configuration + ) + } + + func timeline(for configuration: ConfigurationAppIntent, in context: Context) async -> Timeline { + let sharedDefaults = UserDefaults(suiteName: appGroupIdentifier) + + let themeMode = sharedDefaults?.string(forKey: "themeMode") ?? "light" + let chartImage = sharedDefaults?.string(forKey: "chart_image") ?? "default_chart" + + os_log("ChartWidget - Theme Mode: %@", log: .default, type: .debug, themeMode) + os_log("ChartWidget - Chart Image Path: %@", log: .default, type: .debug, chartImage) + + let entry = ChartWidgetEntry( + date: Date(), + themeMode: themeMode, + chart_image: chartImage, + configuration: configuration + ) + + // Refresh every 30 minutes + return Timeline(entries: [entry], policy: .after(Date(timeIntervalSinceNow: 30 * 60))) + } +} + +struct ChartWidgetEntryView: View { + var entry: ChartProvider.Entry + @Environment(\.widgetFamily) var family + + var isDarkMode: Bool { + return entry.themeMode == "dark" + } + + var body: some View { + GeometryReader { geometry in + ZStack { + (isDarkMode ? Color.black : Color(white: 0.95)) + .ignoresSafeArea() + + VStack(alignment: .leading, spacing: 0) { + header + + if family == .systemSmall { + smallChartContent + } else { + chartContent + .frame(maxWidth: .infinity, maxHeight: .infinity) + } + } + .padding(family == .systemSmall ? 8 : 10) + } + .colorScheme(isDarkMode ? .dark : .light) + } + } + + var header: some View { + HStack { + HStack(spacing: 4) { + Image(systemName: "chart.xyaxis.line") + .foregroundColor(.blue) + .imageScale(family == .systemSmall ? .small : .medium) + + Text("Task Burndown") + .font(family == .systemSmall ? .caption.bold() : .headline) + .foregroundColor(isDarkMode ? .white : .black) + } + + Spacer() + } + .padding(.bottom, family == .systemSmall ? 4 : 8) + } + + var chartContent: some View { + Group { + if let uiImage = loadImageFromPath(entry.chart_image) { + Image(uiImage: uiImage) + .resizable() + .scaledToFit() + .background( + RoundedRectangle(cornerRadius: 8) + .fill(isDarkMode ? Color(white: 0.15) : .white) + ) + .cornerRadius(8) + .padding(4) + } else { + VStack(spacing: 10) { + Image(systemName: "chart.xyaxis.line") + .font(.system(size: 40)) + .foregroundColor(isDarkMode ? Color(white: 0.3) : Color(white: 0.7)) + + Text("No chart data available") + .font(.subheadline) + .foregroundColor(isDarkMode ? .gray : .secondary) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + } + } + } + + var smallChartContent: some View { + Group { + if let uiImage = loadImageFromPath(entry.chart_image) { + Image(uiImage: uiImage) + .resizable() + .scaledToFit() + .background( + RoundedRectangle(cornerRadius: 6) + .fill(isDarkMode ? Color(white: 0.15) : .white) + ) + .cornerRadius(6) + .padding(2) + } else { + VStack(spacing: 4) { + Image(systemName: "chart.xyaxis.line") + .font(.system(size: 20)) + .foregroundColor(isDarkMode ? Color(white: 0.3) : Color(white: 0.7)) + + Text("No chart") + .font(.caption2) + .foregroundColor(isDarkMode ? .gray : .secondary) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + } + } + .widgetURL(URL(string: "taskwarrior://openChartView")) + } + + func loadImageFromPath(_ path: String) -> UIImage? { + // First try to load from shared UserDefaults + let sharedDefaults = UserDefaults(suiteName: appGroupIdentifier) + if let imageData = sharedDefaults?.data(forKey: "widget_image_data") { + os_log("ChartWidget - Loading image from UserDefaults", type: .debug) + return UIImage(data: imageData) + } + + // Then try to load from file path + if FileManager.default.fileExists(atPath: path) { + os_log("ChartWidget - Loading image from file: %@", type: .debug, path) + return UIImage(contentsOfFile: path) + } + + os_log("ChartWidget - No image found", type: .debug) + return nil + } +} + +struct ChartWidget: Widget { + let kind: String = "ChartWidget" + + var body: some WidgetConfiguration { + AppIntentConfiguration(kind: kind, intent: ConfigurationAppIntent.self, provider: ChartProvider()) { entry in + ChartWidgetEntryView(entry: entry) + } + .configurationDisplayName("Burndown Chart") + .description("Shows your task burndown chart") + .supportedFamilies([.systemSmall, .systemMedium, .systemLarge]) + .contentMarginsDisabled() + } +} + +#Preview(as: .systemSmall) { + ChartWidget() +} timeline: { + ChartWidgetEntry(date: .now, themeMode: "light", chart_image: "default_chart", configuration: ConfigurationAppIntent()) + ChartWidgetEntry(date: .now, themeMode: "dark", chart_image: "default_chart", configuration: ConfigurationAppIntent()) +} + +#Preview(as: .systemMedium) { + ChartWidget() +} timeline: { + ChartWidgetEntry(date: .now, themeMode: "light", chart_image: "default_chart", configuration: ConfigurationAppIntent()) + ChartWidgetEntry(date: .now, themeMode: "dark", chart_image: "default_chart", configuration: ConfigurationAppIntent()) +} \ No newline at end of file diff --git a/ios/TaskWarriorWidgets/Info.plist b/ios/TaskWarriorWidgets/Info.plist new file mode 100644 index 00000000..a01af8d6 --- /dev/null +++ b/ios/TaskWarriorWidgets/Info.plist @@ -0,0 +1,16 @@ + + + + + NSExtension + + NSExtensionPointIdentifier + com.apple.widgetkit-extension + + GroupIdentifier + group.taskwarrior + + CFBundleDisplayName + Taskwarrior Widgets + + diff --git a/ios/TaskWarriorWidgets/TaskWarriorWidgets.swift b/ios/TaskWarriorWidgets/TaskWarriorWidgets.swift new file mode 100644 index 00000000..b93abd2b --- /dev/null +++ b/ios/TaskWarriorWidgets/TaskWarriorWidgets.swift @@ -0,0 +1,319 @@ +import WidgetKit +import SwiftUI +import os.log + +// Timeline entry +struct TaskWidgetEntry: TimelineEntry { + let date: Date + let tasks: String + let themeMode: String + let configuration: ConfigurationAppIntent +} + +// Task model +struct Task: Identifiable { + let id = UUID() + let description: String + let urgency: String + let uuid: String + let priority: String + + var priorityColor: Color { + switch priority { + case "H": return .red + case "M": return .orange + case "L": return .green + default: return .gray + } + } +} + +struct TaskProvider: AppIntentTimelineProvider { + func placeholder(in context: Context) -> TaskWidgetEntry { + TaskWidgetEntry( + date: Date(), + tasks: "[{\"description\":\"Sample Task\",\"urgency\":\"urgency: 5.8\",\"uuid\":\"123\",\"priority\":\"H\"}]", + themeMode: "light", + configuration: ConfigurationAppIntent() + ) + } + + func snapshot(for configuration: ConfigurationAppIntent, in context: Context) async -> TaskWidgetEntry { + TaskWidgetEntry( + date: Date(), + tasks: "[{\"description\":\"Sample Task\",\"urgency\":\"urgency: 5.8\",\"uuid\":\"123\",\"priority\":\"H\"}]", + themeMode: "light", + configuration: configuration + ) + } + + func timeline(for configuration: ConfigurationAppIntent, in context: Context) async -> Timeline { + var entries: [TaskWidgetEntry] = [] + let sharedDefaults = UserDefaults(suiteName: appGroupIdentifier) + + if let sharedDefaults = sharedDefaults { + let allKeys = sharedDefaults.dictionaryRepresentation().keys + os_log("TaskWidget - Available UserDefaults keys: %@", log: .default, type: .debug, allKeys.joined(separator: ", ")) + } + + let tasks = sharedDefaults?.string(forKey: "tasks") ?? "[]" + let themeMode = sharedDefaults?.string(forKey: "themeMode") ?? "light" + + os_log("TaskWidget - Retrieved tasks: %d bytes", log: .default, type: .debug, tasks.count) + os_log("TaskWidget - Theme: %@", log: .default, type: .debug, themeMode) + + let entry = TaskWidgetEntry( + date: Date(), + tasks: tasks, + themeMode: themeMode, + configuration: configuration + ) + entries.append(entry) + return Timeline(entries: entries, policy: .after(Date(timeIntervalSinceNow: 15 * 60))) + } +} + +struct TaskWidgetEntryView: View { + var entry: TaskProvider.Entry + @Environment(\.widgetFamily) var family + + // Parse tasks from JSON + var parsedTasks: [Task] { + guard let data = entry.tasks.data(using: .utf8) else { return [] } + do { + if let jsonArray = try JSONSerialization.jsonObject(with: data) as? [[String: Any]] { + return jsonArray.compactMap { taskDict in + guard let description = taskDict["description"] as? String, + let urgency = taskDict["urgency"] as? String, + let uuid = taskDict["uuid"] as? String, + let priority = taskDict["priority"] as? String + else { return nil } + return Task(description: description, urgency: urgency, uuid: uuid, priority: priority) + } + } + } catch { + print("Error parsing task JSON: \(error)") + } + return [] + } + + // Sort tasks by priority: H > M > L > None + var sortedTasks: [Task] { + return parsedTasks.sorted { task1, task2 in + let priorityOrder = ["H": 0, "M": 1, "L": 2, "N": 3] + let priority1 = priorityOrder[task1.priority] ?? 3 + let priority2 = priorityOrder[task2.priority] ?? 3 + return priority1 < priority2 + } + } + + var isDarkMode: Bool { + entry.themeMode == "dark" + } + + var body: some View { + ZStack { + (isDarkMode ? Color.black : Color(white: 0.95)) + .ignoresSafeArea() + + VStack(alignment: .leading, spacing: 8) { + headerView + + if parsedTasks.isEmpty { + emptyStateView + } else { + switch family { + case .systemSmall: + smallTaskList + case .systemMedium: + mediumTaskList + case .systemLarge: + largeTaskList + default: + mediumTaskList + } + } + } + .padding(family == .systemLarge ? 12 : 10) + } + .colorScheme(isDarkMode ? .dark : .light) + } + + var headerView: some View { + HStack { + HStack(spacing: 4) { + Image(systemName: "checkmark.circle.fill") + .foregroundColor(.blue) + .imageScale(family == .systemSmall ? .small : .medium) + Text("Taskwarrior") + .font(family == .systemSmall ? .caption.bold() : .headline) + } + Spacer() + Image(systemName: "plus.circle.fill") + .foregroundColor(.blue) + .imageScale(family == .systemSmall ? .medium : .large) + .widgetURL(URL(string: "taskwarrior://addClicked")) + } + .padding(.bottom, 8) + } + + var emptyStateView: some View { + VStack(spacing: 4) { + Image(systemName: "checkmark.circle") + .font(.title2) + .foregroundColor(.secondary) + Text("No tasks available") + .font(.caption) + .foregroundColor(.secondary) + .multilineTextAlignment(.center) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + } + + // Small widget list with deeplinks (shows 3 tasks) + var smallTaskList: some View { + VStack(spacing: 4) { + ForEach(Array(sortedTasks.prefix(3))) { task in + smallTaskRow(task: task) + .widgetURL(URL(string: "taskwarrior://cardClicked?uuid=\(task.uuid)")) + } + if sortedTasks.count > 3 { + Text("+\(sortedTasks.count - 3) more") + .font(.caption2) + .foregroundColor(.secondary) + .frame(maxWidth: .infinity, alignment: .center) + } + } + } + + // Medium widget + var mediumTaskList: some View { + VStack(spacing: 4) { + ForEach(Array(sortedTasks.prefix(3))) { task in + mediumTaskRow(task: task) + .widgetURL(URL(string: "taskwarrior://cardClicked?uuid=\(task.uuid)")) + } + + if sortedTasks.count > 3 { + Text("+\(sortedTasks.count - 3) more tasks") + .font(.caption2) + .foregroundColor(.secondary) + .frame(maxWidth: .infinity, alignment: .center) + .padding(.top, 2) + } + } + } + + // Large widget + var largeTaskList: some View { + VStack(spacing: 6) { + // Display only top 4 tasks by priority + ForEach(Array(sortedTasks.prefix(4))) { task in + largeTaskRow(task: task) + .widgetURL(URL(string: "taskwarrior://cardClicked?uuid=\(task.uuid)")) + } + + if sortedTasks.count > 4 { + Text("+\(sortedTasks.count - 4) more tasks") + .font(.caption) + .foregroundColor(.secondary) + .frame(maxWidth: .infinity, alignment: .center) + .padding(.top, 4) + } + } + } + + func smallTaskRow(task: Task) -> some View { + HStack(spacing: 6) { + Circle() + .fill(task.priorityColor) + .frame(width: 8, height: 8) + Text(task.description) + .font(.caption2) + .lineLimit(1) + .truncationMode(.tail) + Spacer() + } + .padding(.vertical, 4) + .padding(.horizontal, 6) + .background( + RoundedRectangle(cornerRadius: 4) + .fill(Color(UIColor.secondarySystemBackground)) + ) + } + + func mediumTaskRow(task: Task) -> some View { + HStack(spacing: 8) { + Circle() + .fill(task.priorityColor) + .frame(width: 8, height: 8) + Text(task.description) + .font(.caption) + .lineLimit(1) + .truncationMode(.tail) + Spacer() + } + .padding(.vertical, 5) + .padding(.horizontal, 8) + .background( + RoundedRectangle(cornerRadius: 6) + .fill(Color(UIColor.secondarySystemBackground)) + ) + } + + func largeTaskRow(task: Task) -> some View { + HStack(spacing: 10) { + Circle() + .fill(task.priorityColor) + .frame(width: 10, height: 10) + VStack(alignment: .leading, spacing: 2) { + Text(task.description) + .font(.subheadline) + .lineLimit(1) + .truncationMode(.tail) + Text(task.urgency) + .font(.caption2) + .foregroundColor(.secondary) + } + Spacer() + } + .padding(.vertical, 6) + .padding(.horizontal, 8) + .background( + RoundedRectangle(cornerRadius: 8) + .fill(Color(UIColor.secondarySystemBackground)) + ) + .widgetURL(URL(string: "taskwarrior://cardClicked?uuid=\(task.uuid)")) + } +} + +struct TasksWidget: Widget { + let kind: String = "TaskWarriorWidgets" + + var body: some WidgetConfiguration { + AppIntentConfiguration(kind: kind, intent: ConfigurationAppIntent.self, provider: TaskProvider()) { entry in + TaskWidgetEntryView(entry: entry) + } + .configurationDisplayName("Tasks") + .description("Shows your pending tasks") + .supportedFamilies([.systemSmall, .systemMedium, .systemLarge]) + .contentMarginsDisabled() + } +} + +#Preview(as: .systemLarge) { + TasksWidget() +} timeline: { + TaskWidgetEntry( + date: .now, + tasks: "[{\"description\":\"High priority task\",\"urgency\":\"urgency: 5.8\",\"uuid\":\"123\",\"priority\":\"H\"}, {\"description\":\"Medium task\",\"urgency\":\"urgency: 3.2\",\"uuid\":\"456\",\"priority\":\"M\"}, {\"description\":\"Low priority task\",\"urgency\":\"urgency: 1.5\",\"uuid\":\"789\",\"priority\":\"L\"}, {\"description\":\"Another high priority\",\"urgency\":\"urgency: 5.9\",\"uuid\":\"124\",\"priority\":\"H\"}, {\"description\":\"Second medium task\",\"urgency\":\"urgency: 3.1\",\"uuid\":\"457\",\"priority\":\"M\"}, {\"description\":\"No priority task\",\"urgency\":\"urgency: 1.0\",\"uuid\":\"790\",\"priority\":\"N\"}]", + themeMode: "light", + configuration: ConfigurationAppIntent() + ) + TaskWidgetEntry( + date: .now, + tasks: "[{\"description\":\"Critical bug fix\",\"urgency\":\"urgency: 6.5\",\"uuid\":\"abc\",\"priority\":\"H\"}, {\"description\":\"Update docs\",\"urgency\":\"urgency: 2.8\",\"uuid\":\"def\",\"priority\":\"M\"}, {\"description\":\"Review PR\",\"urgency\":\"urgency: 4.5\",\"uuid\":\"ghi\",\"priority\":\"H\"}, {\"description\":\"Fix typos\",\"urgency\":\"urgency: 1.2\",\"uuid\":\"jkl\",\"priority\":\"L\"}, {\"description\":\"Add tests\",\"urgency\":\"urgency: 3.8\",\"uuid\":\"mno\",\"priority\":\"M\"}, {\"description\":\"Refactor code\",\"urgency\":\"urgency: 2.5\",\"uuid\":\"pqr\",\"priority\":\"N\"}]", + themeMode: "dark", + configuration: ConfigurationAppIntent() + ) +} diff --git a/ios/TaskWarriorWidgets/TaskWarriorWidgetsBundle.swift b/ios/TaskWarriorWidgets/TaskWarriorWidgetsBundle.swift new file mode 100644 index 00000000..f39f37ac --- /dev/null +++ b/ios/TaskWarriorWidgets/TaskWarriorWidgetsBundle.swift @@ -0,0 +1,14 @@ +import WidgetKit +import SwiftUI + + +// App group identifier for shared UserDefaults +let appGroupIdentifier = "group.taskwarrior" + +@main +struct TaskWarriorWidgetsBundle: WidgetBundle { + var body: some Widget { + TasksWidget() + ChartWidget() + } +} \ No newline at end of file diff --git a/ios/TaskWarriorWidgetsExtensionDebug.entitlements b/ios/TaskWarriorWidgetsExtensionDebug.entitlements new file mode 100644 index 00000000..6385ba4d --- /dev/null +++ b/ios/TaskWarriorWidgetsExtensionDebug.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.application-groups + + group.taskwarrior + + + diff --git a/lib/app/modules/home/controllers/home_controller.dart b/lib/app/modules/home/controllers/home_controller.dart index 32df5896..26935eb6 100644 --- a/lib/app/modules/home/controllers/home_controller.dart +++ b/lib/app/modules/home/controllers/home_controller.dart @@ -87,7 +87,7 @@ class HomeController extends GetxController { selectedSort, selectedTags, ], (_) { - if (Platform.isAndroid) { + if (Platform.isAndroid|| Platform.isIOS) { WidgetController widgetController = Get.put(WidgetController()); widgetController.fetchAllData(); @@ -581,7 +581,9 @@ class HomeController extends GetxController { selectedLanguage.value = AppSettings.selectedLanguage; HomeWidget.saveWidgetData( "themeMode", AppSettings.isDarkMode ? "dark" : "light"); - HomeWidget.updateWidget(androidName: "TaskWarriorWidgetProvider"); + HomeWidget.updateWidget( + androidName: "TaskWarriorWidgetProvider", + iOSName: "TaskWarriorWidgets"); // print("called and value is${isDarkModeOn.value}"); } diff --git a/lib/app/modules/home/controllers/widget.controller.dart b/lib/app/modules/home/controllers/widget.controller.dart index 5c17c844..9b3bd9b5 100644 --- a/lib/app/modules/home/controllers/widget.controller.dart +++ b/lib/app/modules/home/controllers/widget.controller.dart @@ -146,7 +146,7 @@ class WidgetController extends GetxController { Future updateWidget() async { try { return HomeWidget.updateWidget( - name: 'TaskWarriorWidgetProvider', iOSName: 'HomeWidgetExample'); + name: 'TaskWarriorWidgetProvider', iOSName: 'TaskWarriorWidgets'); } on PlatformException catch (exception) { debugPrint('Error Updating Widget. $exception'); } diff --git a/lib/app/modules/home/views/add_task_bottom_sheet.dart b/lib/app/modules/home/views/add_task_bottom_sheet.dart index 73d4f5d0..a990efe6 100644 --- a/lib/app/modules/home/views/add_task_bottom_sheet.dart +++ b/lib/app/modules/home/views/add_task_bottom_sheet.dart @@ -505,7 +505,7 @@ class AddTaskBottomSheet extends StatelessWidget { homeController.update(); // Navigator.of(context).pop(); Get.back(); - if (Platform.isAndroid) { + if (Platform.isAndroid||Platform.isIOS) { WidgetController widgetController = Get.put(WidgetController()); widgetController.fetchAllData(); diff --git a/lib/app/modules/home/views/tasks_builder.dart b/lib/app/modules/home/views/tasks_builder.dart index d17c5875..1ab1dbed 100644 --- a/lib/app/modules/home/views/tasks_builder.dart +++ b/lib/app/modules/home/views/tasks_builder.dart @@ -216,7 +216,7 @@ class TasksBuilder extends StatelessWidget { dtb!.add(const Duration(minutes: 1)); cancelNotification(task); } - if (Platform.isAndroid) { + if (Platform.isAndroid||Platform.isIOS) { WidgetController widgetController = Get.put(WidgetController()); widgetController.fetchAllData(); diff --git a/lib/app/modules/reports/controllers/reports_controller.dart b/lib/app/modules/reports/controllers/reports_controller.dart index 17ab277f..2f260163 100644 --- a/lib/app/modules/reports/controllers/reports_controller.dart +++ b/lib/app/modules/reports/controllers/reports_controller.dart @@ -88,7 +88,7 @@ class ReportsController extends GetxController await Future.delayed(const Duration(milliseconds: 500)); // Send a broadcast to update the widget - const platform = MethodChannel('com.example.taskwarrior/widget'); + const platform = MethodChannel('com.ccextractor.taskwarriorflutter/widget'); try { await platform.invokeMethod('updateWidget'); } on PlatformException catch (e) { diff --git a/lib/app/utils/app_settings/app_settings.dart b/lib/app/utils/app_settings/app_settings.dart index ce37244f..c4f990c9 100644 --- a/lib/app/utils/app_settings/app_settings.dart +++ b/lib/app/utils/app_settings/app_settings.dart @@ -1,3 +1,4 @@ +import 'package:home_widget/home_widget.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:taskwarrior/app/utils/language/supported_language.dart'; @@ -10,6 +11,7 @@ class AppSettings { static SupportedLanguage selectedLanguage = SupportedLanguage.english; static Future init() async { + await HomeWidget.setAppGroupId("group.taskwarrior"); await SelectedTheme.init(); await SelectedLanguage.init(); await SaveTourStatus.init(); diff --git a/linux/CMakeLists.txt b/linux/CMakeLists.txt index 3e5728a9..573a8199 100644 --- a/linux/CMakeLists.txt +++ b/linux/CMakeLists.txt @@ -7,7 +7,7 @@ project(runner LANGUAGES CXX) set(BINARY_NAME "taskwarrior") # The unique GTK application identifier for this application. See: # https://wiki.gnome.org/HowDoI/ChooseApplicationID -set(APPLICATION_ID "com.ccextractor.taskwarrior") +set(APPLICATION_ID "com.ccextractor.taskwarriorflutter") # Explicitly opt in to modern CMake behaviors to avoid warnings with recent # versions of CMake. diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj index 0c0079a0..6b783a15 100644 --- a/macos/Runner.xcodeproj/project.pbxproj +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -479,7 +479,7 @@ CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.ccextractor.taskwarrior.RunnerTests; + PRODUCT_BUNDLE_IDENTIFIER = com.ccextractor.taskwarriorflutter.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/taskwarrior.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/taskwarrior"; @@ -494,7 +494,7 @@ CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.ccextractor.taskwarrior.RunnerTests; + PRODUCT_BUNDLE_IDENTIFIER = com.ccextractor.taskwarriorflutter.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/taskwarrior.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/taskwarrior"; @@ -509,7 +509,7 @@ CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.ccextractor.taskwarrior.RunnerTests; + PRODUCT_BUNDLE_IDENTIFIER = com.ccextractor.taskwarriorflutter.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/taskwarrior.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/taskwarrior"; diff --git a/macos/Runner/Configs/AppInfo.xcconfig b/macos/Runner/Configs/AppInfo.xcconfig index 4a8c5a8d..25621bd6 100644 --- a/macos/Runner/Configs/AppInfo.xcconfig +++ b/macos/Runner/Configs/AppInfo.xcconfig @@ -8,7 +8,7 @@ PRODUCT_NAME = taskwarrior // The application's bundle identifier -PRODUCT_BUNDLE_IDENTIFIER = com.ccextractor.taskwarrior +PRODUCT_BUNDLE_IDENTIFIER = com.ccextractor.taskwarriorflutter // The copyright displayed in application information PRODUCT_COPYRIGHT = Copyright © 2024 com.ccextractor. All rights reserved. diff --git a/test/models/storage/client_test.dart b/test/models/storage/client_test.dart index d4bbbf28..f2beae6d 100644 --- a/test/models/storage/client_test.dart +++ b/test/models/storage/client_test.dart @@ -10,7 +10,7 @@ void main() { test('should return package name and version', () async { PackageInfo.setMockInitialValues( appName: 'App', - packageName: 'com.example.app', + packageName: 'com.ccextractor.app', version: '1.0.0', buildNumber: '1', buildSignature: '', @@ -18,7 +18,7 @@ void main() { final result = await client(); - expect(result, 'com.example.app 1.0.0'); + expect(result, 'com.ccextractor.app 1.0.0'); }); }); }