diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5182029e..a5006321 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,8 +4,8 @@ # Getting Started -You can setup taskserver to sync your tasks across clients. -You can find the steps to setup one [here](https://github.com/CCExtractor/taskwarrior-flutter?tab=readme-ov-file#taskserver-setup) +In order to use the mobile app, you would need the flood backend running on your local machine. There are several ways +you can do that and all these approaches can be found at [Taskwarrior-Flutter](https://github.com/CCExtractor/taskwarrior-flutter/wiki) ## Steps diff --git a/README.md b/README.md index a60307d0..7b40c054 100644 --- a/README.md +++ b/README.md @@ -115,13 +115,9 @@ flutter doctor 5. Run the app: ```bash -flutter run --flavor production +flutter run ``` -OR -```bash -flutter run --flavor nightly -``` ## Contributing Help is always appreciated, whether it comes in the form of feature requests or suggestions, code improvements, refactoring, or performance enhancements. The more is done, the better it gets. If you find any bug(s), consider opening an [issue](https://github.com/NishantSinghal19/taskwarrior-gsoc/issues/new). diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index b6ffe5bd..7475c650 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,59 +1,69 @@ - + - - - - - - + + + + + - - + - - + + + + + + - + - + + + + - - - - + - - - + + + android:resource="@xml/burndownchartconfig" /> + + @@ -62,15 +72,17 @@ - - + - - + + + @@ -80,4 +92,4 @@ - \ No newline at end of file + diff --git a/android/app/src/main/jniLibs/arm64-v8a/libcrc_fast-b3182f249ae653b7.so b/android/app/src/main/jniLibs/arm64-v8a/libcrc_fast-b3182f249ae653b7.so deleted file mode 100755 index 3a8b6827..00000000 Binary files a/android/app/src/main/jniLibs/arm64-v8a/libcrc_fast-b3182f249ae653b7.so and /dev/null differ diff --git a/android/app/src/main/jniLibs/arm64-v8a/libcrc_fast-b481d560940c8edb.so b/android/app/src/main/jniLibs/arm64-v8a/libcrc_fast-b481d560940c8edb.so new file mode 100755 index 00000000..3c91e621 Binary files /dev/null and b/android/app/src/main/jniLibs/arm64-v8a/libcrc_fast-b481d560940c8edb.so differ diff --git a/android/app/src/main/jniLibs/arm64-v8a/libtc_helper.so b/android/app/src/main/jniLibs/arm64-v8a/libtc_helper.so index b0fe44b3..60c2cd68 100755 Binary files a/android/app/src/main/jniLibs/arm64-v8a/libtc_helper.so and b/android/app/src/main/jniLibs/arm64-v8a/libtc_helper.so differ diff --git a/android/app/src/main/jniLibs/armeabi-v7a/libcrc_fast-c8bd19aec5c73f3a.so b/android/app/src/main/jniLibs/armeabi-v7a/libcrc_fast-c8bd19aec5c73f3a.so deleted file mode 100755 index df205bcd..00000000 Binary files a/android/app/src/main/jniLibs/armeabi-v7a/libcrc_fast-c8bd19aec5c73f3a.so and /dev/null differ diff --git a/android/app/src/main/jniLibs/armeabi-v7a/libtc_helper.so b/android/app/src/main/jniLibs/armeabi-v7a/libtc_helper.so deleted file mode 100755 index 47fd5b25..00000000 Binary files a/android/app/src/main/jniLibs/armeabi-v7a/libtc_helper.so and /dev/null differ diff --git a/android/app/src/main/kotlin/com/ccextractor/taskwarriorflutter/BurndownChart.kt b/android/app/src/main/kotlin/com/ccextractor/taskwarriorflutter/BurndownChart.kt new file mode 100644 index 00000000..7318b9de --- /dev/null +++ b/android/app/src/main/kotlin/com/ccextractor/taskwarriorflutter/BurndownChart.kt @@ -0,0 +1,13 @@ +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 0e0b084b..3a8aba62 100644 --- a/android/app/src/main/kotlin/com/ccextractor/taskwarriorflutter/MainActivity.kt +++ b/android/app/src/main/kotlin/com/ccextractor/taskwarriorflutter/MainActivity.kt @@ -1,7 +1,36 @@ 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() { - // No custom code needed! The home_widget plugin attaches automatically. + 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 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 d57d4cb0..5662cd2a 100644 --- a/android/app/src/main/kotlin/com/ccextractor/taskwarriorflutter/TaskWarriorWidgetProvider.kt +++ b/android/app/src/main/kotlin/com/ccextractor/taskwarriorflutter/TaskWarriorWidgetProvider.kt @@ -23,33 +23,24 @@ import android.os.Build class TaskWarriorWidgetProvider : AppWidgetProvider() { override fun onReceive(context: Context, intent: 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) - } + // 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) + } fun getLayoutId(context: Context) : Int{ val sharedPrefs = HomeWidgetPlugin.getData(context) val theme = sharedPrefs.getString("themeMode", "") @@ -60,74 +51,58 @@ class TaskWarriorWidgetProvider : AppWidgetProvider() { } return layoutId } -@TargetApi(Build.VERSION_CODES.DONUT) -override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) { - appWidgetIds.forEach { widgetId -> - // 1. Get the latest data from HomeWidget/SharedPrefs - val sharedPrefs = HomeWidgetPlugin.getData(context) - val tasks = sharedPrefs.getString("tasks", "") - - // 2. Create the Intent for the ListView service - // We add the widgetId to the data URI to make it unique, preventing caching issues - val intent = Intent(context, ListViewRemoteViewsService::class.java).apply { - putExtra("tasksJsonString", tasks) - data = Uri.parse(toUri(Intent.URI_INTENT_SCHEME) + widgetId) - } + @TargetApi(Build.VERSION_CODES.DONUT) + override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) { + appWidgetIds.forEach { widgetId -> + val sharedPrefs = HomeWidgetPlugin.getData(context) + val tasks = sharedPrefs.getString("tasks", "") + val intent = Intent(context, ListViewRemoteViewsService::class.java).apply { + putExtra("tasksJsonString", tasks) + data = Uri.parse(toUri(Intent.URI_INTENT_SCHEME)) + } + val views = RemoteViews(context.packageName, getLayoutId(context)).apply { + val pendingIntent: PendingIntent = HomeWidgetLaunchIntent.getActivity( + context, + MainActivity::class.java + ) + setOnClickPendingIntent(R.id.logo, pendingIntent) + val intent_for_add = Intent(context, TaskWarriorWidgetProvider::class.java).apply { + setAction("TASK_ACTION") + data=Uri.parse(toUri(Intent.URI_INTENT_SCHEME)) + putExtra("launchedFor", "ADD_TASK") + } + val pendingIntentAdd: PendingIntent = PendingIntent.getBroadcast( + context, + 0, // requestCode, can be any unique integer + intent_for_add, + PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT or Intent.FILL_IN_COMPONENT // Use appropriate flags + ) + setOnClickPendingIntent(R.id.add_btn, pendingIntentAdd) + setRemoteAdapter(R.id.list_view, intent) + } - // 3. Initialize RemoteViews with the THEMED layout (getLayoutId handles dark/light logic) - val views = RemoteViews(context.packageName, getLayoutId(context)).apply { - - // Set up the Logo click (Open App) - val pendingIntent: PendingIntent = HomeWidgetLaunchIntent.getActivity( - context, - MainActivity::class.java - ) - setOnClickPendingIntent(R.id.logo, pendingIntent) - - // Set up the Add Button click (Custom Action) - val intent_for_add = Intent(context, TaskWarriorWidgetProvider::class.java).apply { - action = "TASK_ACTION" - putExtra("launchedFor", "ADD_TASK") - // Unique data to ensure the broadcast is fresh - data = Uri.parse("taskwarrior://addtask/$widgetId") - } - - val pendingIntentAdd: PendingIntent = PendingIntent.getBroadcast( - context, - widgetId, - intent_for_add, - PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT - ) - setOnClickPendingIntent(R.id.add_btn, pendingIntentAdd) - - // Attach the adapter to the ListView - setRemoteAdapter(R.id.list_view, intent) - } + val clickPendingIntent: PendingIntent = Intent( + context, + TaskWarriorWidgetProvider::class.java + ).run { + setAction("TASK_ACTION") + setIdentifier("uuid") + data = Uri.parse(toUri(Intent.URI_INTENT_SCHEME)) + + PendingIntent.getBroadcast( + context, + 0, + this, + PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT or Intent.FILL_IN_COMPONENT + ) + } - // 4. Set up the Click Template for List Items (Deep Linking) - val clickPendingIntent: PendingIntent = Intent( - context, - TaskWarriorWidgetProvider::class.java - ).run { - action = "TASK_ACTION" - // Important: Use widgetId as requestCode to keep it unique - PendingIntent.getBroadcast( - context, - widgetId, - this, - PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT - ) + views.setPendingIntentTemplate(R.id.list_view, clickPendingIntent) + appWidgetManager.updateAppWidget(widgetId, views) + } + super.onUpdate(context, appWidgetManager, appWidgetIds) } - views.setPendingIntentTemplate(R.id.list_view, clickPendingIntent) - - // 5. THE THEME FIX: Notify the manager that the list data/layout needs a refresh - appWidgetManager.notifyAppWidgetViewDataChanged(widgetId, R.id.list_view) - - // 6. Push the update to the widget - appWidgetManager.updateAppWidget(widgetId, views) } - super.onUpdate(context, appWidgetManager, appWidgetIds) -} } class ListViewRemoteViewsFactory( private val context: Context, private val tasksJsonString: String? @@ -137,22 +112,18 @@ class ListViewRemoteViewsFactory( override fun onCreate() {} - override fun onDataSetChanged() { - tasks.clear() // Add this! - val sharedPrefs = HomeWidgetPlugin.getData(context) - val latestTasksJson = sharedPrefs.getString("tasks", "") - - if (!latestTasksJson.isNullOrEmpty()) { - try { - val jsonArray = OrgJSONArray(latestTasksJson) - for (i in 0 until jsonArray.length()) { - tasks.add(Task.fromJson(jsonArray.getJSONObject(i))) - } - } catch (e: JSONException) { - e.printStackTrace() - } - } - } + override fun onDataSetChanged() { + if (tasksJsonString != null) { + try { + val jsonArray = OrgJSONArray(tasksJsonString as String) + for (i in 0 until jsonArray.length()) { + tasks.add(Task.fromJson(jsonArray.getJSONObject(i))) + } + } catch (e: JSONException) { + e.printStackTrace() + } + } + } override fun onDestroy() {} @@ -213,7 +184,7 @@ class ListViewRemoteViewsFactory( } override fun getLoadingView(): RemoteViews? = null - override fun getViewTypeCount(): Int = 2 + override fun getViewTypeCount(): Int = 1 override fun getItemId(position: Int): Long = position.toLong() diff --git a/android/app/src/main/kotlin/com/ccextractor/taskwarriorflutter/WidgetUpdateReceiver.kt b/android/app/src/main/kotlin/com/ccextractor/taskwarriorflutter/WidgetUpdateReceiver.kt new file mode 100644 index 00000000..49d9375c --- /dev/null +++ b/android/app/src/main/kotlin/com/ccextractor/taskwarriorflutter/WidgetUpdateReceiver.kt @@ -0,0 +1,68 @@ +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 new file mode 100644 index 00000000..fff84148 --- /dev/null +++ b/android/app/src/main/res/xml/burndownchartconfig.xml @@ -0,0 +1,10 @@ + + \ No newline at end of file diff --git a/build_droidtchelper b/build_droidtchelper deleted file mode 100644 index e69de29b..00000000 diff --git a/build_itchelper b/build_itchelper deleted file mode 100755 index dbfb9ce3..00000000 --- a/build_itchelper +++ /dev/null @@ -1,104 +0,0 @@ -#!/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 e2010f6c..a7b8abbb 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1,6 +1,7 @@ PODS: - connectivity_plus (0.0.1): - Flutter + - ReachabilitySwift - DKImagePickerController/Core (4.3.4): - DKImagePickerController/ImageDataManager - DKImagePickerController/Resource @@ -42,9 +43,9 @@ PODS: - Flutter (1.0.0) - flutter_local_notifications (0.0.1): - Flutter - - flutter_native_splash (2.4.3): + - flutter_native_splash (0.0.1): - Flutter - - flutter_timezone (0.0.1): + - flutter_native_timezone (0.0.1): - Flutter - home_widget (0.0.1): - Flutter @@ -53,17 +54,15 @@ PODS: - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS - - permission_handler_apple (9.3.0): + - permission_handler_apple (9.0.4): - 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 @@ -76,19 +75,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_timezone (from `.symlinks/plugins/flutter_timezone/ios`) + - flutter_native_timezone (from `.symlinks/plugins/flutter_native_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 @@ -107,8 +106,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/flutter_local_notifications/ios" flutter_native_splash: :path: ".symlinks/plugins/flutter_native_splash/ios" - flutter_timezone: - :path: ".symlinks/plugins/flutter_timezone/ios" + flutter_native_timezone: + :path: ".symlinks/plugins/flutter_native_timezone/ios" home_widget: :path: ".symlinks/plugins/home_widget/ios" package_info_plus: @@ -119,32 +118,30 @@ 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: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd + connectivity_plus: bf0076dd84a130856aa636df1c71ccaff908fa1d DKImagePickerController: b512c28220a2b8ac7419f21c491fc8534b7601ac DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179 - file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be - file_picker_writable: 3a458aa8cdb7c1378cb3e8ec0543ead87a77d0be - file_selector_ios: f92e583d43608aebc2e4a18daac30b8902845502 + file_picker: 15fd9539e4eb735dc54bae8c0534a7a9511a03de + file_picker_writable: 67959f5c516feb5121693a14eda63fcbe6cbb6dc + file_selector_ios: 8c25d700d625e1dcdd6599f2d927072f2254647b Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 - 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 + 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 SDWebImage: 981fd7e860af070920f249fd092420006014c3eb - shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 - sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0 + shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126 SwiftyGif: 93a1cc87bf3a51916001cf8f3d63835fb64c819f - url_launcher_ios: 694010445543906933d732453a59da0a173ae33d + url_launcher_ios: bbd758c6e7f9fd7b5b1d4cde34d2b95fcce5e812 PODFILE CHECKSUM: c4c93c5f6502fe2754f48404d3594bf779584011 -COCOAPODS: 1.16.2 +COCOAPODS: 1.14.3 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 23749b75..dbf0250c 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -8,51 +8,25 @@ /* 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 = 12; + buildActionMask = 2147483647; 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 */ @@ -60,7 +34,6 @@ 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 = ""; }; @@ -72,40 +45,10 @@ 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; @@ -115,15 +58,6 @@ ); 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 */ @@ -141,11 +75,8 @@ 97C146E51CF9000F007C117D = { isa = PBXGroup; children = ( - 197929172EE87FCF00023EC4 /* tc_helper.xcframework */, - AABC0F822DA481B0008D692D /* TaskWarriorWidgetsExtensionDebug.entitlements */, 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, - AABC0EBA2DA46F0C008D692D /* TaskWarriorWidgets */, 97C146EF1CF9000F007C117D /* Products */, C3171261BB8F68A7E2CB70A5 /* Pods */, EC04A4346BDDFBE60E9077C7 /* Frameworks */, @@ -156,7 +87,6 @@ isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, - AABC0EB52DA46F0C008D692D /* TaskWarriorWidgetsExtension.appex */, ); name = Products; sourceTree = ""; @@ -164,7 +94,6 @@ 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( - AABC0F812DA481A8008D692D /* RunnerDebug.entitlements */, 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, @@ -184,6 +113,7 @@ DB57389FBBE4024E44956E34 /* Pods-Runner.release.xcconfig */, 162E9301614D923D4E360425 /* Pods-Runner.profile.xcconfig */, ); + name = Pods; path = Pods; sourceTree = ""; }; @@ -191,8 +121,6 @@ isa = PBXGroup; children = ( 089D9819C0366C673FE320BF /* Pods_Runner.framework */, - AABC0EB62DA46F0C008D692D /* WidgetKit.framework */, - AABC0EB82DA46F0C008D692D /* SwiftUI.framework */, ); name = Frameworks; sourceTree = ""; @@ -210,48 +138,24 @@ 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 = { @@ -259,9 +163,6 @@ CreatedOnToolsVersion = 7.3.1; LastSwiftMigration = 1100; }; - AABC0EB42DA46F0C008D692D = { - CreatedOnToolsVersion = 16.2; - }; }; }; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; @@ -278,7 +179,6 @@ projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, - AABC0EB42DA46F0C008D692D /* TaskWarriorWidgetsExtension */, ); }; /* End PBXProject section */ @@ -295,33 +195,9 @@ ); 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; @@ -404,23 +280,8 @@ ); 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; @@ -504,7 +365,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.ccextractor.taskwarriorflutter; + PRODUCT_BUNDLE_IDENTIFIER = com.example.taskwarrior; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -625,16 +486,14 @@ 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.ccextractor.taskwarriorflutter; + PRODUCT_BUNDLE_IDENTIFIER = com.example.taskwarrior; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -656,7 +515,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.ccextractor.taskwarriorflutter; + PRODUCT_BUNDLE_IDENTIFIER = com.example.taskwarrior; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -664,125 +523,6 @@ }; 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 */ @@ -806,16 +546,6 @@ 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 c53e2b31..5e31d3d3 100644 --- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -48,7 +48,6 @@ ignoresPersistentStateOnLaunch = "NO" debugDocumentVersioning = "YES" debugServiceExtension = "internal" - enableGPUValidationMode = "1" allowLocationSimulation = "YES"> diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 1b4601cd..090af1a3 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -1,13 +1,16 @@ import UIKit import Flutter -@main +@UIApplicationMain @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 c32c5e5f..a878453f 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -1,71 +1,58 @@ - - 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 - - + + 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 + + diff --git a/ios/Runner/RunnerDebug.entitlements b/ios/Runner/RunnerDebug.entitlements deleted file mode 100644 index 6385ba4d..00000000 --- a/ios/Runner/RunnerDebug.entitlements +++ /dev/null @@ -1,10 +0,0 @@ - - - - - com.apple.security.application-groups - - group.taskwarrior - - - diff --git a/ios/TaskWarriorWidgets/AppIntent.swift b/ios/TaskWarriorWidgets/AppIntent.swift deleted file mode 100644 index 15628463..00000000 --- a/ios/TaskWarriorWidgets/AppIntent.swift +++ /dev/null @@ -1,9 +0,0 @@ -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 deleted file mode 100644 index eb878970..00000000 --- a/ios/TaskWarriorWidgets/Assets.xcassets/AccentColor.colorset/Contents.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "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 deleted file mode 100644 index 23058801..00000000 --- a/ios/TaskWarriorWidgets/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "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 deleted file mode 100644 index 73c00596..00000000 --- a/ios/TaskWarriorWidgets/Assets.xcassets/Contents.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "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 deleted file mode 100644 index eb878970..00000000 --- a/ios/TaskWarriorWidgets/Assets.xcassets/WidgetBackground.colorset/Contents.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "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 deleted file mode 100644 index 6c5aae59..00000000 --- a/ios/TaskWarriorWidgets/Assets.xcassets/taskwarrior.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "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 deleted file mode 100644 index 06fcb3df..00000000 Binary files a/ios/TaskWarriorWidgets/Assets.xcassets/taskwarrior.imageset/taskwarrior.png and /dev/null differ diff --git a/ios/TaskWarriorWidgets/Info.plist b/ios/TaskWarriorWidgets/Info.plist deleted file mode 100644 index a01af8d6..00000000 --- a/ios/TaskWarriorWidgets/Info.plist +++ /dev/null @@ -1,16 +0,0 @@ - - - - - NSExtension - - NSExtensionPointIdentifier - com.apple.widgetkit-extension - - GroupIdentifier - group.taskwarrior - - CFBundleDisplayName - Taskwarrior Widgets - - diff --git a/ios/TaskWarriorWidgets/TaskWarriorWidgets.swift b/ios/TaskWarriorWidgets/TaskWarriorWidgets.swift deleted file mode 100644 index 0c71db14..00000000 --- a/ios/TaskWarriorWidgets/TaskWarriorWidgets.swift +++ /dev/null @@ -1,292 +0,0 @@ -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 deleted file mode 100644 index bce01ad3..00000000 --- a/ios/TaskWarriorWidgets/TaskWarriorWidgetsBundle.swift +++ /dev/null @@ -1,13 +0,0 @@ -import WidgetKit -import SwiftUI - - -// App group identifier for shared UserDefaults -let appGroupIdentifier = "group.taskwarrior" - -@main -struct TaskWarriorWidgetsBundle: WidgetBundle { - var body: some Widget { - TasksWidget() - } -} \ No newline at end of file diff --git a/ios/TaskWarriorWidgetsExtensionDebug.entitlements b/ios/TaskWarriorWidgetsExtensionDebug.entitlements deleted file mode 100644 index 6385ba4d..00000000 --- a/ios/TaskWarriorWidgetsExtensionDebug.entitlements +++ /dev/null @@ -1,10 +0,0 @@ - - - - - com.apple.security.application-groups - - group.taskwarrior - - - diff --git a/ios/tc_helper.xcframework/Info.plist b/ios/tc_helper.xcframework/Info.plist deleted file mode 100644 index cf3e2802..00000000 --- a/ios/tc_helper.xcframework/Info.plist +++ /dev/null @@ -1,44 +0,0 @@ - - - - - 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 deleted file mode 100644 index 4117562b..00000000 --- a/ios/tc_helper.xcframework/ios-arm64/tc_helper.framework/Info.plist +++ /dev/null @@ -1,20 +0,0 @@ - - - - - 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 deleted file mode 100755 index f639754f..00000000 Binary files a/ios/tc_helper.xcframework/ios-arm64/tc_helper.framework/tc_helper and /dev/null 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 deleted file mode 100644 index 4117562b..00000000 --- a/ios/tc_helper.xcframework/ios-arm64_x86_64-simulator/tc_helper.framework/Info.plist +++ /dev/null @@ -1,20 +0,0 @@ - - - - - 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 deleted file mode 100755 index bdf11cda..00000000 Binary files a/ios/tc_helper.xcframework/ios-arm64_x86_64-simulator/tc_helper.framework/tc_helper and /dev/null differ diff --git a/lib/app/modules/detailRoute/controllers/detail_route_controller.dart b/lib/app/modules/detailRoute/controllers/detail_route_controller.dart index 8b65bc1c..4820e881 100644 --- a/lib/app/modules/detailRoute/controllers/detail_route_controller.dart +++ b/lib/app/modules/detailRoute/controllers/detail_route_controller.dart @@ -10,7 +10,6 @@ import 'package:taskwarrior/app/utils/taskfunctions/modify.dart'; import 'package:taskwarrior/app/utils/taskfunctions/urgency.dart'; import 'package:taskwarrior/app/utils/app_settings/app_settings.dart'; import 'package:tutorial_coach_mark/tutorial_coach_mark.dart'; -import 'package:taskwarrior/app/utils/language/sentence_manager.dart'; class DetailRouteController extends GetxController { late String uuid; @@ -18,9 +17,6 @@ class DetailRouteController extends GetxController { var onEdit = false.obs; var isReadOnly = false.obs; - // Description Edit State - final descriptionController = TextEditingController(); - var descriptionErrorText = Rxn(); // Track whether user explicitly selected a start date bool startEdited = false; @@ -63,24 +59,6 @@ class DetailRouteController extends GetxController { initValues(); } - // Validation Logic for Description - bool validateDescription() { - if (descriptionController.text.trim().isEmpty) { - descriptionErrorText.value = - SentenceManager(currentLanguage: AppSettings.selectedLanguage) - .sentences - .descriprtionCannotBeEmpty; - return false; - } - descriptionErrorText.value = null; - return true; - } - - void prepareDescriptionEdit(String initialValue) { - descriptionController.text = initialValue; - descriptionErrorText.value = null; - } - Future saveChanges() async { // If start was never edited AND backend auto-generated it (start == entry) if (!startEdited && @@ -197,10 +175,4 @@ class DetailRouteController extends GetxController { }, ); } - - @override - void onClose() { - descriptionController.dispose(); - super.onClose(); - } } diff --git a/lib/app/modules/detailRoute/views/dateTimePicker.dart b/lib/app/modules/detailRoute/views/dateTimePicker.dart index 8601e848..5bda263f 100644 --- a/lib/app/modules/detailRoute/views/dateTimePicker.dart +++ b/lib/app/modules/detailRoute/views/dateTimePicker.dart @@ -163,14 +163,6 @@ class DateTimeWidget extends StatelessWidget { var time = await showTimePicker( context: context, initialTime: TimeOfDay.now(), - builder: (BuildContext context, Widget? child) { - return MediaQuery( - data: MediaQuery.of(context).copyWith( - alwaysUse24HourFormat: AppSettings.use24HourFormatRx.value, - ), - child: child!, - ); - }, ); if (time != null) { var dateTime = date.add( diff --git a/lib/app/modules/detailRoute/views/description_widget.dart b/lib/app/modules/detailRoute/views/description_widget.dart index 33d848d3..6c87f6ba 100644 --- a/lib/app/modules/detailRoute/views/description_widget.dart +++ b/lib/app/modules/detailRoute/views/description_widget.dart @@ -8,7 +8,6 @@ import 'package:taskwarrior/app/utils/constants/utilites.dart'; import 'package:taskwarrior/app/utils/gen/fonts.gen.dart'; import 'package:taskwarrior/app/utils/themes/theme_extension.dart'; import 'package:taskwarrior/app/utils/language/sentence_manager.dart'; -import 'package:taskwarrior/app/modules/detailRoute/controllers/detail_route_controller.dart'; class DescriptionWidget extends StatelessWidget { const DescriptionWidget({ @@ -26,7 +25,6 @@ class DescriptionWidget extends StatelessWidget { @override Widget build(BuildContext context) { - final controller = Get.find(); TaskwarriorColorTheme tColors = Theme.of(context).extension()!; return Card( @@ -88,66 +86,64 @@ class DescriptionWidget extends StatelessWidget { ), ), onTap: () { - controller.prepareDescriptionEdit(value ?? ''); + var controller = TextEditingController( + text: value, + ); showDialog( context: context, - builder: (context) => Obx( - () => Utils.showAlertDialog( - scrollable: true, - title: Text( - SentenceManager(currentLanguage: AppSettings.selectedLanguage) - .sentences - .editDescription, - style: TextStyle( - color: tColors.primaryTextColor, - ), + builder: (context) => Utils.showAlertDialog( + scrollable: true, + title: Text( + SentenceManager(currentLanguage: AppSettings.selectedLanguage) + .sentences + .editDescription, + style: TextStyle( + color: tColors.primaryTextColor, ), - content: TextField( - style: TextStyle( - color: tColors.primaryTextColor, - ), - decoration: InputDecoration( - errorText: controller.descriptionErrorText.value, - errorStyle: const TextStyle( - color: Colors.red, - ), - ), - autofocus: true, - maxLines: null, - controller: controller.descriptionController, + ), + content: TextField( + style: TextStyle( + color: tColors.primaryTextColor, ), - actions: [ - TextButton( - onPressed: () => Get.back(), - child: Text( - SentenceManager( - currentLanguage: AppSettings.selectedLanguage) - .sentences - .cancel, - style: TextStyle( - color: tColors.primaryTextColor, - ), + autofocus: true, + maxLines: null, + controller: controller, + ), + actions: [ + TextButton( + onPressed: () { + Get.back(); + }, + child: Text( + SentenceManager( + currentLanguage: AppSettings.selectedLanguage) + .sentences + .cancel, + style: TextStyle( + color: tColors.primaryTextColor, ), ), - TextButton( - onPressed: () { - if (controller.validateDescription()) { - callback(controller.descriptionController.text); - Get.back(); - } - }, - child: Text( - SentenceManager( - currentLanguage: AppSettings.selectedLanguage) - .sentences - .submit, - style: TextStyle( - color: tColors.primaryTextColor, - ), + ), + TextButton( + onPressed: () { + try { + callback(controller.text); + Get.back(); + } on FormatException catch (e, trace) { + logError(e, trace); + } + }, + child: Text( + SentenceManager( + currentLanguage: AppSettings.selectedLanguage) + .sentences + .submit, + style: TextStyle( + color: tColors.primaryTextColor, ), ), - ], - ), + ), + ], ), ); }, diff --git a/lib/app/modules/detailRoute/views/detail_route_view.dart b/lib/app/modules/detailRoute/views/detail_route_view.dart index dab0aac2..f62c48a7 100644 --- a/lib/app/modules/detailRoute/views/detail_route_view.dart +++ b/lib/app/modules/detailRoute/views/detail_route_view.dart @@ -262,10 +262,7 @@ class AttributeWidget extends StatelessWidget { @override Widget build(BuildContext context) { var localValue = (value is DateTime) - ? DateFormat(AppSettings.use24HourFormatRx.value - ? 'EEE, yyyy-MM-dd HH:mm:ss' - : 'EEE, yyyy-MM-dd hh:mm:ss a') - .format(value.toLocal()) + ? DateFormat.yMEd().add_jms().format(value.toLocal()) : ((value is BuiltList) ? (value).toBuilder() : value); TaskwarriorColorTheme tColors = Theme.of(context).extension()!; diff --git a/lib/app/modules/detailRoute/views/tags_widget.dart b/lib/app/modules/detailRoute/views/tags_widget.dart index d8bcafd5..12561753 100644 --- a/lib/app/modules/detailRoute/views/tags_widget.dart +++ b/lib/app/modules/detailRoute/views/tags_widget.dart @@ -99,17 +99,27 @@ class TagsRouteState extends State { Map? _pendingTags; ListBuilder? draftTags; - void _addTag(String tag) { - if (tag.isNotEmpty) { - // Add this condition to ensure the tag is not empty - if (draftTags == null) { - draftTags = ListBuilder([tag]); - } else { + List _parseTags(String input) { + return input + .split(',') + .map((e) => e.trim()) + .where((e) => e.isNotEmpty) + .toList(); + } + + void _addTags(List tags) { + if (tags.isEmpty) return; + + draftTags ??= ListBuilder(); + + for (final tag in tags) { + if (!draftTags!.build().contains(tag)) { draftTags!.add(tag); } - widget.callback(draftTags); - setState(() {}); } + + widget.callback(draftTags); + setState(() {}); } void _removeTag(String tag) { @@ -197,7 +207,7 @@ class TagsRouteState extends State { !(draftTags?.build().contains(tag.key) ?? false))) FilterChip( backgroundColor: TaskWarriorColors.grey, - onSelected: (_) => _addTag(tag.key), + onSelected: (_) => _addTags([tag.key]), label: Text( '${tag.key} ${tag.value.frequency}', ), @@ -234,11 +244,22 @@ class TagsRouteState extends State { color: tColors.primaryTextColor, ), validator: (value) { - if (value != null) { - if (value.isNotEmpty && value.contains(" ")) { + final tags = _parseTags(value ?? ''); + + if (tags.isEmpty) { + return "Please enter a tag"; + } + + for (final tag in tags) { + if (tag.contains(' ')) { return "Tags cannot contain spaces"; } + + if (draftTags?.build().contains(tag) ?? false) { + return "Tag already exists"; + } } + return null; }, autofocus: true, @@ -264,9 +285,8 @@ class TagsRouteState extends State { onPressed: () { if (formKey.currentState!.validate()) { try { - validateTaskTags(controller.text); - _addTag(controller.text); - // Navigator.of(context).pop(); + final tags = _parseTags(controller.text); + _addTags(tags); Get.back(); } on FormatException catch (e, trace) { logError(e, trace); diff --git a/lib/app/modules/home/controllers/home_controller.dart b/lib/app/modules/home/controllers/home_controller.dart index b4ba1f6f..2bb2f105 100644 --- a/lib/app/modules/home/controllers/home_controller.dart +++ b/lib/app/modules/home/controllers/home_controller.dart @@ -8,6 +8,7 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:home_widget/home_widget.dart'; import 'package:loggy/loggy.dart'; +import 'package:path_provider/path_provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:taskwarrior/app/models/filters.dart'; @@ -16,8 +17,9 @@ import 'package:taskwarrior/app/models/storage.dart'; import 'package:taskwarrior/app/models/storage/client.dart'; import 'package:taskwarrior/app/models/tag_meta_data.dart'; 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/services/deep_link_service.dart'; +import 'package:taskwarrior/app/routes/app_pages.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'; @@ -36,6 +38,7 @@ import 'package:taskwarrior/app/v3/db/task_database.dart'; import 'package:taskwarrior/app/v3/db/update.dart'; import 'package:taskwarrior/app/v3/models/task.dart'; import 'package:taskwarrior/app/v3/net/fetch.dart'; +import 'package:taskwarrior/rust_bridge/api.dart'; import 'package:textfield_tags/textfield_tags.dart'; import 'package:taskwarrior/app/utils/themes/theme_extension.dart'; import 'package:tutorial_coach_mark/tutorial_coach_mark.dart'; @@ -84,13 +87,10 @@ class HomeController extends GetxController { taskdb.open(); getUniqueProjects(); _loadTaskChampion(); + if (Platform.isAndroid) { + handleHomeWidgetClicked(); + } fetchTasksFromDB(); - - ever(AppSettings.use24HourFormatRx, (_) { - _refreshTasks(); - update(); - }); - ever(taskReplica, (_) { if (taskReplica.value) refreshReplicaTaskList(); }); @@ -102,11 +102,12 @@ class HomeController extends GetxController { selectedSort, selectedTags, tasks, - tasksFromReplica, + tasksFromReplica ], (_) { - if (Platform.isAndroid || Platform.isIOS) { + if (Platform.isAndroid) { WidgetController widgetController = Get.put(WidgetController()); widgetController.fetchAllData(); + widgetController.update(); } }); @@ -115,7 +116,7 @@ class HomeController extends GetxController { "TW3") { refreshTaskWithNewProfile(); } - if (Platform.isAndroid || Platform.isIOS) { + if (Platform.isAndroid) { WidgetController widgetController = Get.put(WidgetController()); widgetController.fetchAllData(); widgetController.updateWidget(); @@ -123,14 +124,6 @@ class HomeController extends GetxController { }); } - @override - void onReady() { - super.onReady(); - if (Get.isRegistered()) { - Get.find().consumePendingActions(this); - } - } - Future> getUniqueProjects() async { if (taskReplica.value) { return tasksFromReplica @@ -564,6 +557,7 @@ class HomeController extends GetxController { RxBool syncOnStart = false.obs; RxBool syncOnTaskCreate = false.obs; RxBool delaytask = false.obs; + RxBool change24hr = false.obs; RxBool taskchampion = false.obs; RxBool taskReplica = false.obs; @@ -632,16 +626,6 @@ 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 { @@ -656,9 +640,7 @@ class HomeController extends GetxController { selectedLanguage.value = AppSettings.selectedLanguage; HomeWidget.saveWidgetData( "themeMode", AppSettings.isDarkMode ? "dark" : "light"); - HomeWidget.updateWidget( - androidName: "TaskWarriorWidgetProvider", - iOSName: "TaskWarriorWidgets"); + HomeWidget.updateWidget(androidName: "TaskWarriorWidgetProvider"); // print("called and value is${isDarkModeOn.value}"); } @@ -781,65 +763,49 @@ class HomeController extends GetxController { }); } - Iterable get allTagsInCurrentTasks { - if (taskReplica.value) { - var tagSet = {}; - for (var task in tasksFromReplica) { - if (task.tags != null) { - tagSet.addAll(task.tags!); + 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(); } - var sortedTags = tagSet.toList()..sort(); - return sortedTags; } - return tagSet(storage.data.allData()); - } - - RxString uuid = "".obs; - 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, - // forReplica: taskReplica.value)); - // Get.dialog(showDialog); - // } + 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); + } } diff --git a/lib/app/modules/home/controllers/widget.controller.dart b/lib/app/modules/home/controllers/widget.controller.dart index 4137f137..0e14775b 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: 'TaskWarriorWidgets'); + name: 'TaskWarriorWidgetProvider', iOSName: 'HomeWidgetExample'); } 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 3442246f..05d290ce 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 @@ -14,6 +14,7 @@ import 'package:taskwarrior/app/utils/app_settings/app_settings.dart'; import 'package:taskwarrior/app/utils/constants/constants.dart'; import 'package:taskwarrior/app/utils/language/sentence_manager.dart'; import 'package:taskwarrior/app/utils/taskfunctions/add_task_dialog_utils.dart'; +import 'package:taskwarrior/app/utils/taskfunctions/tags.dart'; import 'package:taskwarrior/app/utils/taskfunctions/taskparser.dart'; import 'package:taskwarrior/app/utils/themes/theme_extension.dart'; import 'package:taskwarrior/app/v3/champion/replica.dart'; @@ -31,8 +32,6 @@ class AddTaskBottomSheet extends StatelessWidget { @override Widget build(BuildContext context) { - debugPrint( - "Building Add Task Bottom Sheet for ${forTaskC ? "TaskC" : forReplica ? "Replica" : "Normal Task"}"); const padding = 12.0; return Padding( padding: EdgeInsets.only( @@ -97,7 +96,7 @@ class AddTaskBottomSheet extends StatelessWidget { padding: const EdgeInsets.all(padding), child: TextFormField( controller: homeController.namecontroller, - validator: (value) => value!.trim().isEmpty + validator: (value) => value!.isEmpty ? SentenceManager( currentLanguage: homeController.selectedLanguage.value) @@ -217,7 +216,7 @@ class AddTaskBottomSheet extends StatelessWidget { } Widget buildTagsInput(BuildContext context) => AddTaskTagsInput( - suggestions: homeController.allTagsInCurrentTasks, + suggestions: tagSet(homeController.storage.data.allData()), onTagsChanges: (p0) => homeController.tags.value = p0, ); @@ -225,8 +224,7 @@ class AddTaskBottomSheet extends StatelessWidget { onDateChanges: (List p0) { homeController.selectedDates.value = p0; }, - allowedIndexes: forReplica ? [0, 1] : [0, 1, 2, 3], - onlyDueDate: forTaskC, + onlyDueDate: forTaskC || forReplica, ); Widget buildPriority(BuildContext context) => Column( @@ -309,11 +307,6 @@ class AddTaskBottomSheet extends StatelessWidget { ); Set getProjects() { - if (homeController.taskReplica.value) { - return homeController.tasksFromReplica - .map((task) => task.project ?? '') - .toSet(); - } Iterable tasks = homeController.storage.data.allData(); return tasks .where((task) => task.project != null) @@ -324,7 +317,7 @@ class AddTaskBottomSheet extends StatelessWidget { if (homeController.formKey.currentState!.validate()) { debugPrint("tags ${homeController.tags}"); var task = TaskForC( - description: homeController.namecontroller.text.trim(), + description: homeController.namecontroller.text, status: 'pending', priority: homeController.priority.value, entry: DateTime.now().toIso8601String(), @@ -372,7 +365,7 @@ class AddTaskBottomSheet extends StatelessWidget { void onSaveButtonClicked(BuildContext context) async { if (homeController.formKey.currentState!.validate()) { try { - var task = taskParser(homeController.namecontroller.text.trim()) + var task = taskParser(homeController.namecontroller.text) .rebuild((b) => b..due = getDueDate(homeController.selectedDates)?.toUtc()) .rebuild((p) => p..priority = homeController.priority.value) @@ -430,12 +423,6 @@ 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( @@ -458,7 +445,7 @@ class AddTaskBottomSheet extends StatelessWidget { if (homeController.formKey.currentState!.validate()) { try { await Replica.addTaskToReplica(HashMap.from({ - "description": homeController.namecontroller.text.trim(), + "description": homeController.namecontroller.text, "due": getDueDate(homeController.selectedDates)?.toUtc(), "priority": homeController.priority.value, "project": homeController.projectcontroller.text != "" diff --git a/lib/app/modules/home/views/home_view.dart b/lib/app/modules/home/views/home_view.dart index e22e889a..e2f62bb1 100644 --- a/lib/app/modules/home/views/home_view.dart +++ b/lib/app/modules/home/views/home_view.dart @@ -44,10 +44,6 @@ class HomeView extends GetView { ? TaskWarriorColors.kprimaryBackgroundColor : TaskWarriorColors.kLightPrimaryBackgroundColor, drawer: NavDrawer(homeController: controller), - - drawerEnableOpenDragGesture: true, - drawerEdgeDragWidth: 80, - body: HomePageBody( controller: controller, ), diff --git a/lib/app/modules/home/views/nav_drawer.dart b/lib/app/modules/home/views/nav_drawer.dart index 82c0d074..ed781a7a 100644 --- a/lib/app/modules/home/views/nav_drawer.dart +++ b/lib/app/modules/home/views/nav_drawer.dart @@ -161,6 +161,8 @@ class NavDrawer extends StatelessWidget { prefs.getBool('sync-OnTaskCreate') ?? false; homeController.delaytask.value = prefs.getBool('delaytask') ?? false; + homeController.change24hr.value = + prefs.getBool('24hourformate') ?? false; Get.toNamed(Routes.SETTINGS); }, diff --git a/lib/app/modules/home/views/tasks_builder.dart b/lib/app/modules/home/views/tasks_builder.dart index 54846126..abdb0a2c 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||Platform.isIOS) { + if (Platform.isAndroid) { WidgetController widgetController = Get.put(WidgetController()); widgetController.fetchAllData(); diff --git a/lib/app/modules/manage_task_champion_creds/views/manage_task_champion_creds_view.dart b/lib/app/modules/manage_task_champion_creds/views/manage_task_champion_creds_view.dart index 797eaabb..13447087 100644 --- a/lib/app/modules/manage_task_champion_creds/views/manage_task_champion_creds_view.dart +++ b/lib/app/modules/manage_task_champion_creds/views/manage_task_champion_creds_view.dart @@ -93,26 +93,18 @@ class ManageTaskChampionCredsView ), ), const SizedBox(height: 10), - Obx(() => TextField( - style: TextStyle(color: tColors.primaryTextColor), - controller: controller.ccsyncBackendUrlController, - decoration: InputDecoration( - labelText: controller.taskReplica.value - ? SentenceManager( - currentLanguage: - AppSettings.selectedLanguage) - .sentences - .taskchampionBackendUrl - : SentenceManager( - currentLanguage: - AppSettings.selectedLanguage) - .sentences - .ccsyncBackendUrl, - labelStyle: - TextStyle(color: tColors.primaryTextColor), - border: const OutlineInputBorder(), - ), - )), + TextField( + style: TextStyle(color: tColors.primaryTextColor), + controller: controller.ccsyncBackendUrlController, + decoration: InputDecoration( + labelText: SentenceManager( + currentLanguage: AppSettings.selectedLanguage) + .sentences + .ccsyncBackendUrl, + labelStyle: TextStyle(color: tColors.primaryTextColor), + border: const OutlineInputBorder(), + ), + ), const SizedBox(height: 20), Obx(() => Center( child: ElevatedButton( diff --git a/lib/app/modules/profile/views/profile_view.dart b/lib/app/modules/profile/views/profile_view.dart index a50eec66..9a33de0e 100644 --- a/lib/app/modules/profile/views/profile_view.dart +++ b/lib/app/modules/profile/views/profile_view.dart @@ -255,8 +255,8 @@ class ProfileView extends GetView { mainAxisSize: MainAxisSize.min, // Use minimum space children: [ RadioListTile( - title: const Text('Taskchampion (v3)'), - value: 'TW3C', + title: const Text('CCSync (v3)'), + value: 'TW3', groupValue: selectedMode, onChanged: (String? value) { setState(() { @@ -264,17 +264,6 @@ class ProfileView extends GetView { }); }, ), - // CCSync v3 is deprecated, so hiding it for now - // RadioListTile( - // title: const Text('CCSync (v3)'), - // value: 'TW3', - // groupValue: selectedMode, - // onChanged: (String? value) { - // setState(() { - // selectedMode = value; - // }); - // }, - // ), RadioListTile( title: const Text('TaskServer'), value: 'TW2', @@ -285,6 +274,16 @@ class ProfileView extends GetView { }); }, ), + RadioListTile( + title: const Text('Taskchampion (v3)'), + value: 'TW3C', + groupValue: selectedMode, + onChanged: (String? value) { + setState(() { + selectedMode = value; + }); + }, + ), ], ); }, diff --git a/lib/app/modules/reports/controllers/reports_controller.dart b/lib/app/modules/reports/controllers/reports_controller.dart index fd81d97e..d46ee206 100644 --- a/lib/app/modules/reports/controllers/reports_controller.dart +++ b/lib/app/modules/reports/controllers/reports_controller.dart @@ -1,4 +1,6 @@ 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'; @@ -13,6 +15,8 @@ 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'; @@ -32,6 +36,69 @@ 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 dd459087..2e4ed6c5 100644 --- a/lib/app/modules/reports/views/burn_down_daily.dart +++ b/lib/app/modules/reports/views/burn_down_daily.dart @@ -17,95 +17,121 @@ 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; - - // Removed the Stack and Positioned button since the controller doesn't support capturing yet - return Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, + return Stack( 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, - ), - ), - ), - 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, + 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', + ), + ], ), ), ), - 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, + ), + ], ), - 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, + ), + ), + ), ), ], ); diff --git a/lib/app/modules/settings/controllers/settings_controller.dart b/lib/app/modules/settings/controllers/settings_controller.dart index 20d02627..dd9a89da 100644 --- a/lib/app/modules/settings/controllers/settings_controller.dart +++ b/lib/app/modules/settings/controllers/settings_controller.dart @@ -49,7 +49,6 @@ class SettingsController extends GetxController { void pickDirectory(BuildContext context) { TaskwarriorColorTheme tColors = Theme.of(context).extension()!; - final scaffoldMessenger = ScaffoldMessenger.of(context); FilePicker.platform.getDirectoryPath().then((value) async { if (value != null) { isMovingDirectory.value = true; @@ -68,15 +67,11 @@ class SettingsController extends GetxController { SharedPreferences prefs = await SharedPreferences.getInstance(); prefs.setString('baseDirectory', destination.path); baseDirectory.value = destination.path; - scaffoldMessenger.showSnackBar( - SnackBar( - content: Text( - 'Base directory moved successfully', - style: TextStyle(color: tColors.primaryTextColor), - ), - backgroundColor: tColors.secondaryBackgroundColor, - duration: const Duration(seconds: 2), - ), + Get.snackbar( + 'Success', + 'Base directory moved successfully', + snackPosition: SnackPosition.BOTTOM, + duration: const Duration(seconds: 2), ); } else { Get.dialog( @@ -222,6 +217,7 @@ class SettingsController extends GetxController { RxBool isSyncOnStartActivel = false.obs; RxBool isSyncOnTaskCreateActivel = false.obs; RxBool delaytask = false.obs; + RxBool change24hr = false.obs; RxBool taskchampion = false.obs; RxBool isDarkModeOn = false.obs; @@ -236,6 +232,7 @@ class SettingsController extends GetxController { isSyncOnTaskCreateActivel.value = prefs.getBool('sync-OnTaskCreate') ?? false; delaytask.value = prefs.getBool('delaytask') ?? false; + change24hr.value = prefs.getBool('24hourformate') ?? false; taskchampion.value = prefs.getBool('settings_taskc') ?? false; initDarkMode(); baseDirectory.value = await getBaseDirectory(); diff --git a/lib/app/modules/settings/views/settings_page_app_bar.dart b/lib/app/modules/settings/views/settings_page_app_bar.dart index 1360707c..333caefa 100644 --- a/lib/app/modules/settings/views/settings_page_app_bar.dart +++ b/lib/app/modules/settings/views/settings_page_app_bar.dart @@ -58,10 +58,7 @@ class SettingsPageAppBar extends StatelessWidget onTap: () { // Get.toNamed(Routes.b) // ; - if (Get.isSnackbarOpen == true) { - Get.closeCurrentSnackbar(); - } - Navigator.of(context).pop(); + Get.back(); }, child: Icon( Icons.chevron_left, diff --git a/lib/app/modules/settings/views/settings_page_body.dart b/lib/app/modules/settings/views/settings_page_body.dart index be8ab981..64afe40d 100644 --- a/lib/app/modules/settings/views/settings_page_body.dart +++ b/lib/app/modules/settings/views/settings_page_body.dart @@ -41,19 +41,15 @@ class SettingsPageBody extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, children: [ const CircularProgressIndicator(), - const SizedBox(height: 16), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 24.0), - child: Text( - SentenceManager( - currentLanguage: controller.selectedLanguage.value, - ).sentences.settingsPageMovingDataToNewDirectory, - textAlign: TextAlign.center, - style: GoogleFonts.poppins( - fontWeight: FontWeight.w500, - fontSize: TaskWarriorFonts.fontSizeMedium, - color: tColors.primaryTextColor, - ), + const SizedBox(height: 10), + Text( + SentenceManager( + currentLanguage: controller.selectedLanguage.value, + ).sentences.settingsPageMovingDataToNewDirectory, + style: GoogleFonts.poppins( + fontWeight: FontWeight.bold, + fontSize: TaskWarriorFonts.fontSizeMedium, + color: tColors.primaryTextColor, ), ), ], @@ -61,321 +57,186 @@ class SettingsPageBody extends StatelessWidget { ); } else { return ListView( - padding: const EdgeInsets.symmetric(vertical: 8.0), children: [ - _buildSectionHeader( - context, - SentenceManager( - currentLanguage: controller.selectedLanguage.value) - .sentences - .syncSetting, - Icons.sync, - tColors, - ), - _buildSettingsCard( - context, - tColors, - [ - Obx( - () => SettingsPageListTile( - title: SentenceManager( - currentLanguage: controller.selectedLanguage.value) - .sentences - .settingsPageSyncOnStartTitle, - subTitle: SentenceManager( - currentLanguage: controller.selectedLanguage.value) - .sentences - .settingsPageSyncOnStartDescription, - trailing: SettingsPageOnTaskStartListTileTrailing( - controller: controller, - ), - ), + Obx( + () => SettingsPageListTile( + title: SentenceManager( + currentLanguage: controller.selectedLanguage.value) + .sentences + .settingsPageSyncOnStartTitle, + subTitle: SentenceManager( + currentLanguage: controller.selectedLanguage.value) + .sentences + .settingsPageSyncOnStartDescription, + trailing: SettingsPageOnTaskStartListTileTrailing( + controller: controller, ), - Divider( - height: 1, - color: tColors.primaryDisabledTextColor?.withOpacity(0.1)), - Obx( - () => SettingsPageListTile( - title: SentenceManager( - currentLanguage: controller.selectedLanguage.value) - .sentences - .settingsPageEnableSyncOnTaskCreateTitle, - subTitle: SentenceManager( - currentLanguage: controller.selectedLanguage.value) - .sentences - .settingsPageEnableSyncOnTaskCreateDescription, - trailing: SettingsPageOnTaskCreateListTileTrailing( - controller: controller, - ), - ), + ), + ), + const Divider(), + Obx( + () => SettingsPageListTile( + title: SentenceManager( + currentLanguage: controller.selectedLanguage.value) + .sentences + .settingsPageEnableSyncOnTaskCreateTitle, + subTitle: SentenceManager( + currentLanguage: controller.selectedLanguage.value) + .sentences + .settingsPageEnableSyncOnTaskCreateDescription, + trailing: SettingsPageOnTaskCreateListTileTrailing( + controller: controller, ), - ], + ), ), - const SizedBox(height: 16), - _buildSectionHeader( - context, - SentenceManager( + const Divider(), + SettingsPageListTile( + title: SentenceManager( currentLanguage: controller.selectedLanguage.value) .sentences - .displaySettings, - Icons.display_settings, - tColors, - ), - _buildSettingsCard( - context, - tColors, - [ - SettingsPageListTile( - title: SentenceManager( - currentLanguage: controller.selectedLanguage.value) - .sentences - .settingsPageHighlightTaskTitle, - subTitle: SentenceManager( - currentLanguage: controller.selectedLanguage.value) - .sentences - .settingsPageHighlightTaskDescription, - trailing: SettingsPageHighlistTaskListTileTrailing( - controller: controller, - ), - ), - Divider( - height: 1, - color: tColors.primaryDisabledTextColor?.withOpacity(0.1)), - SettingsPageListTile( - title: SentenceManager( - currentLanguage: controller.selectedLanguage.value) - .sentences - .settingsPageEnable24hrFormatTitle, - subTitle: SentenceManager( - currentLanguage: controller.selectedLanguage.value) - .sentences - .settingsPageEnable24hrFormatDescription, - trailing: SettingsPageEnable24hrFormatListTileTrailing( - controller: controller, - ), - ), - Divider( - height: 1, - color: tColors.primaryDisabledTextColor?.withOpacity(0.1)), - SettingsPageListTile( - title: SentenceManager( - currentLanguage: controller.selectedLanguage.value) - .sentences - .settingsPageSelectLanguage, - subTitle: SentenceManager( - currentLanguage: controller.selectedLanguage.value) - .sentences - .settingsPageToggleNativeLanguage, - trailing: SettingsPageSelectTheLanguageTrailing( - controller: controller, - ), - ), - ], - ), - const SizedBox(height: 16), - _buildSectionHeader( - context, - SentenceManager( + .settingsPageHighlightTaskTitle, + subTitle: SentenceManager( currentLanguage: controller.selectedLanguage.value) .sentences - .storageAndData, - Icons.folder_outlined, - tColors, + .settingsPageHighlightTaskDescription, + trailing: SettingsPageHighlistTaskListTileTrailing( + controller: controller, + ), ), - _buildSettingsCard( - context, - tColors, - [ - SettingsPageSelectDirectoryListTile(controller: controller), - ], + const Divider(), + SettingsPageSelectDirectoryListTile(controller: controller), + const Divider(), + SettingsPageListTile( + title: SentenceManager( + currentLanguage: controller.selectedLanguage.value) + .sentences + .settingsPageEnable24hrFormatTitle, + subTitle: SentenceManager( + currentLanguage: controller.selectedLanguage.value) + .sentences + .settingsPageEnable24hrFormatDescription, + trailing: SettingsPageEnable24hrFormatListTileTrailing( + controller: controller, + ), ), - const SizedBox(height: 16), - _buildSectionHeader( - context, - SentenceManager( + const Divider(), + SettingsPageListTile( + title: SentenceManager( + currentLanguage: controller.selectedLanguage.value) + .sentences + .settingsPageSelectLanguage, + subTitle: SentenceManager( currentLanguage: controller.selectedLanguage.value) .sentences - .advanced, - Icons.settings_suggest, - tColors, + .settingsPageToggleNativeLanguage, + trailing: SettingsPageSelectTheLanguageTrailing( + controller: controller, + ), ), - _buildSettingsCard( - context, - tColors, - [ - SettingsPageListTile( - title: SentenceManager( - currentLanguage: AppSettings.selectedLanguage) + const Divider(), + SettingsPageListTile( + title: + SentenceManager(currentLanguage: AppSettings.selectedLanguage) .sentences .logs, - subTitle: SentenceManager( - currentLanguage: AppSettings.selectedLanguage) + subTitle: + SentenceManager(currentLanguage: AppSettings.selectedLanguage) .sentences .checkAllDebugLogsHere, - trailing: IconButton( - onPressed: () { - Get.toNamed(Routes.LOGS); - }, - icon: const Icon(Icons.login), - ), - ), - FutureBuilder( - future: _getFlag(), - builder: (context, snapshot) { - switch (snapshot.connectionState) { - case ConnectionState.waiting: - return const SizedBox.shrink(); - case ConnectionState.done: - final show = snapshot.data ?? false; - if (!show) return const SizedBox.shrink(); - return Column( - children: [ - Divider( - height: 1, - color: tColors.primaryDisabledTextColor - ?.withOpacity(0.1)), - SettingsPageListTile( - title: SentenceManager( - currentLanguage: AppSettings.selectedLanguage, - ).sentences.deleteTaskTitle, - subTitle: SentenceManager( - currentLanguage: AppSettings.selectedLanguage, - ).sentences.deleteAllTasksWillBeMarkedAsDeleted, - trailing: IconButton( - onPressed: () { - showDialog( - context: context, - builder: (BuildContext context) { - return Utils.showAlertDialog( - title: Text( - SentenceManager( - currentLanguage: - AppSettings.selectedLanguage, - ).sentences.deleteTaskConfirmation, - style: TextStyle( - color: tColors.primaryTextColor, - ), - ), - content: Text( - SentenceManager( - currentLanguage: - AppSettings.selectedLanguage, - ).sentences.deleteTaskWarning, - style: TextStyle( - color: tColors - .primaryDisabledTextColor, - ), - ), - actions: [ - TextButton( - child: Text( - SentenceManager( - currentLanguage: AppSettings - .selectedLanguage, - ).sentences.homePageCancel, - style: TextStyle( - color: tColors.primaryTextColor, - ), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - TextButton( - child: Text( - SentenceManager( - currentLanguage: AppSettings - .selectedLanguage, - ).sentences.navDrawerConfirm, - style: TextStyle( - color: tColors.primaryTextColor, - ), - ), - onPressed: () { - controller.deleteAllTasksInDB(); - Navigator.of(context).pop(); - }, - ), - ], - ); - }, - ); - }, - icon: const Icon(Icons.delete), - ), - ), - ], - ); - default: - return const SizedBox.shrink(); - } + trailing: IconButton( + onPressed: () { + Get.toNamed(Routes.LOGS); }, - ), - ], + icon: const Icon(Icons.login)), ), - const SizedBox(height: 16), + const Divider(), + FutureBuilder( + future: _getFlag(), // method to fetch SharedPreference value + builder: (context, snapshot) { + switch (snapshot.connectionState) { + case ConnectionState.waiting: + return const SizedBox + .shrink(); // show nothing while loading + case ConnectionState.done: + final show = snapshot.data ?? false; + if (!show) return const SizedBox.shrink(); // hide if false + return SettingsPageListTile( + title: SentenceManager( + currentLanguage: AppSettings.selectedLanguage, + ).sentences.deleteTaskTitle, + subTitle: SentenceManager( + currentLanguage: AppSettings.selectedLanguage, + ).sentences.deleteAllTasksWillBeMarkedAsDeleted, + trailing: IconButton( + onPressed: () { + showDialog( + context: context, + builder: (BuildContext context) { + return Utils.showAlertDialog( + title: Text( + SentenceManager( + currentLanguage: + AppSettings.selectedLanguage, + ).sentences.deleteTaskConfirmation, + style: TextStyle( + color: tColors.primaryTextColor, + ), + ), + content: Text( + SentenceManager( + currentLanguage: + AppSettings.selectedLanguage, + ).sentences.deleteTaskWarning, + style: TextStyle( + color: tColors.primaryDisabledTextColor, + ), + ), + actions: [ + TextButton( + child: Text( + SentenceManager( + currentLanguage: + AppSettings.selectedLanguage, + ).sentences.homePageCancel, + style: TextStyle( + color: tColors.primaryTextColor, + ), + ), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: Text( + SentenceManager( + currentLanguage: + AppSettings.selectedLanguage, + ).sentences.navDrawerConfirm, + style: TextStyle( + color: tColors.primaryTextColor, + ), + ), + onPressed: () { + controller.deleteAllTasksInDB(); + Navigator.of(context).pop(); + }, + ), + ], + ); + }, + ); + }, + icon: const Icon(Icons.delete), + ), + ); + default: + return const SizedBox.shrink(); + } + }, + ) ], ); } }); } - - Widget _buildSectionHeader( - BuildContext context, - String title, - IconData icon, - TaskwarriorColorTheme tColors, - ) { - return Padding( - padding: const EdgeInsets.fromLTRB(16.0, 8.0, 16.0, 8.0), - child: Row( - children: [ - Icon( - icon, - size: 20, - color: tColors.primaryTextColor?.withOpacity(0.7), - ), - const SizedBox(width: 8), - Text( - title, - style: GoogleFonts.poppins( - fontSize: 13, - fontWeight: FontWeight.w600, - color: tColors.primaryTextColor?.withOpacity(0.7), - letterSpacing: 0.5, - ), - ), - ], - ), - ); - } - - Widget _buildSettingsCard( - BuildContext context, - TaskwarriorColorTheme tColors, - List children, - ) { - return Container( - margin: const EdgeInsets.symmetric(horizontal: 12.0), - decoration: BoxDecoration( - color: tColors.primaryBackgroundColor, - borderRadius: BorderRadius.circular(12), - border: Border.all( - color: tColors.primaryDisabledTextColor?.withOpacity(0.1) ?? - Colors.grey.withOpacity(0.1), - width: 1, - ), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.02), - blurRadius: 4, - offset: const Offset(0, 2), - ), - ], - ), - child: Column( - children: children, - ), - ); - } } diff --git a/lib/app/modules/settings/views/settings_page_enable_24hr_format_list_tile_trailing.dart b/lib/app/modules/settings/views/settings_page_enable_24hr_format_list_tile_trailing.dart index 6f54e321..19b44a58 100644 --- a/lib/app/modules/settings/views/settings_page_enable_24hr_format_list_tile_trailing.dart +++ b/lib/app/modules/settings/views/settings_page_enable_24hr_format_list_tile_trailing.dart @@ -1,10 +1,12 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import 'package:taskwarrior/app/utils/app_settings/app_settings.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:taskwarrior/app/modules/home/controllers/home_controller.dart'; import '../controllers/settings_controller.dart'; + class SettingsPageEnable24hrFormatListTileTrailing extends StatelessWidget { final SettingsController controller; const SettingsPageEnable24hrFormatListTileTrailing( @@ -14,14 +16,13 @@ class SettingsPageEnable24hrFormatListTileTrailing extends StatelessWidget { Widget build(BuildContext context) { return Obx( () => Switch( - value: AppSettings.use24HourFormatRx.value, + value: controller.change24hr.value, onChanged: (bool value) async { - AppSettings.use24HourFormatRx.value = value; - AppSettings.saveSettings( - AppSettings.isDarkMode, - AppSettings.selectedLanguage, - value, - ); + controller.change24hr.value = value; + + final SharedPreferences prefs = await SharedPreferences.getInstance(); + await prefs.setBool('24hourformate', value); + Get.find().change24hr.value = value; }, ), ); diff --git a/lib/app/modules/settings/views/settings_page_select_directory_list_tile.dart b/lib/app/modules/settings/views/settings_page_select_directory_list_tile.dart index e5d76a6e..05a62197 100644 --- a/lib/app/modules/settings/views/settings_page_select_directory_list_tile.dart +++ b/lib/app/modules/settings/views/settings_page_select_directory_list_tile.dart @@ -25,8 +25,7 @@ class SettingsPageSelectDirectoryListTile extends StatelessWidget { @override Widget build(BuildContext context) { - TaskwarriorColorTheme tColors = - Theme.of(context).extension()!; + TaskwarriorColorTheme tColors = Theme.of(context).extension()!; return ListTile( title: Text( SentenceManager(currentLanguage: AppSettings.selectedLanguage) @@ -52,165 +51,148 @@ class SettingsPageSelectDirectoryListTile extends StatelessWidget { const SizedBox( height: 10, ), - IntrinsicHeight( - child: Row( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - // Reset to Default Button - Expanded( - child: TextButton( - style: ButtonStyle( - backgroundColor: WidgetStateProperty.all( - tColors.secondaryBackgroundColor!, - ), - ), - onPressed: () async { - if (await controller.getBaseDirectory() == "Default") { - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Text( - SentenceManager( - currentLanguage: - AppSettings.selectedLanguage) - .sentences - .settingsAlreadyDefault, - style: TextStyle( - color: tColors.primaryTextColor, - ), + Row( + children: [ + //Reset to default + TextButton( + style: ButtonStyle( + backgroundColor: WidgetStateProperty.all( + tColors.secondaryBackgroundColor!, + ), + ), + onPressed: () async { + if (await controller.getBaseDirectory() == "Default") { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text( + SentenceManager( + currentLanguage: AppSettings.selectedLanguage) + .sentences + .settingsAlreadyDefault, + style: TextStyle( + color: tColors.primaryTextColor, + ), + ), + backgroundColor: tColors.secondaryBackgroundColor, + duration: const Duration(seconds: 2))); + } else { + showDialog( + context: context, + builder: (BuildContext context) { + return Utils.showAlertDialog( + title: Text( + SentenceManager( + currentLanguage: + AppSettings.selectedLanguage) + .sentences + .settingsResetToDefault, + style: GoogleFonts.poppins( + fontWeight: FontWeight.bold, + fontSize: TaskWarriorFonts.fontSizeMedium, + color: tColors.primaryTextColor, ), - backgroundColor: tColors.secondaryBackgroundColor, - duration: const Duration(seconds: 2))); - } else { - showDialog( - context: context, - builder: (BuildContext context) { - return Utils.showAlertDialog( - title: Text( + ), + content: Text( + SentenceManager( + currentLanguage: + AppSettings.selectedLanguage) + .sentences + .settingsConfirmReset, + style: GoogleFonts.poppins( + color: TaskWarriorColors.grey, + fontSize: TaskWarriorFonts.fontSizeMedium, + ), + ), + actions: [ + TextButton( + onPressed: () { + Navigator.pop(context); + }, + child: Text( SentenceManager( currentLanguage: AppSettings.selectedLanguage) .sentences - .settingsResetToDefault, + .settingsNoButton, style: GoogleFonts.poppins( - fontWeight: FontWeight.bold, - fontSize: TaskWarriorFonts.fontSizeMedium, color: tColors.primaryTextColor, ), ), - content: Text( + ), + TextButton( + onPressed: () async { + Navigator.pop(context); + controller.isMovingDirectory.value = true; + + // InheritedProfiles profilesWidget = + // ProfilesWidget.of(context); + var profilesWidget = + Get.find(); + + Directory source = + profilesWidget.baseDirectory(); + Directory destination = + await profilesWidget.getDefaultDirectory(); + controller + .moveDirectory( + source.path, destination.path) + .then((value) async { + profilesWidget.setBaseDirectory(destination); + SharedPreferences prefs = + await SharedPreferences.getInstance(); + await prefs.remove('baseDirectory'); + controller.isMovingDirectory.value = false; + controller.baseDirectory.value = "Default"; + }); + }, + child: Text( SentenceManager( currentLanguage: AppSettings.selectedLanguage) .sentences - .settingsConfirmReset, + .settingsYesButton, style: GoogleFonts.poppins( - color: TaskWarriorColors.grey, - fontSize: TaskWarriorFonts.fontSizeMedium, + color: tColors.primaryTextColor, ), ), - actions: [ - TextButton( - onPressed: () { - Navigator.pop(context); - }, - child: Text( - SentenceManager( - currentLanguage: - AppSettings.selectedLanguage) - .sentences - .settingsNoButton, - style: GoogleFonts.poppins( - color: tColors.primaryTextColor, - ), - ), - ), - TextButton( - onPressed: () async { - Navigator.pop(context); - controller.isMovingDirectory.value = true; - - // InheritedProfiles profilesWidget = - // ProfilesWidget.of(context); - var profilesWidget = - Get.find(); - - Directory source = - profilesWidget.baseDirectory(); - Directory destination = await profilesWidget - .getDefaultDirectory(); - controller - .moveDirectory( - source.path, destination.path) - .then((value) async { - profilesWidget - .setBaseDirectory(destination); - SharedPreferences prefs = - await SharedPreferences.getInstance(); - await prefs.remove('baseDirectory'); - controller.isMovingDirectory.value = - false; - controller.baseDirectory.value = - "Default"; - }); - }, - child: Text( - SentenceManager( - currentLanguage: - AppSettings.selectedLanguage) - .sentences - .settingsYesButton, - style: GoogleFonts.poppins( - color: tColors.primaryTextColor, - ), - ), - ), - ], - ); - }, + ), + ], ); - } - }, - child: Text( - SentenceManager( - currentLanguage: - controller.selectedLanguage.value) - .sentences - .settingsPageSetToDefault, - textAlign: TextAlign.center, - softWrap: true, - maxLines: 2, - style: TextStyle( - color: tColors.purpleShade, - ), - ), + }, + ); + } + }, + child: Text( + SentenceManager( + currentLanguage: controller.selectedLanguage.value) + .sentences + .settingsPageSetToDefault, + style: TextStyle( + color: tColors.purpleShade, ), ), - const SizedBox(width: 10), - - // Change Directory Button - Expanded( - child: TextButton( - style: ButtonStyle( - backgroundColor: WidgetStateProperty.all( - tColors.secondaryBackgroundColor!, - ), - ), - onPressed: () => controller.pickDirectory(context), - child: Text( - SentenceManager( - currentLanguage: - controller.selectedLanguage.value) - .sentences - .settingsPageChangeDirectory, - textAlign: TextAlign.center, - softWrap: true, - maxLines: 2, - overflow: TextOverflow.visible, - style: TextStyle(color: tColors.purpleShade), - ), + ), + const Spacer(), + //Change directory + TextButton( + style: ButtonStyle( + backgroundColor: WidgetStateProperty.all( + tColors.secondaryBackgroundColor!, ), ), - ], - ), + onPressed: () { + controller.pickDirectory(context); + }, + child: Text( + SentenceManager( + currentLanguage: controller.selectedLanguage.value) + .sentences + .settingsPageChangeDirectory, + style: TextStyle( + color: tColors.purpleShade, + ), + ), + ), + ], ), ], ), diff --git a/lib/app/modules/splash/controllers/splash_controller.dart b/lib/app/modules/splash/controllers/splash_controller.dart index 126aaebb..1f99f6df 100644 --- a/lib/app/modules/splash/controllers/splash_controller.dart +++ b/lib/app/modules/splash/controllers/splash_controller.dart @@ -59,7 +59,6 @@ class SplashController extends GetxController { void setBaseDirectory(Directory newBaseDirectory) { baseDirectory.value = newBaseDirectory; profilesMap.value = _profiles.profilesMap(); - Get.find().changeInDirectory(); } void addProfile() { @@ -91,9 +90,6 @@ class SplashController extends GetxController { await prefs.setBool('settings_taskr_repl', getMode(profile) == 'TW3C'); Get.find().taskchampion.value = getMode(profile) == 'TW3'; Get.find().taskReplica.value = getMode(profile) == 'TW3C'; - if (getMode(profile) == "TW3C") { - Get.find().refreshReplicaTaskList(); - } Get.find().tasks.value = [].obs; currentProfile.value = _profiles.getCurrentProfile()!; } diff --git a/lib/app/modules/taskc_details/controllers/taskc_details_controller.dart b/lib/app/modules/taskc_details/controllers/taskc_details_controller.dart index 6116cf7e..584aa90c 100644 --- a/lib/app/modules/taskc_details/controllers/taskc_details_controller.dart +++ b/lib/app/modules/taskc_details/controllers/taskc_details_controller.dart @@ -50,9 +50,9 @@ class TaskcDetailsController extends GetxController { // Support both TaskForC (local tasks) and TaskForReplica (replica tasks) if (task is TaskForC) { description = task.description.obs; - project = (task.project ?? 'None').obs; + project = (task.project ?? '-').obs; status = task.status.obs; - priority = (task.priority ?? 'None').obs; + priority = (task.priority ?? '-').obs; due = formatDate(task.due).obs; start = "".obs; wait = "".obs; @@ -66,9 +66,9 @@ class TaskcDetailsController extends GetxController { annotations = [].obs; } else if (task is TaskForReplica) { description = (task.description ?? '').obs; - project = (task.project ?? 'None').obs; + project = (task.project ?? '-').obs; status = (task.status ?? '').obs; - priority = (task.priority ?? 'None').obs; + priority = (task.priority ?? '-').obs; // TaskForReplica stores epoch seconds; convert to ISO string for formatting debugPrint('Replica task due: ${task.due}'); due = formatDate(task.due).obs; @@ -88,10 +88,10 @@ class TaskcDetailsController extends GetxController { } else { // Fallback description = ''.obs; - project = 'None'.obs; + project = '-'.obs; status = ''.obs; - priority = 'None'.obs; - due = 'None'.obs; + priority = '-'.obs; + due = '-'.obs; start = "".obs; wait = "".obs; tags = [].obs; @@ -104,43 +104,27 @@ class TaskcDetailsController extends GetxController { } String formatDate(dynamic date) { - if (date == null) return 'None'; + if (date == null) return '-'; // If date is epoch seconds as int - bool is24hrFormat = AppSettings.use24HourFormatRx.value; - final pattern = is24hrFormat - ? 'EEE, yyyy-MM-dd HH:mm:ss' - : 'EEE, yyyy-MM-dd hh:mm:ss a'; - - if (date == null) return 'None'; - if (date is int) { try { final dt = DateTime.fromMillisecondsSinceEpoch(date * 1000); - return DateFormat(pattern).format(dt.toLocal()); + return DateFormat('yyyy-MM-dd HH:mm:ss').format(dt); } catch (e) { - debugPrint('Error formatting epoch date: $e'); - return 'None'; + return '-'; } } - if (date is DateTime) { - try { - return DateFormat(pattern).format(date.toLocal()); - } catch (e) { - debugPrint('Error formatting DateTime: $e'); - return 'None'; - } + return DateFormat('yyyy-MM-dd HH:mm:ss').format(date); } - final dateString = date?.toString() ?? ''; - if (dateString.isEmpty || dateString == 'None') return 'None'; - + if (dateString.isEmpty || dateString == '-') return '-'; try { - final parsedDate = DateTime.parse(dateString).toLocal(); - return DateFormat(pattern).format(parsedDate); + DateTime parsedDate = DateTime.parse(dateString); + return DateFormat('yyyy-MM-dd HH:mm:ss').format(parsedDate); } catch (e) { - debugPrint('Error parsing date: $dateString $e'); - return 'None'; + debugPrint('Error parsing date: $dateString'); + return '-'; } } @@ -164,13 +148,13 @@ class TaskcDetailsController extends GetxController { // model shape than TaskForC). String initialTaskUuidDisplay() { try { - if (initialTask == null) return 'None'; + if (initialTask == null) return '-'; if (initialTask is TaskForC) - return (initialTask.uuid ?? 'None')?.toString() ?? 'None'; - if (initialTask is TaskForReplica) return initialTask.uuid ?? 'None'; - return 'None'; + return (initialTask.uuid ?? '-')?.toString() ?? '-'; + if (initialTask is TaskForReplica) return initialTask.uuid ?? '-'; + return '-'; } catch (_) { - return 'None'; + return '-'; } } @@ -178,12 +162,12 @@ class TaskcDetailsController extends GetxController { try { if (initialTask is TaskForC) { final u = initialTask.urgency as double?; - return (u != null) ? u.toStringAsFixed(2) : 'None'; + return (u != null) ? u.toStringAsFixed(2) : '-'; } // TaskForReplica doesn't have urgency - return 'None'; + return '-'; } catch (_) { - return 'None'; + return '-'; } } @@ -228,11 +212,6 @@ class TaskcDetailsController extends GetxController { } Future saveTask() async { - bool is24hrFormat = AppSettings.use24HourFormatRx.value; - final datePattern = is24hrFormat - ? 'EEE, yyyy-MM-dd HH:mm:ss' - : 'EEE, yyyy-MM-dd hh:mm:ss a'; - if (tags.length == 1 && tags[0] == "") { tags.clear(); } @@ -266,9 +245,9 @@ class TaskcDetailsController extends GetxController { final modifiedTask = TaskForReplica( modified: nowEpoch, due: () { - if (due.string == 'None' || due.string.isEmpty) return null; + if (due.string == '-' || due.string.isEmpty) return null; try { - final parsed = DateFormat(datePattern).parse(due.string); + final parsed = DateFormat('yyyy-MM-dd HH:mm:ss').parse(due.string); return parsed.toUtc().toIso8601String(); } catch (e) { try { @@ -282,10 +261,10 @@ class TaskcDetailsController extends GetxController { } }(), start: () { - if (start.string == 'None' || start.string.isEmpty) return null; - if (start.string == "stop") return "stop"; + if (start.string == '-' || start.string.isEmpty) return null; try { - final parsed = DateFormat(datePattern).parse(start.string); + final parsed = + DateFormat('yyyy-MM-dd HH:mm:ss').parse(start.string); return parsed.toUtc().toIso8601String(); } catch (e) { try { @@ -299,9 +278,9 @@ class TaskcDetailsController extends GetxController { } }(), wait: () { - if (wait.string == 'None' || wait.string.isEmpty) return null; + if (wait.string == '-' || wait.string.isEmpty) return null; try { - final parsed = DateFormat(datePattern).parse(wait.string); + final parsed = DateFormat('yyyy-MM-dd HH:mm:ss').parse(wait.string); return parsed.toUtc().toIso8601String(); } catch (e) { try { @@ -319,7 +298,7 @@ class TaskcDetailsController extends GetxController { tags: tags.isNotEmpty ? tags.toList() : null, uuid: initialTask.uuid ?? '', priority: priority.string.isNotEmpty ? priority.string : null, - project: project.string != 'None' ? project.string : null, + project: project.string != '-' ? project.string : null, ); debugPrint('Modified replica task: $modifiedTask'); hasChanges.value = false; @@ -353,7 +332,7 @@ class TaskcDetailsController extends GetxController { final BuildContext context = Get.context!; final DateTime? pickedDate = await showDatePicker( context: context, - initialDate: field.value != 'None' + initialDate: field.value != '-' ? DateTime.tryParse(field.value) ?? DateTime.now() : DateTime.now(), firstDate: DateTime(2000), @@ -363,7 +342,7 @@ class TaskcDetailsController extends GetxController { if (pickedDate != null) { final TimeOfDay? pickedTime = await showTimePicker( context: context, - initialTime: TimeOfDay.fromDateTime(field.value != 'None' + initialTime: TimeOfDay.fromDateTime(field.value != '-' ? DateTime.tryParse(field.value) ?? DateTime.now() : DateTime.now()), ); diff --git a/lib/app/modules/taskc_details/views/taskc_details_view.dart b/lib/app/modules/taskc_details/views/taskc_details_view.dart index a3938743..eb2b46d9 100644 --- a/lib/app/modules/taskc_details/views/taskc_details_view.dart +++ b/lib/app/modules/taskc_details/views/taskc_details_view.dart @@ -5,7 +5,6 @@ import 'package:get/get.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:taskwarrior/app/utils/app_settings/app_settings.dart'; import 'package:taskwarrior/app/utils/constants/taskwarrior_colors.dart'; -import 'package:taskwarrior/app/utils/constants/taskwarrior_fonts.dart'; import 'package:taskwarrior/app/utils/themes/theme_extension.dart'; import 'package:taskwarrior/app/utils/language/sentence_manager.dart'; import '../controllers/taskc_details_controller.dart'; @@ -58,7 +57,7 @@ class TaskcDetailsView extends GetView { context, '${SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences.detailPagePriority}:', controller.priority.value, - ['H', 'M', 'L', 'None'], + ['H', 'M', 'L', '-'], (value) => controller.updateField(controller.priority, value), ), _buildDatePickerDetail( @@ -72,17 +71,8 @@ class TaskcDetailsView extends GetView { _buildDatePickerDetail( context, '${SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences.detailPageStart}:', - controller.start.value == "stop" - ? "None" - : controller.start.value, - () => controller.start.value == "stop" || - controller.start.value == "None" || - controller.start.value.isEmpty - ? controller.updateField( - controller.start, - controller.formatDate(DateTime.now()), - ) - : controller.updateField(controller.start, "stop"), + controller.start.value, + () => controller.pickDateTime(controller.start), ), _buildDatePickerDetail( context, @@ -148,11 +138,11 @@ class TaskcDetailsView extends GetView { // Modified is available for both; show it for both types _buildDetail( - context, - '${SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences.detailPageModified}:', - controller.formatDate( - controller.initialTaskModifiedForFormatting()), - disabled: true), + context, + '${SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences.detailPageModified}:', + controller.formatDate( + controller.initialTaskModifiedForFormatting()), + ), ], ), ), @@ -203,13 +193,9 @@ class TaskcDetailsView extends GetView { ); } - Widget _buildDetail(BuildContext context, String label, String value, - {bool disabled = false}) { + Widget _buildDetail(BuildContext context, String label, String value) { TaskwarriorColorTheme tColors = Theme.of(context).extension()!; - final Color? textColor = - disabled ? tColors.primaryDisabledTextColor : tColors.primaryTextColor; - return Container( width: double.infinity, decoration: BoxDecoration( @@ -231,20 +217,19 @@ class TaskcDetailsView extends GetView { children: [ Text( label, - style: GoogleFonts.poppins( - fontWeight: TaskWarriorFonts.bold, - fontSize: TaskWarriorFonts.fontSizeMedium, - color: textColor, + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 18, + color: tColors.primaryTextColor, ), ), const SizedBox(width: 8), Expanded( child: Text( value, - style: GoogleFonts.poppins( - fontWeight: FontWeight.normal, - fontSize: TaskWarriorFonts.fontSizeMedium, - color: textColor, + style: TextStyle( + fontSize: 18, + color: tColors.primaryTextColor, ), ), ), diff --git a/lib/app/services/deep_link_service.dart b/lib/app/services/deep_link_service.dart deleted file mode 100644 index f016c93c..00000000 --- a/lib/app/services/deep_link_service.dart +++ /dev/null @@ -1,69 +0,0 @@ -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/services/notification_services.dart b/lib/app/services/notification_services.dart index 9a24c7fc..1b6c8a9b 100644 --- a/lib/app/services/notification_services.dart +++ b/lib/app/services/notification_services.dart @@ -7,8 +7,6 @@ import 'package:flutter/foundation.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:timezone/data/latest.dart' as tz; import 'package:timezone/timezone.dart' as tz; -import 'package:intl/intl.dart'; -import 'package:taskwarrior/app/utils/app_settings/app_settings.dart'; class NotificationService { final FlutterLocalNotificationsPlugin _flutterLocalNotificationsPlugin = @@ -55,21 +53,13 @@ class NotificationService { return notificationId; } - // Helper method to format date time based on user preferences - String getFormattedDateTime(DateTime dateTime) { - String timeFormat = AppSettings.use24HourFormatRx.value - ? 'yyyy-MM-dd HH:mm:ss' - : 'yyyy-MM-dd hh:mm:ss a'; - return DateFormat(timeFormat).format(dateTime); - } - void sendNotification( DateTime dtb, String taskname, bool isWait, DateTime entryTime) async { DateTime dateTime = DateTime.now(); tz.initializeTimeZones(); if (kDebugMode) { print("date and time are:-$dateTime"); - print("date and time are:-${getFormattedDateTime(dtb)}"); + print("date and time are:-$dtb"); } final tz.TZDateTime scheduledAt = tz.TZDateTime.from(dtb.add(const Duration(minutes: 0)), tz.local); diff --git a/lib/app/utils/add_task_dialogue/date_picker_input.dart b/lib/app/utils/add_task_dialogue/date_picker_input.dart index 17a817d5..50475052 100644 --- a/lib/app/utils/add_task_dialogue/date_picker_input.dart +++ b/lib/app/utils/add_task_dialogue/date_picker_input.dart @@ -2,16 +2,13 @@ import 'package:flutter/material.dart'; import 'package:taskwarrior/app/utils/app_settings/app_settings.dart'; import 'package:taskwarrior/app/utils/language/sentence_manager.dart'; import 'package:taskwarrior/app/utils/taskfunctions/add_task_dialog_utils.dart'; +import 'package:taskwarrior/app/utils/themes/theme_extension.dart'; class AddTaskDatePickerInput extends StatefulWidget { final Function(List)? onDateChanges; final bool onlyDueDate; - final List allowedIndexes; const AddTaskDatePickerInput( - {super.key, - this.onDateChanges, - this.onlyDueDate = false, - this.allowedIndexes = const [0, 1, 2, 3]}); + {super.key, this.onDateChanges, this.onlyDueDate = false}); @override _AddTaskDatePickerInputState createState() => _AddTaskDatePickerInputState(); @@ -25,6 +22,22 @@ class _AddTaskDatePickerInputState extends State { final int length = 4; int currentIndex = 0; + int getNextIndex() => (currentIndex + 1) % length; + + int getPreviousIndex() => (currentIndex - 1) % length; + + void _showNextItem() { + setState(() { + currentIndex = getNextIndex(); + }); + } + + void _showPreviousItem() { + setState(() { + currentIndex = getPreviousIndex(); + }); + } + @override void dispose() { for (var controller in _controllers) { @@ -35,41 +48,75 @@ class _AddTaskDatePickerInputState extends State { @override Widget build(BuildContext context) { - return Row( - crossAxisAlignment: CrossAxisAlignment.start, + TaskwarriorColorTheme tColors = + Theme.of(context).extension()!; + bool isNextDateSelected = _selectedDates[getNextIndex()] != null; + bool isPreviousDateSelected = _selectedDates[getPreviousIndex()] != null; + String nextDateText = isNextDateSelected + ? "${SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences.change} ${dateLabels[getNextIndex()]} ${SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences.date}" + : "${SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences.add} ${dateLabels[getNextIndex()]} ${SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences.date}"; + + String prevDateText = isPreviousDateSelected + ? "${SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences.change} ${dateLabels[getPreviousIndex()]} ${SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences.date}" + : "${SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences.add} ${dateLabels[getPreviousIndex()]} ${SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences.date}"; + return Column( + mainAxisSize: MainAxisSize.min, children: [ - // Dropdown for date type selection - if (!widget.onlyDueDate) - Container( - margin: const EdgeInsets.only(right: 8), - child: DropdownButtonHideUnderline( - child: DropdownButton( - value: currentIndex, - items: [ - for (int index = 0; index < length; index++) - if (widget.allowedIndexes - .contains(index)) // Only add if allowed - DropdownMenuItem( - value: index, - child: Row( - children: [ - Text(dateLabels[index]), - if (_selectedDates[index] != null) - const Icon(Icons.check_circle, size: 14), - ], - ), - ), - ], - onChanged: (value) { - if (value != null) setState(() => currentIndex = value); - }, - ), - ), - ), - // Date picker field - Expanded( + // Display the current input field + Flexible( child: buildDatePicker(context, currentIndex), ), + // Navigation buttons + Visibility( + visible: !widget.onlyDueDate, + child: Row( + children: [ + Expanded( + child: TextButton.icon( + onPressed: _showPreviousItem, + label: Text( + prevDateText, + style: TextStyle( + fontSize: 12, + decoration: isPreviousDateSelected + ? TextDecoration.none + : TextDecoration.underline, + decorationStyle: TextDecorationStyle.wavy, + ), + ), + icon: Icon( + Icons.arrow_back_ios_rounded, + size: 12, + color: tColors.primaryTextColor, + ), + iconAlignment: IconAlignment.start, + ), + ), + const SizedBox(width: 8), // Space between buttons + Expanded( + child: TextButton.icon( + onPressed: _showNextItem, + label: Text( + nextDateText, + style: TextStyle( + fontSize: 12, + decoration: isNextDateSelected + ? TextDecoration.none + : TextDecoration.underline, + decorationStyle: TextDecorationStyle.wavy, + ), + ), + icon: Icon( + Icons.arrow_forward_ios_rounded, + size: 12, + color: tColors.primaryTextColor, + ), + iconAlignment: IconAlignment.end, + ), + ), + ], + ), + ) ], ); } @@ -83,11 +130,9 @@ class _AddTaskDatePickerInputState extends State { controller: _controllers[forIndex], decoration: InputDecoration( labelText: - SentenceManager(currentLanguage: AppSettings.selectedLanguage) - .sentences - .date, + '${dateLabels[forIndex]} ${SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences.date}', hintText: - '${SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences.select} ${SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences.date}', + '${SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences.select} ${dateLabels[forIndex]}', suffixIcon: const Icon(Icons.calendar_today), border: const OutlineInputBorder(), ), @@ -100,36 +145,11 @@ class _AddTaskDatePickerInputState extends State { firstDate: DateTime.now(), lastDate: DateTime(2101), ); - - // FIX: Check if date was selected before showing time picker - if (picked == null) { - return; // User canceled date picker, exit early - } - - // Only show time picker if date was selected final TimeOfDay? time = await showTimePicker( context: context, initialTime: TimeOfDay.now(), ); - - // If user cancels time picker, still set the date with default time - if (time == null) { - setState(() { - // Set date with end-of-day time (23:59) - _selectedDates[forIndex] = picked.add( - const Duration(hours: 23, minutes: 59), - ); - // Update the controller text - _controllers[forIndex].text = - dateToStringForAddTask(_selectedDates[forIndex]!); - }); - if (widget.onDateChanges != null) { - widget.onDateChanges!(_selectedDates); - } - return; - } - - // Both date and time selected + if (picked == null || time == null) return; setState(() { _selectedDates[forIndex] = picked.add(Duration(hours: time.hour, minutes: time.minute)); diff --git a/lib/app/utils/app_settings/app_settings.dart b/lib/app/utils/app_settings/app_settings.dart index 335132ab..6c27b838 100644 --- a/lib/app/utils/app_settings/app_settings.dart +++ b/lib/app/utils/app_settings/app_settings.dart @@ -1,24 +1,18 @@ -import 'package:get/get.dart'; -import 'package:home_widget/home_widget.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:taskwarrior/app/utils/language/supported_language.dart'; part 'save_tour_status.dart'; part 'selected_theme.dart'; part 'selected_language.dart'; -part 'selected_time_format.dart'; class AppSettings { static bool isDarkMode = true; static SupportedLanguage selectedLanguage = SupportedLanguage.english; - static final RxBool use24HourFormatRx = false.obs; static Future init() async { - await HomeWidget.setAppGroupId("group.taskwarrior"); await SelectedTheme.init(); await SelectedLanguage.init(); await SaveTourStatus.init(); - await SelectedTimeFormat.init(); isDarkMode = SelectedTheme.getMode() ?? true; @@ -33,13 +27,11 @@ class AppSettings { // Save the system language as the user's preference await SelectedLanguage.saveSelectedLanguage(selectedLanguage); } - use24HourFormatRx.value = SelectedTimeFormat.getTimeFormat() ?? false; } static Future saveSettings( - bool isDarkMode, SupportedLanguage language, bool use24hour) async { + bool isDarkMode, SupportedLanguage language) async { await SelectedTheme.saveMode(isDarkMode); await SelectedLanguage.saveSelectedLanguage(language); - await SelectedTimeFormat.saveTimeFormat(use24hour); } } diff --git a/lib/app/utils/app_settings/selected_time_format.dart b/lib/app/utils/app_settings/selected_time_format.dart deleted file mode 100644 index 79d2700d..00000000 --- a/lib/app/utils/app_settings/selected_time_format.dart +++ /dev/null @@ -1,17 +0,0 @@ -part of 'app_settings.dart'; - -class SelectedTimeFormat { - static SharedPreferences? _preferences; - - static Future init() async { - _preferences = await SharedPreferences.getInstance(); - } - - static Future saveTimeFormat(bool mode) async { - await _preferences?.setBool('24hourformate', mode); - } - - static bool? getTimeFormat() { - return _preferences?.getBool('24hourformate'); - } -} diff --git a/lib/app/utils/language/bengali_sentences.dart b/lib/app/utils/language/bengali_sentences.dart index 4768b418..77f8584c 100644 --- a/lib/app/utils/language/bengali_sentences.dart +++ b/lib/app/utils/language/bengali_sentences.dart @@ -664,15 +664,4 @@ class BengaliSentences extends Sentences { String get logs => 'লগস'; @override String get checkAllDebugLogsHere => 'এখানে সমস্ত ডিবাগ লগ পরীক্ষা করুন'; - // সেটিংস - @override - String get syncSetting => 'সিঙ্ক সেটিংস'; - @override - String get displaySettings => 'প্রদর্শন সেটিংস'; - @override - String get storageAndData => 'স্টোরেজ এবং ডাটা'; - @override - String get advanced => 'উন্নত'; - @override - String get taskchampionBackendUrl => 'Taskchampion ব্যাকএন্ড URL'; } diff --git a/lib/app/utils/language/english_sentences.dart b/lib/app/utils/language/english_sentences.dart index 6e631539..36852521 100644 --- a/lib/app/utils/language/english_sentences.dart +++ b/lib/app/utils/language/english_sentences.dart @@ -653,15 +653,4 @@ class EnglishSentences extends Sentences { String get logs => 'Logs'; @override String get checkAllDebugLogsHere => 'Check all debug logs here'; - // Settings - @override - String get syncSetting => 'Sync Settings'; - @override - String get displaySettings => 'Display Settings'; - @override - String get storageAndData => 'Storage and Data'; - @override - String get advanced => 'Advanced'; - @override - String get taskchampionBackendUrl => 'Taskchampion URL'; } diff --git a/lib/app/utils/language/french_sentences.dart b/lib/app/utils/language/french_sentences.dart index 6558fc35..c9d37bcf 100644 --- a/lib/app/utils/language/french_sentences.dart +++ b/lib/app/utils/language/french_sentences.dart @@ -681,16 +681,4 @@ class FrenchSentences extends Sentences { @override String get checkAllDebugLogsHere => 'Vérifiez tous les journaux de débogage ici'; - - // Paramètres - @override - String get syncSetting => 'Paramètres de Synchronisation'; - @override - String get displaySettings => "Paramètres d'affichage"; - @override - String get storageAndData => 'Stockage et données'; - @override - String get advanced => 'Avancé'; - @override - String get taskchampionBackendUrl => 'URL de Taskchampion'; } diff --git a/lib/app/utils/language/hindi_sentences.dart b/lib/app/utils/language/hindi_sentences.dart index fd148096..99e78cb7 100644 --- a/lib/app/utils/language/hindi_sentences.dart +++ b/lib/app/utils/language/hindi_sentences.dart @@ -642,15 +642,4 @@ class HindiSentences extends Sentences { String get logs => 'लॉग्स'; @override String get checkAllDebugLogsHere => 'यहाँ सभी डिबग लॉग्स देखें'; - // सेटिंग - @override - String get syncSetting => 'सिंक सेटिंग'; - @override - String get displaySettings => 'डिस्प्ले सेटिंग्स'; - @override - String get storageAndData => 'स्टोरेज और डेटा'; - @override - String get advanced => 'अड्वांस्ड'; - @override - String get taskchampionBackendUrl => 'Taskchampion URL'; } diff --git a/lib/app/utils/language/marathi_sentences.dart b/lib/app/utils/language/marathi_sentences.dart index c4abf2d9..d7758f12 100644 --- a/lib/app/utils/language/marathi_sentences.dart +++ b/lib/app/utils/language/marathi_sentences.dart @@ -664,16 +664,4 @@ class MarathiSentences extends Sentences { String get logs => 'लॉग्ज'; @override String get checkAllDebugLogsHere => 'येथे सर्व डीबग लॉग्ज तपासा'; - - // शेटिंग - @override - String get syncSetting => 'सिंक शेटिंग'; - @override - String get displaySettings => 'डिस्प्ले शेटिंग्स'; - @override - String get storageAndData => 'स्टोरेज आणि डेटा'; - @override - String get advanced => 'अड्वांस्ड'; - @override - String get taskchampionBackendUrl => 'Taskchampion URL'; } diff --git a/lib/app/utils/language/sentence_manager.dart b/lib/app/utils/language/sentence_manager.dart index 78927ae2..4a8a10a3 100644 --- a/lib/app/utils/language/sentence_manager.dart +++ b/lib/app/utils/language/sentence_manager.dart @@ -19,11 +19,11 @@ class SentenceManager { case SupportedLanguage.marathi: return MarathiSentences(); case SupportedLanguage.french: - return FrenchSentences(); + return FrenchSentences(); case SupportedLanguage.spanish: - return SpanishSentences(); + return SpanishSentences(); case SupportedLanguage.bengali: - return BengaliSentences(); + return BengaliSentences(); case SupportedLanguage.english: default: return EnglishSentences(); diff --git a/lib/app/utils/language/sentences.dart b/lib/app/utils/language/sentences.dart index 77cb1877..5fbd2e85 100644 --- a/lib/app/utils/language/sentences.dart +++ b/lib/app/utils/language/sentences.dart @@ -282,10 +282,6 @@ abstract class Sentences { // Splash screen strings String get splashSettingUpApp; - String get syncSetting; - String get displaySettings; - String get storageAndData; - String get advanced; // Tour strings - reports String get tourReportsDaily; @@ -340,7 +336,6 @@ abstract class Sentences { String get configureTaskchampion; String get encryptionSecret; String get ccsyncBackendUrl; - String get taskchampionBackendUrl; String get ccsyncClientId; String get success; String get credentialsSavedSuccessfully; diff --git a/lib/app/utils/language/spanish_sentences.dart b/lib/app/utils/language/spanish_sentences.dart index 17d73340..a85e76b7 100644 --- a/lib/app/utils/language/spanish_sentences.dart +++ b/lib/app/utils/language/spanish_sentences.dart @@ -669,16 +669,4 @@ class SpanishSentences extends Sentences { @override String get checkAllDebugLogsHere => 'Consulta todos los registros de depuración aquí'; - - // - @override - String get syncSetting => 'Configuración de sincronización'; - @override - String get displaySettings => 'Configuración de visualización'; - @override - String get storageAndData => 'Almacenamiento y datos'; - @override - String get advanced => 'Avanzado'; - @override - String get taskchampionBackendUrl => 'Taskchampion URL'; } diff --git a/lib/app/utils/taskfunctions/datetime_differences.dart b/lib/app/utils/taskfunctions/datetime_differences.dart index 41586ced..94b4aeb9 100644 --- a/lib/app/utils/taskfunctions/datetime_differences.dart +++ b/lib/app/utils/taskfunctions/datetime_differences.dart @@ -1,11 +1,10 @@ -import 'package:intl/intl.dart'; -import 'package:taskwarrior/app/utils/app_settings/app_settings.dart'; +String age(DateTime dt) => difference(DateTime.now().difference(dt)); -String age(DateTime dt, {bool? use24HourFormat}) { - final format = AppSettings.use24HourFormatRx.value; +String when(DateTime dt) => difference(dt.difference(DateTime.now())); +String difference(Duration difference) { String result; - var days = DateTime.now().difference(dt).abs().inDays; + var days = difference.abs().inDays; if (days > 365) { result = '${(days / 365).toStringAsFixed(1).replaceFirst(RegExp(r'\.0$'), '')}y'; @@ -15,46 +14,12 @@ String age(DateTime dt, {bool? use24HourFormat}) { result = '${days ~/ 7}w'; } else if (days > 0) { result = '${days}d'; - } else if (DateTime.now().difference(dt).abs().inHours > 0) { - result = '${DateTime.now().difference(dt).abs().inHours}h'; - } else if (DateTime.now().difference(dt).abs().inMinutes > 0) { - result = '${DateTime.now().difference(dt).abs().inMinutes}min'; + } else if (difference.abs().inHours > 0) { + result = '${difference.abs().inHours}h'; + } else if (difference.abs().inMinutes > 0) { + result = '${difference.abs().inMinutes}min'; } else { - result = '${DateTime.now().difference(dt).abs().inSeconds}s'; + result = '${difference.abs().inSeconds}s'; } - - // Format the time part according to the format preference - String timeFormat = format ? 'HH:mm' : 'hh:mm a'; - String formattedTime = DateFormat(timeFormat).format(dt); - - return '$result ago ($formattedTime)'; -} - -String when(DateTime dt, {bool? use24HourFormat}) { - final format = AppSettings.use24HourFormatRx.value; - - String result; - var days = dt.difference(DateTime.now()).abs().inDays; - if (days > 365) { - result = - '${(days / 365).toStringAsFixed(1).replaceFirst(RegExp(r'\.0$'), '')}y'; - } else if (days > 30) { - result = '${days ~/ 30}mo'; - } else if (days > 7) { - result = '${days ~/ 7}w'; - } else if (days > 0) { - result = '${days}d'; - } else if (dt.difference(DateTime.now()).abs().inHours > 0) { - result = '${dt.difference(DateTime.now()).abs().inHours}h'; - } else if (dt.difference(DateTime.now()).abs().inMinutes > 0) { - result = '${dt.difference(DateTime.now()).abs().inMinutes}min'; - } else { - result = '${dt.difference(DateTime.now()).abs().inSeconds}s'; - } - - // Format the time part according to the format preference - String timeFormat = format ? 'HH:mm' : 'hh:mm a'; - String formattedTime = DateFormat(timeFormat).format(dt); - - return '$result ($formattedTime)'; + return '$result ${(difference.isNegative) ? 'ago ' : ''}'; } diff --git a/lib/app/utils/taskfunctions/profiles.dart b/lib/app/utils/taskfunctions/profiles.dart index 67e5f74a..ff51482c 100644 --- a/lib/app/utils/taskfunctions/profiles.dart +++ b/lib/app/utils/taskfunctions/profiles.dart @@ -3,7 +3,6 @@ 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'; @@ -72,7 +71,6 @@ class Profiles { entity.uri.pathSegments.lastWhere((segment) => segment.isNotEmpty)) .toList() ..sort(comparator); - debugPrint('Profiles found: $dirs'); return dirs; } diff --git a/lib/app/utils/taskfunctions/validate.dart b/lib/app/utils/taskfunctions/validate.dart index 7f64e8bf..ee644382 100644 --- a/lib/app/utils/taskfunctions/validate.dart +++ b/lib/app/utils/taskfunctions/validate.dart @@ -1,7 +1,7 @@ void validateTaskDescription(String description) { - if (description.trim().isEmpty) { + if (description.isEmpty) { throw FormatException( - 'Description cannot be empty or contain only spaces.', + 'Empty description will provoke a server error.', description, 0, ); diff --git a/lib/app/v3/net/fetch.dart b/lib/app/v3/net/fetch.dart index 54adde77..c38ef639 100644 --- a/lib/app/v3/net/fetch.dart +++ b/lib/app/v3/net/fetch.dart @@ -14,7 +14,7 @@ Future> fetchTasks(String uuid, String encryptionSecret) async { var response = await http.get(Uri.parse(url), headers: { "Content-Type": "application/json", - }).timeout(const Duration(milliseconds: 10000)); + }).timeout(const Duration(seconds: 10000)); debugPrint("Fetch tasks response: ${response.statusCode}"); debugPrint("Fetch tasks body: ${response.body}"); if (response.statusCode == 200) { diff --git a/lib/main.dart b/lib/main.dart index 09f0268c..7ea0e8df 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,12 +1,8 @@ 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'; @@ -16,17 +12,9 @@ import 'app/routes/app_pages.dart'; LogDatabaseHelper _logDatabaseHelper = LogDatabaseHelper(); -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'); -} +const buildOfTcHelperForAndroid = "libtc_helper.so"; +final dyLibOfTcHelperForAndroid = + DynamicLibrary.open(buildOfTcHelperForAndroid); void main() async { debugPrint = (String? message, {int? wrapWidth}) { @@ -36,13 +24,11 @@ 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 573a8199..3e5728a9 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.taskwarriorflutter") +set(APPLICATION_ID "com.ccextractor.taskwarrior") # 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 6b783a15..0c0079a0 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.taskwarriorflutter.RunnerTests; + PRODUCT_BUNDLE_IDENTIFIER = com.ccextractor.taskwarrior.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.taskwarriorflutter.RunnerTests; + PRODUCT_BUNDLE_IDENTIFIER = com.ccextractor.taskwarrior.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.taskwarriorflutter.RunnerTests; + PRODUCT_BUNDLE_IDENTIFIER = com.ccextractor.taskwarrior.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 25621bd6..4a8c5a8d 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.taskwarriorflutter +PRODUCT_BUNDLE_IDENTIFIER = com.ccextractor.taskwarrior // The copyright displayed in application information PRODUCT_COPYRIGHT = Copyright © 2024 com.ccextractor. All rights reserved. diff --git a/pubspec.yaml b/pubspec.yaml index 12e80726..47a0cb14 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -51,7 +51,7 @@ dependencies: package_info_plus: ^8.3.0 pem: ^2.0.1 permission_handler: ^11.4.0 - shared_preferences: ^2.5.3 + shared_preferences: ^2.5.2 shared_preferences_web: ^2.0.3 sizer: ^3.0.5 sqflite: ^2.3.3+1 @@ -68,7 +68,6 @@ 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/rust/README.md b/rust/README.md index dc30d093..714bdae1 100644 --- a/rust/README.md +++ b/rust/README.md @@ -1,34 +1,18 @@ -## Rust ⇄ Flutter: Commands +`For dart file generation` +`flutter_rust_bridge_codegen generate \` + `--rust-input crate::api \` + `--rust-root rust \` + `--dart-output lib/bridge/bridge_generated.dart` -### Generate Dart bindings +`For Compiling RustLib to android` +`cargo ndk -t arm64-v8a -t armeabi-v7a -t x86 -t x86_64 -o ../android/main/app/src/main/jniLibs build` -```bash -flutter_rust_bridge_codegen generate \ - --rust-input crate::api \ - --rust-root rust \ - --dart-output lib/bridge/bridge_generated.dart -``` +- [ ] `these targets are not added yet` -### Compile Rust library for Android +`For running application` +`[fvm] flutter run –flavor {production/nightly}` -```bash -cargo ndk -t arm64-v8a -t armeabi-v7a -o ../android/app/src/main/jniLibs build --release -``` +`For Compiling/Building apk` +`[fvm] flutter build apk –-flavor production` -- [ ] these targets are not added yet - -### Run the app - -```bash -# using fvm -fvm flutter run --flavor {production|nightly} -``` - -### Build APK - -```bash -# using fvm -fvm flutter build apk --flavor production -``` - -Note: Nightly is only for CI. +* `Nightly is only for CI` \ No newline at end of file diff --git a/rust/src/api.rs b/rust/src/api.rs index 6129cc0f..6f049705 100644 --- a/rust/src/api.rs +++ b/rust/src/api.rs @@ -108,11 +108,7 @@ pub fn update_task( let _ = t.set_due(parse_datetime(&value), &mut ops); } "start" => { - if value == "stop" { - let _ = t.stop(&mut ops); - } else { - let _ = t.start(&mut ops); - } + let _ = t.start(&mut ops); } "wait" => { let _ = t.set_wait(parse_datetime(&value), &mut ops); @@ -139,7 +135,7 @@ pub fn update_task( } } "project" => { - let _ = t.set_value("project", Some(value), &mut ops); + let _ = t.set_user_defined_attribute("project", value, &mut ops); } "status" => { let status = match value.as_str() { diff --git a/test/api_service_test.dart b/test/api_service_test.dart index 85d84391..cfa115f8 100644 --- a/test/api_service_test.dart +++ b/test/api_service_test.dart @@ -27,15 +27,6 @@ void main() { setUpAll(() { sqfliteFfiInit(); - - // Mock SharedPreferences plugin - const MethodChannel('plugins.flutter.io/shared_preferences') - .setMockMethodCallHandler((MethodCall methodCall) async { - if (methodCall.method == 'getAll') { - return {}; // Return empty prefs - } - return null; - }); }); group('Tasks model', () { @@ -187,9 +178,9 @@ void main() { await taskDatabase.insertTask(task); await taskDatabase.deleteAllTasksInDB(); - // The implementation has a bug where it calls maps.last on empty results - // This will throw "Bad state: No element" when there are no tasks - expect(() => taskDatabase.fetchTasksFromDatabase(), throwsStateError); + final tasks = await taskDatabase.fetchTasksFromDatabase(); + + expect(tasks.length, 0); }); }); } diff --git a/test/models/storage/client_test.dart b/test/models/storage/client_test.dart index f2beae6d..d4bbbf28 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.ccextractor.app', + packageName: 'com.example.app', version: '1.0.0', buildNumber: '1', buildSignature: '', @@ -18,7 +18,7 @@ void main() { final result = await client(); - expect(result, 'com.ccextractor.app 1.0.0'); + expect(result, 'com.example.app 1.0.0'); }); }); } diff --git a/test/modules/home/task_list_item_test.dart b/test/modules/home/task_list_item_test.dart index dcef33c7..b8a60720 100644 --- a/test/modules/home/task_list_item_test.dart +++ b/test/modules/home/task_list_item_test.dart @@ -5,33 +5,10 @@ import 'package:taskwarrior/app/models/json/task.dart'; import 'package:taskwarrior/app/modules/home/views/tas_list_item.dart'; import 'package:taskwarrior/app/utils/language/supported_language.dart'; import 'package:taskwarrior/app/utils/taskfunctions/modify.dart'; -import 'package:taskwarrior/app/utils/themes/theme_extension.dart'; -import 'package:taskwarrior/app/utils/constants/taskwarrior_colors.dart'; class MockModify extends Mock implements Modify {} void main() { - // Helper function to create a theme with TaskwarriorColorTheme extension - ThemeData createTestTheme() { - return ThemeData( - extensions: [ - TaskwarriorColorTheme( - dialogBackgroundColor: Colors.white, - primaryBackgroundColor: Colors.blue, - primaryDisabledTextColor: Colors.grey, - primaryTextColor: Colors.black, - secondaryBackgroundColor: Colors.grey[200], - secondaryTextColor: Colors.black54, - dividerColor: Colors.grey, - purpleShade: Colors.purple, - greyShade: Colors.grey, - icons: Icons.star, - dimCol: Colors.grey, - ), - ], - ); - } - group('TaskListItem', () { late Task normalTask; late Task dueSoonTask; @@ -69,7 +46,6 @@ void main() { testWidgets('renders normal task without highlight', (WidgetTester tester) async { await tester.pumpWidget(MaterialApp( - theme: createTestTheme(), home: Scaffold( body: TaskListItem( normalTask, @@ -92,7 +68,6 @@ void main() { testWidgets('renders due soon task with red border', (WidgetTester tester) async { await tester.pumpWidget(MaterialApp( - theme: createTestTheme(), home: Scaffold( body: TaskListItem( dueSoonTask, @@ -114,7 +89,6 @@ void main() { testWidgets('renders overdue task with red background', (WidgetTester tester) async { await tester.pumpWidget(MaterialApp( - theme: createTestTheme(), home: Scaffold( body: TaskListItem( overdueTask, @@ -137,7 +111,6 @@ void main() { testWidgets('does not highlight tasks when useDelayTask is false', (WidgetTester tester) async { await tester.pumpWidget(MaterialApp( - theme: createTestTheme(), home: Scaffold( body: TaskListItem( overdueTask, diff --git a/test/routes/app_pages_test.dart b/test/routes/app_pages_test.dart index d46d23e9..58d8fd91 100644 --- a/test/routes/app_pages_test.dart +++ b/test/routes/app_pages_test.dart @@ -30,7 +30,7 @@ void main() { test('All routes should be defined correctly', () { final routes = AppPages.routes; - expect(routes.length, 11); + expect(routes.length, 10); expect( routes.any((route) => diff --git a/test/taskfunctions/datetime_differences_test.dart b/test/taskfunctions/datetime_differences_test.dart index fcf639d5..434771e9 100644 --- a/test/taskfunctions/datetime_differences_test.dart +++ b/test/taskfunctions/datetime_differences_test.dart @@ -62,5 +62,35 @@ void main() { result = when(dt); expect(result, contains('29s')); }); + + test('Test difference function', () { + DateTime dt = DateTime.now().subtract(const Duration(days: 365)); + String result = difference(DateTime.now().difference(dt)); + expect(result, contains('12mo ')); + + dt = DateTime.now().subtract(const Duration(days: 60)); + result = difference(DateTime.now().difference(dt)); + expect(result, contains('2mo ')); + + dt = DateTime.now().subtract(const Duration(days: 21)); + result = difference(DateTime.now().difference(dt)); + expect(result, contains('3w ')); + + dt = DateTime.now().subtract(const Duration(days: 4)); + result = difference(DateTime.now().difference(dt)); + expect(result, contains('4d ')); + + dt = DateTime.now().subtract(const Duration(hours: 5)); + result = difference(DateTime.now().difference(dt)); + expect(result, contains('5h ')); + + dt = DateTime.now().subtract(const Duration(minutes: 10)); + result = difference(DateTime.now().difference(dt)); + expect(result, contains('10min ')); + + dt = DateTime.now().subtract(const Duration(seconds: 30)); + result = difference(DateTime.now().difference(dt)); + expect(result, contains('30s ')); + }); }); } diff --git a/test/utils/app_settings/app_settings_test.dart b/test/utils/app_settings/app_settings_test.dart index 7544936a..592de7a1 100644 --- a/test/utils/app_settings/app_settings_test.dart +++ b/test/utils/app_settings/app_settings_test.dart @@ -4,10 +4,6 @@ import 'package:taskwarrior/app/utils/app_settings/app_settings.dart'; import 'package:taskwarrior/app/utils/language/supported_language.dart'; void main() { - setUpAll(() { - TestWidgetsFlutterBinding.ensureInitialized(); - }); - group('AppSettings', () { setUp(() async { SharedPreferences.setMockInitialValues({}); @@ -17,15 +13,12 @@ void main() { test('should initialize settings correctly', () async { expect(AppSettings.isDarkMode, true); expect(AppSettings.selectedLanguage, SupportedLanguage.english); - expect(AppSettings.use24HourFormatRx.value, false); }); test('should save settings correctly', () async { - await AppSettings.saveSettings(true, SupportedLanguage.english, true); - await AppSettings.init(); + await AppSettings.saveSettings(false, SupportedLanguage.english); expect(AppSettings.isDarkMode, true); expect(AppSettings.selectedLanguage, SupportedLanguage.english); - expect(AppSettings.use24HourFormatRx.value, true); }); }); diff --git a/test/utils/app_settings/selected_time_format_test.dart b/test/utils/app_settings/selected_time_format_test.dart deleted file mode 100644 index c5bd8e8a..00000000 --- a/test/utils/app_settings/selected_time_format_test.dart +++ /dev/null @@ -1,34 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:shared_preferences/shared_preferences.dart'; -import 'package:taskwarrior/app/utils/app_settings/app_settings.dart'; - -void main() { - TestWidgetsFlutterBinding.ensureInitialized(); - - setUp(() async { - SharedPreferences.setMockInitialValues({}); - await AppSettings.init(); - }); - - test('should save and retrieve time format setting', () async { - // Initially should be false (12-hour format) - expect(AppSettings.use24HourFormatRx.value, false); - - // Set to true (24-hour format) - await SelectedTimeFormat.saveTimeFormat(true); - expect(SelectedTimeFormat.getTimeFormat(), true); - - // Set back to false (12-hour format) - await SelectedTimeFormat.saveTimeFormat(false); - expect(SelectedTimeFormat.getTimeFormat(), false); - }); - - test('AppSettings should initialize with the stored time format', () async { - // Set format to true (24-hour) - await SelectedTimeFormat.saveTimeFormat(true); - - // Re-initialize and check - await AppSettings.init(); - expect(AppSettings.use24HourFormatRx.value, true); - }); -} diff --git a/test/utils/taskfunctions/datetime_differences_test.dart b/test/utils/taskfunctions/datetime_differences_test.dart index d0a5b8e4..9e47a0c0 100644 --- a/test/utils/taskfunctions/datetime_differences_test.dart +++ b/test/utils/taskfunctions/datetime_differences_test.dart @@ -1,92 +1,71 @@ import 'package:flutter_test/flutter_test.dart'; -import 'package:get/get.dart'; -import 'package:taskwarrior/app/utils/app_settings/app_settings.dart'; import 'package:taskwarrior/app/utils/taskfunctions/datetime_differences.dart'; void main() { - setUp(() { - AppSettings.use24HourFormatRx.value = false; - }); - group('DateTime Differences', () { test('age function should return correct string for years', () { final now = DateTime.now(); final dt = now.subtract(const Duration(days: 2 * 365)); - expect(age(dt), startsWith('2y ago (')); + expect(age(dt), '2y '); }); test('age function should return correct string for months', () { final now = DateTime.now(); final dt = now.subtract(const Duration(days: 2 * 30)); - expect(age(dt), startsWith('2mo ago (')); + expect(age(dt), '2mo '); }); test('age function should return correct string for weeks', () { final now = DateTime.now(); final dt = now.subtract(const Duration(days: 2 * 7)); - expect(age(dt), startsWith('2w ago (')); + expect(age(dt), '2w '); }); test('age function should return correct string for days', () { final now = DateTime.now(); final dt = now.subtract(const Duration(days: 2)); // 2 days - expect(age(dt), startsWith('2d ago (')); + expect(age(dt), '2d '); }); test('age function should return correct string for hours', () { final now = DateTime.now(); final dt = now.subtract(const Duration(hours: 2)); // 2 hours - expect(age(dt), startsWith('2h ago (')); + expect(age(dt), '2h '); }); test('age function should return correct string for minutes', () { final now = DateTime.now(); final dt = now.subtract(const Duration(minutes: 2)); // 2 minutes - expect(age(dt), startsWith('2min ago (')); + expect(age(dt), '2min '); }); test('age function should return correct string for seconds', () { final now = DateTime.now(); final dt = now.subtract(const Duration(seconds: 2)); // 2 seconds - expect(age(dt), startsWith('2s ago (')); - }); - - test('age function should respect use24HourFormat app setting', () { - final now = DateTime.now(); - final dt = now.subtract(const Duration(days: 2)); // 2 days - - // Test with 12-hour format - AppSettings.use24HourFormatRx.value = false; - expect(age(dt), contains('2d')); - expect(age(dt), matches(r'.*\d{1,2}:\d{2} [AP]M\)')); - - // Test with 24-hour format - AppSettings.use24HourFormatRx.value = true; - expect(age(dt), contains('2d')); - expect(age(dt), matches(r'.*\d{1,2}:\d{2}\)')); + expect(age(dt), '2s '); }); test('when function should return correct string for future dates', () { final now = DateTime.now(); - final dt = - now.add(const Duration(days: 1, hours: 12)); // 1+ days from now - expect(when(dt), startsWith('1d (')); + final dt = now.add(const Duration(days: 2)); // 2 days from now + expect(when(dt), '1d '); }); - test('when function should respect use24HourFormat app setting', () { - final now = DateTime.now(); - final dt = - now.add(const Duration(days: 1, hours: 12)); // 1+ days from now - - // Test with 12-hour format - AppSettings.use24HourFormatRx.value = false; - expect(when(dt), contains('1d')); - expect(when(dt), matches(r'.*\d{1,2}:\d{2} [AP]M\)')); + test( + 'difference function should return correct string for various durations', + () { + expect(difference(const Duration(days: 2 * 365)), '2y '); + expect(difference(const Duration(days: 2 * 30)), '2mo '); + expect(difference(const Duration(days: 2 * 7)), '2w '); + expect(difference(const Duration(days: 2)), '2d '); + expect(difference(const Duration(hours: 2)), '2h '); + expect(difference(const Duration(minutes: 2)), '2min '); + expect(difference(const Duration(seconds: 2)), '2s '); - // Test with 24-hour format - AppSettings.use24HourFormatRx.value = true; - expect(when(dt), contains('1d')); - expect(when(dt), matches(r'.*\d{1,2}:\d{2}\)')); + expect(difference(const Duration(days: -2)), '2d ago '); + expect(difference(const Duration(hours: -2)), '2h ago '); + expect(difference(const Duration(minutes: -2)), '2min ago '); + expect(difference(const Duration(seconds: -2)), '2s ago '); }); }); } diff --git a/test/utils/taskfunctions/datetime_format_test.dart b/test/utils/taskfunctions/datetime_format_test.dart deleted file mode 100644 index 029b4a68..00000000 --- a/test/utils/taskfunctions/datetime_format_test.dart +++ /dev/null @@ -1,57 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:intl/intl.dart'; -import 'package:taskwarrior/app/utils/app_settings/app_settings.dart'; -import 'package:get/get.dart'; - -void main() { - setUp(() { - AppSettings.use24HourFormatRx.value = false; - }); - - test('DateFormat should respect 24-hour format setting', () { - // Test date: May 15, 2023, 2:30 PM / 14:30 - final dateTime = DateTime(2023, 5, 15, 14, 30); - - // Test with 12-hour format - AppSettings.use24HourFormatRx.value = false; - final format12h = DateFormat(AppSettings.use24HourFormatRx.value - ? 'yyyy-MM-dd HH:mm' - : 'yyyy-MM-dd hh:mm a'); - String formatted12h = format12h.format(dateTime); - expect(formatted12h, contains('02:30 PM')); - expect(formatted12h, isNot(contains('14:30'))); - - // Test with 24-hour format - AppSettings.use24HourFormatRx.value = true; - final format24h = DateFormat(AppSettings.use24HourFormatRx.value - ? 'yyyy-MM-dd HH:mm' - : 'yyyy-MM-dd hh:mm a'); - String formatted24h = format24h.format(dateTime); - expect(formatted24h, contains('14:30')); - expect(formatted24h, isNot(contains('PM'))); - expect(formatted24h, isNot(contains('AM'))); - }); - - test('DateTime formatting in detail view should respect 24-hour format', () { - // Test date: May 15, 2023, 2:30 PM / 14:30 - final dateTime = DateTime(2023, 5, 15, 14, 30); - - // Test with 12-hour format - AppSettings.use24HourFormatRx.value = false; - final format12h = DateFormat(AppSettings.use24HourFormatRx.value - ? 'EEE, yyyy-MM-dd HH:mm:ss' - : 'EEE, yyyy-MM-dd hh:mm:ss a'); - String formatted12h = format12h.format(dateTime); - expect(formatted12h, contains('02:30:00 PM')); - expect(formatted12h, isNot(contains('14:30:00'))); - - // Test with 24-hour format - AppSettings.use24HourFormatRx.value = true; - final format24h = DateFormat(AppSettings.use24HourFormatRx.value - ? 'EEE, yyyy-MM-dd HH:mm:ss' - : 'EEE, yyyy-MM-dd hh:mm:ss a'); - String formatted24h = format24h.format(dateTime); - expect(formatted24h, contains('14:30:00')); - expect(formatted24h, isNot(contains('PM'))); - }); -}