Skip to content

Commit 6250e78

Browse files
authored
Merge pull request #448 from tank0nf/final_report_widget
Added Feature For Reports Charts Widget #440
2 parents 2353be9 + a7a6288 commit 6250e78

File tree

12 files changed

+430
-110
lines changed

12 files changed

+430
-110
lines changed

android/app/src/main/AndroidManifest.xml

+17
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
<action android:name="es.antonborri.home_widget.action.LAUNCH" />
3737
</intent-filter>
3838
</activity>
39+
3940
<receiver android:name="TaskWarriorWidgetProvider"
4041
android:exported="true">
4142
<intent-filter>
@@ -44,6 +45,22 @@
4445
<meta-data android:name="android.appwidget.provider"
4546
android:resource="@xml/taskwarriorconfig" />
4647
</receiver>
48+
49+
<receiver android:name=".WidgetUpdateReceiver"
50+
android:exported="true">
51+
<intent-filter>
52+
<action android:name="UPDATE_WIDGET" />
53+
</intent-filter>
54+
</receiver>
55+
56+
<receiver android:name=".BurndownChartProvider"
57+
android:exported="true">
58+
<intent-filter>
59+
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
60+
</intent-filter>
61+
<meta-data android:name="android.appwidget.provider"
62+
android:resource="@xml/burndownchartconfig" />
63+
</receiver>
4764

4865
<!-- Used for Background Work -->
4966
<receiver android:name="es.antonborri.home_widget.HomeWidgetBackgroundReceiver"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.ccextractor.taskwarriorflutter
2+
3+
import android.appwidget.AppWidgetManager
4+
import android.content.Context
5+
import android.content.SharedPreferences
6+
import es.antonborri.home_widget.HomeWidgetProvider
7+
8+
class BurndownChartProvider : HomeWidgetProvider() {
9+
override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray, widgetData: SharedPreferences) {
10+
// This method is intentionally left blank.
11+
// Widget updates are handled by WidgetUpdateReceiver.
12+
}
13+
}
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,36 @@
11
package com.ccextractor.taskwarriorflutter
22

3+
import android.content.Intent
4+
import android.os.Bundle
35
import io.flutter.embedding.android.FlutterActivity
6+
import io.flutter.embedding.engine.FlutterEngine
7+
import io.flutter.plugin.common.MethodChannel
8+
import android.content.Context
9+
import android.content.IntentFilter
10+
import android.appwidget.AppWidgetManager
11+
import android.content.ComponentName
412

5-
class MainActivity: FlutterActivity()
13+
class MainActivity: FlutterActivity() {
14+
private val channel = "com.example.taskwarrior/widget"
15+
16+
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
17+
super.configureFlutterEngine(flutterEngine)
18+
19+
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, channel).setMethodCallHandler {
20+
call, result ->
21+
if (call.method == "updateWidget") {
22+
updateWidget()
23+
result.success("Widget updated")
24+
} else {
25+
result.notImplemented()
26+
}
27+
}
28+
}
29+
30+
private fun updateWidget() {
31+
val intent = Intent(this, WidgetUpdateReceiver::class.java).apply {
32+
action = "UPDATE_WIDGET"
33+
}
34+
sendBroadcast(intent)
35+
}
36+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package com.ccextractor.taskwarriorflutter
2+
3+
import android.appwidget.AppWidgetManager
4+
import android.view.View
5+
import android.content.BroadcastReceiver
6+
import android.content.ComponentName
7+
import android.content.Context
8+
import android.content.Intent
9+
import android.graphics.BitmapFactory
10+
import android.util.Log
11+
import android.widget.RemoteViews
12+
import java.io.File
13+
import com.ccextractor.taskwarriorflutter.R
14+
import es.antonborri.home_widget.HomeWidgetPlugin
15+
16+
class WidgetUpdateReceiver : BroadcastReceiver() {
17+
18+
override fun onReceive(context: Context, intent: Intent) {
19+
if (intent.action == "UPDATE_WIDGET") {
20+
Log.d("WidgetUpdateReceiver", "Received UPDATE_WIDGET broadcast")
21+
22+
val appWidgetManager = AppWidgetManager.getInstance(context)
23+
val appWidgetIds = appWidgetManager.getAppWidgetIds(ComponentName(context, BurndownChartProvider::class.java))
24+
25+
for (appWidgetId in appWidgetIds) {
26+
updateAppWidget(context, appWidgetManager, appWidgetId)
27+
}
28+
}
29+
}
30+
31+
private fun updateAppWidget(context: Context, appWidgetManager: AppWidgetManager, appWidgetId: Int) {
32+
Log.d("WidgetUpdateReceiver", "Updating widget $appWidgetId")
33+
34+
val views = RemoteViews(context.packageName, R.layout.report_layout)
35+
36+
// Retrieve the chart image path from HomeWidget
37+
val chartImage = HomeWidgetPlugin.getData(context).getString("chart_image", null)
38+
39+
if (chartImage != null) {
40+
Log.d("WidgetUpdateReceiver", "Chart image path: $chartImage")
41+
val file = File(chartImage)
42+
if (file.exists()) {
43+
Log.d("WidgetUpdateReceiver", "File exists!")
44+
val b = BitmapFactory.decodeFile(file.absolutePath)
45+
if (b != null) {
46+
Log.d("WidgetUpdateReceiver", "Bitmap decoded successfully!")
47+
views.setImageViewBitmap(R.id.widget_image, b)
48+
views.setViewVisibility(R.id.widget_image, View.VISIBLE)
49+
views.setViewVisibility(R.id.no_image_text, View.GONE)
50+
} else {
51+
Log.e("WidgetUpdateReceiver", "Bitmap decoding failed!")
52+
views.setViewVisibility(R.id.widget_image, View.GONE)
53+
views.setViewVisibility(R.id.no_image_text, View.VISIBLE)
54+
}
55+
} else {
56+
Log.e("WidgetUpdateReceiver", "File does not exist: $chartImage")
57+
views.setViewVisibility(R.id.widget_image, View.GONE)
58+
views.setViewVisibility(R.id.no_image_text, View.VISIBLE)
59+
}
60+
} else {
61+
Log.d("WidgetUpdateReceiver", "No chart image saved yet")
62+
views.setViewVisibility(R.id.widget_image, View.GONE)
63+
views.setViewVisibility(R.id.no_image_text, View.VISIBLE)
64+
}
65+
66+
appWidgetManager.updateAppWidget(appWidgetId, views)
67+
}
68+
}
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
2+
android:layout_width="match_parent"
3+
android:layout_height="match_parent"
4+
android:padding="1dp"
5+
android:background="#80000000"> <!-- Transparent dark background -->
6+
7+
<TextView
8+
android:id="@+id/widget_header"
9+
android:layout_width="match_parent"
10+
android:layout_height="wrap_content"
11+
android:gravity="center"
12+
android:padding="8dp"
13+
android:text="Daily Report"
14+
android:textColor="#FFFFFF"
15+
android:textSize="16sp"
16+
android:textStyle="bold" />
17+
18+
<ImageView
19+
android:id="@+id/widget_image"
20+
android:layout_width="match_parent"
21+
android:layout_height="match_parent"
22+
android:layout_below="@id/widget_header"
23+
android:scaleType="fitXY"
24+
android:contentDescription="Burndown Chart" />
25+
26+
<TextView
27+
android:id="@+id/no_image_text"
28+
android:layout_width="wrap_content"
29+
android:layout_height="wrap_content"
30+
android:layout_centerInParent="true"
31+
android:text="@string/my_widget_description3"
32+
android:textColor="#FFFFFF"
33+
android:textSize="16sp"
34+
android:visibility="visible" />
35+
36+
<!--scaleType values -->
37+
<!--
38+
android:scaleType="centerCrop"
39+
android:scaleType="centerInside"
40+
android:scaleType="fitCenter"
41+
android:scaleType="fitEnd"
42+
android:scaleType="fitStart"
43+
android:scaleType="fitXY"
44+
android:scaleType="matrix"
45+
-->
46+
47+
</RelativeLayout>

android/app/src/main/res/values/strings.xml

+2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
<string name="app_widget_card_clicked_uri">taskwarriorappwidget://cardclicked</string>
1010
<string name="app_widget_add_clicked_uri">taskwarriorappwidget://addclicked</string>
1111
<string name="my_widget_description">This widget shows pending tasks from TaskWarrior app</string>
12+
<string name="my_widget_description2">This widget shows the daily reports graph to track your progress</string>
13+
<string name="my_widget_description3">Click Refresh button in Daily Reports Tab</string>
1214
<string name="appwidget_text">TaskWarrior</string>
1315
<string name="add_widget">Add widget</string>
1416
<string name="text_task_list">Task List</string>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
2+
android:minWidth="200dp"
3+
android:minHeight="150dp"
4+
android:updatePeriodMillis="0"
5+
android:initialLayout="@layout/report_layout"
6+
android:resizeMode="horizontal|vertical"
7+
android:previewImage="@drawable/preview_report"
8+
android:widgetCategory="home_screen"
9+
android:description="@string/my_widget_description2">
10+
</appwidget-provider>

lib/app/modules/reports/controllers/reports_controller.dart

+69-4
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
// ignore_for_file: prefer_typing_uninitialized_variables
2-
31
import 'dart:io';
4-
2+
import 'package:home_widget/home_widget.dart';
3+
import 'dart:ui';
54
import 'package:flutter/material.dart';
5+
import 'package:flutter/rendering.dart';
66
import 'package:get/get.dart';
7+
import 'package:shared_preferences/shared_preferences.dart';
78
import 'package:syncfusion_flutter_charts/charts.dart';
89
import 'package:taskwarrior/api_service.dart';
910
import 'package:taskwarrior/app/models/json/task.dart';
@@ -16,7 +17,8 @@ import 'package:taskwarrior/app/utils/constants/taskwarrior_fonts.dart';
1617
import 'package:taskwarrior/app/utils/constants/utilites.dart';
1718
import 'package:taskwarrior/app/utils/gen/fonts.gen.dart';
1819
import 'package:taskwarrior/app/utils/app_settings/app_settings.dart';
19-
20+
import 'package:path_provider/path_provider.dart';
21+
import 'package:flutter/services.dart';
2022
import 'package:tutorial_coach_mark/tutorial_coach_mark.dart';
2123

2224
class ReportsController extends GetxController
@@ -34,6 +36,69 @@ class ReportsController extends GetxController
3436
late Storage storage;
3537
var storageWidget;
3638

39+
final GlobalKey _chartKey = GlobalKey();
40+
41+
GlobalKey get chartKey => _chartKey;
42+
43+
Future<void> captureChart() async {
44+
try {
45+
if (chartKey.currentContext == null) {
46+
print('Error: chartKey.currentContext is null');
47+
return;
48+
}
49+
50+
RenderRepaintBoundary? boundary =
51+
chartKey.currentContext!.findRenderObject() as RenderRepaintBoundary?;
52+
53+
if (boundary == null) {
54+
print('Error: boundary is null');
55+
return;
56+
}
57+
58+
final image = await boundary.toImage();
59+
final byteData = await image.toByteData(format: ImageByteFormat.png);
60+
61+
if (byteData == null) {
62+
print('Error: byteData is null');
63+
return;
64+
}
65+
66+
final pngBytes = byteData.buffer.asUint8List();
67+
68+
// Get the documents directory
69+
final directory = await getApplicationDocumentsDirectory();
70+
final imagePath = '${directory.path}/daily_burndown_chart.png';
71+
72+
// Save the image to the documents directory
73+
File imgFile = File(imagePath);
74+
await imgFile.writeAsBytes(pngBytes);
75+
print('Image saved to: $imagePath');
76+
77+
// Save the image path to HomeWidget
78+
await HomeWidget.saveWidgetData<String>('chart_image', imagePath);
79+
80+
// Verify that the file exists
81+
if (await imgFile.exists()) {
82+
print('Image file exists!');
83+
} else {
84+
print('Image file does not exist!');
85+
}
86+
87+
// Add a delay before sending the broadcast
88+
await Future.delayed(const Duration(milliseconds: 500));
89+
90+
// Send a broadcast to update the widget
91+
const platform = MethodChannel('com.example.taskwarrior/widget');
92+
try {
93+
await platform.invokeMethod('updateWidget');
94+
} on PlatformException catch (e) {
95+
print("Failed to Invoke: '${e.message}'.");
96+
}
97+
} catch (e) {
98+
print('Error capturing chart: $e');
99+
}
100+
}
101+
37102
// void _initReportsTour() {
38103
// tutorialCoachMark = TutorialCoachMark(
39104
// targets: reportsDrawer(

0 commit comments

Comments
 (0)