这是indexloc提供的服务,不要输入任何密码
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
<action android:name="es.antonborri.home_widget.action.LAUNCH" />
</intent-filter>
</activity>

<receiver android:name="TaskWarriorWidgetProvider"
android:exported="true">
<intent-filter>
Expand All @@ -44,6 +45,22 @@
<meta-data android:name="android.appwidget.provider"
android:resource="@xml/taskwarriorconfig" />
</receiver>

<receiver android:name=".WidgetUpdateReceiver"
android:exported="true">
<intent-filter>
<action android:name="UPDATE_WIDGET" />
</intent-filter>
</receiver>

<receiver android:name=".BurndownChartProvider"
android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data android:name="android.appwidget.provider"
android:resource="@xml/burndownchartconfig" />
</receiver>

<!-- Used for Background Work -->
<receiver android:name="es.antonborri.home_widget.HomeWidgetBackgroundReceiver"
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +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()
class MainActivity: FlutterActivity() {
private val channel = "com.example.taskwarrior/widget"

override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)

MethodChannel(flutterEngine.dartExecutor.binaryMessenger, channel).setMethodCallHandler {
call, result ->
if (call.method == "updateWidget") {
updateWidget()
result.success("Widget updated")
} else {
result.notImplemented()
}
}
}

private fun updateWidget() {
val intent = Intent(this, WidgetUpdateReceiver::class.java).apply {
action = "UPDATE_WIDGET"
}
sendBroadcast(intent)
}
}
Original file line number Diff line number Diff line change
@@ -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)
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
47 changes: 47 additions & 0 deletions android/app/src/main/res/layout/report_layout.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="1dp"
android:background="#80000000"> <!-- Transparent dark background -->

<TextView
android:id="@+id/widget_header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:padding="8dp"
android:text="Daily Report"
android:textColor="#FFFFFF"
android:textSize="16sp"
android:textStyle="bold" />

<ImageView
android:id="@+id/widget_image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/widget_header"
android:scaleType="fitXY"
android:contentDescription="Burndown Chart" />

<TextView
android:id="@+id/no_image_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="@string/my_widget_description3"
android:textColor="#FFFFFF"
android:textSize="16sp"
android:visibility="visible" />

<!--scaleType values -->
<!--
android:scaleType="centerCrop"
android:scaleType="centerInside"
android:scaleType="fitCenter"
android:scaleType="fitEnd"
android:scaleType="fitStart"
android:scaleType="fitXY"
android:scaleType="matrix"
-->

</RelativeLayout>
2 changes: 2 additions & 0 deletions android/app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
<string name="app_widget_card_clicked_uri">taskwarriorappwidget://cardclicked</string>
<string name="app_widget_add_clicked_uri">taskwarriorappwidget://addclicked</string>
<string name="my_widget_description">This widget shows pending tasks from TaskWarrior app</string>
<string name="my_widget_description2">This widget shows the daily reports graph to track your progress</string>
<string name="my_widget_description3">Click Refresh button in Daily Reports Tab</string>
<string name="appwidget_text">TaskWarrior</string>
<string name="add_widget">Add widget</string>
<string name="text_task_list">Task List</string>
Expand Down
10 changes: 10 additions & 0 deletions android/app/src/main/res/xml/burndownchartconfig.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="200dp"
android:minHeight="150dp"
android:updatePeriodMillis="0"
android:initialLayout="@layout/report_layout"
android:resizeMode="horizontal|vertical"
android:previewImage="@drawable/preview_report"
android:widgetCategory="home_screen"
android:description="@string/my_widget_description2">
</appwidget-provider>
73 changes: 69 additions & 4 deletions lib/app/modules/reports/controllers/reports_controller.dart
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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
Expand All @@ -34,6 +36,69 @@ class ReportsController extends GetxController
late Storage storage;
var storageWidget;

final GlobalKey _chartKey = GlobalKey();

GlobalKey get chartKey => _chartKey;

Future<void> 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<String>('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(
Expand Down
Loading
Loading