feat(flutter): add Rust bridge backend and generated bindings

This commit is contained in:
Tristan Michael 2026-03-31 07:02:58 -07:00
parent 8bfbbd0444
commit 85e616c256
10 changed files with 2893 additions and 4 deletions

View file

@ -5,6 +5,7 @@ members = [
] ]
exclude = [ exclude = [
"apps/tauri/src-tauri", "apps/tauri/src-tauri",
"apps/flutter/rust",
] ]
resolver = "2" resolver = "2"

View file

@ -0,0 +1,195 @@
// This file is automatically generated, so please do not edit it.
// @generated by `flutter_rust_bridge`@ 2.11.1.
// ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import
import 'frb_generated.dart';
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
// These functions are ignored because they are not marked as `pub`: `config_to_dto`, `ensure_repo`, `task_to_dto`
// These types are ignored because they are neither used by any `pub` functions nor (for structs and enums) marked `#[frb(unignore)]`: `AppState`
Future<AppConfigDto> getConfig() => RustLib.instance.api.crateApiGetConfig();
Future<void> initWorkspace({required String path}) =>
RustLib.instance.api.crateApiInitWorkspace(path: path);
Future<void> addWorkspace({required String name, required String path}) =>
RustLib.instance.api.crateApiAddWorkspace(name: name, path: path);
Future<void> setCurrentWorkspace({required String name}) =>
RustLib.instance.api.crateApiSetCurrentWorkspace(name: name);
Future<void> removeWorkspace({required String name}) =>
RustLib.instance.api.crateApiRemoveWorkspace(name: name);
Future<List<TaskListDto>> getLists() => RustLib.instance.api.crateApiGetLists();
Future<TaskListDto> createList({required String name}) =>
RustLib.instance.api.crateApiCreateList(name: name);
Future<void> deleteList({required String listId}) =>
RustLib.instance.api.crateApiDeleteList(listId: listId);
Future<List<TaskDto>> listTasks({required String listId}) =>
RustLib.instance.api.crateApiListTasks(listId: listId);
Future<TaskDto> createTask({
required String listId,
required String title,
required String description,
}) => RustLib.instance.api.crateApiCreateTask(
listId: listId,
title: title,
description: description,
);
Future<void> updateTask({required String listId, required TaskDto task}) =>
RustLib.instance.api.crateApiUpdateTask(listId: listId, task: task);
Future<void> deleteTask({required String listId, required String taskId}) =>
RustLib.instance.api.crateApiDeleteTask(listId: listId, taskId: taskId);
Future<TaskDto> toggleTask({required String listId, required String taskId}) =>
RustLib.instance.api.crateApiToggleTask(listId: listId, taskId: taskId);
Future<void> reorderTask({
required String listId,
required String taskId,
required int newPosition,
}) => RustLib.instance.api.crateApiReorderTask(
listId: listId,
taskId: taskId,
newPosition: newPosition,
);
Future<String> greet({required String name}) =>
RustLib.instance.api.crateApiGreet(name: name);
class AppConfigDto {
final List<WorkspaceEntry> workspaces;
final String? currentWorkspace;
const AppConfigDto({required this.workspaces, this.currentWorkspace});
@override
int get hashCode => workspaces.hashCode ^ currentWorkspace.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is AppConfigDto &&
runtimeType == other.runtimeType &&
workspaces == other.workspaces &&
currentWorkspace == other.currentWorkspace;
}
class TaskDto {
final String id;
final String title;
final String description;
final String status;
final String? dueDate;
final String createdAt;
final String updatedAt;
final String? parentId;
const TaskDto({
required this.id,
required this.title,
required this.description,
required this.status,
this.dueDate,
required this.createdAt,
required this.updatedAt,
this.parentId,
});
@override
int get hashCode =>
id.hashCode ^
title.hashCode ^
description.hashCode ^
status.hashCode ^
dueDate.hashCode ^
createdAt.hashCode ^
updatedAt.hashCode ^
parentId.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is TaskDto &&
runtimeType == other.runtimeType &&
id == other.id &&
title == other.title &&
description == other.description &&
status == other.status &&
dueDate == other.dueDate &&
createdAt == other.createdAt &&
updatedAt == other.updatedAt &&
parentId == other.parentId;
}
class TaskListDto {
final String id;
final String title;
final String createdAt;
final String updatedAt;
final bool groupByDueDate;
const TaskListDto({
required this.id,
required this.title,
required this.createdAt,
required this.updatedAt,
required this.groupByDueDate,
});
@override
int get hashCode =>
id.hashCode ^
title.hashCode ^
createdAt.hashCode ^
updatedAt.hashCode ^
groupByDueDate.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is TaskListDto &&
runtimeType == other.runtimeType &&
id == other.id &&
title == other.title &&
createdAt == other.createdAt &&
updatedAt == other.updatedAt &&
groupByDueDate == other.groupByDueDate;
}
class WorkspaceEntry {
final String name;
final String path;
final String? webdavUrl;
final String? lastSync;
const WorkspaceEntry({
required this.name,
required this.path,
this.webdavUrl,
this.lastSync,
});
@override
int get hashCode =>
name.hashCode ^ path.hashCode ^ webdavUrl.hashCode ^ lastSync.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is WorkspaceEntry &&
runtimeType == other.runtimeType &&
name == other.name &&
path == other.path &&
webdavUrl == other.webdavUrl &&
lastSync == other.lastSync;
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,190 @@
// This file is automatically generated, so please do not edit it.
// @generated by `flutter_rust_bridge`@ 2.11.1.
// ignore_for_file: unused_import, unused_element, unnecessary_import, duplicate_ignore, invalid_use_of_internal_member, annotate_overrides, non_constant_identifier_names, curly_braces_in_flow_control_structures, prefer_const_literals_to_create_immutables, unused_field
import 'api.dart';
import 'dart:async';
import 'dart:convert';
import 'dart:ffi' as ffi;
import 'frb_generated.dart';
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated_io.dart';
abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
RustLibApiImplPlatform({
required super.handler,
required super.wire,
required super.generalizedFrbRustBinding,
required super.portManager,
});
@protected
String dco_decode_String(dynamic raw);
@protected
AppConfigDto dco_decode_app_config_dto(dynamic raw);
@protected
bool dco_decode_bool(dynamic raw);
@protected
TaskDto dco_decode_box_autoadd_task_dto(dynamic raw);
@protected
Uint8List dco_decode_list_prim_u_8_strict(dynamic raw);
@protected
List<TaskDto> dco_decode_list_task_dto(dynamic raw);
@protected
List<TaskListDto> dco_decode_list_task_list_dto(dynamic raw);
@protected
List<WorkspaceEntry> dco_decode_list_workspace_entry(dynamic raw);
@protected
String? dco_decode_opt_String(dynamic raw);
@protected
TaskDto dco_decode_task_dto(dynamic raw);
@protected
TaskListDto dco_decode_task_list_dto(dynamic raw);
@protected
int dco_decode_u_32(dynamic raw);
@protected
int dco_decode_u_8(dynamic raw);
@protected
void dco_decode_unit(dynamic raw);
@protected
WorkspaceEntry dco_decode_workspace_entry(dynamic raw);
@protected
String sse_decode_String(SseDeserializer deserializer);
@protected
AppConfigDto sse_decode_app_config_dto(SseDeserializer deserializer);
@protected
bool sse_decode_bool(SseDeserializer deserializer);
@protected
TaskDto sse_decode_box_autoadd_task_dto(SseDeserializer deserializer);
@protected
Uint8List sse_decode_list_prim_u_8_strict(SseDeserializer deserializer);
@protected
List<TaskDto> sse_decode_list_task_dto(SseDeserializer deserializer);
@protected
List<TaskListDto> sse_decode_list_task_list_dto(SseDeserializer deserializer);
@protected
List<WorkspaceEntry> sse_decode_list_workspace_entry(
SseDeserializer deserializer,
);
@protected
String? sse_decode_opt_String(SseDeserializer deserializer);
@protected
TaskDto sse_decode_task_dto(SseDeserializer deserializer);
@protected
TaskListDto sse_decode_task_list_dto(SseDeserializer deserializer);
@protected
int sse_decode_u_32(SseDeserializer deserializer);
@protected
int sse_decode_u_8(SseDeserializer deserializer);
@protected
void sse_decode_unit(SseDeserializer deserializer);
@protected
WorkspaceEntry sse_decode_workspace_entry(SseDeserializer deserializer);
@protected
int sse_decode_i_32(SseDeserializer deserializer);
@protected
void sse_encode_String(String self, SseSerializer serializer);
@protected
void sse_encode_app_config_dto(AppConfigDto self, SseSerializer serializer);
@protected
void sse_encode_bool(bool self, SseSerializer serializer);
@protected
void sse_encode_box_autoadd_task_dto(TaskDto self, SseSerializer serializer);
@protected
void sse_encode_list_prim_u_8_strict(
Uint8List self,
SseSerializer serializer,
);
@protected
void sse_encode_list_task_dto(List<TaskDto> self, SseSerializer serializer);
@protected
void sse_encode_list_task_list_dto(
List<TaskListDto> self,
SseSerializer serializer,
);
@protected
void sse_encode_list_workspace_entry(
List<WorkspaceEntry> self,
SseSerializer serializer,
);
@protected
void sse_encode_opt_String(String? self, SseSerializer serializer);
@protected
void sse_encode_task_dto(TaskDto self, SseSerializer serializer);
@protected
void sse_encode_task_list_dto(TaskListDto self, SseSerializer serializer);
@protected
void sse_encode_u_32(int self, SseSerializer serializer);
@protected
void sse_encode_u_8(int self, SseSerializer serializer);
@protected
void sse_encode_unit(void self, SseSerializer serializer);
@protected
void sse_encode_workspace_entry(
WorkspaceEntry self,
SseSerializer serializer,
);
@protected
void sse_encode_i_32(int self, SseSerializer serializer);
}
// Section: wire_class
class RustLibWire implements BaseWire {
factory RustLibWire.fromExternalLibrary(ExternalLibrary lib) =>
RustLibWire(lib.ffiDynamicLibrary);
/// Holds the symbol lookup function.
final ffi.Pointer<T> Function<T extends ffi.NativeType>(String symbolName)
_lookup;
/// The symbols are looked up in [dynamicLibrary].
RustLibWire(ffi.DynamicLibrary dynamicLibrary)
: _lookup = dynamicLibrary.lookup;
}

View file

@ -0,0 +1,190 @@
// This file is automatically generated, so please do not edit it.
// @generated by `flutter_rust_bridge`@ 2.11.1.
// ignore_for_file: unused_import, unused_element, unnecessary_import, duplicate_ignore, invalid_use_of_internal_member, annotate_overrides, non_constant_identifier_names, curly_braces_in_flow_control_structures, prefer_const_literals_to_create_immutables, unused_field
// Static analysis wrongly picks the IO variant, thus ignore this
// ignore_for_file: argument_type_not_assignable
import 'api.dart';
import 'dart:async';
import 'dart:convert';
import 'frb_generated.dart';
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated_web.dart';
abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
RustLibApiImplPlatform({
required super.handler,
required super.wire,
required super.generalizedFrbRustBinding,
required super.portManager,
});
@protected
String dco_decode_String(dynamic raw);
@protected
AppConfigDto dco_decode_app_config_dto(dynamic raw);
@protected
bool dco_decode_bool(dynamic raw);
@protected
TaskDto dco_decode_box_autoadd_task_dto(dynamic raw);
@protected
Uint8List dco_decode_list_prim_u_8_strict(dynamic raw);
@protected
List<TaskDto> dco_decode_list_task_dto(dynamic raw);
@protected
List<TaskListDto> dco_decode_list_task_list_dto(dynamic raw);
@protected
List<WorkspaceEntry> dco_decode_list_workspace_entry(dynamic raw);
@protected
String? dco_decode_opt_String(dynamic raw);
@protected
TaskDto dco_decode_task_dto(dynamic raw);
@protected
TaskListDto dco_decode_task_list_dto(dynamic raw);
@protected
int dco_decode_u_32(dynamic raw);
@protected
int dco_decode_u_8(dynamic raw);
@protected
void dco_decode_unit(dynamic raw);
@protected
WorkspaceEntry dco_decode_workspace_entry(dynamic raw);
@protected
String sse_decode_String(SseDeserializer deserializer);
@protected
AppConfigDto sse_decode_app_config_dto(SseDeserializer deserializer);
@protected
bool sse_decode_bool(SseDeserializer deserializer);
@protected
TaskDto sse_decode_box_autoadd_task_dto(SseDeserializer deserializer);
@protected
Uint8List sse_decode_list_prim_u_8_strict(SseDeserializer deserializer);
@protected
List<TaskDto> sse_decode_list_task_dto(SseDeserializer deserializer);
@protected
List<TaskListDto> sse_decode_list_task_list_dto(SseDeserializer deserializer);
@protected
List<WorkspaceEntry> sse_decode_list_workspace_entry(
SseDeserializer deserializer,
);
@protected
String? sse_decode_opt_String(SseDeserializer deserializer);
@protected
TaskDto sse_decode_task_dto(SseDeserializer deserializer);
@protected
TaskListDto sse_decode_task_list_dto(SseDeserializer deserializer);
@protected
int sse_decode_u_32(SseDeserializer deserializer);
@protected
int sse_decode_u_8(SseDeserializer deserializer);
@protected
void sse_decode_unit(SseDeserializer deserializer);
@protected
WorkspaceEntry sse_decode_workspace_entry(SseDeserializer deserializer);
@protected
int sse_decode_i_32(SseDeserializer deserializer);
@protected
void sse_encode_String(String self, SseSerializer serializer);
@protected
void sse_encode_app_config_dto(AppConfigDto self, SseSerializer serializer);
@protected
void sse_encode_bool(bool self, SseSerializer serializer);
@protected
void sse_encode_box_autoadd_task_dto(TaskDto self, SseSerializer serializer);
@protected
void sse_encode_list_prim_u_8_strict(
Uint8List self,
SseSerializer serializer,
);
@protected
void sse_encode_list_task_dto(List<TaskDto> self, SseSerializer serializer);
@protected
void sse_encode_list_task_list_dto(
List<TaskListDto> self,
SseSerializer serializer,
);
@protected
void sse_encode_list_workspace_entry(
List<WorkspaceEntry> self,
SseSerializer serializer,
);
@protected
void sse_encode_opt_String(String? self, SseSerializer serializer);
@protected
void sse_encode_task_dto(TaskDto self, SseSerializer serializer);
@protected
void sse_encode_task_list_dto(TaskListDto self, SseSerializer serializer);
@protected
void sse_encode_u_32(int self, SseSerializer serializer);
@protected
void sse_encode_u_8(int self, SseSerializer serializer);
@protected
void sse_encode_unit(void self, SseSerializer serializer);
@protected
void sse_encode_workspace_entry(
WorkspaceEntry self,
SseSerializer serializer,
);
@protected
void sse_encode_i_32(int self, SseSerializer serializer);
}
// Section: wire_class
class RustLibWire implements BaseWire {
RustLibWire.fromExternalLibrary(ExternalLibrary lib);
}
@JS('wasm_bindgen')
external RustLibWasmModule get wasmModule;
@JS()
@anonymous
extension type RustLibWasmModule._(JSObject _) implements JSObject {}

View file

@ -126,15 +126,13 @@ dependencies = [
] ]
[[package]] [[package]]
name = "bevy-tasks-flutter-bridge" name = "bevy-tasks-flutter"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"bevy-tasks-core", "bevy-tasks-core",
"chrono", "chrono",
"flutter_rust_bridge", "flutter_rust_bridge",
"serde", "once_cell",
"serde_json",
"tokio",
"uuid", "uuid",
] ]

View file

@ -0,0 +1,14 @@
[package]
name = "bevy-tasks-flutter"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib", "staticlib"]
[dependencies]
flutter_rust_bridge = "=2.11.1"
bevy-tasks-core = { path = "../../../crates/bevy-tasks-core" }
uuid = { version = "1", features = ["serde", "v4"] }
chrono = { version = "0.4", features = ["serde"] }
once_cell = "1"

View file

@ -0,0 +1,257 @@
use std::path::PathBuf;
use std::sync::Mutex;
use once_cell::sync::Lazy;
use uuid::Uuid;
use bevy_tasks_core::{
config::{AppConfig, WorkspaceConfig},
models::{Task, TaskList, TaskStatus},
repository::TaskRepository,
};
// ── State ───────────────────────────────────────────────────────────
struct AppState {
config: AppConfig,
repo: Option<TaskRepository>,
}
static STATE: Lazy<Mutex<AppState>> = Lazy::new(|| {
let config_path = AppConfig::get_config_path();
let config = AppConfig::load_from_file(&config_path).unwrap_or_default();
Mutex::new(AppState { config, repo: None })
});
fn ensure_repo(state: &mut AppState) -> Result<(), String> {
if state.repo.is_some() {
return Ok(());
}
let (_name, ws) = state.config.get_current_workspace().map_err(|e| e.to_string())?;
let repo = TaskRepository::new(ws.path.clone()).map_err(|e| e.to_string())?;
state.repo = Some(repo);
Ok(())
}
// ── DTOs ────────────────────────────────────────────────────────────
pub struct TaskDto {
pub id: String,
pub title: String,
pub description: String,
pub status: String,
pub due_date: Option<String>,
pub created_at: String,
pub updated_at: String,
pub parent_id: Option<String>,
}
pub struct TaskListDto {
pub id: String,
pub title: String,
pub created_at: String,
pub updated_at: String,
pub group_by_due_date: bool,
}
pub struct WorkspaceEntry {
pub name: String,
pub path: String,
pub webdav_url: Option<String>,
pub last_sync: Option<String>,
}
pub struct AppConfigDto {
pub workspaces: Vec<WorkspaceEntry>,
pub current_workspace: Option<String>,
}
fn task_to_dto(t: &Task) -> TaskDto {
TaskDto {
id: t.id.to_string(),
title: t.title.clone(),
description: t.description.clone(),
status: match t.status {
TaskStatus::Backlog => "backlog".into(),
TaskStatus::Completed => "completed".into(),
},
due_date: t.due_date.map(|d| d.to_rfc3339()),
created_at: t.created_at.to_rfc3339(),
updated_at: t.updated_at.to_rfc3339(),
parent_id: t.parent_id.map(|id| id.to_string()),
}
}
fn config_to_dto(c: &AppConfig) -> AppConfigDto {
AppConfigDto {
workspaces: c
.workspaces
.iter()
.map(|(name, ws)| WorkspaceEntry {
name: name.clone(),
path: ws.path.to_string_lossy().into_owned(),
webdav_url: ws.webdav_url.clone(),
last_sync: ws.last_sync.map(|d| d.to_rfc3339()),
})
.collect(),
current_workspace: c.current_workspace.clone(),
}
}
// ── Config commands ─────────────────────────────────────────────────
pub fn get_config() -> Result<AppConfigDto, String> {
let s = STATE.lock().unwrap();
Ok(config_to_dto(&s.config))
}
pub fn init_workspace(path: String) -> Result<(), String> {
TaskRepository::init(PathBuf::from(path))
.map(|_| ())
.map_err(|e| e.to_string())
}
pub fn add_workspace(name: String, path: String) -> Result<(), String> {
let mut s = STATE.lock().unwrap();
let ws = WorkspaceConfig::new(PathBuf::from(&path));
s.config.add_workspace(name.clone(), ws);
s.config.set_current_workspace(name).map_err(|e| e.to_string())?;
s.repo = None;
let config_path = AppConfig::get_config_path();
s.config.save_to_file(&config_path).map_err(|e| e.to_string())
}
pub fn set_current_workspace(name: String) -> Result<(), String> {
let mut s = STATE.lock().unwrap();
s.config.set_current_workspace(name).map_err(|e| e.to_string())?;
s.repo = None;
let config_path = AppConfig::get_config_path();
s.config.save_to_file(&config_path).map_err(|e| e.to_string())
}
pub fn remove_workspace(name: String) -> Result<(), String> {
let mut s = STATE.lock().unwrap();
s.config.remove_workspace(&name);
s.repo = None;
let config_path = AppConfig::get_config_path();
s.config.save_to_file(&config_path).map_err(|e| e.to_string())
}
// ── List commands ───────────────────────────────────────────────────
pub fn get_lists() -> Result<Vec<TaskListDto>, String> {
let mut s = STATE.lock().unwrap();
ensure_repo(&mut s)?;
let lists = s.repo.as_ref().unwrap().get_lists().map_err(|e| e.to_string())?;
Ok(lists
.iter()
.map(|l| TaskListDto {
id: l.id.to_string(),
title: l.title.clone(),
created_at: l.created_at.to_rfc3339(),
updated_at: l.updated_at.to_rfc3339(),
group_by_due_date: l.group_by_due_date,
})
.collect())
}
pub fn create_list(name: String) -> Result<TaskListDto, String> {
let mut s = STATE.lock().unwrap();
ensure_repo(&mut s)?;
let list = s.repo.as_mut().unwrap().create_list(name).map_err(|e| e.to_string())?;
Ok(TaskListDto {
id: list.id.to_string(),
title: list.title.clone(),
created_at: list.created_at.to_rfc3339(),
updated_at: list.updated_at.to_rfc3339(),
group_by_due_date: list.group_by_due_date,
})
}
pub fn delete_list(list_id: String) -> Result<(), String> {
let mut s = STATE.lock().unwrap();
ensure_repo(&mut s)?;
let id = Uuid::parse_str(&list_id).map_err(|e| e.to_string())?;
s.repo.as_mut().unwrap().delete_list(id).map_err(|e| e.to_string())
}
// ── Task commands ───────────────────────────────────────────────────
pub fn list_tasks(list_id: String) -> Result<Vec<TaskDto>, String> {
let mut s = STATE.lock().unwrap();
ensure_repo(&mut s)?;
let id = Uuid::parse_str(&list_id).map_err(|e| e.to_string())?;
let tasks = s.repo.as_ref().unwrap().list_tasks(id).map_err(|e| e.to_string())?;
Ok(tasks.iter().map(|t| task_to_dto(t)).collect())
}
pub fn create_task(list_id: String, title: String, description: String) -> Result<TaskDto, String> {
let mut s = STATE.lock().unwrap();
ensure_repo(&mut s)?;
let id = Uuid::parse_str(&list_id).map_err(|e| e.to_string())?;
let mut task = Task::new(title);
if !description.is_empty() {
task.description = description;
}
let created = s.repo.as_mut().unwrap().create_task(id, task).map_err(|e| e.to_string())?;
Ok(task_to_dto(&created))
}
pub fn update_task(list_id: String, task: TaskDto) -> Result<(), String> {
let mut s = STATE.lock().unwrap();
ensure_repo(&mut s)?;
let lid = Uuid::parse_str(&list_id).map_err(|e| e.to_string())?;
let tid = Uuid::parse_str(&task.id).map_err(|e| e.to_string())?;
let mut existing = s.repo.as_ref().unwrap().get_task(lid, tid).map_err(|e| e.to_string())?;
existing.title = task.title;
existing.description = task.description;
existing.due_date = task
.due_date
.as_deref()
.and_then(|d| chrono::DateTime::parse_from_rfc3339(d).ok())
.map(|d| d.with_timezone(&chrono::Utc));
s.repo.as_mut().unwrap().update_task(lid, existing).map_err(|e| e.to_string())
}
pub fn delete_task(list_id: String, task_id: String) -> Result<(), String> {
let mut s = STATE.lock().unwrap();
ensure_repo(&mut s)?;
let lid = Uuid::parse_str(&list_id).map_err(|e| e.to_string())?;
let tid = Uuid::parse_str(&task_id).map_err(|e| e.to_string())?;
s.repo.as_mut().unwrap().delete_task(lid, tid).map_err(|e| e.to_string())
}
pub fn toggle_task(list_id: String, task_id: String) -> Result<TaskDto, String> {
let mut s = STATE.lock().unwrap();
ensure_repo(&mut s)?;
let lid = Uuid::parse_str(&list_id).map_err(|e| e.to_string())?;
let tid = Uuid::parse_str(&task_id).map_err(|e| e.to_string())?;
let repo = s.repo.as_mut().unwrap();
let mut task = repo.get_task(lid, tid).map_err(|e| e.to_string())?;
match task.status {
TaskStatus::Backlog => task.complete(),
TaskStatus::Completed => task.uncomplete(),
}
repo.update_task(lid, task.clone()).map_err(|e| e.to_string())?;
Ok(task_to_dto(&task))
}
pub fn reorder_task(list_id: String, task_id: String, new_position: u32) -> Result<(), String> {
let mut s = STATE.lock().unwrap();
ensure_repo(&mut s)?;
let lid = Uuid::parse_str(&list_id).map_err(|e| e.to_string())?;
let tid = Uuid::parse_str(&task_id).map_err(|e| e.to_string())?;
s.repo
.as_mut()
.unwrap()
.reorder_task(lid, tid, new_position as usize)
.map_err(|e| e.to_string())
}
// ── Test function ───────────────────────────────────────────────────
pub fn greet(name: String) -> String {
format!("Hello, {name}! From Rust via flutter_rust_bridge.")
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,2 @@
mod frb_generated; /* AUTO INJECTED BY flutter_rust_bridge. This line may not be accurate, and you can change it according to your needs. */
pub mod api;