feat: add update and delete functionality to tags and improve report screen
This commit is contained in:
parent
2df0390ae2
commit
ebc4cdf754
12 changed files with 464 additions and 43 deletions
|
|
@ -68,15 +68,7 @@ class MyApp extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
title: 'Flutter Rust Time Tracker',
|
title: 'Flutter Rust Time Tracker',
|
||||||
theme: ThemeData(
|
theme: ThemeData(primarySwatch: Colors.blue, useMaterial3: true),
|
||||||
primarySwatch: Colors.blue,
|
|
||||||
useMaterial3: true,
|
|
||||||
// Optional: Theme für BottomNavigationBar anpassen
|
|
||||||
// bottomNavigationBarTheme: const BottomNavigationBarThemeData(
|
|
||||||
// selectedItemColor: Colors.deepPurple,
|
|
||||||
// unselectedItemColor: Colors.grey,
|
|
||||||
// ),
|
|
||||||
),
|
|
||||||
home: const InitializerWidget(),
|
home: const InitializerWidget(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import 'package:flutter_platform_widgets/flutter_platform_widgets.dart';
|
||||||
import 'package:timetracker/screens/home_screen.dart';
|
import 'package:timetracker/screens/home_screen.dart';
|
||||||
import 'package:timetracker/screens/tags_screen.dart';
|
import 'package:timetracker/screens/tags_screen.dart';
|
||||||
import 'package:timetracker/screens/report_screen.dart';
|
import 'package:timetracker/screens/report_screen.dart';
|
||||||
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
|
|
||||||
class MainScreen extends StatefulWidget {
|
class MainScreen extends StatefulWidget {
|
||||||
const MainScreen({super.key});
|
const MainScreen({super.key});
|
||||||
|
|
@ -37,8 +38,8 @@ class _MainScreenState extends State<MainScreen> {
|
||||||
|
|
||||||
items: [
|
items: [
|
||||||
BottomNavigationBarItem(
|
BottomNavigationBarItem(
|
||||||
icon: Icon(context.platformIcons.clockSolid),
|
icon: FaIcon(FontAwesomeIcons.clock),
|
||||||
activeIcon: Icon(context.platformIcons.clockSolid),
|
activeIcon: FaIcon(FontAwesomeIcons.solidClock),
|
||||||
label: 'Tracking',
|
label: 'Tracking',
|
||||||
),
|
),
|
||||||
BottomNavigationBarItem(
|
BottomNavigationBarItem(
|
||||||
|
|
@ -47,8 +48,12 @@ class _MainScreenState extends State<MainScreen> {
|
||||||
label: 'Tags',
|
label: 'Tags',
|
||||||
),
|
),
|
||||||
BottomNavigationBarItem(
|
BottomNavigationBarItem(
|
||||||
icon: Icon(context.platformIcons.bookmarkOutline),
|
icon: FaIcon(
|
||||||
activeIcon: Icon(context.platformIcons.bookmarkSolid),
|
FontAwesomeIcons.chartBar,
|
||||||
|
), //Icon(context.platformIcons.bookmarkOutline),
|
||||||
|
activeIcon: FaIcon(
|
||||||
|
FontAwesomeIcons.solidChartBar,
|
||||||
|
), //Icon(context.platformIcons.bookmarkSolid),
|
||||||
label: 'Reports',
|
label: 'Reports',
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -487,8 +487,8 @@ class _ReportScreenState extends State<ReportScreen> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 250, child: _buildPieChart(_reportData!)),
|
// SizedBox(height: 250, child: _buildPieChart(_reportData!)),
|
||||||
const Divider(),
|
// const Divider(),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: 250,
|
height: 250,
|
||||||
child: _buildBarChart(_reportData!, startDate, endDate),
|
child: _buildBarChart(_reportData!, startDate, endDate),
|
||||||
|
|
@ -507,7 +507,7 @@ class _ReportScreenState extends State<ReportScreen> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 300, child: _buildReportList(_reportData!.entries)),
|
SizedBox(height: 600, child: _buildReportList(_reportData!.entries)),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -649,8 +649,8 @@ class _ReportScreenState extends State<ReportScreen> {
|
||||||
|
|
||||||
Widget _buildReportList(List<TimeEntry> entries) {
|
Widget _buildReportList(List<TimeEntry> entries) {
|
||||||
return ListView.builder(
|
return ListView.builder(
|
||||||
shrinkWrap: true,
|
// shrinkWrap: true,
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
// physics: const NeverScrollableScrollPhysics(),
|
||||||
itemCount: entries.length,
|
itemCount: entries.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final entry = entries[index];
|
final entry = entries[index];
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
import 'package:flutter_platform_widgets/flutter_platform_widgets.dart';
|
import 'package:flutter_platform_widgets/flutter_platform_widgets.dart';
|
||||||
import 'package:flutter/cupertino.dart' show CupertinoIcons;
|
import 'package:flutter/cupertino.dart' show CupertinoIcons;
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:timetracker/src/rust/api.dart';
|
||||||
import 'package:timetracker/time_tracking_service.dart';
|
import 'package:timetracker/time_tracking_service.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:flutter_slidable/flutter_slidable.dart';
|
||||||
|
|
||||||
class TagsScreen extends StatefulWidget {
|
class TagsScreen extends StatefulWidget {
|
||||||
const TagsScreen({super.key});
|
const TagsScreen({super.key});
|
||||||
|
|
@ -40,6 +42,129 @@ class _TagsScreenState extends State<TagsScreen> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _showEditTagDialog(BuildContext context, Tag tag) {
|
||||||
|
final TextEditingController editController = TextEditingController(
|
||||||
|
text: tag.name,
|
||||||
|
);
|
||||||
|
|
||||||
|
showPlatformDialog(
|
||||||
|
context: context,
|
||||||
|
builder:
|
||||||
|
(dialogContext) => PlatformAlertDialog(
|
||||||
|
title: PlatformText('Tag bearbeiten'),
|
||||||
|
content: PlatformTextField(
|
||||||
|
controller: editController,
|
||||||
|
autofocus: true,
|
||||||
|
hintText: 'Neuer Tag-Name',
|
||||||
|
material:
|
||||||
|
(_, __) => MaterialTextFieldData(
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Neuer Tag-Name',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
cupertino:
|
||||||
|
(_, __) =>
|
||||||
|
CupertinoTextFieldData(placeholder: 'Neuer Tag-Name'),
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
PlatformDialogAction(
|
||||||
|
child: PlatformText('Abbrechen'),
|
||||||
|
onPressed: () => Navigator.pop(dialogContext),
|
||||||
|
),
|
||||||
|
PlatformDialogAction(
|
||||||
|
child: PlatformText('Speichern'),
|
||||||
|
onPressed: () async {
|
||||||
|
final newName = editController.text.trim();
|
||||||
|
if (newName.isEmpty) {
|
||||||
|
_showPlatformFeedbackDialog(
|
||||||
|
context,
|
||||||
|
'Fehler',
|
||||||
|
'Tag-Name darf nicht leer sein.',
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (newName == tag.name) {
|
||||||
|
Navigator.pop(dialogContext);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final success = await context
|
||||||
|
.read<TimeTrackingService>()
|
||||||
|
.flUpdateTag(tag.id.toInt(), newName);
|
||||||
|
|
||||||
|
if (!mounted) return;
|
||||||
|
|
||||||
|
Navigator.pop(dialogContext);
|
||||||
|
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
if (!mounted) return;
|
||||||
|
if (success) {
|
||||||
|
_showPlatformFeedbackDialog(
|
||||||
|
context,
|
||||||
|
'Erfolg',
|
||||||
|
'Tag aktualisiert.',
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
_showPlatformFeedbackDialog(
|
||||||
|
context,
|
||||||
|
'Fehler',
|
||||||
|
'Tag konnte nicht aktualisiert werden (Name evtl. vergeben?).',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _confirmAndDeleteTag(BuildContext context, Tag tag) {
|
||||||
|
showPlatformDialog(
|
||||||
|
context: context,
|
||||||
|
builder:
|
||||||
|
(dialogContext) => PlatformAlertDialog(
|
||||||
|
title: PlatformText('Tag löschen?'),
|
||||||
|
content: PlatformText(
|
||||||
|
'Möchtest du den Tag "${tag.name}" wirklich löschen? Zugeordnete Zeiteinträge verlieren ihre Tag-Zuweisung.',
|
||||||
|
),
|
||||||
|
actions: <Widget>[
|
||||||
|
PlatformDialogAction(
|
||||||
|
child: PlatformText('Abbrechen'),
|
||||||
|
onPressed: () => Navigator.pop(dialogContext),
|
||||||
|
),
|
||||||
|
PlatformDialogAction(
|
||||||
|
child: PlatformText('Löschen'),
|
||||||
|
cupertino:
|
||||||
|
(_, __) =>
|
||||||
|
CupertinoDialogActionData(isDestructiveAction: true),
|
||||||
|
onPressed: () async {
|
||||||
|
Navigator.pop(dialogContext);
|
||||||
|
|
||||||
|
final success = await context
|
||||||
|
.read<TimeTrackingService>()
|
||||||
|
.flDeleteTag(tag.id.toInt());
|
||||||
|
|
||||||
|
if (!mounted) return;
|
||||||
|
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
if (!mounted) return;
|
||||||
|
if (success) {
|
||||||
|
} else {
|
||||||
|
_showPlatformFeedbackDialog(
|
||||||
|
context,
|
||||||
|
'Fehler',
|
||||||
|
'Tag konnte nicht gelöscht werden.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final tags = context.watch<TimeTrackingService>().tags;
|
final tags = context.watch<TimeTrackingService>().tags;
|
||||||
|
|
@ -51,6 +176,7 @@ class _TagsScreenState extends State<TagsScreen> {
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
child: Row(
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: PlatformTextField(
|
child: PlatformTextField(
|
||||||
|
|
@ -85,9 +211,36 @@ class _TagsScreenState extends State<TagsScreen> {
|
||||||
itemCount: tags.length,
|
itemCount: tags.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final tag = tags[index];
|
final tag = tags[index];
|
||||||
return ListTile(
|
return Slidable(
|
||||||
|
key: ValueKey(tag.id.toInt()),
|
||||||
|
endActionPane: ActionPane(
|
||||||
|
motion: const StretchMotion(),
|
||||||
|
children: [
|
||||||
|
SlidableAction(
|
||||||
|
onPressed: (context) {
|
||||||
|
_showEditTagDialog(this.context, tag);
|
||||||
|
},
|
||||||
|
backgroundColor: Colors.blue,
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
icon: PlatformIcons(context).edit,
|
||||||
|
label: 'Bearbeiten',
|
||||||
|
),
|
||||||
|
SlidableAction(
|
||||||
|
onPressed: (context) {
|
||||||
|
_confirmAndDeleteTag(this.context, tag);
|
||||||
|
},
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
icon: PlatformIcons(context).delete,
|
||||||
|
label: 'Löschen',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
child: ListTile(
|
||||||
title: PlatformText(tag.name),
|
title: PlatformText(tag.name),
|
||||||
trailing: PlatformText('ID: ${tag.id}'),
|
trailing: PlatformText('ID: ${tag.id.toInt()}'),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,12 @@ Future<void> initApp({required String dbDirectoryPath}) =>
|
||||||
Future<PlatformInt64> createTag({required String name}) =>
|
Future<PlatformInt64> createTag({required String name}) =>
|
||||||
RustLib.instance.api.crateApiCreateTag(name: name);
|
RustLib.instance.api.crateApiCreateTag(name: name);
|
||||||
|
|
||||||
|
Future<void> updateTag({required PlatformInt64 id, required String newName}) =>
|
||||||
|
RustLib.instance.api.crateApiUpdateTag(id: id, newName: newName);
|
||||||
|
|
||||||
|
Future<void> deleteTag({required PlatformInt64 id}) =>
|
||||||
|
RustLib.instance.api.crateApiDeleteTag(id: id);
|
||||||
|
|
||||||
Future<List<Tag>> getTags() => RustLib.instance.api.crateApiGetTags();
|
Future<List<Tag>> getTags() => RustLib.instance.api.crateApiGetTags();
|
||||||
|
|
||||||
Future<PlatformInt64> startTracking({
|
Future<PlatformInt64> startTracking({
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ class RustLib extends BaseEntrypoint<RustLibApi, RustLibApiImpl, RustLibWire> {
|
||||||
String get codegenVersion => '2.9.0';
|
String get codegenVersion => '2.9.0';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get rustContentHash => -79634774;
|
int get rustContentHash => 350676645;
|
||||||
|
|
||||||
static const kDefaultExternalLibraryLoaderConfig =
|
static const kDefaultExternalLibraryLoaderConfig =
|
||||||
ExternalLibraryLoaderConfig(
|
ExternalLibraryLoaderConfig(
|
||||||
|
|
@ -75,6 +75,8 @@ class RustLib extends BaseEntrypoint<RustLibApi, RustLibApiImpl, RustLibWire> {
|
||||||
abstract class RustLibApi extends BaseApi {
|
abstract class RustLibApi extends BaseApi {
|
||||||
Future<PlatformInt64> crateApiCreateTag({required String name});
|
Future<PlatformInt64> crateApiCreateTag({required String name});
|
||||||
|
|
||||||
|
Future<void> crateApiDeleteTag({required PlatformInt64 id});
|
||||||
|
|
||||||
Future<void> crateApiDeleteTimeEntry({required PlatformInt64 id});
|
Future<void> crateApiDeleteTimeEntry({required PlatformInt64 id});
|
||||||
|
|
||||||
Future<ReportData> crateApiGenerateReport({
|
Future<ReportData> crateApiGenerateReport({
|
||||||
|
|
@ -99,6 +101,11 @@ abstract class RustLibApi extends BaseApi {
|
||||||
required PlatformInt64 endTimeUnixTs,
|
required PlatformInt64 endTimeUnixTs,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Future<void> crateApiUpdateTag({
|
||||||
|
required PlatformInt64 id,
|
||||||
|
required String newName,
|
||||||
|
});
|
||||||
|
|
||||||
Future<void> crateApiUpdateTimeEntry({
|
Future<void> crateApiUpdateTimeEntry({
|
||||||
required PlatformInt64 entryId,
|
required PlatformInt64 entryId,
|
||||||
PlatformInt64? newTagId,
|
PlatformInt64? newTagId,
|
||||||
|
|
@ -144,7 +151,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
const TaskConstMeta(debugName: "create_tag", argNames: ["name"]);
|
const TaskConstMeta(debugName: "create_tag", argNames: ["name"]);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> crateApiDeleteTimeEntry({required PlatformInt64 id}) {
|
Future<void> crateApiDeleteTag({required PlatformInt64 id}) {
|
||||||
return handler.executeNormal(
|
return handler.executeNormal(
|
||||||
NormalTask(
|
NormalTask(
|
||||||
callFfi: (port_) {
|
callFfi: (port_) {
|
||||||
|
|
@ -161,6 +168,34 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
decodeSuccessData: sse_decode_unit,
|
decodeSuccessData: sse_decode_unit,
|
||||||
decodeErrorData: sse_decode_AnyhowException,
|
decodeErrorData: sse_decode_AnyhowException,
|
||||||
),
|
),
|
||||||
|
constMeta: kCrateApiDeleteTagConstMeta,
|
||||||
|
argValues: [id],
|
||||||
|
apiImpl: this,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TaskConstMeta get kCrateApiDeleteTagConstMeta =>
|
||||||
|
const TaskConstMeta(debugName: "delete_tag", argNames: ["id"]);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> crateApiDeleteTimeEntry({required PlatformInt64 id}) {
|
||||||
|
return handler.executeNormal(
|
||||||
|
NormalTask(
|
||||||
|
callFfi: (port_) {
|
||||||
|
final serializer = SseSerializer(generalizedFrbRustBinding);
|
||||||
|
sse_encode_i_64(id, serializer);
|
||||||
|
pdeCallFfi(
|
||||||
|
generalizedFrbRustBinding,
|
||||||
|
serializer,
|
||||||
|
funcId: 3,
|
||||||
|
port: port_,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
codec: SseCodec(
|
||||||
|
decodeSuccessData: sse_decode_unit,
|
||||||
|
decodeErrorData: sse_decode_AnyhowException,
|
||||||
|
),
|
||||||
constMeta: kCrateApiDeleteTimeEntryConstMeta,
|
constMeta: kCrateApiDeleteTimeEntryConstMeta,
|
||||||
argValues: [id],
|
argValues: [id],
|
||||||
apiImpl: this,
|
apiImpl: this,
|
||||||
|
|
@ -187,7 +222,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
pdeCallFfi(
|
pdeCallFfi(
|
||||||
generalizedFrbRustBinding,
|
generalizedFrbRustBinding,
|
||||||
serializer,
|
serializer,
|
||||||
funcId: 3,
|
funcId: 4,
|
||||||
port: port_,
|
port: port_,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -216,7 +251,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
pdeCallFfi(
|
pdeCallFfi(
|
||||||
generalizedFrbRustBinding,
|
generalizedFrbRustBinding,
|
||||||
serializer,
|
serializer,
|
||||||
funcId: 4,
|
funcId: 5,
|
||||||
port: port_,
|
port: port_,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -246,7 +281,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
pdeCallFfi(
|
pdeCallFfi(
|
||||||
generalizedFrbRustBinding,
|
generalizedFrbRustBinding,
|
||||||
serializer,
|
serializer,
|
||||||
funcId: 5,
|
funcId: 6,
|
||||||
port: port_,
|
port: port_,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -274,7 +309,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
pdeCallFfi(
|
pdeCallFfi(
|
||||||
generalizedFrbRustBinding,
|
generalizedFrbRustBinding,
|
||||||
serializer,
|
serializer,
|
||||||
funcId: 6,
|
funcId: 7,
|
||||||
port: port_,
|
port: port_,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -306,7 +341,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
pdeCallFfi(
|
pdeCallFfi(
|
||||||
generalizedFrbRustBinding,
|
generalizedFrbRustBinding,
|
||||||
serializer,
|
serializer,
|
||||||
funcId: 7,
|
funcId: 8,
|
||||||
port: port_,
|
port: port_,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -340,7 +375,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
pdeCallFfi(
|
pdeCallFfi(
|
||||||
generalizedFrbRustBinding,
|
generalizedFrbRustBinding,
|
||||||
serializer,
|
serializer,
|
||||||
funcId: 8,
|
funcId: 9,
|
||||||
port: port_,
|
port: port_,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -360,6 +395,38 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
argNames: ["entryId", "endTimeUnixTs"],
|
argNames: ["entryId", "endTimeUnixTs"],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> crateApiUpdateTag({
|
||||||
|
required PlatformInt64 id,
|
||||||
|
required String newName,
|
||||||
|
}) {
|
||||||
|
return handler.executeNormal(
|
||||||
|
NormalTask(
|
||||||
|
callFfi: (port_) {
|
||||||
|
final serializer = SseSerializer(generalizedFrbRustBinding);
|
||||||
|
sse_encode_i_64(id, serializer);
|
||||||
|
sse_encode_String(newName, serializer);
|
||||||
|
pdeCallFfi(
|
||||||
|
generalizedFrbRustBinding,
|
||||||
|
serializer,
|
||||||
|
funcId: 10,
|
||||||
|
port: port_,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
codec: SseCodec(
|
||||||
|
decodeSuccessData: sse_decode_unit,
|
||||||
|
decodeErrorData: sse_decode_AnyhowException,
|
||||||
|
),
|
||||||
|
constMeta: kCrateApiUpdateTagConstMeta,
|
||||||
|
argValues: [id, newName],
|
||||||
|
apiImpl: this,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TaskConstMeta get kCrateApiUpdateTagConstMeta =>
|
||||||
|
const TaskConstMeta(debugName: "update_tag", argNames: ["id", "newName"]);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> crateApiUpdateTimeEntry({
|
Future<void> crateApiUpdateTimeEntry({
|
||||||
required PlatformInt64 entryId,
|
required PlatformInt64 entryId,
|
||||||
|
|
@ -378,7 +445,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
pdeCallFfi(
|
pdeCallFfi(
|
||||||
generalizedFrbRustBinding,
|
generalizedFrbRustBinding,
|
||||||
serializer,
|
serializer,
|
||||||
funcId: 9,
|
funcId: 11,
|
||||||
port: port_,
|
port: port_,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -105,16 +105,13 @@ class TimeTrackingService extends ChangeNotifier {
|
||||||
|
|
||||||
Future<bool> flCreateTag(String name) async {
|
Future<bool> flCreateTag(String name) async {
|
||||||
if (name.trim().isEmpty) {
|
if (name.trim().isEmpty) {
|
||||||
log("Cannot create tag: Name is empty.");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
final trimmedName = name.trim();
|
final trimmedName = name.trim();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final tagId = await createTag(name: trimmedName);
|
final tagId = await createTag(name: trimmedName);
|
||||||
log("Tag created with ID: $tagId");
|
log("Tag created with ID: $tagId");
|
||||||
_tags = await getTags();
|
await _loadUpdatedTags();
|
||||||
notifyListeners();
|
|
||||||
return true;
|
return true;
|
||||||
} on FrbException catch (e) {
|
} on FrbException catch (e) {
|
||||||
log("Error creating tag: $e");
|
log("Error creating tag: $e");
|
||||||
|
|
@ -124,6 +121,27 @@ class TimeTrackingService extends ChangeNotifier {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Future<bool> flCreateTag(String name) async {
|
||||||
|
// if (name.trim().isEmpty) {
|
||||||
|
// log("Cannot create tag: Name is empty.");
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
// final trimmedName = name.trim();
|
||||||
|
|
||||||
|
// try {
|
||||||
|
// final tagId = await createTag(name: trimmedName);
|
||||||
|
// log("Tag created with ID: $tagId");
|
||||||
|
// _tags = await getTags();
|
||||||
|
// notifyListeners();
|
||||||
|
// return true;
|
||||||
|
// } on FrbException catch (e) {
|
||||||
|
// log("Error creating tag: $e");
|
||||||
|
// return false;
|
||||||
|
// } catch (e) {
|
||||||
|
// log("Unexpected error creating tag: $e");
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
Future<ReportData?> flGetReport(
|
Future<ReportData?> flGetReport(
|
||||||
int? tagId,
|
int? tagId,
|
||||||
|
|
@ -184,4 +202,50 @@ class TimeTrackingService extends ChangeNotifier {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<bool> flUpdateTag(int tagId, String newName) async {
|
||||||
|
if (newName.trim().isEmpty) {
|
||||||
|
log('Service: Cannot update tag $tagId: New name is empty.');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final trimmedName = newName.trim();
|
||||||
|
log('Service: Attempting to update tag $tagId to "$trimmedName"');
|
||||||
|
try {
|
||||||
|
await updateTag(id: tagId.toInt(), newName: trimmedName);
|
||||||
|
log('Service: Successfully updated tag $tagId');
|
||||||
|
await _loadUpdatedTags();
|
||||||
|
return true; // Erfolg
|
||||||
|
} on FrbException catch (e, s) {
|
||||||
|
log('Service: Error updating tag $tagId: $e\n$s');
|
||||||
|
return false;
|
||||||
|
} catch (e, s) {
|
||||||
|
log('Service: Unexpected error updating tag $tagId: $e\n$s');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> flDeleteTag(int tagId) async {
|
||||||
|
log('Service: Attempting to delete tag $tagId');
|
||||||
|
try {
|
||||||
|
await deleteTag(id: tagId.toInt());
|
||||||
|
log('Service: Successfully deleted tag $tagId');
|
||||||
|
await _loadUpdatedTags();
|
||||||
|
return true;
|
||||||
|
} on FrbException catch (e, s) {
|
||||||
|
log('Service: Error deleting tag $tagId: $e\n$s');
|
||||||
|
return false;
|
||||||
|
} catch (e, s) {
|
||||||
|
log('Service: Unexpected error deleting tag $tagId: $e\n$s');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _loadUpdatedTags() async {
|
||||||
|
try {
|
||||||
|
_tags = await getTags();
|
||||||
|
notifyListeners();
|
||||||
|
} catch (e) {
|
||||||
|
log("Error refreshing tags after CUD operation: $e");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -152,6 +152,14 @@ packages:
|
||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
font_awesome_flutter:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: font_awesome_flutter
|
||||||
|
sha256: d3a89184101baec7f4600d58840a764d2ef760fe1c5a20ef9e6b0e9b24a07a3a
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "10.8.0"
|
||||||
fuchsia_remote_debug_protocol:
|
fuchsia_remote_debug_protocol:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ dependencies:
|
||||||
fl_chart: ^0.70.2
|
fl_chart: ^0.70.2
|
||||||
flutter_platform_widgets: ^8.0.0
|
flutter_platform_widgets: ^8.0.0
|
||||||
flutter_slidable: ^4.0.0
|
flutter_slidable: ^4.0.0
|
||||||
|
font_awesome_flutter: ^10.8.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,20 @@ pub fn create_tag(name: String) -> Result<i64> {
|
||||||
database::create_tag_internal(name.trim())
|
database::create_tag_internal(name.trim())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn update_tag(id: i64, new_name: String) -> Result<()> {
|
||||||
|
log::debug!(
|
||||||
|
"API: update_tag called for id {} with name '{}'",
|
||||||
|
id,
|
||||||
|
new_name
|
||||||
|
);
|
||||||
|
database::update_tag_internal(id, &new_name)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn delete_tag(id: i64) -> Result<()> {
|
||||||
|
log::debug!("API: delete_tag called for id {}", id);
|
||||||
|
database::delete_tag_internal(id)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_tags() -> Result<Vec<Tag>> {
|
pub fn get_tags() -> Result<Vec<Tag>> {
|
||||||
log::debug!("API: get_tags called");
|
log::debug!("API: get_tags called");
|
||||||
database::get_tags_internal()
|
database::get_tags_internal()
|
||||||
|
|
|
||||||
|
|
@ -106,6 +106,44 @@ pub(super) fn get_tags_internal() -> Result<Vec<TagInternal>> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) fn update_tag_internal(id: i64, new_name: &str) -> Result<()> {
|
||||||
|
log::debug!(
|
||||||
|
"RUST_DB: Attempting to update tag_id {} to name '{}'",
|
||||||
|
id,
|
||||||
|
new_name
|
||||||
|
);
|
||||||
|
ensure!(!new_name.trim().is_empty(), "New tag name cannot be empty");
|
||||||
|
|
||||||
|
let name = new_name.trim();
|
||||||
|
|
||||||
|
with_db_connection(|conn| {
|
||||||
|
let rows_affected = conn
|
||||||
|
.execute("UPDATE tags SET name = ?1 WHERE id = ?2", params![name, id])
|
||||||
|
.context("Failed to execute update tag SQL")?;
|
||||||
|
|
||||||
|
ensure!(rows_affected > 0, "Tag {} not found for update", id);
|
||||||
|
|
||||||
|
log::info!("RUST_DB: Successfully updated tag {}", id);
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.with_context(|| format!("Failed operation for updating tag_id {}", id))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn delete_tag_internal(id: i64) -> Result<()> {
|
||||||
|
log::debug!("RUST_DB: Attempting to delete tag_id {}", id);
|
||||||
|
with_db_connection(|conn| {
|
||||||
|
let rows_affected = conn
|
||||||
|
.execute("DELETE FROM tags WHERE id = ?1", params![id])
|
||||||
|
.context("Failed to execute delete tag SQL")?;
|
||||||
|
|
||||||
|
ensure!(rows_affected > 0, "Tag {} not found for deletion", id);
|
||||||
|
|
||||||
|
log::info!("RUST_DB: Successfully deleted tag {}", id);
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.with_context(|| format!("Failed operation for deleting tag_id {}", id))
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) fn start_tracking_internal(tag_id: Option<i64>, start_time_unix_ts: i64) -> Result<i64> {
|
pub(super) fn start_tracking_internal(tag_id: Option<i64>, start_time_unix_ts: i64) -> Result<i64> {
|
||||||
with_db_connection(|conn| {
|
with_db_connection(|conn| {
|
||||||
conn.execute(
|
conn.execute(
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ flutter_rust_bridge::frb_generated_boilerplate!(
|
||||||
default_rust_auto_opaque = RustAutoOpaqueMoi,
|
default_rust_auto_opaque = RustAutoOpaqueMoi,
|
||||||
);
|
);
|
||||||
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_VERSION: &str = "2.9.0";
|
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_VERSION: &str = "2.9.0";
|
||||||
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = -79634774;
|
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = 350676645;
|
||||||
|
|
||||||
// Section: executor
|
// Section: executor
|
||||||
|
|
||||||
|
|
@ -80,6 +80,41 @@ fn wire__crate__api__create_tag_impl(
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
fn wire__crate__api__delete_tag_impl(
|
||||||
|
port_: flutter_rust_bridge::for_generated::MessagePort,
|
||||||
|
ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
|
||||||
|
rust_vec_len_: i32,
|
||||||
|
data_len_: i32,
|
||||||
|
) {
|
||||||
|
FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::<flutter_rust_bridge::for_generated::SseCodec, _, _>(
|
||||||
|
flutter_rust_bridge::for_generated::TaskInfo {
|
||||||
|
debug_name: "delete_tag",
|
||||||
|
port: Some(port_),
|
||||||
|
mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal,
|
||||||
|
},
|
||||||
|
move || {
|
||||||
|
let message = unsafe {
|
||||||
|
flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire(
|
||||||
|
ptr_,
|
||||||
|
rust_vec_len_,
|
||||||
|
data_len_,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
let mut deserializer =
|
||||||
|
flutter_rust_bridge::for_generated::SseDeserializer::new(message);
|
||||||
|
let api_id = <i64>::sse_decode(&mut deserializer);
|
||||||
|
deserializer.end();
|
||||||
|
move |context| {
|
||||||
|
transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>(
|
||||||
|
(move || {
|
||||||
|
let output_ok = crate::api::delete_tag(api_id)?;
|
||||||
|
Ok(output_ok)
|
||||||
|
})(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
fn wire__crate__api__delete_time_entry_impl(
|
fn wire__crate__api__delete_time_entry_impl(
|
||||||
port_: flutter_rust_bridge::for_generated::MessagePort,
|
port_: flutter_rust_bridge::for_generated::MessagePort,
|
||||||
ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
|
ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
|
||||||
|
|
@ -333,6 +368,42 @@ fn wire__crate__api__stop_tracking_impl(
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
fn wire__crate__api__update_tag_impl(
|
||||||
|
port_: flutter_rust_bridge::for_generated::MessagePort,
|
||||||
|
ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
|
||||||
|
rust_vec_len_: i32,
|
||||||
|
data_len_: i32,
|
||||||
|
) {
|
||||||
|
FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::<flutter_rust_bridge::for_generated::SseCodec, _, _>(
|
||||||
|
flutter_rust_bridge::for_generated::TaskInfo {
|
||||||
|
debug_name: "update_tag",
|
||||||
|
port: Some(port_),
|
||||||
|
mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal,
|
||||||
|
},
|
||||||
|
move || {
|
||||||
|
let message = unsafe {
|
||||||
|
flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire(
|
||||||
|
ptr_,
|
||||||
|
rust_vec_len_,
|
||||||
|
data_len_,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
let mut deserializer =
|
||||||
|
flutter_rust_bridge::for_generated::SseDeserializer::new(message);
|
||||||
|
let api_id = <i64>::sse_decode(&mut deserializer);
|
||||||
|
let api_new_name = <String>::sse_decode(&mut deserializer);
|
||||||
|
deserializer.end();
|
||||||
|
move |context| {
|
||||||
|
transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>(
|
||||||
|
(move || {
|
||||||
|
let output_ok = crate::api::update_tag(api_id, api_new_name)?;
|
||||||
|
Ok(output_ok)
|
||||||
|
})(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
fn wire__crate__api__update_time_entry_impl(
|
fn wire__crate__api__update_time_entry_impl(
|
||||||
port_: flutter_rust_bridge::for_generated::MessagePort,
|
port_: flutter_rust_bridge::for_generated::MessagePort,
|
||||||
ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
|
ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
|
||||||
|
|
@ -551,14 +622,16 @@ fn pde_ffi_dispatcher_primary_impl(
|
||||||
// Codec=Pde (Serialization + dispatch), see doc to use other codecs
|
// Codec=Pde (Serialization + dispatch), see doc to use other codecs
|
||||||
match func_id {
|
match func_id {
|
||||||
1 => wire__crate__api__create_tag_impl(port, ptr, rust_vec_len, data_len),
|
1 => wire__crate__api__create_tag_impl(port, ptr, rust_vec_len, data_len),
|
||||||
2 => wire__crate__api__delete_time_entry_impl(port, ptr, rust_vec_len, data_len),
|
2 => wire__crate__api__delete_tag_impl(port, ptr, rust_vec_len, data_len),
|
||||||
3 => wire__crate__api__generate_report_impl(port, ptr, rust_vec_len, data_len),
|
3 => wire__crate__api__delete_time_entry_impl(port, ptr, rust_vec_len, data_len),
|
||||||
4 => wire__crate__api__get_last_unfinished_tracking_impl(port, ptr, rust_vec_len, data_len),
|
4 => wire__crate__api__generate_report_impl(port, ptr, rust_vec_len, data_len),
|
||||||
5 => wire__crate__api__get_tags_impl(port, ptr, rust_vec_len, data_len),
|
5 => wire__crate__api__get_last_unfinished_tracking_impl(port, ptr, rust_vec_len, data_len),
|
||||||
6 => wire__crate__api__init_app_impl(port, ptr, rust_vec_len, data_len),
|
6 => wire__crate__api__get_tags_impl(port, ptr, rust_vec_len, data_len),
|
||||||
7 => wire__crate__api__start_tracking_impl(port, ptr, rust_vec_len, data_len),
|
7 => wire__crate__api__init_app_impl(port, ptr, rust_vec_len, data_len),
|
||||||
8 => wire__crate__api__stop_tracking_impl(port, ptr, rust_vec_len, data_len),
|
8 => wire__crate__api__start_tracking_impl(port, ptr, rust_vec_len, data_len),
|
||||||
9 => wire__crate__api__update_time_entry_impl(port, ptr, rust_vec_len, data_len),
|
9 => wire__crate__api__stop_tracking_impl(port, ptr, rust_vec_len, data_len),
|
||||||
|
10 => wire__crate__api__update_tag_impl(port, ptr, rust_vec_len, data_len),
|
||||||
|
11 => wire__crate__api__update_time_entry_impl(port, ptr, rust_vec_len, data_len),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue