feat: add update and delete functionality to tags and improve report screen

This commit is contained in:
Patryk Hegenberg 2025-04-16 16:08:45 +02:00
parent 2df0390ae2
commit ebc4cdf754
12 changed files with 464 additions and 43 deletions

View file

@ -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(),
);
}

View file

@ -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',
),
],

View file

@ -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];

View file

@ -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()}'),
),
);
},
),

View file

@ -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({

View file

@ -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_,
);
},

View file

@ -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");
}
}
}

View file

@ -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

View file

@ -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:

View file

@ -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()

View file

@ -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(

View file

@ -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!(),
}
}