From a7a6288b40ba874612636b38b81808c73accc1ac Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 13 Feb 2025 00:25:56 +0530 Subject: [PATCH] Added Feature For Report Widget --- android/app/src/main/AndroidManifest.xml | 17 ++ .../taskwarriorflutter/BurndownChart.kt | 13 ++ .../taskwarriorflutter/MainActivity.kt | 33 +++- .../WidgetUpdateReceiver.kt | 68 +++++++ .../src/main/res/drawable/preview_report.jpg | Bin 0 -> 52154 bytes .../app/src/main/res/layout/report_layout.xml | 47 +++++ android/app/src/main/res/values/strings.xml | 2 + .../src/main/res/xml/burndownchartconfig.xml | 10 + .../controllers/reports_controller.dart | 73 ++++++- .../reports/views/burn_down_daily.dart | 184 +++++++++++------- pubspec.lock | 92 ++++++--- pubspec.yaml | 1 + 12 files changed, 430 insertions(+), 110 deletions(-) create mode 100644 android/app/src/main/kotlin/com/ccextractor/taskwarriorflutter/BurndownChart.kt create mode 100644 android/app/src/main/kotlin/com/ccextractor/taskwarriorflutter/WidgetUpdateReceiver.kt create mode 100644 android/app/src/main/res/drawable/preview_report.jpg create mode 100644 android/app/src/main/res/layout/report_layout.xml create mode 100644 android/app/src/main/res/xml/burndownchartconfig.xml diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 6be7f747..b8f116a9 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -36,6 +36,7 @@ + @@ -44,6 +45,22 @@ + + + + + + + + + + + + + + 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/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/drawable/preview_report.jpg b/android/app/src/main/res/drawable/preview_report.jpg new file mode 100644 index 0000000000000000000000000000000000000000..761c8ff898138b9c11a3c70b4d5b9155ed83a54c GIT binary patch literal 52154 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 + + + + + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index ff9b9745..182c4b81 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -9,6 +9,8 @@ taskwarriorappwidget://cardclicked taskwarriorappwidget://addclicked This widget shows pending tasks from TaskWarrior app + This widget shows the daily reports graph to track your progress + Click Refresh button in Daily Reports Tab TaskWarrior Add widget Task List 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/lib/app/modules/reports/controllers/reports_controller.dart b/lib/app/modules/reports/controllers/reports_controller.dart index e33e2d66..17ab277f 100644 --- a/lib/app/modules/reports/controllers/reports_controller.dart +++ b/lib/app/modules/reports/controllers/reports_controller.dart @@ -1,9 +1,10 @@ -// ignore_for_file: prefer_typing_uninitialized_variables - 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'; +import 'package:shared_preferences/shared_preferences.dart'; import 'package:syncfusion_flutter_charts/charts.dart'; import 'package:taskwarrior/api_service.dart'; import 'package:taskwarrior/app/models/json/task.dart'; @@ -16,7 +17,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:tutorial_coach_mark/tutorial_coach_mark.dart'; class ReportsController extends GetxController @@ -34,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 e57096a8..ff2c33c3 100644 --- a/lib/app/modules/reports/views/burn_down_daily.dart +++ b/lib/app/modules/reports/views/burn_down_daily.dart @@ -18,88 +18,122 @@ class BurnDownDaily extends StatelessWidget { Widget build(BuildContext context) { double height = MediaQuery.of(context).size.height; - return Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, + return Stack( children: [ - Expanded( - child: SizedBox( - height: height * 0.6, - child: Obx( - () => SfCartesianChart( - primaryXAxis: CategoryAxis( - title: AxisTitle( - text: SentenceManager( - currentLanguage: AppSettings.selectedLanguage) - .sentences - .reportsPageDailyDayMonth, - textStyle: TextStyle( - fontFamily: FontFamily.poppins, - fontWeight: TaskWarriorFonts.bold, - color: AppSettings.isDarkMode - ? Colors.white - : Colors.black, - 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: AppSettings.isDarkMode + ? Colors.white + : Colors.black, + fontSize: TaskWarriorFonts.fontSizeSmall, + ), + ), ), - ), - ), - primaryYAxis: NumericAxis( - title: AxisTitle( - text: SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences.reportsPageTasks, - textStyle: TextStyle( - fontFamily: FontFamily.poppins, - fontWeight: TaskWarriorFonts.bold, - color: AppSettings.isDarkMode - ? Colors.white - : Colors.black, - fontSize: TaskWarriorFonts.fontSizeSmall, + primaryYAxis: NumericAxis( + title: AxisTitle( + text: SentenceManager( + currentLanguage: AppSettings.selectedLanguage) + .sentences + .reportsPageTasks, + textStyle: TextStyle( + fontFamily: FontFamily.poppins, + fontWeight: TaskWarriorFonts.bold, + color: AppSettings.isDarkMode + ? Colors.white + : Colors.black, + 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', - ), + 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', + /// 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/pubspec.lock b/pubspec.lock index 90d1a52e..9a596b38 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -165,10 +165,10 @@ packages: dependency: transitive description: name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.19.0" color: dependency: transitive description: @@ -273,6 +273,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.10" + dio: + dependency: transitive + description: + name: dio + sha256: "7d328c4d898a61efc3cd93655a0955858e29a0aa647f0f9e02d59b3bb275e2e8" + url: "https://pub.dev" + source: hosted + version: "4.0.6" double_back_to_close_app: dependency: "direct main" description: @@ -616,6 +624,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.2" + http_mock_adapter: + dependency: "direct dev" + description: + name: http_mock_adapter + sha256: "6fa1a00932a5758750f54c5594f8bf37393fe5ae86e49325281b5a9d99114f33" + url: "https://pub.dev" + source: hosted + version: "0.3.3" http_multi_server: dependency: transitive description: @@ -700,18 +716,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06" url: "https://pub.dev" source: hosted - version: "10.0.4" + version: "10.0.7" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.8" leak_tracker_testing: dependency: transitive description: @@ -756,18 +772,18 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.15.0" mime: dependency: transitive description: @@ -841,21 +857,21 @@ packages: source: hosted version: "1.0.1" path_provider: - dependency: transitive + dependency: "direct main" description: name: path_provider - sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161 + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.1.5" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: e595b98692943b4881b219f0a9e3945118d3c16bd7e2813f98ec6e532d905f72 + sha256: "4adf4fd5423ec60a29506c76581bc05854c55e3a0b72d35bb28d661c9686edf2" url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.2.15" path_provider_foundation: dependency: transitive description: @@ -1105,7 +1121,7 @@ packages: dependency: transitive description: flutter source: sdk - version: "0.0.99" + version: "0.0.0" source_gen: dependency: transitive description: @@ -1162,14 +1178,30 @@ packages: url: "https://pub.dev" source: hosted version: "2.5.4" + sqflite_common_ffi: + dependency: "direct dev" + description: + name: sqflite_common_ffi + sha256: "4d6137c29e930d6e4a8ff373989dd9de7bac12e3bc87bce950f6e844e8ad3bb5" + url: "https://pub.dev" + source: hosted + version: "2.3.3" + sqlite3: + dependency: transitive + description: + name: sqlite3 + sha256: "1abbeb84bf2b1a10e5e1138c913123c8aa9d83cd64e5f9a0dd847b3c83063202" + url: "https://pub.dev" + source: hosted + version: "2.4.2" stack_trace: dependency: transitive description: name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.12.0" stream_channel: dependency: transitive description: @@ -1190,10 +1222,10 @@ packages: dependency: transitive description: name: string_scanner - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.3.0" syncfusion_flutter_charts: dependency: "direct main" description: @@ -1230,26 +1262,26 @@ packages: dependency: "direct main" description: name: test - sha256: "7ee446762c2c50b3bd4ea96fe13ffac69919352bd3b4b17bac3f3465edc58073" + sha256: "713a8789d62f3233c46b4a90b174737b2c04cb6ae4500f2aa8b1be8f03f5e67f" url: "https://pub.dev" source: hosted - version: "1.25.2" + version: "1.25.8" test_api: dependency: transitive description: name: test_api - sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.3" test_core: dependency: transitive description: name: test_core - sha256: "2bc4b4ecddd75309300d8096f781c0e3280ca1ef85beda558d33fcbedc2eead4" + sha256: "12391302411737c176b0b5d6491f466b0dd56d4763e347b6714efbaa74d7953d" url: "https://pub.dev" source: hosted - version: "0.6.0" + version: "0.6.5" time: dependency: transitive description: @@ -1422,10 +1454,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b url: "https://pub.dev" source: hosted - version: "14.2.1" + version: "14.3.0" watcher: dependency: transitive description: @@ -1491,5 +1523,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.3.0 <4.0.0" - flutter: ">=3.20.0-1.2.pre" + dart: ">=3.5.0 <4.0.0" + flutter: ">=3.24.0" diff --git a/pubspec.yaml b/pubspec.yaml index 19db109c..41fbc734 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -64,6 +64,7 @@ dependencies: url_launcher: ^6.1.14 uuid: ^4.2.2 built_collection: ^5.1.1 + path_provider: ^2.1.5 dev_dependencies: build_runner: null