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) {
|
||||
return MaterialApp(
|
||||
title: 'Flutter Rust Time Tracker',
|
||||
theme: ThemeData(
|
||||
primarySwatch: Colors.blue,
|
||||
useMaterial3: true,
|
||||
// Optional: Theme für BottomNavigationBar anpassen
|
||||
// bottomNavigationBarTheme: const BottomNavigationBarThemeData(
|
||||
// selectedItemColor: Colors.deepPurple,
|
||||
// unselectedItemColor: Colors.grey,
|
||||
// ),
|
||||
),
|
||||
theme: ThemeData(primarySwatch: Colors.blue, useMaterial3: true),
|
||||
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/tags_screen.dart';
|
||||
import 'package:timetracker/screens/report_screen.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
|
||||
class MainScreen extends StatefulWidget {
|
||||
const MainScreen({super.key});
|
||||
|
|
@ -37,8 +38,8 @@ class _MainScreenState extends State<MainScreen> {
|
|||
|
||||
items: [
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(context.platformIcons.clockSolid),
|
||||
activeIcon: Icon(context.platformIcons.clockSolid),
|
||||
icon: FaIcon(FontAwesomeIcons.clock),
|
||||
activeIcon: FaIcon(FontAwesomeIcons.solidClock),
|
||||
label: 'Tracking',
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
|
|
@ -47,8 +48,12 @@ class _MainScreenState extends State<MainScreen> {
|
|||
label: 'Tags',
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(context.platformIcons.bookmarkOutline),
|
||||
activeIcon: Icon(context.platformIcons.bookmarkSolid),
|
||||
icon: FaIcon(
|
||||
FontAwesomeIcons.chartBar,
|
||||
), //Icon(context.platformIcons.bookmarkOutline),
|
||||
activeIcon: FaIcon(
|
||||
FontAwesomeIcons.solidChartBar,
|
||||
), //Icon(context.platformIcons.bookmarkSolid),
|
||||
label: 'Reports',
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -487,8 +487,8 @@ class _ReportScreenState extends State<ReportScreen> {
|
|||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 250, child: _buildPieChart(_reportData!)),
|
||||
const Divider(),
|
||||
// SizedBox(height: 250, child: _buildPieChart(_reportData!)),
|
||||
// const Divider(),
|
||||
SizedBox(
|
||||
height: 250,
|
||||
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) {
|
||||
return ListView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
// shrinkWrap: true,
|
||||
// physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: entries.length,
|
||||
itemBuilder: (context, index) {
|
||||
final entry = entries[index];
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
import 'package:flutter_platform_widgets/flutter_platform_widgets.dart';
|
||||
import 'package:flutter/cupertino.dart' show CupertinoIcons;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:timetracker/src/rust/api.dart';
|
||||
import 'package:timetracker/time_tracking_service.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_slidable/flutter_slidable.dart';
|
||||
|
||||
class TagsScreen extends StatefulWidget {
|
||||
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
|
||||
Widget build(BuildContext context) {
|
||||
final tags = context.watch<TimeTrackingService>().tags;
|
||||
|
|
@ -51,6 +176,7 @@ class _TagsScreenState extends State<TagsScreen> {
|
|||
Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Expanded(
|
||||
child: PlatformTextField(
|
||||
|
|
@ -85,9 +211,36 @@ class _TagsScreenState extends State<TagsScreen> {
|
|||
itemCount: tags.length,
|
||||
itemBuilder: (context, 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),
|
||||
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}) =>
|
||||
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<PlatformInt64> startTracking({
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ class RustLib extends BaseEntrypoint<RustLibApi, RustLibApiImpl, RustLibWire> {
|
|||
String get codegenVersion => '2.9.0';
|
||||
|
||||
@override
|
||||
int get rustContentHash => -79634774;
|
||||
int get rustContentHash => 350676645;
|
||||
|
||||
static const kDefaultExternalLibraryLoaderConfig =
|
||||
ExternalLibraryLoaderConfig(
|
||||
|
|
@ -75,6 +75,8 @@ class RustLib extends BaseEntrypoint<RustLibApi, RustLibApiImpl, RustLibWire> {
|
|||
abstract class RustLibApi extends BaseApi {
|
||||
Future<PlatformInt64> crateApiCreateTag({required String name});
|
||||
|
||||
Future<void> crateApiDeleteTag({required PlatformInt64 id});
|
||||
|
||||
Future<void> crateApiDeleteTimeEntry({required PlatformInt64 id});
|
||||
|
||||
Future<ReportData> crateApiGenerateReport({
|
||||
|
|
@ -99,6 +101,11 @@ abstract class RustLibApi extends BaseApi {
|
|||
required PlatformInt64 endTimeUnixTs,
|
||||
});
|
||||
|
||||
Future<void> crateApiUpdateTag({
|
||||
required PlatformInt64 id,
|
||||
required String newName,
|
||||
});
|
||||
|
||||
Future<void> crateApiUpdateTimeEntry({
|
||||
required PlatformInt64 entryId,
|
||||
PlatformInt64? newTagId,
|
||||
|
|
@ -144,7 +151,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
const TaskConstMeta(debugName: "create_tag", argNames: ["name"]);
|
||||
|
||||
@override
|
||||
Future<void> crateApiDeleteTimeEntry({required PlatformInt64 id}) {
|
||||
Future<void> crateApiDeleteTag({required PlatformInt64 id}) {
|
||||
return handler.executeNormal(
|
||||
NormalTask(
|
||||
callFfi: (port_) {
|
||||
|
|
@ -161,6 +168,34 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
decodeSuccessData: sse_decode_unit,
|
||||
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,
|
||||
argValues: [id],
|
||||
apiImpl: this,
|
||||
|
|
@ -187,7 +222,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 3,
|
||||
funcId: 4,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
@ -216,7 +251,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 4,
|
||||
funcId: 5,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
@ -246,7 +281,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 5,
|
||||
funcId: 6,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
@ -274,7 +309,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 6,
|
||||
funcId: 7,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
@ -306,7 +341,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 7,
|
||||
funcId: 8,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
@ -340,7 +375,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 8,
|
||||
funcId: 9,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
@ -360,6 +395,38 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
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
|
||||
Future<void> crateApiUpdateTimeEntry({
|
||||
required PlatformInt64 entryId,
|
||||
|
|
@ -378,7 +445,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 9,
|
||||
funcId: 11,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -105,16 +105,13 @@ class TimeTrackingService extends ChangeNotifier {
|
|||
|
||||
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();
|
||||
await _loadUpdatedTags();
|
||||
return true;
|
||||
} on FrbException catch (e) {
|
||||
log("Error creating tag: $e");
|
||||
|
|
@ -124,6 +121,27 @@ class TimeTrackingService extends ChangeNotifier {
|
|||
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(
|
||||
int? tagId,
|
||||
|
|
@ -184,4 +202,50 @@ class TimeTrackingService extends ChangeNotifier {
|
|||
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
|
||||
source: sdk
|
||||
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:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ dependencies:
|
|||
fl_chart: ^0.70.2
|
||||
flutter_platform_widgets: ^8.0.0
|
||||
flutter_slidable: ^4.0.0
|
||||
font_awesome_flutter: ^10.8.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
|
|
|||
|
|
@ -45,6 +45,20 @@ pub fn create_tag(name: String) -> Result<i64> {
|
|||
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>> {
|
||||
log::debug!("API: get_tags called");
|
||||
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> {
|
||||
with_db_connection(|conn| {
|
||||
conn.execute(
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ flutter_rust_bridge::frb_generated_boilerplate!(
|
|||
default_rust_auto_opaque = RustAutoOpaqueMoi,
|
||||
);
|
||||
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
|
||||
|
||||
|
|
@ -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(
|
||||
port_: flutter_rust_bridge::for_generated::MessagePort,
|
||||
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(
|
||||
port_: flutter_rust_bridge::for_generated::MessagePort,
|
||||
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
|
||||
match func_id {
|
||||
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),
|
||||
3 => wire__crate__api__generate_report_impl(port, ptr, rust_vec_len, data_len),
|
||||
4 => wire__crate__api__get_last_unfinished_tracking_impl(port, ptr, rust_vec_len, data_len),
|
||||
5 => wire__crate__api__get_tags_impl(port, ptr, rust_vec_len, data_len),
|
||||
6 => wire__crate__api__init_app_impl(port, ptr, rust_vec_len, data_len),
|
||||
7 => wire__crate__api__start_tracking_impl(port, ptr, rust_vec_len, data_len),
|
||||
8 => wire__crate__api__stop_tracking_impl(port, ptr, rust_vec_len, data_len),
|
||||
9 => wire__crate__api__update_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__delete_time_entry_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_last_unfinished_tracking_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__init_app_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__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!(),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue