diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 7475c650..b6ffe5bd 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -1,69 +1,59 @@
-
+
-
-
-
-
-
+
+
+
+
+
+
+
-
+
+
-
-
-
-
-
-
+
-
+
-
-
-
-
+
-
+
+
+
+
-
-
-
+
+
+ android:resource="@xml/taskwarriorconfig" />
-
-
@@ -72,17 +62,15 @@
-
-
+
-
-
-
+
+
@@ -92,4 +80,4 @@
-
+
\ No newline at end of file
diff --git a/android/app/src/main/kotlin/com/ccextractor/taskwarriorflutter/BurndownChart.kt b/android/app/src/main/kotlin/com/ccextractor/taskwarriorflutter/BurndownChart.kt
deleted file mode 100644
index 7318b9de..00000000
--- a/android/app/src/main/kotlin/com/ccextractor/taskwarriorflutter/BurndownChart.kt
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.ccextractor.taskwarriorflutter
-
-import android.appwidget.AppWidgetManager
-import android.content.Context
-import android.content.SharedPreferences
-import es.antonborri.home_widget.HomeWidgetProvider
-
-class BurndownChartProvider : HomeWidgetProvider() {
- override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray, widgetData: SharedPreferences) {
- // This method is intentionally left blank.
- // Widget updates are handled by WidgetUpdateReceiver.
- }
-}
\ No newline at end of file
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..0e0b084b 100644
--- a/android/app/src/main/kotlin/com/ccextractor/taskwarriorflutter/MainActivity.kt
+++ b/android/app/src/main/kotlin/com/ccextractor/taskwarriorflutter/MainActivity.kt
@@ -1,36 +1,7 @@
package com.ccextractor.taskwarriorflutter
-import android.content.Intent
-import android.os.Bundle
import io.flutter.embedding.android.FlutterActivity
-import io.flutter.embedding.engine.FlutterEngine
-import io.flutter.plugin.common.MethodChannel
-import android.content.Context
-import android.content.IntentFilter
-import android.appwidget.AppWidgetManager
-import android.content.ComponentName
class MainActivity: FlutterActivity() {
- private val channel = "com.example.taskwarrior/widget"
-
- override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
- super.configureFlutterEngine(flutterEngine)
-
- MethodChannel(flutterEngine.dartExecutor.binaryMessenger, channel).setMethodCallHandler {
- call, result ->
- if (call.method == "updateWidget") {
- updateWidget()
- result.success("Widget updated")
- } else {
- result.notImplemented()
- }
- }
- }
-
- private fun updateWidget() {
- val intent = Intent(this, WidgetUpdateReceiver::class.java).apply {
- action = "UPDATE_WIDGET"
- }
- sendBroadcast(intent)
- }
+ // No custom code needed! The home_widget plugin attaches automatically.
}
\ No newline at end of file
diff --git a/android/app/src/main/kotlin/com/ccextractor/taskwarriorflutter/TaskWarriorWidgetProvider.kt b/android/app/src/main/kotlin/com/ccextractor/taskwarriorflutter/TaskWarriorWidgetProvider.kt
index 5662cd2a..0feba3f7 100644
--- a/android/app/src/main/kotlin/com/ccextractor/taskwarriorflutter/TaskWarriorWidgetProvider.kt
+++ b/android/app/src/main/kotlin/com/ccextractor/taskwarriorflutter/TaskWarriorWidgetProvider.kt
@@ -23,24 +23,33 @@ import android.os.Build
class TaskWarriorWidgetProvider : AppWidgetProvider() {
override fun onReceive(context: Context, intent: Intent) {
- // val myaction = intent.action
- if (intent.action == "TASK_ACTION") {
- val extras = intent.extras
- if(extras!=null){
- val uuid = extras.getString("uuid")?:""
- val add_task = extras.getString("launchedFor")
- val host = if(add_task == "ADD_TASK"){context.getString(R.string.app_widget_add_clicked_uri)}else{context.getString(R.string.app_widget_card_clicked_uri)}
- val launchIntent = Intent(context, MainActivity::class.java).apply {
- action = context.getString(R.string.app_widget_launch_action)
- data = Uri.parse("$host?uuid=$uuid")
- flags = Intent. FLAG_ACTIVITY_NEW_TASK
- context?.startActivity(this)
- }
- // HomeWidgetLaunchIntent.getActivity(context, MainActivity::class.java, Uri.parse("TaskWarrior://taskView?taskId=$uuid"))
- }
- }
- super.onReceive(context, intent)
- }
+ // Handle the custom action from your Widget buttons/list
+ if (intent.action == "TASK_ACTION") {
+ val uuid = intent.getStringExtra("uuid") ?: ""
+ val launchedFor = intent.getStringExtra("launchedFor")
+
+ // 1. Construct the URI exactly as Flutter expects it
+ // Scheme: taskwarrior://
+ // Host: cardclicked OR addclicked
+ val deepLinkUri = if (launchedFor == "ADD_TASK") {
+ Uri.parse("taskwarrior://addclicked")
+ } else {
+ // For list items, we attach the UUID
+ Uri.parse("taskwarrior://cardclicked?uuid=$uuid")
+ }
+
+ // 2. Create the Intent to open MainActivity
+ val launchIntent = Intent(context, MainActivity::class.java).apply {
+ action = Intent.ACTION_VIEW
+ data = deepLinkUri
+ // These flags ensure the app opens correctly whether running or not
+ flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
+ }
+
+ context.startActivity(launchIntent)
+ }
+ super.onReceive(context, intent)
+ }
fun getLayoutId(context: Context) : Int{
val sharedPrefs = HomeWidgetPlugin.getData(context)
val theme = sharedPrefs.getString("themeMode", "")
diff --git a/android/app/src/main/kotlin/com/ccextractor/taskwarriorflutter/WidgetUpdateReceiver.kt b/android/app/src/main/kotlin/com/ccextractor/taskwarriorflutter/WidgetUpdateReceiver.kt
deleted file mode 100644
index 49d9375c..00000000
--- a/android/app/src/main/kotlin/com/ccextractor/taskwarriorflutter/WidgetUpdateReceiver.kt
+++ /dev/null
@@ -1,68 +0,0 @@
-package com.ccextractor.taskwarriorflutter
-
-import android.appwidget.AppWidgetManager
-import android.view.View
-import android.content.BroadcastReceiver
-import android.content.ComponentName
-import android.content.Context
-import android.content.Intent
-import android.graphics.BitmapFactory
-import android.util.Log
-import android.widget.RemoteViews
-import java.io.File
-import com.ccextractor.taskwarriorflutter.R
-import es.antonborri.home_widget.HomeWidgetPlugin
-
-class WidgetUpdateReceiver : BroadcastReceiver() {
-
- override fun onReceive(context: Context, intent: Intent) {
- if (intent.action == "UPDATE_WIDGET") {
- Log.d("WidgetUpdateReceiver", "Received UPDATE_WIDGET broadcast")
-
- val appWidgetManager = AppWidgetManager.getInstance(context)
- val appWidgetIds = appWidgetManager.getAppWidgetIds(ComponentName(context, BurndownChartProvider::class.java))
-
- for (appWidgetId in appWidgetIds) {
- updateAppWidget(context, appWidgetManager, appWidgetId)
- }
- }
- }
-
- private fun updateAppWidget(context: Context, appWidgetManager: AppWidgetManager, appWidgetId: Int) {
- Log.d("WidgetUpdateReceiver", "Updating widget $appWidgetId")
-
- val views = RemoteViews(context.packageName, R.layout.report_layout)
-
- // Retrieve the chart image path from HomeWidget
- val chartImage = HomeWidgetPlugin.getData(context).getString("chart_image", null)
-
- if (chartImage != null) {
- Log.d("WidgetUpdateReceiver", "Chart image path: $chartImage")
- val file = File(chartImage)
- if (file.exists()) {
- Log.d("WidgetUpdateReceiver", "File exists!")
- val b = BitmapFactory.decodeFile(file.absolutePath)
- if (b != null) {
- Log.d("WidgetUpdateReceiver", "Bitmap decoded successfully!")
- views.setImageViewBitmap(R.id.widget_image, b)
- views.setViewVisibility(R.id.widget_image, View.VISIBLE)
- views.setViewVisibility(R.id.no_image_text, View.GONE)
- } else {
- Log.e("WidgetUpdateReceiver", "Bitmap decoding failed!")
- views.setViewVisibility(R.id.widget_image, View.GONE)
- views.setViewVisibility(R.id.no_image_text, View.VISIBLE)
- }
- } else {
- Log.e("WidgetUpdateReceiver", "File does not exist: $chartImage")
- views.setViewVisibility(R.id.widget_image, View.GONE)
- views.setViewVisibility(R.id.no_image_text, View.VISIBLE)
- }
- } else {
- Log.d("WidgetUpdateReceiver", "No chart image saved yet")
- views.setViewVisibility(R.id.widget_image, View.GONE)
- views.setViewVisibility(R.id.no_image_text, View.VISIBLE)
- }
-
- appWidgetManager.updateAppWidget(appWidgetId, views)
- }
-}
\ No newline at end of file
diff --git a/android/app/src/main/res/xml/burndownchartconfig.xml b/android/app/src/main/res/xml/burndownchartconfig.xml
deleted file mode 100644
index fff84148..00000000
--- a/android/app/src/main/res/xml/burndownchartconfig.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
\ No newline at end of file
diff --git a/build_itchelper b/build_itchelper
new file mode 100755
index 00000000..dbfb9ce3
--- /dev/null
+++ b/build_itchelper
@@ -0,0 +1,104 @@
+#!/bin/bash
+
+# --- Configuration ---
+CRATE_NAME="tc_helper"
+RUST_DIR="rust"
+IOS_DIR="ios"
+FRAMEWORK_NAME="tc_helper.framework"
+XCFRAMEWORK_NAME="tc_helper.xcframework"
+BUNDLE_ID="com.ccextractor.taskwarriorflutter.tc-helper"
+# !!! CRITICAL FIX !!!
+# We force the build to target iOS 13.0.
+# This fixes the "___chkstk_darwin" and "version mismatch" errors.
+export IPHONEOS_DEPLOYMENT_TARGET=13.0
+
+echo "🚀 Starting Build for $CRATE_NAME..."
+
+cd $RUST_DIR
+
+# 1. Build for Device (iPhone - arm64)
+echo "🛠️ Building for iPhone (arm64)..."
+# We pass the CFLAG to ensure C dependencies (like aws-lc-sys) respect the target
+CFLAGS="-miphoneos-version-min=13.0" \
+cargo build --release --target aarch64-apple-ios
+
+# 2. Build for Simulator (Apple Silicon - arm64)
+echo "🛠️ Building for Simulator (arm64)..."
+CFLAGS="-miphoneos-version-min=13.0" \
+cargo build --release --target aarch64-apple-ios-sim
+
+# 3. Build for Simulator (Intel - x86_64)
+echo "🛠️ Building for Simulator (x86_64)..."
+CFLAGS="-miphoneos-version-min=13.0" \
+cargo build --release --target x86_64-apple-ios
+
+cd ..
+
+# --- Package Creation ---
+echo "📦 Packaging..."
+
+# Create a temporary directory for the Simulator "Fat" library
+mkdir -p build/ios_sim
+SIM_LIB="build/ios_sim/lib$CRATE_NAME.dylib"
+
+# Combine the two Simulator architectures (Intel + M1) into one file
+lipo -create \
+ "$RUST_DIR/target/x86_64-apple-ios/release/lib$CRATE_NAME.dylib" \
+ "$RUST_DIR/target/aarch64-apple-ios-sim/release/lib$CRATE_NAME.dylib" \
+ -output "$SIM_LIB"
+
+# Define function to create a .framework structure
+create_framework() {
+ local LIB_PATH=$1
+ local TARGET_DIR=$2
+
+ mkdir -p "$TARGET_DIR/$FRAMEWORK_NAME"
+
+ # Copy and rename binary (libtc_helper.dylib -> tc_helper)
+ cp "$LIB_PATH" "$TARGET_DIR/$FRAMEWORK_NAME/$CRATE_NAME"
+
+ # Create Info.plist
+ cat < "$TARGET_DIR/$FRAMEWORK_NAME/Info.plist"
+
+
+
+
+ CFBundleExecutable
+ $CRATE_NAME
+ CFBundleIdentifier
+ $BUNDLE_ID
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundlePackageType
+ FMWK
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ 1.0
+ MinimumOSVersion
+ 13.0
+
+
+EOF
+}
+
+# Create framework structure for Simulator
+create_framework "$SIM_LIB" "build/ios_sim"
+
+# Create framework structure for Device
+mkdir -p build/ios_device
+create_framework "$RUST_DIR/target/aarch64-apple-ios/release/lib$CRATE_NAME.dylib" "build/ios_device"
+
+# --- Create XCFramework ---
+echo "🔗 Creating XCFramework..."
+rm -rf "$IOS_DIR/$XCFRAMEWORK_NAME"
+
+xcodebuild -create-xcframework \
+ -framework "build/ios_device/$FRAMEWORK_NAME" \
+ -framework "build/ios_sim/$FRAMEWORK_NAME" \
+ -output "$IOS_DIR/$XCFRAMEWORK_NAME"
+
+# Clean up temp build folder
+rm -rf build/ios_sim build/ios_device
+
+echo "✅ Success! Created $IOS_DIR/$XCFRAMEWORK_NAME"
\ No newline at end of file
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..23749b75 100644
--- a/ios/Runner.xcodeproj/project.pbxproj
+++ b/ios/Runner.xcodeproj/project.pbxproj
@@ -8,25 +8,51 @@
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
+ 197929182EE87FCF00023EC4 /* tc_helper.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 197929172EE87FCF00023EC4 /* tc_helper.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
64C7FEE08035BC9BB0C038EF /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 089D9819C0366C673FE320BF /* Pods_Runner.framework */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
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;
- buildActionMask = 2147483647;
+ buildActionMask = 12;
dstPath = "";
dstSubfolderSpec = 10;
files = (
+ 197929182EE87FCF00023EC4 /* tc_helper.xcframework in Embed Frameworks */,
);
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 */
@@ -34,6 +60,7 @@
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; };
162E9301614D923D4E360425 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; };
+ 197929172EE87FCF00023EC4 /* tc_helper.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = tc_helper.xcframework; sourceTree = ""; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
@@ -45,10 +72,40 @@
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 /* Exceptions for "TaskWarriorWidgets" folder in "TaskWarriorWidgetsExtension" target */ = {
+ isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
+ membershipExceptions = (
+ Info.plist,
+ );
+ target = AABC0EB42DA46F0C008D692D /* TaskWarriorWidgetsExtension */;
+ };
+/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
+
+/* Begin PBXFileSystemSynchronizedRootGroup section */
+ AABC0EBA2DA46F0C008D692D /* TaskWarriorWidgets */ = {
+ isa = PBXFileSystemSynchronizedRootGroup;
+ exceptions = (
+ AABC0ECB2DA46F0E008D692D /* Exceptions for "TaskWarriorWidgets" folder in "TaskWarriorWidgetsExtension" target */,
+ );
+ explicitFileTypes = {
+ };
+ explicitFolders = (
+ );
+ path = TaskWarriorWidgets;
+ sourceTree = "";
+ };
+/* End PBXFileSystemSynchronizedRootGroup section */
+
/* Begin PBXFrameworksBuildPhase section */
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
@@ -58,6 +115,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 +141,11 @@
97C146E51CF9000F007C117D = {
isa = PBXGroup;
children = (
+ 197929172EE87FCF00023EC4 /* tc_helper.xcframework */,
+ AABC0F822DA481B0008D692D /* TaskWarriorWidgetsExtensionDebug.entitlements */,
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
+ AABC0EBA2DA46F0C008D692D /* TaskWarriorWidgets */,
97C146EF1CF9000F007C117D /* Products */,
C3171261BB8F68A7E2CB70A5 /* Pods */,
EC04A4346BDDFBE60E9077C7 /* Frameworks */,
@@ -87,6 +156,7 @@
isa = PBXGroup;
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
+ AABC0EB52DA46F0C008D692D /* TaskWarriorWidgetsExtension.appex */,
);
name = Products;
sourceTree = "";
@@ -94,6 +164,7 @@
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
+ AABC0F812DA481A8008D692D /* RunnerDebug.entitlements */,
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
@@ -113,7 +184,6 @@
DB57389FBBE4024E44956E34 /* Pods-Runner.release.xcconfig */,
162E9301614D923D4E360425 /* Pods-Runner.profile.xcconfig */,
);
- name = Pods;
path = Pods;
sourceTree = "";
};
@@ -121,6 +191,8 @@
isa = PBXGroup;
children = (
089D9819C0366C673FE320BF /* Pods_Runner.framework */,
+ AABC0EB62DA46F0C008D692D /* WidgetKit.framework */,
+ AABC0EB82DA46F0C008D692D /* SwiftUI.framework */,
);
name = Frameworks;
sourceTree = "";
@@ -138,24 +210,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 +259,9 @@
CreatedOnToolsVersion = 7.3.1;
LastSwiftMigration = 1100;
};
+ AABC0EB42DA46F0C008D692D = {
+ CreatedOnToolsVersion = 16.2;
+ };
};
};
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
@@ -179,6 +278,7 @@
projectRoot = "";
targets = (
97C146ED1CF9000F007C117D /* Runner */,
+ AABC0EB42DA46F0C008D692D /* TaskWarriorWidgetsExtension */,
);
};
/* End PBXProject section */
@@ -195,9 +295,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 +404,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 +504,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 +625,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 +656,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 +664,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 +806,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..1b4601cd 100644
--- a/ios/Runner/AppDelegate.swift
+++ b/ios/Runner/AppDelegate.swift
@@ -1,16 +1,13 @@
import UIKit
import Flutter
-@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
- }
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
-}
+}
\ No newline at end of file
diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist
index a878453f..c32c5e5f 100644
--- a/ios/Runner/Info.plist
+++ b/ios/Runner/Info.plist
@@ -1,58 +1,71 @@
-
- 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
-
-
+
+ CFBundleURLTypes
+
+
+ CFBundleTypeRole
+ Editor
+ CFBundleURLName
+ taskwarrior
+ CFBundleURLSchemes
+
+ taskwarrior
+
+
+
+ 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/Assets.xcassets/taskwarrior.imageset/Contents.json b/ios/TaskWarriorWidgets/Assets.xcassets/taskwarrior.imageset/Contents.json
new file mode 100644
index 00000000..6c5aae59
--- /dev/null
+++ b/ios/TaskWarriorWidgets/Assets.xcassets/taskwarrior.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "filename" : "taskwarrior.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/ios/TaskWarriorWidgets/Assets.xcassets/taskwarrior.imageset/taskwarrior.png b/ios/TaskWarriorWidgets/Assets.xcassets/taskwarrior.imageset/taskwarrior.png
new file mode 100644
index 00000000..06fcb3df
Binary files /dev/null and b/ios/TaskWarriorWidgets/Assets.xcassets/taskwarrior.imageset/taskwarrior.png differ
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..0c71db14
--- /dev/null
+++ b/ios/TaskWarriorWidgets/TaskWarriorWidgets.swift
@@ -0,0 +1,292 @@
+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
+
+ // 1. DATA PARSING
+ 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 []
+ }
+
+ 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"
+ }
+
+ // 2. MAIN BODY LAYOUT
+ var body: some View {
+ ZStack {
+ // Background Layer
+ (isDarkMode ? Color.black : Color(white: 0.95))
+ .ignoresSafeArea()
+
+ // Content Layer
+ VStack(spacing: 0) {
+
+ // --- TOP BAR (Pinned) ---
+ headerView
+ .padding(.top, 12)
+ .padding(.horizontal, 16)
+ .padding(.bottom, 8) // Slightly tighter to fit content
+
+ // --- LIST AREA ---
+ if parsedTasks.isEmpty {
+ emptyStateView
+ } else {
+ VStack(alignment: .leading, spacing: 0) {
+ switch family {
+ case .systemMedium:
+ mediumTaskList
+ case .systemLarge:
+ largeTaskList
+ default:
+ mediumTaskList
+ }
+ }
+ .padding(.horizontal, 12)
+ }
+
+ // --- BOTTOM SPACER ---
+ Spacer()
+ }
+ }
+ .colorScheme(isDarkMode ? .dark : .light)
+ }
+
+ // 3. SUBVIEWS
+
+ var headerView: some View {
+ HStack {
+ HStack(spacing: 6) {
+ Image("taskwarrior")
+ .resizable()
+ .aspectRatio(contentMode: .fit)
+ .frame(width: 30, height: 30)
+
+
+ }
+ Spacer()
+ Text("Taskwarrior")
+ .font(.headline)
+ Spacer()
+
+ // Add Button
+ Link(destination: URL(string: "taskwarrior://addclicked")!) {
+ Image(systemName: "plus.circle.fill")
+ .foregroundColor(.blue)
+ .font(.system(size: 26))
+ }
+ }
+ }
+
+ var emptyStateView: some View {
+ VStack(spacing: 6) {
+ Spacer()
+ Image(systemName: "checkmark.circle")
+ .font(.largeTitle)
+ .foregroundColor(.secondary.opacity(0.5))
+ Text("No tasks pending")
+ .font(.caption)
+ .foregroundColor(.secondary)
+ Spacer()
+ }
+ }
+
+ // -- List Variations --
+
+ // MEDIUM: With urgency text removed, we can fit 3 items comfortably
+ var mediumTaskList: some View {
+ VStack(spacing: 4) { // Compact spacing
+ ForEach(Array(sortedTasks.prefix(2))) { task in
+ taskRow(task: task)
+ }
+
+ if sortedTasks.count > 2 {
+ Text("+\(sortedTasks.count - 2) more")
+ .font(.caption2)
+ .foregroundColor(.secondary)
+ .frame(maxWidth: .infinity, alignment: .center)
+ .padding(.trailing, 4)
+ }
+ }
+ }
+
+ // LARGE: With urgency text removed, we can fit 6 items
+ var largeTaskList: some View {
+ VStack(spacing: 7) {
+ ForEach(Array(sortedTasks.prefix(6))) { task in
+ taskRow(task: task)
+
+ }
+
+ if sortedTasks.count > 6 {
+ Text("+\(sortedTasks.count - 6) more tasks")
+ .font(.caption)
+ .foregroundColor(.secondary)
+ .frame(maxWidth: .infinity, alignment: .center)
+ .padding(.top, 2)
+ }
+ }
+ }
+
+ func taskRow(task: Task) -> some View {
+ let isRealTask = task.uuid != "NO_TASK"
+
+ return Link(destination: URL(string: "taskwarrior://cardclicked?uuid=\(task.uuid)")!) {
+ HStack(spacing: 10) {
+
+ // 1. Center alignment for status messages
+ if !isRealTask {
+ Spacer()
+ }
+
+ // 2. Priority Dot (Real tasks only)
+ if isRealTask {
+ Capsule()
+ .fill(task.priorityColor)
+ .frame(width: 10, height: 10)
+ }
+
+ // 3. Task Description
+ Text(task.description)
+ .font(.subheadline)
+ .fontWeight(.medium)
+ .lineLimit(1)
+ .foregroundColor(isRealTask ? .primary : .secondary)
+
+ // 4. Trailing Spacer
+ Spacer()
+ }
+ .padding(.vertical, 8)
+ .padding(.horizontal, 12)
+ .background(
+ RoundedRectangle(cornerRadius: 12)
+ // 5. Hide background if it's not a real task
+ .fill(isRealTask ? Color(UIColor.secondarySystemBackground) : Color.clear)
+ )
+ }
+ .fixedSize(horizontal: false, vertical: true)
+ }
+}
+
+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([.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()
+ )
+}
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/ios/tc_helper.xcframework/Info.plist b/ios/tc_helper.xcframework/Info.plist
new file mode 100644
index 00000000..cf3e2802
--- /dev/null
+++ b/ios/tc_helper.xcframework/Info.plist
@@ -0,0 +1,44 @@
+
+
+
+
+ AvailableLibraries
+
+
+ BinaryPath
+ tc_helper.framework/tc_helper
+ LibraryIdentifier
+ ios-arm64
+ LibraryPath
+ tc_helper.framework
+ SupportedArchitectures
+
+ arm64
+
+ SupportedPlatform
+ ios
+
+
+ BinaryPath
+ tc_helper.framework/tc_helper
+ LibraryIdentifier
+ ios-arm64_x86_64-simulator
+ LibraryPath
+ tc_helper.framework
+ SupportedArchitectures
+
+ arm64
+ x86_64
+
+ SupportedPlatform
+ ios
+ SupportedPlatformVariant
+ simulator
+
+
+ CFBundlePackageType
+ XFWK
+ XCFrameworkFormatVersion
+ 1.0
+
+
diff --git a/ios/tc_helper.xcframework/ios-arm64/tc_helper.framework/Info.plist b/ios/tc_helper.xcframework/ios-arm64/tc_helper.framework/Info.plist
new file mode 100644
index 00000000..4117562b
--- /dev/null
+++ b/ios/tc_helper.xcframework/ios-arm64/tc_helper.framework/Info.plist
@@ -0,0 +1,20 @@
+
+
+
+
+ CFBundleExecutable
+ tc_helper
+ CFBundleIdentifier
+ com.ccextractor.taskwarriorflutter.tc-helper
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundlePackageType
+ FMWK
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ 1.0
+ MinimumOSVersion
+ 13.0
+
+
diff --git a/ios/tc_helper.xcframework/ios-arm64/tc_helper.framework/tc_helper b/ios/tc_helper.xcframework/ios-arm64/tc_helper.framework/tc_helper
new file mode 100755
index 00000000..b471a32f
Binary files /dev/null and b/ios/tc_helper.xcframework/ios-arm64/tc_helper.framework/tc_helper differ
diff --git a/ios/tc_helper.xcframework/ios-arm64_x86_64-simulator/tc_helper.framework/Info.plist b/ios/tc_helper.xcframework/ios-arm64_x86_64-simulator/tc_helper.framework/Info.plist
new file mode 100644
index 00000000..4117562b
--- /dev/null
+++ b/ios/tc_helper.xcframework/ios-arm64_x86_64-simulator/tc_helper.framework/Info.plist
@@ -0,0 +1,20 @@
+
+
+
+
+ CFBundleExecutable
+ tc_helper
+ CFBundleIdentifier
+ com.ccextractor.taskwarriorflutter.tc-helper
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundlePackageType
+ FMWK
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ 1.0
+ MinimumOSVersion
+ 13.0
+
+
diff --git a/ios/tc_helper.xcframework/ios-arm64_x86_64-simulator/tc_helper.framework/tc_helper b/ios/tc_helper.xcframework/ios-arm64_x86_64-simulator/tc_helper.framework/tc_helper
new file mode 100755
index 00000000..60e3787d
Binary files /dev/null and b/ios/tc_helper.xcframework/ios-arm64_x86_64-simulator/tc_helper.framework/tc_helper differ
diff --git a/lib/app/modules/home/controllers/home_controller.dart b/lib/app/modules/home/controllers/home_controller.dart
index 2bb2f105..a7b6d353 100644
--- a/lib/app/modules/home/controllers/home_controller.dart
+++ b/lib/app/modules/home/controllers/home_controller.dart
@@ -20,6 +20,7 @@ import 'package:taskwarrior/app/modules/home/controllers/widget.controller.dart'
import 'package:taskwarrior/app/modules/home/views/add_task_bottom_sheet_new.dart';
import 'package:taskwarrior/app/modules/splash/controllers/splash_controller.dart';
import 'package:taskwarrior/app/routes/app_pages.dart';
+import 'package:taskwarrior/app/services/deep_link_service.dart';
import 'package:taskwarrior/app/services/tag_filter.dart';
import 'package:taskwarrior/app/tour/filter_drawer_tour.dart';
import 'package:taskwarrior/app/tour/home_page_tour.dart';
@@ -87,9 +88,6 @@ class HomeController extends GetxController {
taskdb.open();
getUniqueProjects();
_loadTaskChampion();
- if (Platform.isAndroid) {
- handleHomeWidgetClicked();
- }
fetchTasksFromDB();
ever(taskReplica, (_) {
if (taskReplica.value) refreshReplicaTaskList();
@@ -102,12 +100,11 @@ class HomeController extends GetxController {
selectedSort,
selectedTags,
tasks,
- tasksFromReplica
+ tasksFromReplica,
], (_) {
- if (Platform.isAndroid) {
+ if (Platform.isAndroid || Platform.isIOS) {
WidgetController widgetController = Get.put(WidgetController());
widgetController.fetchAllData();
-
widgetController.update();
}
});
@@ -116,7 +113,7 @@ class HomeController extends GetxController {
"TW3") {
refreshTaskWithNewProfile();
}
- if (Platform.isAndroid) {
+ if (Platform.isAndroid || Platform.isIOS) {
WidgetController widgetController = Get.put(WidgetController());
widgetController.fetchAllData();
widgetController.updateWidget();
@@ -124,6 +121,14 @@ class HomeController extends GetxController {
});
}
+ @override
+ void onReady() {
+ super.onReady();
+ if (Get.isRegistered()) {
+ Get.find().consumePendingActions(this);
+ }
+ }
+
Future> getUniqueProjects() async {
if (taskReplica.value) {
return tasksFromReplica
@@ -626,6 +631,16 @@ class HomeController extends GetxController {
_refreshTasks();
}
+ void changeInDirectory() {
+ print("directory change to ${splashController.baseDirectory.value.path}");
+ storage = Storage(
+ Directory(
+ '${splashController.baseDirectory.value.path}/profiles/${splashController.currentProfile.value}',
+ ),
+ );
+ _refreshTasks();
+ }
+
RxBool useDelayTask = false.obs;
Future loadDelayTask() async {
@@ -640,7 +655,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}");
}
@@ -766,46 +783,48 @@ class HomeController extends GetxController {
late RxString uuid = "".obs;
late RxBool isHomeWidgetTaskTapped = false.obs;
- void handleHomeWidgetClicked() async {
- Uri? uri = await HomeWidget.initiallyLaunchedFromHomeWidget();
- if (uri != null) {
- if (uri.host == "cardclicked") {
- if (uri.queryParameters["uuid"] != null &&
- !taskchampion.value &&
- !taskReplica.value) {
- uuid.value = uri.queryParameters["uuid"] as String;
- isHomeWidgetTaskTapped.value = true;
- Future.delayed(Duration.zero, () {
- Get.toNamed(Routes.DETAIL_ROUTE, arguments: ["uuid", uuid.value]);
- });
- }
- } else if (uri.host == "addclicked") {
- showAddDialogAfterWidgetClick();
- }
- }
- HomeWidget.widgetClicked.listen((uri) async {
- if (uri != null) {
- if (uri.host == "cardclicked") {
- if (uri.queryParameters["uuid"] != null &&
- !taskchampion.value &&
- !taskReplica.value) {
- uuid.value = uri.queryParameters["uuid"] as String;
- isHomeWidgetTaskTapped.value = true;
-
- debugPrint('uuid is $uuid');
- Get.toNamed(Routes.DETAIL_ROUTE, arguments: ["uuid", uuid.value]);
- }
- } else if (uri.host == "addclicked") {
- showAddDialogAfterWidgetClick();
- }
- }
- });
- }
-
- void showAddDialogAfterWidgetClick() {
- Widget showDialog = Material(
- child: AddTaskBottomSheet(
- homeController: this, forTaskC: taskchampion.value));
- Get.dialog(showDialog);
- }
+ // void handleHomeWidgetClicked() async {
+ // Uri? uri = await HomeWidget.initiallyLaunchedFromHomeWidget();
+ // if (uri != null) {
+ // if (uri.host == "cardclicked") {
+ // if (uri.queryParameters["uuid"] != null &&
+ // !taskchampion.value &&
+ // !taskReplica.value) {
+ // uuid.value = uri.queryParameters["uuid"] as String;
+ // isHomeWidgetTaskTapped.value = true;
+ // Future.delayed(Duration.zero, () {
+ // Get.toNamed(Routes.DETAIL_ROUTE, arguments: ["uuid", uuid.value]);
+ // });
+ // }
+ // } else if (uri.host == "addclicked") {
+ // showAddDialogAfterWidgetClick();
+ // }
+ // }
+ // HomeWidget.widgetClicked.listen((uri) async {
+ // if (uri != null) {
+ // if (uri.host == "cardclicked") {
+ // if (uri.queryParameters["uuid"] != null &&
+ // !taskchampion.value &&
+ // !taskReplica.value) {
+ // uuid.value = uri.queryParameters["uuid"] as String;
+ // isHomeWidgetTaskTapped.value = true;
+
+ // debugPrint('uuid is $uuid');
+ // Get.toNamed(Routes.DETAIL_ROUTE, arguments: ["uuid", uuid.value]);
+ // }
+ // } else if (uri.host == "addclicked") {
+ // showAddDialogAfterWidgetClick();
+ // }
+ // }
+ // });
+ // }
+
+ // void showAddDialogAfterWidgetClick() {
+ // Widget showDialog = Material(
+ // child: AddTaskBottomSheet(
+ // homeController: this,
+ // forTaskC: taskchampion.value,
+ // forReplica: taskReplica.value));
+ // Get.dialog(showDialog);
+ // }
}
diff --git a/lib/app/modules/home/controllers/widget.controller.dart b/lib/app/modules/home/controllers/widget.controller.dart
index 0e14775b..4137f137 100644
--- a/lib/app/modules/home/controllers/widget.controller.dart
+++ b/lib/app/modules/home/controllers/widget.controller.dart
@@ -182,7 +182,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_new.dart b/lib/app/modules/home/views/add_task_bottom_sheet_new.dart
index 05d290ce..2679b140 100644
--- a/lib/app/modules/home/views/add_task_bottom_sheet_new.dart
+++ b/lib/app/modules/home/views/add_task_bottom_sheet_new.dart
@@ -423,6 +423,12 @@ class AddTaskBottomSheet extends StatelessWidget {
if (value) {
storageWidget.synchronize(context, true);
}
+ if (Platform.isAndroid || Platform.isIOS) {
+ WidgetController widgetController = Get.put(WidgetController());
+ widgetController.fetchAllData();
+
+ widgetController.update();
+ }
} on FormatException catch (e) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(
diff --git a/lib/app/modules/home/views/tasks_builder.dart b/lib/app/modules/home/views/tasks_builder.dart
index abdb0a2c..54846126 100644
--- a/lib/app/modules/home/views/tasks_builder.dart
+++ b/lib/app/modules/home/views/tasks_builder.dart
@@ -220,7 +220,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 d46ee206..fd81d97e 100644
--- a/lib/app/modules/reports/controllers/reports_controller.dart
+++ b/lib/app/modules/reports/controllers/reports_controller.dart
@@ -1,6 +1,4 @@
import 'dart:io';
-import 'package:home_widget/home_widget.dart';
-import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:get/get.dart';
@@ -15,8 +13,6 @@ import 'package:taskwarrior/app/utils/constants/taskwarrior_fonts.dart';
import 'package:taskwarrior/app/utils/constants/utilites.dart';
import 'package:taskwarrior/app/utils/gen/fonts.gen.dart';
import 'package:taskwarrior/app/utils/app_settings/app_settings.dart';
-import 'package:path_provider/path_provider.dart';
-import 'package:flutter/services.dart';
import 'package:taskwarrior/app/v3/db/task_database.dart';
import 'package:taskwarrior/app/v3/models/task.dart';
import 'package:tutorial_coach_mark/tutorial_coach_mark.dart';
@@ -36,69 +32,6 @@ class ReportsController extends GetxController
late Storage storage;
var storageWidget;
- final GlobalKey _chartKey = GlobalKey();
-
- GlobalKey get chartKey => _chartKey;
-
- Future captureChart() async {
- try {
- if (chartKey.currentContext == null) {
- print('Error: chartKey.currentContext is null');
- return;
- }
-
- RenderRepaintBoundary? boundary =
- chartKey.currentContext!.findRenderObject() as RenderRepaintBoundary?;
-
- if (boundary == null) {
- print('Error: boundary is null');
- return;
- }
-
- final image = await boundary.toImage();
- final byteData = await image.toByteData(format: ImageByteFormat.png);
-
- if (byteData == null) {
- print('Error: byteData is null');
- return;
- }
-
- final pngBytes = byteData.buffer.asUint8List();
-
- // Get the documents directory
- final directory = await getApplicationDocumentsDirectory();
- final imagePath = '${directory.path}/daily_burndown_chart.png';
-
- // Save the image to the documents directory
- File imgFile = File(imagePath);
- await imgFile.writeAsBytes(pngBytes);
- print('Image saved to: $imagePath');
-
- // Save the image path to HomeWidget
- await HomeWidget.saveWidgetData('chart_image', imagePath);
-
- // Verify that the file exists
- if (await imgFile.exists()) {
- print('Image file exists!');
- } else {
- print('Image file does not exist!');
- }
-
- // Add a delay before sending the broadcast
- await Future.delayed(const Duration(milliseconds: 500));
-
- // Send a broadcast to update the widget
- const platform = MethodChannel('com.example.taskwarrior/widget');
- try {
- await platform.invokeMethod('updateWidget');
- } on PlatformException catch (e) {
- print("Failed to Invoke: '${e.message}'.");
- }
- } catch (e) {
- print('Error capturing chart: $e');
- }
- }
-
// void _initReportsTour() {
// tutorialCoachMark = TutorialCoachMark(
// targets: reportsDrawer(
diff --git a/lib/app/modules/reports/views/burn_down_daily.dart b/lib/app/modules/reports/views/burn_down_daily.dart
index 2e4ed6c5..dd459087 100644
--- a/lib/app/modules/reports/views/burn_down_daily.dart
+++ b/lib/app/modules/reports/views/burn_down_daily.dart
@@ -17,122 +17,96 @@ class BurnDownDaily extends StatelessWidget {
@override
Widget build(BuildContext context) {
+ // Keeping your Theme change
TaskwarriorColorTheme tColors =
Theme.of(context).extension()!;
double height = MediaQuery.of(context).size.height;
- return Stack(
- children: [
- Column(
- mainAxisAlignment: MainAxisAlignment.center,
- crossAxisAlignment: CrossAxisAlignment.center,
- children: [
- Expanded(
- child: SizedBox(
- height: height * 0.6,
- child: RepaintBoundary(
- key: reportsController.chartKey,
- child: Obx(
- () => SfCartesianChart(
- primaryXAxis: CategoryAxis(
- title: AxisTitle(
- text: SentenceManager(
- currentLanguage: AppSettings.selectedLanguage)
- .sentences
- .reportsPageDailyDayMonth,
- textStyle: TextStyle(
- fontFamily: FontFamily.poppins,
- fontWeight: TaskWarriorFonts.bold,
- color: tColors.primaryTextColor,
- fontSize: TaskWarriorFonts.fontSizeSmall,
- ),
- ),
- ),
- primaryYAxis: NumericAxis(
- title: AxisTitle(
- text: SentenceManager(
- currentLanguage: AppSettings.selectedLanguage)
- .sentences
- .reportsPageTasks,
- textStyle: TextStyle(
- fontFamily: FontFamily.poppins,
- fontWeight: TaskWarriorFonts.bold,
- color: tColors.primaryTextColor,
- fontSize: TaskWarriorFonts.fontSizeSmall,
- ),
- ),
- ),
- tooltipBehavior:
- reportsController.dailyBurndownTooltipBehaviour,
- series: [
- /// This is the completed tasks
- StackedColumnSeries(
- groupName: 'Group A',
- enableTooltip: true,
- color: TaskWarriorColors.green,
- dataSource: reportsController.dailyInfo.entries
- .map((entry) => ChartData(
- entry.key,
- entry.value['pending'] ?? 0,
- entry.value['completed'] ?? 0,
- ))
- .toList(),
- xValueMapper: (ChartData data, _) => data.x,
- yValueMapper: (ChartData data, _) => data.y2,
- name: 'Completed',
- ),
- /// This is the pending tasks
- StackedColumnSeries(
- groupName: 'Group A',
- color: TaskWarriorColors.yellow,
- enableTooltip: true,
- dataSource: reportsController.dailyInfo.entries
- .map((entry) => ChartData(
- entry.key,
- entry.value['pending'] ?? 0,
- entry.value['completed'] ?? 0,
- ))
- .toList(),
- xValueMapper: (ChartData data, _) => data.x,
- yValueMapper: (ChartData data, _) => data.y1,
- name: 'Pending',
- ),
- ],
+ // Removed the Stack and Positioned button since the controller doesn't support capturing yet
+ return Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Expanded(
+ child: SizedBox(
+ height: height * 0.6,
+ // Removed RepaintBoundary and chartKey
+ child: Obx(
+ () => SfCartesianChart(
+ primaryXAxis: CategoryAxis(
+ title: AxisTitle(
+ text: SentenceManager(
+ currentLanguage: AppSettings.selectedLanguage)
+ .sentences
+ .reportsPageDailyDayMonth,
+ textStyle: TextStyle(
+ fontFamily: FontFamily.poppins,
+ fontWeight: TaskWarriorFonts.bold,
+ color: tColors.primaryTextColor,
+ fontSize: TaskWarriorFonts.fontSizeSmall,
),
),
),
- ),
- ),
- CommonChartIndicator(
- title:
- SentenceManager(currentLanguage: AppSettings.selectedLanguage)
- .sentences
- .reportsPageDailyBurnDownChart,
- ),
- ],
- ),
- Positioned(
- bottom: 16,
- right: 16,
- child: InkWell(
- onTap: () {
- WidgetsBinding.instance.addPostFrameCallback((_) {
- reportsController.captureChart();
- });
- },
- child: Container(
- padding: const EdgeInsets.all(8),
- decoration: BoxDecoration(
- color: Colors.grey.withOpacity(0.3), // Adjust opacity as needed
- borderRadius: BorderRadius.circular(8),
- ),
- child: const Icon(
- Icons.refresh,
- color: Colors.white,
+ primaryYAxis: NumericAxis(
+ title: AxisTitle(
+ text: SentenceManager(
+ currentLanguage: AppSettings.selectedLanguage)
+ .sentences
+ .reportsPageTasks,
+ textStyle: TextStyle(
+ fontFamily: FontFamily.poppins,
+ fontWeight: TaskWarriorFonts.bold,
+ color: tColors.primaryTextColor,
+ fontSize: TaskWarriorFonts.fontSizeSmall,
+ ),
+ ),
+ ),
+ tooltipBehavior:
+ reportsController.dailyBurndownTooltipBehaviour,
+ series: [
+ /// This is the completed tasks
+ StackedColumnSeries(
+ groupName: 'Group A',
+ enableTooltip: true,
+ color: TaskWarriorColors.green,
+ dataSource: reportsController.dailyInfo.entries
+ .map((entry) => ChartData(
+ entry.key,
+ entry.value['pending'] ?? 0,
+ entry.value['completed'] ?? 0,
+ ))
+ .toList(),
+ xValueMapper: (ChartData data, _) => data.x,
+ yValueMapper: (ChartData data, _) => data.y2,
+ name: 'Completed',
+ ),
+
+ /// This is the pending tasks
+ StackedColumnSeries(
+ groupName: 'Group A',
+ color: TaskWarriorColors.yellow,
+ enableTooltip: true,
+ dataSource: reportsController.dailyInfo.entries
+ .map((entry) => ChartData(
+ entry.key,
+ entry.value['pending'] ?? 0,
+ entry.value['completed'] ?? 0,
+ ))
+ .toList(),
+ xValueMapper: (ChartData data, _) => data.x,
+ yValueMapper: (ChartData data, _) => data.y1,
+ name: 'Pending',
+ ),
+ ],
),
),
),
),
+ CommonChartIndicator(
+ title: SentenceManager(currentLanguage: AppSettings.selectedLanguage)
+ .sentences
+ .reportsPageDailyBurnDownChart,
+ ),
],
);
}
diff --git a/lib/app/modules/splash/controllers/splash_controller.dart b/lib/app/modules/splash/controllers/splash_controller.dart
index 1f99f6df..ec60796c 100644
--- a/lib/app/modules/splash/controllers/splash_controller.dart
+++ b/lib/app/modules/splash/controllers/splash_controller.dart
@@ -59,6 +59,7 @@ class SplashController extends GetxController {
void setBaseDirectory(Directory newBaseDirectory) {
baseDirectory.value = newBaseDirectory;
profilesMap.value = _profiles.profilesMap();
+ Get.find().changeInDirectory();
}
void addProfile() {
diff --git a/lib/app/services/deep_link_service.dart b/lib/app/services/deep_link_service.dart
new file mode 100644
index 00000000..f016c93c
--- /dev/null
+++ b/lib/app/services/deep_link_service.dart
@@ -0,0 +1,69 @@
+import 'package:flutter/material.dart';
+import 'package:get/get.dart';
+import 'package:app_links/app_links.dart';
+import 'package:taskwarrior/app/modules/home/views/add_task_bottom_sheet_new.dart';
+import 'package:taskwarrior/app/modules/home/controllers/home_controller.dart';
+import 'package:taskwarrior/app/routes/app_pages.dart';
+
+class DeepLinkService extends GetxService {
+ late AppLinks _appLinks;
+ Uri? _queuedUri;
+
+ @override
+ void onReady() {
+ super.onReady();
+ _initDeepLinks();
+ }
+
+ void _initDeepLinks() {
+ _appLinks = AppLinks();
+ _appLinks.uriLinkStream.listen((uri) {
+ debugPrint('🔗 LINK RECEIVED: $uri');
+ _handleWidgetUri(uri);
+ });
+ }
+
+ void _handleWidgetUri(Uri uri) {
+ if (Get.isRegistered()) {
+ _executeAction(uri, Get.find());
+ } else {
+ debugPrint("⏳ HomeController not ready. Queuing action.");
+ _queuedUri = uri;
+ }
+ }
+
+ void consumePendingActions(HomeController controller) {
+ if (_queuedUri != null) {
+ debugPrint("🚀 Executing queued action...");
+ _executeAction(_queuedUri!, controller);
+ _queuedUri = null;
+ }
+ }
+
+ void _executeAction(Uri uri, HomeController controller) {
+ final bool isTaskChampion = controller.taskchampion.value;
+ final bool isReplica = controller.taskReplica.value;
+
+ if (uri.host == "cardclicked") {
+ if (uri.queryParameters["uuid"] != null &&
+ uri.queryParameters["uuid"] != "NO_TASK" &&
+ !isTaskChampion &&
+ !isReplica) {
+ String uuid = uri.queryParameters["uuid"] as String;
+ Get.toNamed(Routes.DETAIL_ROUTE, arguments: ["uuid", uuid]);
+ }
+ } else if (uri.host == "addclicked") {
+ if (Get.context != null) {
+ Get.dialog(
+ Material(
+ child: AddTaskBottomSheet(
+ homeController: controller,
+ forTaskC: isTaskChampion,
+ forReplica: isReplica,
+ ),
+ ),
+ );
+ }
+ }
+ }
+}
diff --git a/lib/app/utils/app_settings/app_settings.dart b/lib/app/utils/app_settings/app_settings.dart
index 6c27b838..04453bf2 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/lib/app/utils/taskfunctions/profiles.dart b/lib/app/utils/taskfunctions/profiles.dart
index ff51482c..67e5f74a 100644
--- a/lib/app/utils/taskfunctions/profiles.dart
+++ b/lib/app/utils/taskfunctions/profiles.dart
@@ -3,6 +3,7 @@
import 'dart:collection';
import 'dart:io';
+import 'package:flutter/widgets.dart';
import 'package:sqflite/sqflite.dart';
import 'package:taskwarrior/app/models/storage.dart';
import 'package:uuid/uuid.dart';
@@ -71,6 +72,7 @@ class Profiles {
entity.uri.pathSegments.lastWhere((segment) => segment.isNotEmpty))
.toList()
..sort(comparator);
+ debugPrint('Profiles found: $dirs');
return dirs;
}
diff --git a/lib/main.dart b/lib/main.dart
index 7ea0e8df..09f0268c 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -1,8 +1,12 @@
import 'dart:ffi';
-
+import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
+// 1. Add this import
+import 'package:app_links/app_links.dart';
+import 'package:taskwarrior/app/services/deep_link_service.dart';
+
import 'package:taskwarrior/app/utils/app_settings/app_settings.dart';
import 'package:taskwarrior/app/utils/debug_logger/log_databse_helper.dart';
import 'package:taskwarrior/app/utils/themes/dark_theme.dart';
@@ -12,9 +16,17 @@ import 'app/routes/app_pages.dart';
LogDatabaseHelper _logDatabaseHelper = LogDatabaseHelper();
-const buildOfTcHelperForAndroid = "libtc_helper.so";
-final dyLibOfTcHelperForAndroid =
- DynamicLibrary.open(buildOfTcHelperForAndroid);
+DynamicLibrary loadNativeLibrary() {
+ if (Platform.isIOS) {
+ return DynamicLibrary.open('Frameworks/tc_helper.framework/tc_helper');
+ } else if (Platform.isAndroid) {
+ return DynamicLibrary.open('libtc_helper.so');
+ } else if (Platform.isMacOS) {
+ return DynamicLibrary.open('tc_helper.framework/tc_helper');
+ }
+ throw UnsupportedError(
+ 'Platform ${Platform.operatingSystem} is not supported');
+}
void main() async {
debugPrint = (String? message, {int? wrapWidth}) {
@@ -24,11 +36,13 @@ void main() async {
}
};
+ loadNativeLibrary();
await RustLib.init();
WidgetsFlutterBinding.ensureInitialized();
await AppSettings.init();
+ Get.put(DeepLinkService(), permanent: true);
runApp(
GetMaterialApp(
darkTheme: darkTheme,
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/pubspec.yaml b/pubspec.yaml
index 47a0cb14..d8646792 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -68,6 +68,7 @@ dependencies:
path_provider: ^2.1.5
flutter_rust_bridge: ^2.11.1
ffi: any # Required for FFI
+ app_links: ^6.4.1
dev_dependencies:
build_runner: null
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');
});
});
}