Skip to content

Commit 6904d1e

Browse files
ctrlVntmarcnause
andauthored
feat: home widget of saved logs (only for Android) (#3285)
Co-authored-by: Marc Nause <marc.nause@audioattack.de>
1 parent 63680c8 commit 6904d1e

9 files changed

Lines changed: 241 additions & 0 deletions

File tree

android/app/build.gradle

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ plugins {
22
id "com.android.application"
33
id "kotlin-android"
44
id "dev.flutter.flutter-gradle-plugin"
5+
id "org.jetbrains.kotlin.plugin.compose"
56
}
67

78
def localProperties = new Properties()
@@ -75,6 +76,14 @@ android {
7576
signingConfig = signingConfigs.release
7677
}
7778
}
79+
buildFeatures {
80+
compose true
81+
}
82+
}
83+
84+
dependencies {
85+
implementation "androidx.glance:glance-appwidget:1.1.1"
86+
implementation "androidx.glance:glance-material3:1.1.1"
7887
}
7988

8089
flutter {

android/app/src/main/AndroidManifest.xml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,15 @@
4747
<meta-data
4848
android:name="flutterEmbedding"
4949
android:value="2" />
50+
51+
<receiver android:name="io.pslab.widget.WidgetReceiver" android:exported="true">
52+
<intent-filter>
53+
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
54+
</intent-filter>
55+
<meta-data
56+
android:name="android.appwidget.provider"
57+
android:resource="@xml/widget_info" />
58+
</receiver>
5059
</application>
5160
<!-- Required to query activities that can process text, see:
5261
https://developer.android.com/training/package-visibility and
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
package io.pslab.widget
2+
3+
import android.content.Context
4+
import android.content.Intent
5+
import androidx.compose.runtime.Composable
6+
import androidx.compose.ui.unit.dp
7+
import androidx.compose.ui.unit.sp
8+
import androidx.glance.GlanceId
9+
import androidx.glance.GlanceModifier
10+
import androidx.glance.action.clickable
11+
import androidx.glance.appwidget.GlanceAppWidget
12+
import androidx.glance.appwidget.action.actionStartActivity
13+
import androidx.glance.appwidget.lazy.LazyColumn
14+
import androidx.glance.appwidget.lazy.items
15+
import androidx.glance.appwidget.provideContent
16+
import androidx.glance.background
17+
import androidx.glance.layout.*
18+
import androidx.glance.text.Text
19+
import androidx.glance.text.TextStyle
20+
import androidx.glance.text.FontWeight
21+
import androidx.glance.unit.ColorProvider
22+
import androidx.compose.ui.graphics.Color
23+
import org.json.JSONArray
24+
import org.json.JSONException
25+
import io.pslab.MainActivity
26+
27+
data class LogItem(val fileName: String, val instrument: String)
28+
29+
class WidgetProvider : GlanceAppWidget() {
30+
31+
override suspend fun provideGlance(context: Context, id: GlanceId) {
32+
val prefs = context.getSharedPreferences("HomeWidgetPreferences", Context.MODE_PRIVATE)
33+
val jsonString = prefs.getString("logs_json_key", "[]") ?: "[]"
34+
val logList = mutableListOf<LogItem>()
35+
36+
try {
37+
val jsonArray = JSONArray(jsonString)
38+
for (i in 0 until jsonArray.length()) {
39+
val obj = jsonArray.getJSONObject(i)
40+
logList.add(
41+
LogItem(
42+
fileName = obj.optString("fileName", "Unknown File"),
43+
instrument = obj.optString("instrument", "General")
44+
)
45+
)
46+
}
47+
} catch (e: JSONException) {
48+
// fallback
49+
}
50+
51+
provideContent {
52+
GlanceWidgetLayout(context, logList)
53+
}
54+
}
55+
56+
@Composable
57+
private fun GlanceWidgetLayout(context: Context, logs: List<LogItem>) {
58+
val openAppAction = actionStartActivity(Intent(context, MainActivity::class.java))
59+
60+
Column(
61+
modifier = GlanceModifier
62+
.fillMaxSize()
63+
.background(Color(0xFFF5F5F5)),
64+
verticalAlignment = Alignment.Top,
65+
horizontalAlignment = Alignment.Start
66+
) {
67+
Row(
68+
modifier = GlanceModifier
69+
.fillMaxWidth()
70+
.background(Color(0xFFD32F2F))
71+
.padding(horizontal = 16.dp, vertical = 12.dp)
72+
.clickable(openAppAction),
73+
verticalAlignment = Alignment.CenterVertically
74+
) {
75+
Text(
76+
text = "PSLab Saved Logs",
77+
style = TextStyle(
78+
color = ColorProvider(Color.White),
79+
fontSize = 16.sp,
80+
fontWeight = FontWeight.Bold
81+
)
82+
)
83+
}
84+
85+
if (logs.isEmpty()) {
86+
Box(
87+
modifier = GlanceModifier
88+
.fillMaxSize()
89+
.padding(16.dp)
90+
.clickable(openAppAction),
91+
contentAlignment = Alignment.Center
92+
) {
93+
Text(
94+
text = "No logged data found",
95+
style = TextStyle(color = ColorProvider(Color.Gray), fontSize = 14.sp)
96+
)
97+
}
98+
} else {
99+
LazyColumn(
100+
modifier = GlanceModifier
101+
.fillMaxWidth()
102+
.defaultWeight()
103+
.padding(horizontal = 8.dp, vertical = 4.dp)
104+
) {
105+
items(logs) { log ->
106+
LogItemCard(log, context)
107+
}
108+
}
109+
}
110+
}
111+
}
112+
113+
@Composable
114+
private fun LogItemCard(log: LogItem, context: Context) {
115+
Box(
116+
modifier = GlanceModifier
117+
.fillMaxWidth()
118+
.padding(vertical = 4.dp, horizontal = 4.dp)
119+
.background(Color.White)
120+
.clickable(actionStartActivity(Intent(context, MainActivity::class.java)))
121+
) {
122+
Column(
123+
modifier = GlanceModifier
124+
.fillMaxWidth()
125+
.padding(12.dp)
126+
) {
127+
Text(
128+
text = log.fileName,
129+
style = TextStyle(
130+
color = ColorProvider(Color(0xFF212121)),
131+
fontSize = 14.sp,
132+
fontWeight = FontWeight.Medium
133+
)
134+
)
135+
Text(
136+
text = log.instrument.uppercase(),
137+
style = TextStyle(
138+
color = ColorProvider(Color(0xFFD32F2F)),
139+
fontSize = 11.sp,
140+
fontWeight = FontWeight.Bold
141+
),
142+
modifier = GlanceModifier.padding(top = 4.dp)
143+
)
144+
}
145+
}
146+
}
147+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package io.pslab.widget
2+
3+
import androidx.glance.appwidget.GlanceAppWidget
4+
import androidx.glance.appwidget.GlanceAppWidgetReceiver
5+
6+
class WidgetReceiver : GlanceAppWidgetReceiver() {
7+
override val glanceAppWidget: GlanceAppWidget = WidgetProvider()
8+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
2+
android:initialLayout="@layout/glance_default_loading_layout"
3+
android:minWidth="115dp"
4+
android:minHeight="115dp"
5+
android:resizeMode="horizontal|vertical"
6+
android:updatePeriodMillis="0">
7+
</appwidget-provider>

android/settings.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ plugins {
2020
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
2121
id "com.android.application" version "8.12.2" apply false
2222
id "org.jetbrains.kotlin.android" version "2.3.21" apply false
23+
id "org.jetbrains.kotlin.plugin.compose" version "2.3.21" apply false
2324
}
2425

2526
include ":app"

lib/others/csv_service.dart

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import 'dart:io';
22
import 'dart:convert';
33
import 'package:csv/csv.dart' as csv;
44
import 'package:file_picker/file_picker.dart';
5+
import 'package:home_widget/home_widget.dart';
56
import 'package:path_provider/path_provider.dart';
67
import 'package:pslab/others/logger_service.dart';
78
import 'package:share_plus/share_plus.dart';
@@ -59,6 +60,44 @@ class CsvService {
5960
final csvData = codec.encode(data);
6061
await file.writeAsString(csvData);
6162
logger.i('${appLocalizations.csvFileSaved}: ${file.path}');
63+
64+
if (Platform.isAndroid) {
65+
try {
66+
final externalDir = await getExternalStorageDirectory();
67+
final pslabDir = Directory('${externalDir?.path}/PSLab');
68+
69+
final logEntries =
70+
<({String fileName, String instrument, DateTime modified})>[];
71+
if (await pslabDir.exists()) {
72+
for (final entity in pslabDir.listSync(followLinks: false)) {
73+
if (entity is! Directory) continue;
74+
final instrument = entity.path.split('/').last;
75+
for (final file in entity
76+
.listSync(followLinks: false)
77+
.whereType<File>()
78+
.where((f) => f.path.endsWith('.csv'))) {
79+
logEntries.add((
80+
fileName: file.path.split('/').last,
81+
instrument: instrument,
82+
modified: file.statSync().modified,
83+
));
84+
}
85+
}
86+
}
87+
logEntries.sort((a, b) => b.modified.compareTo(a.modified));
88+
final widgetListData = logEntries
89+
.take(20)
90+
.map((e) => {'fileName': e.fileName, 'instrument': e.instrument})
91+
.toList();
92+
93+
await HomeWidget.saveWidgetData<String>(
94+
'logs_json_key', jsonEncode(widgetListData));
95+
await HomeWidget.updateWidget(androidName: 'widget.WidgetReceiver');
96+
} catch (widgetError) {
97+
logger.w('Error during widget update: $widgetError');
98+
}
99+
}
100+
62101
return file;
63102
} catch (e) {
64103
logger.e('${appLocalizations.csvSavingError}: $e');

lib/view/logged_data_screen.dart

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
import 'dart:convert';
12
import 'dart:io';
23
import 'package:flutter/material.dart';
4+
import 'package:home_widget/home_widget.dart';
35
import 'package:intl/intl.dart';
46
import 'package:pslab/others/csv_service.dart';
57
import 'package:pslab/theme/colors.dart';
@@ -67,6 +69,24 @@ class _LoggedDataScreenState extends State<LoggedDataScreen> {
6769
_allFiles.add(LoggedDataFile(instrumentName: name, file: file));
6870
}
6971
}
72+
73+
if (Platform.isAndroid) {
74+
try {
75+
final List<Map<String, String>> widgetListData = _allFiles.map((f) {
76+
final fileName = f.file.path.split('/').last;
77+
return {
78+
'fileName': fileName,
79+
'instrument': f.instrumentName,
80+
};
81+
}).toList();
82+
83+
await HomeWidget.saveWidgetData<String>(
84+
'logs_json_key', jsonEncode(widgetListData));
85+
86+
await HomeWidget.updateWidget(androidName: 'widget.WidgetReceiver');
87+
} catch (_) {}
88+
}
89+
7090
if (mounted) {
7191
setState(() {
7292
_isLoading = false;

pubspec.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ dependencies:
5555
web: ^1.1.1
5656
mp_audio_stream: ^0.2.2
5757
ffi: ^2.2.0
58+
home_widget: ^0.9.2
5859
girix_code_gauge: ^0.0.6
5960
dev_dependencies:
6061
flutter_launcher_icons: ^0.14.1

0 commit comments

Comments
 (0)