Settings: add WebDAV sync UI and syncing backend
Add WebDAV configuration, credential storage/testing, and sync controls across Flutter UI and Rust API. This implements a stateful Settings screen with fields to enter server URL, username, and password, plus Test and Save actions; persist/load credentials and workspace WebDAV URL via the Rust API; add sync mode selection, a Sync Now action, and a sync status indicator in Tasks screen; thread has_time through date/time pickers, new task creation, task detail updates, and task DTOs; implement async Rust functions for testing connections, storing/loading credentials, setting workspace WebDAV config, and triggering workspace sync with a SyncResult mapped back to Flutter; add tokio runtime dependency. These changes were needed to enable full WebDAV-based synchronization and provide users controls and feedback for configuring and running syncs from the Flutter app.
This commit is contained in:
parent
39239fadc3
commit
6ccc167239
|
|
@ -87,6 +87,53 @@ Future<void> setGroupByDueDate({
|
||||||
Future<bool> getGroupByDueDate({required String listId}) =>
|
Future<bool> getGroupByDueDate({required String listId}) =>
|
||||||
RustLib.instance.api.crateApiGetGroupByDueDate(listId: listId);
|
RustLib.instance.api.crateApiGetGroupByDueDate(listId: listId);
|
||||||
|
|
||||||
|
Future<void> storeCredentials({
|
||||||
|
required String domain,
|
||||||
|
required String username,
|
||||||
|
required String password,
|
||||||
|
}) => RustLib.instance.api.crateApiStoreCredentials(
|
||||||
|
domain: domain,
|
||||||
|
username: username,
|
||||||
|
password: password,
|
||||||
|
);
|
||||||
|
|
||||||
|
Future<List<String>> loadCredentials({required String domain}) =>
|
||||||
|
RustLib.instance.api.crateApiLoadCredentials(domain: domain);
|
||||||
|
|
||||||
|
Future<void> setWebdavConfig({
|
||||||
|
required String workspaceName,
|
||||||
|
required String webdavUrl,
|
||||||
|
}) => RustLib.instance.api.crateApiSetWebdavConfig(
|
||||||
|
workspaceName: workspaceName,
|
||||||
|
webdavUrl: webdavUrl,
|
||||||
|
);
|
||||||
|
|
||||||
|
Future<void> testWebdavConnection({
|
||||||
|
required String url,
|
||||||
|
required String username,
|
||||||
|
required String password,
|
||||||
|
}) => RustLib.instance.api.crateApiTestWebdavConnection(
|
||||||
|
url: url,
|
||||||
|
username: username,
|
||||||
|
password: password,
|
||||||
|
);
|
||||||
|
|
||||||
|
Future<SyncResultDto> syncWorkspaceCmd({
|
||||||
|
required String workspaceName,
|
||||||
|
required String workspacePath,
|
||||||
|
required String webdavUrl,
|
||||||
|
required String username,
|
||||||
|
required String password,
|
||||||
|
required String mode,
|
||||||
|
}) => RustLib.instance.api.crateApiSyncWorkspaceCmd(
|
||||||
|
workspaceName: workspaceName,
|
||||||
|
workspacePath: workspacePath,
|
||||||
|
webdavUrl: webdavUrl,
|
||||||
|
username: username,
|
||||||
|
password: password,
|
||||||
|
mode: mode,
|
||||||
|
);
|
||||||
|
|
||||||
Future<Stream<void>> watchWorkspaceChanges({required String path}) =>
|
Future<Stream<void>> watchWorkspaceChanges({required String path}) =>
|
||||||
RustLib.instance.api.crateApiWatchWorkspaceChanges(path: path);
|
RustLib.instance.api.crateApiWatchWorkspaceChanges(path: path);
|
||||||
|
|
||||||
|
|
@ -111,12 +158,52 @@ class AppConfigDto {
|
||||||
currentWorkspace == other.currentWorkspace;
|
currentWorkspace == other.currentWorkspace;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class SyncResultDto {
|
||||||
|
final int uploaded;
|
||||||
|
final int downloaded;
|
||||||
|
final int deletedLocal;
|
||||||
|
final int deletedRemote;
|
||||||
|
final int conflicts;
|
||||||
|
final List<String> errors;
|
||||||
|
|
||||||
|
const SyncResultDto({
|
||||||
|
required this.uploaded,
|
||||||
|
required this.downloaded,
|
||||||
|
required this.deletedLocal,
|
||||||
|
required this.deletedRemote,
|
||||||
|
required this.conflicts,
|
||||||
|
required this.errors,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode =>
|
||||||
|
uploaded.hashCode ^
|
||||||
|
downloaded.hashCode ^
|
||||||
|
deletedLocal.hashCode ^
|
||||||
|
deletedRemote.hashCode ^
|
||||||
|
conflicts.hashCode ^
|
||||||
|
errors.hashCode;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) =>
|
||||||
|
identical(this, other) ||
|
||||||
|
other is SyncResultDto &&
|
||||||
|
runtimeType == other.runtimeType &&
|
||||||
|
uploaded == other.uploaded &&
|
||||||
|
downloaded == other.downloaded &&
|
||||||
|
deletedLocal == other.deletedLocal &&
|
||||||
|
deletedRemote == other.deletedRemote &&
|
||||||
|
conflicts == other.conflicts &&
|
||||||
|
errors == other.errors;
|
||||||
|
}
|
||||||
|
|
||||||
class TaskDto {
|
class TaskDto {
|
||||||
final String id;
|
final String id;
|
||||||
final String title;
|
final String title;
|
||||||
final String description;
|
final String description;
|
||||||
final String status;
|
final String status;
|
||||||
final String? dueDate;
|
final String? dueDate;
|
||||||
|
final bool hasTime;
|
||||||
final String createdAt;
|
final String createdAt;
|
||||||
final String updatedAt;
|
final String updatedAt;
|
||||||
final String? parentId;
|
final String? parentId;
|
||||||
|
|
@ -127,6 +214,7 @@ class TaskDto {
|
||||||
required this.description,
|
required this.description,
|
||||||
required this.status,
|
required this.status,
|
||||||
this.dueDate,
|
this.dueDate,
|
||||||
|
required this.hasTime,
|
||||||
required this.createdAt,
|
required this.createdAt,
|
||||||
required this.updatedAt,
|
required this.updatedAt,
|
||||||
this.parentId,
|
this.parentId,
|
||||||
|
|
@ -139,6 +227,7 @@ class TaskDto {
|
||||||
description.hashCode ^
|
description.hashCode ^
|
||||||
status.hashCode ^
|
status.hashCode ^
|
||||||
dueDate.hashCode ^
|
dueDate.hashCode ^
|
||||||
|
hasTime.hashCode ^
|
||||||
createdAt.hashCode ^
|
createdAt.hashCode ^
|
||||||
updatedAt.hashCode ^
|
updatedAt.hashCode ^
|
||||||
parentId.hashCode;
|
parentId.hashCode;
|
||||||
|
|
@ -153,6 +242,7 @@ class TaskDto {
|
||||||
description == other.description &&
|
description == other.description &&
|
||||||
status == other.status &&
|
status == other.status &&
|
||||||
dueDate == other.dueDate &&
|
dueDate == other.dueDate &&
|
||||||
|
hasTime == other.hasTime &&
|
||||||
createdAt == other.createdAt &&
|
createdAt == other.createdAt &&
|
||||||
updatedAt == other.updatedAt &&
|
updatedAt == other.updatedAt &&
|
||||||
parentId == other.parentId;
|
parentId == other.parentId;
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ class RustLib extends BaseEntrypoint<RustLibApi, RustLibApiImpl, RustLibWire> {
|
||||||
String get codegenVersion => '2.11.1';
|
String get codegenVersion => '2.11.1';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get rustContentHash => -75020133;
|
int get rustContentHash => -1094746925;
|
||||||
|
|
||||||
static const kDefaultExternalLibraryLoaderConfig =
|
static const kDefaultExternalLibraryLoaderConfig =
|
||||||
ExternalLibraryLoaderConfig(
|
ExternalLibraryLoaderConfig(
|
||||||
|
|
@ -107,6 +107,8 @@ abstract class RustLibApi extends BaseApi {
|
||||||
|
|
||||||
Future<List<TaskDto>> crateApiListTasks({required String listId});
|
Future<List<TaskDto>> crateApiListTasks({required String listId});
|
||||||
|
|
||||||
|
Future<List<String>> crateApiLoadCredentials({required String domain});
|
||||||
|
|
||||||
Future<void> crateApiMoveTask({
|
Future<void> crateApiMoveTask({
|
||||||
required String fromListId,
|
required String fromListId,
|
||||||
required String toListId,
|
required String toListId,
|
||||||
|
|
@ -133,6 +135,32 @@ abstract class RustLibApi extends BaseApi {
|
||||||
required bool enabled,
|
required bool enabled,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Future<void> crateApiSetWebdavConfig({
|
||||||
|
required String workspaceName,
|
||||||
|
required String webdavUrl,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<void> crateApiStoreCredentials({
|
||||||
|
required String domain,
|
||||||
|
required String username,
|
||||||
|
required String password,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<SyncResultDto> crateApiSyncWorkspaceCmd({
|
||||||
|
required String workspaceName,
|
||||||
|
required String workspacePath,
|
||||||
|
required String webdavUrl,
|
||||||
|
required String username,
|
||||||
|
required String password,
|
||||||
|
required String mode,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<void> crateApiTestWebdavConnection({
|
||||||
|
required String url,
|
||||||
|
required String username,
|
||||||
|
required String password,
|
||||||
|
});
|
||||||
|
|
||||||
Future<TaskDto> crateApiToggleTask({
|
Future<TaskDto> crateApiToggleTask({
|
||||||
required String listId,
|
required String listId,
|
||||||
required String taskId,
|
required String taskId,
|
||||||
|
|
@ -482,6 +510,34 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
TaskConstMeta get kCrateApiListTasksConstMeta =>
|
TaskConstMeta get kCrateApiListTasksConstMeta =>
|
||||||
const TaskConstMeta(debugName: "list_tasks", argNames: ["listId"]);
|
const TaskConstMeta(debugName: "list_tasks", argNames: ["listId"]);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<String>> crateApiLoadCredentials({required String domain}) {
|
||||||
|
return handler.executeNormal(
|
||||||
|
NormalTask(
|
||||||
|
callFfi: (port_) {
|
||||||
|
final serializer = SseSerializer(generalizedFrbRustBinding);
|
||||||
|
sse_encode_String(domain, serializer);
|
||||||
|
pdeCallFfi(
|
||||||
|
generalizedFrbRustBinding,
|
||||||
|
serializer,
|
||||||
|
funcId: 12,
|
||||||
|
port: port_,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
codec: SseCodec(
|
||||||
|
decodeSuccessData: sse_decode_list_String,
|
||||||
|
decodeErrorData: sse_decode_String,
|
||||||
|
),
|
||||||
|
constMeta: kCrateApiLoadCredentialsConstMeta,
|
||||||
|
argValues: [domain],
|
||||||
|
apiImpl: this,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TaskConstMeta get kCrateApiLoadCredentialsConstMeta =>
|
||||||
|
const TaskConstMeta(debugName: "load_credentials", argNames: ["domain"]);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> crateApiMoveTask({
|
Future<void> crateApiMoveTask({
|
||||||
required String fromListId,
|
required String fromListId,
|
||||||
|
|
@ -498,7 +554,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
pdeCallFfi(
|
pdeCallFfi(
|
||||||
generalizedFrbRustBinding,
|
generalizedFrbRustBinding,
|
||||||
serializer,
|
serializer,
|
||||||
funcId: 12,
|
funcId: 13,
|
||||||
port: port_,
|
port: port_,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -528,7 +584,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
pdeCallFfi(
|
pdeCallFfi(
|
||||||
generalizedFrbRustBinding,
|
generalizedFrbRustBinding,
|
||||||
serializer,
|
serializer,
|
||||||
funcId: 13,
|
funcId: 14,
|
||||||
port: port_,
|
port: port_,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -560,7 +616,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
pdeCallFfi(
|
pdeCallFfi(
|
||||||
generalizedFrbRustBinding,
|
generalizedFrbRustBinding,
|
||||||
serializer,
|
serializer,
|
||||||
funcId: 14,
|
funcId: 15,
|
||||||
port: port_,
|
port: port_,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -596,7 +652,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
pdeCallFfi(
|
pdeCallFfi(
|
||||||
generalizedFrbRustBinding,
|
generalizedFrbRustBinding,
|
||||||
serializer,
|
serializer,
|
||||||
funcId: 15,
|
funcId: 16,
|
||||||
port: port_,
|
port: port_,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -626,7 +682,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
pdeCallFfi(
|
pdeCallFfi(
|
||||||
generalizedFrbRustBinding,
|
generalizedFrbRustBinding,
|
||||||
serializer,
|
serializer,
|
||||||
funcId: 16,
|
funcId: 17,
|
||||||
port: port_,
|
port: port_,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -661,7 +717,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
pdeCallFfi(
|
pdeCallFfi(
|
||||||
generalizedFrbRustBinding,
|
generalizedFrbRustBinding,
|
||||||
serializer,
|
serializer,
|
||||||
funcId: 17,
|
funcId: 18,
|
||||||
port: port_,
|
port: port_,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -681,6 +737,169 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
argNames: ["listId", "enabled"],
|
argNames: ["listId", "enabled"],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> crateApiSetWebdavConfig({
|
||||||
|
required String workspaceName,
|
||||||
|
required String webdavUrl,
|
||||||
|
}) {
|
||||||
|
return handler.executeNormal(
|
||||||
|
NormalTask(
|
||||||
|
callFfi: (port_) {
|
||||||
|
final serializer = SseSerializer(generalizedFrbRustBinding);
|
||||||
|
sse_encode_String(workspaceName, serializer);
|
||||||
|
sse_encode_String(webdavUrl, serializer);
|
||||||
|
pdeCallFfi(
|
||||||
|
generalizedFrbRustBinding,
|
||||||
|
serializer,
|
||||||
|
funcId: 19,
|
||||||
|
port: port_,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
codec: SseCodec(
|
||||||
|
decodeSuccessData: sse_decode_unit,
|
||||||
|
decodeErrorData: sse_decode_String,
|
||||||
|
),
|
||||||
|
constMeta: kCrateApiSetWebdavConfigConstMeta,
|
||||||
|
argValues: [workspaceName, webdavUrl],
|
||||||
|
apiImpl: this,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TaskConstMeta get kCrateApiSetWebdavConfigConstMeta => const TaskConstMeta(
|
||||||
|
debugName: "set_webdav_config",
|
||||||
|
argNames: ["workspaceName", "webdavUrl"],
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> crateApiStoreCredentials({
|
||||||
|
required String domain,
|
||||||
|
required String username,
|
||||||
|
required String password,
|
||||||
|
}) {
|
||||||
|
return handler.executeNormal(
|
||||||
|
NormalTask(
|
||||||
|
callFfi: (port_) {
|
||||||
|
final serializer = SseSerializer(generalizedFrbRustBinding);
|
||||||
|
sse_encode_String(domain, serializer);
|
||||||
|
sse_encode_String(username, serializer);
|
||||||
|
sse_encode_String(password, serializer);
|
||||||
|
pdeCallFfi(
|
||||||
|
generalizedFrbRustBinding,
|
||||||
|
serializer,
|
||||||
|
funcId: 20,
|
||||||
|
port: port_,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
codec: SseCodec(
|
||||||
|
decodeSuccessData: sse_decode_unit,
|
||||||
|
decodeErrorData: sse_decode_String,
|
||||||
|
),
|
||||||
|
constMeta: kCrateApiStoreCredentialsConstMeta,
|
||||||
|
argValues: [domain, username, password],
|
||||||
|
apiImpl: this,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TaskConstMeta get kCrateApiStoreCredentialsConstMeta => const TaskConstMeta(
|
||||||
|
debugName: "store_credentials",
|
||||||
|
argNames: ["domain", "username", "password"],
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<SyncResultDto> crateApiSyncWorkspaceCmd({
|
||||||
|
required String workspaceName,
|
||||||
|
required String workspacePath,
|
||||||
|
required String webdavUrl,
|
||||||
|
required String username,
|
||||||
|
required String password,
|
||||||
|
required String mode,
|
||||||
|
}) {
|
||||||
|
return handler.executeNormal(
|
||||||
|
NormalTask(
|
||||||
|
callFfi: (port_) {
|
||||||
|
final serializer = SseSerializer(generalizedFrbRustBinding);
|
||||||
|
sse_encode_String(workspaceName, serializer);
|
||||||
|
sse_encode_String(workspacePath, serializer);
|
||||||
|
sse_encode_String(webdavUrl, serializer);
|
||||||
|
sse_encode_String(username, serializer);
|
||||||
|
sse_encode_String(password, serializer);
|
||||||
|
sse_encode_String(mode, serializer);
|
||||||
|
pdeCallFfi(
|
||||||
|
generalizedFrbRustBinding,
|
||||||
|
serializer,
|
||||||
|
funcId: 21,
|
||||||
|
port: port_,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
codec: SseCodec(
|
||||||
|
decodeSuccessData: sse_decode_sync_result_dto,
|
||||||
|
decodeErrorData: sse_decode_String,
|
||||||
|
),
|
||||||
|
constMeta: kCrateApiSyncWorkspaceCmdConstMeta,
|
||||||
|
argValues: [
|
||||||
|
workspaceName,
|
||||||
|
workspacePath,
|
||||||
|
webdavUrl,
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
mode,
|
||||||
|
],
|
||||||
|
apiImpl: this,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TaskConstMeta get kCrateApiSyncWorkspaceCmdConstMeta => const TaskConstMeta(
|
||||||
|
debugName: "sync_workspace_cmd",
|
||||||
|
argNames: [
|
||||||
|
"workspaceName",
|
||||||
|
"workspacePath",
|
||||||
|
"webdavUrl",
|
||||||
|
"username",
|
||||||
|
"password",
|
||||||
|
"mode",
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> crateApiTestWebdavConnection({
|
||||||
|
required String url,
|
||||||
|
required String username,
|
||||||
|
required String password,
|
||||||
|
}) {
|
||||||
|
return handler.executeNormal(
|
||||||
|
NormalTask(
|
||||||
|
callFfi: (port_) {
|
||||||
|
final serializer = SseSerializer(generalizedFrbRustBinding);
|
||||||
|
sse_encode_String(url, serializer);
|
||||||
|
sse_encode_String(username, serializer);
|
||||||
|
sse_encode_String(password, serializer);
|
||||||
|
pdeCallFfi(
|
||||||
|
generalizedFrbRustBinding,
|
||||||
|
serializer,
|
||||||
|
funcId: 22,
|
||||||
|
port: port_,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
codec: SseCodec(
|
||||||
|
decodeSuccessData: sse_decode_unit,
|
||||||
|
decodeErrorData: sse_decode_String,
|
||||||
|
),
|
||||||
|
constMeta: kCrateApiTestWebdavConnectionConstMeta,
|
||||||
|
argValues: [url, username, password],
|
||||||
|
apiImpl: this,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TaskConstMeta get kCrateApiTestWebdavConnectionConstMeta =>
|
||||||
|
const TaskConstMeta(
|
||||||
|
debugName: "test_webdav_connection",
|
||||||
|
argNames: ["url", "username", "password"],
|
||||||
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<TaskDto> crateApiToggleTask({
|
Future<TaskDto> crateApiToggleTask({
|
||||||
required String listId,
|
required String listId,
|
||||||
|
|
@ -695,7 +914,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
pdeCallFfi(
|
pdeCallFfi(
|
||||||
generalizedFrbRustBinding,
|
generalizedFrbRustBinding,
|
||||||
serializer,
|
serializer,
|
||||||
funcId: 18,
|
funcId: 23,
|
||||||
port: port_,
|
port: port_,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -729,7 +948,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
pdeCallFfi(
|
pdeCallFfi(
|
||||||
generalizedFrbRustBinding,
|
generalizedFrbRustBinding,
|
||||||
serializer,
|
serializer,
|
||||||
funcId: 19,
|
funcId: 24,
|
||||||
port: port_,
|
port: port_,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -763,7 +982,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
pdeCallFfi(
|
pdeCallFfi(
|
||||||
generalizedFrbRustBinding,
|
generalizedFrbRustBinding,
|
||||||
serializer,
|
serializer,
|
||||||
funcId: 20,
|
funcId: 25,
|
||||||
port: port_,
|
port: port_,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -827,6 +1046,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
return dco_decode_task_dto(raw);
|
return dco_decode_task_dto(raw);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@protected
|
||||||
|
List<String> dco_decode_list_String(dynamic raw) {
|
||||||
|
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||||
|
return (raw as List<dynamic>).map(dco_decode_String).toList();
|
||||||
|
}
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
Uint8List dco_decode_list_prim_u_8_strict(dynamic raw) {
|
Uint8List dco_decode_list_prim_u_8_strict(dynamic raw) {
|
||||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||||
|
|
@ -857,21 +1082,38 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
return raw == null ? null : dco_decode_String(raw);
|
return raw == null ? null : dco_decode_String(raw);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@protected
|
||||||
|
SyncResultDto dco_decode_sync_result_dto(dynamic raw) {
|
||||||
|
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||||
|
final arr = raw as List<dynamic>;
|
||||||
|
if (arr.length != 6)
|
||||||
|
throw Exception('unexpected arr length: expect 6 but see ${arr.length}');
|
||||||
|
return SyncResultDto(
|
||||||
|
uploaded: dco_decode_u_32(arr[0]),
|
||||||
|
downloaded: dco_decode_u_32(arr[1]),
|
||||||
|
deletedLocal: dco_decode_u_32(arr[2]),
|
||||||
|
deletedRemote: dco_decode_u_32(arr[3]),
|
||||||
|
conflicts: dco_decode_u_32(arr[4]),
|
||||||
|
errors: dco_decode_list_String(arr[5]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
TaskDto dco_decode_task_dto(dynamic raw) {
|
TaskDto dco_decode_task_dto(dynamic raw) {
|
||||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||||
final arr = raw as List<dynamic>;
|
final arr = raw as List<dynamic>;
|
||||||
if (arr.length != 8)
|
if (arr.length != 9)
|
||||||
throw Exception('unexpected arr length: expect 8 but see ${arr.length}');
|
throw Exception('unexpected arr length: expect 9 but see ${arr.length}');
|
||||||
return TaskDto(
|
return TaskDto(
|
||||||
id: dco_decode_String(arr[0]),
|
id: dco_decode_String(arr[0]),
|
||||||
title: dco_decode_String(arr[1]),
|
title: dco_decode_String(arr[1]),
|
||||||
description: dco_decode_String(arr[2]),
|
description: dco_decode_String(arr[2]),
|
||||||
status: dco_decode_String(arr[3]),
|
status: dco_decode_String(arr[3]),
|
||||||
dueDate: dco_decode_opt_String(arr[4]),
|
dueDate: dco_decode_opt_String(arr[4]),
|
||||||
createdAt: dco_decode_String(arr[5]),
|
hasTime: dco_decode_bool(arr[5]),
|
||||||
updatedAt: dco_decode_String(arr[6]),
|
createdAt: dco_decode_String(arr[6]),
|
||||||
parentId: dco_decode_opt_String(arr[7]),
|
updatedAt: dco_decode_String(arr[7]),
|
||||||
|
parentId: dco_decode_opt_String(arr[8]),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -967,6 +1209,18 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
return (sse_decode_task_dto(deserializer));
|
return (sse_decode_task_dto(deserializer));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@protected
|
||||||
|
List<String> sse_decode_list_String(SseDeserializer deserializer) {
|
||||||
|
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||||
|
|
||||||
|
var len_ = sse_decode_i_32(deserializer);
|
||||||
|
var ans_ = <String>[];
|
||||||
|
for (var idx_ = 0; idx_ < len_; ++idx_) {
|
||||||
|
ans_.add(sse_decode_String(deserializer));
|
||||||
|
}
|
||||||
|
return ans_;
|
||||||
|
}
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
Uint8List sse_decode_list_prim_u_8_strict(SseDeserializer deserializer) {
|
Uint8List sse_decode_list_prim_u_8_strict(SseDeserializer deserializer) {
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||||
|
|
@ -1025,6 +1279,25 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@protected
|
||||||
|
SyncResultDto sse_decode_sync_result_dto(SseDeserializer deserializer) {
|
||||||
|
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||||
|
var var_uploaded = sse_decode_u_32(deserializer);
|
||||||
|
var var_downloaded = sse_decode_u_32(deserializer);
|
||||||
|
var var_deletedLocal = sse_decode_u_32(deserializer);
|
||||||
|
var var_deletedRemote = sse_decode_u_32(deserializer);
|
||||||
|
var var_conflicts = sse_decode_u_32(deserializer);
|
||||||
|
var var_errors = sse_decode_list_String(deserializer);
|
||||||
|
return SyncResultDto(
|
||||||
|
uploaded: var_uploaded,
|
||||||
|
downloaded: var_downloaded,
|
||||||
|
deletedLocal: var_deletedLocal,
|
||||||
|
deletedRemote: var_deletedRemote,
|
||||||
|
conflicts: var_conflicts,
|
||||||
|
errors: var_errors,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
TaskDto sse_decode_task_dto(SseDeserializer deserializer) {
|
TaskDto sse_decode_task_dto(SseDeserializer deserializer) {
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||||
|
|
@ -1033,6 +1306,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
var var_description = sse_decode_String(deserializer);
|
var var_description = sse_decode_String(deserializer);
|
||||||
var var_status = sse_decode_String(deserializer);
|
var var_status = sse_decode_String(deserializer);
|
||||||
var var_dueDate = sse_decode_opt_String(deserializer);
|
var var_dueDate = sse_decode_opt_String(deserializer);
|
||||||
|
var var_hasTime = sse_decode_bool(deserializer);
|
||||||
var var_createdAt = sse_decode_String(deserializer);
|
var var_createdAt = sse_decode_String(deserializer);
|
||||||
var var_updatedAt = sse_decode_String(deserializer);
|
var var_updatedAt = sse_decode_String(deserializer);
|
||||||
var var_parentId = sse_decode_opt_String(deserializer);
|
var var_parentId = sse_decode_opt_String(deserializer);
|
||||||
|
|
@ -1042,6 +1316,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
description: var_description,
|
description: var_description,
|
||||||
status: var_status,
|
status: var_status,
|
||||||
dueDate: var_dueDate,
|
dueDate: var_dueDate,
|
||||||
|
hasTime: var_hasTime,
|
||||||
createdAt: var_createdAt,
|
createdAt: var_createdAt,
|
||||||
updatedAt: var_updatedAt,
|
updatedAt: var_updatedAt,
|
||||||
parentId: var_parentId,
|
parentId: var_parentId,
|
||||||
|
|
@ -1154,6 +1429,15 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
sse_encode_task_dto(self, serializer);
|
sse_encode_task_dto(self, serializer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@protected
|
||||||
|
void sse_encode_list_String(List<String> self, SseSerializer serializer) {
|
||||||
|
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||||
|
sse_encode_i_32(self.length, serializer);
|
||||||
|
for (final item in self) {
|
||||||
|
sse_encode_String(item, serializer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_list_prim_u_8_strict(
|
void sse_encode_list_prim_u_8_strict(
|
||||||
Uint8List self,
|
Uint8List self,
|
||||||
|
|
@ -1207,6 +1491,20 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@protected
|
||||||
|
void sse_encode_sync_result_dto(
|
||||||
|
SyncResultDto self,
|
||||||
|
SseSerializer serializer,
|
||||||
|
) {
|
||||||
|
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||||
|
sse_encode_u_32(self.uploaded, serializer);
|
||||||
|
sse_encode_u_32(self.downloaded, serializer);
|
||||||
|
sse_encode_u_32(self.deletedLocal, serializer);
|
||||||
|
sse_encode_u_32(self.deletedRemote, serializer);
|
||||||
|
sse_encode_u_32(self.conflicts, serializer);
|
||||||
|
sse_encode_list_String(self.errors, serializer);
|
||||||
|
}
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_task_dto(TaskDto self, SseSerializer serializer) {
|
void sse_encode_task_dto(TaskDto self, SseSerializer serializer) {
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||||
|
|
@ -1215,6 +1513,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
sse_encode_String(self.description, serializer);
|
sse_encode_String(self.description, serializer);
|
||||||
sse_encode_String(self.status, serializer);
|
sse_encode_String(self.status, serializer);
|
||||||
sse_encode_opt_String(self.dueDate, serializer);
|
sse_encode_opt_String(self.dueDate, serializer);
|
||||||
|
sse_encode_bool(self.hasTime, serializer);
|
||||||
sse_encode_String(self.createdAt, serializer);
|
sse_encode_String(self.createdAt, serializer);
|
||||||
sse_encode_String(self.updatedAt, serializer);
|
sse_encode_String(self.updatedAt, serializer);
|
||||||
sse_encode_opt_String(self.parentId, serializer);
|
sse_encode_opt_String(self.parentId, serializer);
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
@protected
|
@protected
|
||||||
TaskDto dco_decode_box_autoadd_task_dto(dynamic raw);
|
TaskDto dco_decode_box_autoadd_task_dto(dynamic raw);
|
||||||
|
|
||||||
|
@protected
|
||||||
|
List<String> dco_decode_list_String(dynamic raw);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
Uint8List dco_decode_list_prim_u_8_strict(dynamic raw);
|
Uint8List dco_decode_list_prim_u_8_strict(dynamic raw);
|
||||||
|
|
||||||
|
|
@ -51,6 +54,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
@protected
|
@protected
|
||||||
String? dco_decode_opt_String(dynamic raw);
|
String? dco_decode_opt_String(dynamic raw);
|
||||||
|
|
||||||
|
@protected
|
||||||
|
SyncResultDto dco_decode_sync_result_dto(dynamic raw);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
TaskDto dco_decode_task_dto(dynamic raw);
|
TaskDto dco_decode_task_dto(dynamic raw);
|
||||||
|
|
||||||
|
|
@ -89,6 +95,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
@protected
|
@protected
|
||||||
TaskDto sse_decode_box_autoadd_task_dto(SseDeserializer deserializer);
|
TaskDto sse_decode_box_autoadd_task_dto(SseDeserializer deserializer);
|
||||||
|
|
||||||
|
@protected
|
||||||
|
List<String> sse_decode_list_String(SseDeserializer deserializer);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
Uint8List sse_decode_list_prim_u_8_strict(SseDeserializer deserializer);
|
Uint8List sse_decode_list_prim_u_8_strict(SseDeserializer deserializer);
|
||||||
|
|
||||||
|
|
@ -106,6 +115,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
@protected
|
@protected
|
||||||
String? sse_decode_opt_String(SseDeserializer deserializer);
|
String? sse_decode_opt_String(SseDeserializer deserializer);
|
||||||
|
|
||||||
|
@protected
|
||||||
|
SyncResultDto sse_decode_sync_result_dto(SseDeserializer deserializer);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
TaskDto sse_decode_task_dto(SseDeserializer deserializer);
|
TaskDto sse_decode_task_dto(SseDeserializer deserializer);
|
||||||
|
|
||||||
|
|
@ -151,6 +163,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_box_autoadd_task_dto(TaskDto self, SseSerializer serializer);
|
void sse_encode_box_autoadd_task_dto(TaskDto self, SseSerializer serializer);
|
||||||
|
|
||||||
|
@protected
|
||||||
|
void sse_encode_list_String(List<String> self, SseSerializer serializer);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_list_prim_u_8_strict(
|
void sse_encode_list_prim_u_8_strict(
|
||||||
Uint8List self,
|
Uint8List self,
|
||||||
|
|
@ -175,6 +190,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_opt_String(String? self, SseSerializer serializer);
|
void sse_encode_opt_String(String? self, SseSerializer serializer);
|
||||||
|
|
||||||
|
@protected
|
||||||
|
void sse_encode_sync_result_dto(SyncResultDto self, SseSerializer serializer);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_task_dto(TaskDto self, SseSerializer serializer);
|
void sse_encode_task_dto(TaskDto self, SseSerializer serializer);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
@protected
|
@protected
|
||||||
TaskDto dco_decode_box_autoadd_task_dto(dynamic raw);
|
TaskDto dco_decode_box_autoadd_task_dto(dynamic raw);
|
||||||
|
|
||||||
|
@protected
|
||||||
|
List<String> dco_decode_list_String(dynamic raw);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
Uint8List dco_decode_list_prim_u_8_strict(dynamic raw);
|
Uint8List dco_decode_list_prim_u_8_strict(dynamic raw);
|
||||||
|
|
||||||
|
|
@ -53,6 +56,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
@protected
|
@protected
|
||||||
String? dco_decode_opt_String(dynamic raw);
|
String? dco_decode_opt_String(dynamic raw);
|
||||||
|
|
||||||
|
@protected
|
||||||
|
SyncResultDto dco_decode_sync_result_dto(dynamic raw);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
TaskDto dco_decode_task_dto(dynamic raw);
|
TaskDto dco_decode_task_dto(dynamic raw);
|
||||||
|
|
||||||
|
|
@ -91,6 +97,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
@protected
|
@protected
|
||||||
TaskDto sse_decode_box_autoadd_task_dto(SseDeserializer deserializer);
|
TaskDto sse_decode_box_autoadd_task_dto(SseDeserializer deserializer);
|
||||||
|
|
||||||
|
@protected
|
||||||
|
List<String> sse_decode_list_String(SseDeserializer deserializer);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
Uint8List sse_decode_list_prim_u_8_strict(SseDeserializer deserializer);
|
Uint8List sse_decode_list_prim_u_8_strict(SseDeserializer deserializer);
|
||||||
|
|
||||||
|
|
@ -108,6 +117,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
@protected
|
@protected
|
||||||
String? sse_decode_opt_String(SseDeserializer deserializer);
|
String? sse_decode_opt_String(SseDeserializer deserializer);
|
||||||
|
|
||||||
|
@protected
|
||||||
|
SyncResultDto sse_decode_sync_result_dto(SseDeserializer deserializer);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
TaskDto sse_decode_task_dto(SseDeserializer deserializer);
|
TaskDto sse_decode_task_dto(SseDeserializer deserializer);
|
||||||
|
|
||||||
|
|
@ -153,6 +165,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_box_autoadd_task_dto(TaskDto self, SseSerializer serializer);
|
void sse_encode_box_autoadd_task_dto(TaskDto self, SseSerializer serializer);
|
||||||
|
|
||||||
|
@protected
|
||||||
|
void sse_encode_list_String(List<String> self, SseSerializer serializer);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_list_prim_u_8_strict(
|
void sse_encode_list_prim_u_8_strict(
|
||||||
Uint8List self,
|
Uint8List self,
|
||||||
|
|
@ -177,6 +192,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_opt_String(String? self, SseSerializer serializer);
|
void sse_encode_opt_String(String? self, SseSerializer serializer);
|
||||||
|
|
||||||
|
@protected
|
||||||
|
void sse_encode_sync_result_dto(SyncResultDto self, SseSerializer serializer);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_task_dto(TaskDto self, SseSerializer serializer);
|
void sse_encode_task_dto(TaskDto self, SseSerializer serializer);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,131 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
import '../rust/api.dart' as api;
|
||||||
import '../state/app_state.dart';
|
import '../state/app_state.dart';
|
||||||
import '../theme.dart';
|
import '../theme.dart';
|
||||||
|
|
||||||
class SettingsScreen extends StatelessWidget {
|
class SettingsScreen extends StatefulWidget {
|
||||||
const SettingsScreen({super.key});
|
const SettingsScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<SettingsScreen> createState() => _SettingsScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SettingsScreenState extends State<SettingsScreen> {
|
||||||
|
final _urlController = TextEditingController();
|
||||||
|
final _userController = TextEditingController();
|
||||||
|
final _passController = TextEditingController();
|
||||||
|
String _testStatus = 'idle'; // idle | testing | ok | fail
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_loadCredentials();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_urlController.dispose();
|
||||||
|
_userController.dispose();
|
||||||
|
_passController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _loadCredentials() async {
|
||||||
|
final state = context.read<AppState>();
|
||||||
|
final wsName = state.config?.currentWorkspace;
|
||||||
|
if (wsName == null) return;
|
||||||
|
final ws = state.config!.workspaces.cast<api.WorkspaceEntry?>().firstWhere(
|
||||||
|
(w) => w?.name == wsName, orElse: () => null);
|
||||||
|
if (ws?.webdavUrl != null) {
|
||||||
|
_urlController.text = ws!.webdavUrl!;
|
||||||
|
try {
|
||||||
|
final domain = Uri.parse(ws.webdavUrl!).host;
|
||||||
|
final creds = await api.loadCredentials(domain: domain);
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_userController.text = creds[0];
|
||||||
|
_passController.text = creds[1];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _testConnection() async {
|
||||||
|
setState(() => _testStatus = 'testing');
|
||||||
|
try {
|
||||||
|
await api.testWebdavConnection(
|
||||||
|
url: _urlController.text,
|
||||||
|
username: _userController.text,
|
||||||
|
password: _passController.text,
|
||||||
|
);
|
||||||
|
if (mounted) setState(() => _testStatus = 'ok');
|
||||||
|
} catch (_) {
|
||||||
|
if (mounted) setState(() => _testStatus = 'fail');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _save() async {
|
||||||
|
final state = context.read<AppState>();
|
||||||
|
final wsName = state.config?.currentWorkspace;
|
||||||
|
if (wsName == null || _urlController.text.trim().isEmpty) return;
|
||||||
|
await api.setWebdavConfig(
|
||||||
|
workspaceName: wsName,
|
||||||
|
webdavUrl: _urlController.text.trim(),
|
||||||
|
);
|
||||||
|
if (_userController.text.isNotEmpty && _passController.text.isNotEmpty) {
|
||||||
|
final domain = Uri.parse(_urlController.text.trim()).host;
|
||||||
|
await api.storeCredentials(
|
||||||
|
domain: domain,
|
||||||
|
username: _userController.text,
|
||||||
|
password: _passController.text,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
await state.loadConfig();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final state = context.watch<AppState>();
|
final state = context.watch<AppState>();
|
||||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||||
|
final borderColor = isDark ? AppTheme.borderDark : AppTheme.borderLight;
|
||||||
|
final inputDecoration = InputDecoration(
|
||||||
|
isDense: true,
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
borderSide: BorderSide(color: borderColor),
|
||||||
|
),
|
||||||
|
enabledBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
borderSide: BorderSide(color: borderColor),
|
||||||
|
),
|
||||||
|
focusedBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
borderSide: const BorderSide(color: AppTheme.primary),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final wsName = state.config?.currentWorkspace;
|
||||||
|
final ws = wsName == null ? null : state.config!.workspaces.cast<api.WorkspaceEntry?>()
|
||||||
|
.firstWhere((w) => w?.name == wsName, orElse: () => null);
|
||||||
|
final lastSync = ws?.lastSync;
|
||||||
|
String? relTime;
|
||||||
|
if (lastSync != null) {
|
||||||
|
final t = DateTime.tryParse(lastSync);
|
||||||
|
if (t != null) {
|
||||||
|
final secsAgo = DateTime.now().difference(t).inSeconds;
|
||||||
|
if (secsAgo < 60) {
|
||||||
|
relTime = 'just now';
|
||||||
|
} else if (secsAgo < 3600) {
|
||||||
|
relTime = '${secsAgo ~/ 60}m ago';
|
||||||
|
} else {
|
||||||
|
relTime = '${secsAgo ~/ 3600}h ago';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () => state.setScreen('tasks'),
|
onTap: () => state.setScreen('tasks'),
|
||||||
child: Container(
|
child: Container(
|
||||||
|
|
@ -20,131 +136,233 @@ class SettingsScreen extends StatelessWidget {
|
||||||
),
|
),
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTap: () {},
|
onTap: () {},
|
||||||
child: AnimatedScale(
|
child: Container(
|
||||||
scale: 1.0,
|
decoration: BoxDecoration(
|
||||||
duration: const Duration(milliseconds: 150),
|
color: isDark ? AppTheme.surfaceDark : AppTheme.surfaceLight,
|
||||||
curve: Curves.easeOut,
|
borderRadius: BorderRadius.circular(16),
|
||||||
child: Container(
|
border: Border.all(color: Colors.white.withValues(alpha: 0.1)),
|
||||||
decoration: BoxDecoration(
|
boxShadow: [
|
||||||
color: isDark ? AppTheme.surfaceDark : AppTheme.surfaceLight,
|
BoxShadow(color: Colors.black.withValues(alpha: 0.7), blurRadius: 60, offset: const Offset(0, 25)),
|
||||||
borderRadius: BorderRadius.circular(16),
|
BoxShadow(color: Colors.black.withValues(alpha: 0.5), blurRadius: 20, offset: const Offset(0, 10)),
|
||||||
border: Border.all(color: Colors.white.withValues(alpha: 0.1)),
|
],
|
||||||
boxShadow: [
|
),
|
||||||
BoxShadow(color: Colors.black.withValues(alpha: 0.7), blurRadius: 60, offset: const Offset(0, 25)),
|
clipBehavior: Clip.antiAlias,
|
||||||
BoxShadow(color: Colors.black.withValues(alpha: 0.5), blurRadius: 20, offset: const Offset(0, 10)),
|
child: Column(
|
||||||
],
|
children: [
|
||||||
),
|
// Header
|
||||||
clipBehavior: Clip.antiAlias,
|
Container(
|
||||||
child: Column(
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||||
children: [
|
decoration: BoxDecoration(
|
||||||
// Header (matching Tauri: text-lg font-bold, border-b, px-4 py-3)
|
border: Border(bottom: BorderSide(color: borderColor, width: 0.5)),
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
border: Border(bottom: BorderSide(color: isDark ? AppTheme.borderDark : AppTheme.borderLight, width: 0.5)),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
const Text('Settings', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w700)),
|
|
||||||
const Spacer(),
|
|
||||||
GestureDetector(
|
|
||||||
onTap: () => state.setScreen('tasks'),
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.all(6),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
),
|
|
||||||
child: Icon(Icons.close, size: 20,
|
|
||||||
color: isDark ? AppTheme.textDark : AppTheme.textLight),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
// Scrollable content
|
child: Row(
|
||||||
Expanded(
|
children: [
|
||||||
child: SingleChildScrollView(
|
const Text('Settings', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w700)),
|
||||||
padding: const EdgeInsets.all(16),
|
const Spacer(),
|
||||||
child: Column(
|
GestureDetector(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
onTap: () => state.setScreen('tasks'),
|
||||||
children: [
|
child: Container(
|
||||||
// WebDAV Sync section (matching Tauri order: sync first)
|
padding: const EdgeInsets.all(6),
|
||||||
Text('WEBDAV SYNC',
|
decoration: BoxDecoration(borderRadius: BorderRadius.circular(8)),
|
||||||
style: TextStyle(
|
child: Icon(Icons.close, size: 20,
|
||||||
fontSize: 14, fontWeight: FontWeight.w600,
|
color: isDark ? AppTheme.textDark : AppTheme.textLight),
|
||||||
letterSpacing: 0.5,
|
),
|
||||||
color: (isDark ? AppTheme.textSecondaryDark : AppTheme.textSecondaryLight).withValues(alpha: 0.5),
|
),
|
||||||
)),
|
],
|
||||||
const SizedBox(height: 12),
|
),
|
||||||
Container(
|
),
|
||||||
width: double.infinity,
|
// Content
|
||||||
padding: const EdgeInsets.all(16),
|
Expanded(
|
||||||
decoration: BoxDecoration(
|
child: SingleChildScrollView(
|
||||||
borderRadius: BorderRadius.circular(12),
|
padding: const EdgeInsets.all(16),
|
||||||
border: Border.all(color: isDark ? AppTheme.borderDark : AppTheme.borderLight),
|
child: Column(
|
||||||
),
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
child: Text(
|
children: [
|
||||||
'WebDAV sync not yet available in Flutter build',
|
// WebDAV section header
|
||||||
style: TextStyle(fontSize: 13, color: isDark ? AppTheme.textSecondaryDark : AppTheme.textSecondaryLight),
|
Text('WEBDAV SYNC',
|
||||||
),
|
style: TextStyle(fontSize: 12, fontWeight: FontWeight.w600,
|
||||||
|
letterSpacing: 0.5,
|
||||||
|
color: (isDark ? AppTheme.textSecondaryDark : AppTheme.textSecondaryLight).withValues(alpha: 0.5))),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
// Credentials card
|
||||||
|
Container(
|
||||||
|
width: double.infinity,
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
border: Border.all(color: borderColor),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 24),
|
child: Column(
|
||||||
// Appearance section
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
Text('APPEARANCE',
|
children: [
|
||||||
style: TextStyle(
|
Text('Server URL', style: TextStyle(fontSize: 11, fontWeight: FontWeight.w500,
|
||||||
fontSize: 14, fontWeight: FontWeight.w600,
|
color: isDark ? AppTheme.textSecondaryDark : AppTheme.textSecondaryLight)),
|
||||||
letterSpacing: 0.5,
|
const SizedBox(height: 4),
|
||||||
color: (isDark ? AppTheme.textSecondaryDark : AppTheme.textSecondaryLight).withValues(alpha: 0.5),
|
TextField(controller: _urlController, style: const TextStyle(fontSize: 13),
|
||||||
)),
|
decoration: inputDecoration.copyWith(hintText: 'https://dav.example.com/tasks/')),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 10),
|
||||||
// Dark mode toggle in bordered card (matching Tauri)
|
Text('Username', style: TextStyle(fontSize: 11, fontWeight: FontWeight.w500,
|
||||||
GestureDetector(
|
color: isDark ? AppTheme.textSecondaryDark : AppTheme.textSecondaryLight)),
|
||||||
onTap: () => state.toggleDarkMode(),
|
const SizedBox(height: 4),
|
||||||
child: Container(
|
TextField(controller: _userController, style: const TextStyle(fontSize: 13),
|
||||||
padding: const EdgeInsets.all(16),
|
decoration: inputDecoration),
|
||||||
decoration: BoxDecoration(
|
const SizedBox(height: 10),
|
||||||
borderRadius: BorderRadius.circular(12),
|
Text('Password', style: TextStyle(fontSize: 11, fontWeight: FontWeight.w500,
|
||||||
border: Border.all(color: isDark ? AppTheme.borderDark : AppTheme.borderLight),
|
color: isDark ? AppTheme.textSecondaryDark : AppTheme.textSecondaryLight)),
|
||||||
),
|
const SizedBox(height: 4),
|
||||||
child: Row(
|
TextField(controller: _passController, obscureText: true,
|
||||||
|
style: const TextStyle(fontSize: 13), decoration: inputDecoration),
|
||||||
|
const SizedBox(height: 14),
|
||||||
|
Row(
|
||||||
children: [
|
children: [
|
||||||
const Text('Dark mode', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500)),
|
Expanded(
|
||||||
const Spacer(),
|
child: OutlinedButton(
|
||||||
// Toggle switch (matching Tauri: h-6 w-11)
|
onPressed: _urlController.text.isEmpty ? null : _testConnection,
|
||||||
AnimatedContainer(
|
style: OutlinedButton.styleFrom(
|
||||||
duration: const Duration(milliseconds: 150),
|
side: BorderSide(color: borderColor),
|
||||||
width: 44,
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||||
height: 24,
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
color: state.darkMode ? AppTheme.primary : (isDark ? const Color(0xFF4B5563) : const Color(0xFFD1D5DB)),
|
|
||||||
),
|
|
||||||
child: AnimatedAlign(
|
|
||||||
duration: const Duration(milliseconds: 150),
|
|
||||||
alignment: state.darkMode ? Alignment.centerRight : Alignment.centerLeft,
|
|
||||||
child: Container(
|
|
||||||
width: 20,
|
|
||||||
height: 20,
|
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 2),
|
|
||||||
decoration: const BoxDecoration(shape: BoxShape.circle, color: Colors.white),
|
|
||||||
),
|
),
|
||||||
|
child: Text(
|
||||||
|
_testStatus == 'testing' ? 'Testing…'
|
||||||
|
: _testStatus == 'ok' ? 'Connected'
|
||||||
|
: _testStatus == 'fail' ? 'Failed — Retry'
|
||||||
|
: 'Test Connection',
|
||||||
|
style: const TextStyle(fontSize: 13),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Expanded(
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: _urlController.text.isEmpty ? null : _save,
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: AppTheme.primary,
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||||
|
),
|
||||||
|
child: const Text('Save', style: TextStyle(fontSize: 13)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Sync direction + Sync Now
|
||||||
|
if (wsName != null) ...[
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(color: borderColor),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
color: isDark ? AppTheme.surfaceDark : AppTheme.surfaceLight,
|
||||||
|
),
|
||||||
|
child: DropdownButton<String>(
|
||||||
|
value: state.syncMode,
|
||||||
|
isExpanded: true,
|
||||||
|
underline: const SizedBox.shrink(),
|
||||||
|
style: TextStyle(fontSize: 13,
|
||||||
|
color: isDark ? AppTheme.textDark : AppTheme.textLight),
|
||||||
|
dropdownColor: isDark ? AppTheme.surfaceDark : AppTheme.surfaceLight,
|
||||||
|
items: const [
|
||||||
|
DropdownMenuItem(value: 'full', child: Text('Sync both ways')),
|
||||||
|
DropdownMenuItem(value: 'push', child: Text('Push only')),
|
||||||
|
DropdownMenuItem(value: 'pull', child: Text('Pull only')),
|
||||||
|
],
|
||||||
|
onChanged: (v) { if (v != null) state.setSyncMode(v); },
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Expanded(
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: state.syncing ? null : () => state.triggerSync(),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: AppTheme.primary,
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||||
|
disabledBackgroundColor: AppTheme.primary.withValues(alpha: 0.4),
|
||||||
|
),
|
||||||
|
child: Text(state.syncing ? 'Syncing…' : 'Sync Now',
|
||||||
|
style: const TextStyle(fontSize: 13)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (relTime != null) ...[
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Text('Last sync: $relTime',
|
||||||
|
style: TextStyle(fontSize: 11,
|
||||||
|
color: (isDark ? AppTheme.textDark : AppTheme.textLight).withValues(alpha: 0.4))),
|
||||||
|
if (state.lastSyncResult != null) ...[
|
||||||
|
Text(' · ↑${state.lastSyncResult!.uploaded} ↓${state.lastSyncResult!.downloaded}',
|
||||||
|
style: TextStyle(fontSize: 11,
|
||||||
|
color: (isDark ? AppTheme.textDark : AppTheme.textLight).withValues(alpha: 0.4))),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
// Appearance
|
||||||
|
Text('APPEARANCE',
|
||||||
|
style: TextStyle(fontSize: 12, fontWeight: FontWeight.w600,
|
||||||
|
letterSpacing: 0.5,
|
||||||
|
color: (isDark ? AppTheme.textSecondaryDark : AppTheme.textSecondaryLight).withValues(alpha: 0.5))),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () => state.toggleDarkMode(),
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
border: Border.all(color: borderColor),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const Text('Dark mode', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500)),
|
||||||
|
const Spacer(),
|
||||||
|
AnimatedContainer(
|
||||||
|
duration: const Duration(milliseconds: 150),
|
||||||
|
width: 44, height: 24,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
color: state.darkMode ? AppTheme.primary : (isDark ? const Color(0xFF4B5563) : const Color(0xFFD1D5DB)),
|
||||||
|
),
|
||||||
|
child: AnimatedAlign(
|
||||||
|
duration: const Duration(milliseconds: 150),
|
||||||
|
alignment: state.darkMode ? Alignment.centerRight : Alignment.centerLeft,
|
||||||
|
child: Container(
|
||||||
|
width: 20, height: 20,
|
||||||
|
margin: const EdgeInsets.symmetric(horizontal: 2),
|
||||||
|
decoration: const BoxDecoration(shape: BoxShape.circle, color: Colors.white),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 32),
|
),
|
||||||
Center(
|
const SizedBox(height: 32),
|
||||||
child: Text('Flutter + Rust', style: TextStyle(fontSize: 12,
|
Center(
|
||||||
|
child: Text('Flutter + Rust',
|
||||||
|
style: TextStyle(fontSize: 12,
|
||||||
color: (isDark ? AppTheme.textDark : AppTheme.textLight).withValues(alpha: 0.3))),
|
color: (isDark ? AppTheme.textDark : AppTheme.textLight).withValues(alpha: 0.3))),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -69,13 +69,13 @@ class _TasksScreenState extends State<TasksScreen> with SingleTickerProviderStat
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _handleCreateTask(String title, String desc, {String? dueDate}) async {
|
Future<void> _handleCreateTask(String title, String desc, {String? dueDate, bool hasTime = false}) async {
|
||||||
final state = context.read<AppState>();
|
final state = context.read<AppState>();
|
||||||
final task = await state.createTask(title, desc);
|
final task = await state.createTask(title, desc);
|
||||||
if (task != null && dueDate != null) {
|
if (task != null && dueDate != null) {
|
||||||
await state.updateTask(api.TaskDto(
|
await state.updateTask(api.TaskDto(
|
||||||
id: task.id, title: task.title, description: task.description,
|
id: task.id, title: task.title, description: task.description,
|
||||||
status: task.status, dueDate: dueDate,
|
status: task.status, dueDate: dueDate, hasTime: hasTime,
|
||||||
createdAt: task.createdAt, updatedAt: task.updatedAt, parentId: task.parentId,
|
createdAt: task.createdAt, updatedAt: task.updatedAt, parentId: task.parentId,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
@ -257,6 +257,32 @@ class _TasksScreenState extends State<TasksScreen> with SingleTickerProviderStat
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
// Sync status indicator
|
||||||
|
Positioned(
|
||||||
|
bottom: 16,
|
||||||
|
right: 16,
|
||||||
|
child: IgnorePointer(
|
||||||
|
child: state.syncing
|
||||||
|
? const SizedBox(
|
||||||
|
width: 20, height: 20,
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
strokeWidth: 2, color: AppTheme.primary))
|
||||||
|
: state.lastSyncResult != null
|
||||||
|
? Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: isDark ? Colors.white.withValues(alpha: 0.1) : Colors.black.withValues(alpha: 0.08),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'↑${state.lastSyncResult!.uploaded} ↓${state.lastSyncResult!.downloaded}',
|
||||||
|
style: TextStyle(fontSize: 11,
|
||||||
|
color: (isDark ? AppTheme.textDark : AppTheme.textLight).withValues(alpha: 0.6)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: const SizedBox.shrink(),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,8 @@ class AppState extends ChangeNotifier {
|
||||||
bool darkMode = true;
|
bool darkMode = true;
|
||||||
StreamSubscription? _watcherSub;
|
StreamSubscription? _watcherSub;
|
||||||
bool syncing = false;
|
bool syncing = false;
|
||||||
|
String syncMode = 'full';
|
||||||
|
api.SyncResultDto? lastSyncResult;
|
||||||
String? error;
|
String? error;
|
||||||
|
|
||||||
// Selected task for detail view
|
// Selected task for detail view
|
||||||
|
|
@ -264,6 +266,45 @@ class AppState extends ChangeNotifier {
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setSyncMode(String mode) {
|
||||||
|
syncMode = mode;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> triggerSync() async {
|
||||||
|
if (config?.currentWorkspace == null) return;
|
||||||
|
final wsName = config!.currentWorkspace!;
|
||||||
|
final ws = config!.workspaces.firstWhere((w) => w.name == wsName);
|
||||||
|
if (ws.webdavUrl == null) {
|
||||||
|
error = 'No WebDAV URL configured';
|
||||||
|
notifyListeners();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
syncing = true;
|
||||||
|
error = null;
|
||||||
|
notifyListeners();
|
||||||
|
try {
|
||||||
|
final domain = Uri.parse(ws.webdavUrl!).host;
|
||||||
|
final creds = await api.loadCredentials(domain: domain);
|
||||||
|
final result = await api.syncWorkspaceCmd(
|
||||||
|
workspaceName: wsName,
|
||||||
|
workspacePath: ws.path,
|
||||||
|
webdavUrl: ws.webdavUrl!,
|
||||||
|
username: creds[0],
|
||||||
|
password: creds[1],
|
||||||
|
mode: syncMode,
|
||||||
|
);
|
||||||
|
lastSyncResult = result;
|
||||||
|
if (result.errors.isNotEmpty) error = result.errors.join('; ');
|
||||||
|
config = await api.getConfig();
|
||||||
|
await loadLists();
|
||||||
|
} catch (e) {
|
||||||
|
error = e.toString();
|
||||||
|
}
|
||||||
|
syncing = false;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
void toggleDarkMode() {
|
void toggleDarkMode() {
|
||||||
darkMode = !darkMode;
|
darkMode = !darkMode;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,11 @@ import '../theme.dart';
|
||||||
|
|
||||||
class DateTimePicker extends StatefulWidget {
|
class DateTimePicker extends StatefulWidget {
|
||||||
final DateTime? initialDate;
|
final DateTime? initialDate;
|
||||||
final void Function(DateTime date) onDone;
|
final bool initialHasTime;
|
||||||
|
final void Function(DateTime date, bool hasTime) onDone;
|
||||||
final VoidCallback onClear;
|
final VoidCallback onClear;
|
||||||
|
|
||||||
const DateTimePicker({super.key, this.initialDate, required this.onDone, required this.onClear});
|
const DateTimePicker({super.key, this.initialDate, this.initialHasTime = false, required this.onDone, required this.onClear});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<DateTimePicker> createState() => _DateTimePickerState();
|
State<DateTimePicker> createState() => _DateTimePickerState();
|
||||||
|
|
@ -27,7 +28,7 @@ class _DateTimePickerState extends State<DateTimePicker> {
|
||||||
if (widget.initialDate != null) {
|
if (widget.initialDate != null) {
|
||||||
_hour = widget.initialDate!.hour;
|
_hour = widget.initialDate!.hour;
|
||||||
_minute = widget.initialDate!.minute;
|
_minute = widget.initialDate!.minute;
|
||||||
_showTime = _hour != 0 || _minute != 0;
|
_showTime = widget.initialHasTime;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -39,7 +40,7 @@ class _DateTimePickerState extends State<DateTimePicker> {
|
||||||
final result = _showTime
|
final result = _showTime
|
||||||
? DateTime(_selected!.year, _selected!.month, _selected!.day, _hour, _minute)
|
? DateTime(_selected!.year, _selected!.month, _selected!.day, _hour, _minute)
|
||||||
: DateTime(_selected!.year, _selected!.month, _selected!.day);
|
: DateTime(_selected!.year, _selected!.month, _selected!.day);
|
||||||
widget.onDone(result);
|
widget.onDone(result, _showTime);
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import '../theme.dart';
|
||||||
import 'date_time_picker.dart';
|
import 'date_time_picker.dart';
|
||||||
|
|
||||||
class NewTaskInput extends StatefulWidget {
|
class NewTaskInput extends StatefulWidget {
|
||||||
final Future<void> Function(String title, String description, {String? dueDate}) onCreate;
|
final Future<void> Function(String title, String description, {String? dueDate, bool hasTime}) onCreate;
|
||||||
|
|
||||||
const NewTaskInput({super.key, required this.onCreate});
|
const NewTaskInput({super.key, required this.onCreate});
|
||||||
|
|
||||||
|
|
@ -16,6 +16,7 @@ class _NewTaskInputState extends State<NewTaskInput> {
|
||||||
final _descController = TextEditingController();
|
final _descController = TextEditingController();
|
||||||
final _titleFocus = FocusNode();
|
final _titleFocus = FocusNode();
|
||||||
DateTime? _selectedDate;
|
DateTime? _selectedDate;
|
||||||
|
bool _selectedHasTime = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
|
@ -34,7 +35,7 @@ class _NewTaskInputState extends State<NewTaskInput> {
|
||||||
Future<void> _submit() async {
|
Future<void> _submit() async {
|
||||||
final title = _titleController.text.trim();
|
final title = _titleController.text.trim();
|
||||||
if (title.isEmpty) return;
|
if (title.isEmpty) return;
|
||||||
await widget.onCreate(title, _descController.text.trim(), dueDate: _selectedDate?.toUtc().toIso8601String());
|
await widget.onCreate(title, _descController.text.trim(), dueDate: _selectedDate?.toUtc().toIso8601String(), hasTime: _selectedHasTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _pickDate() {
|
void _pickDate() {
|
||||||
|
|
@ -47,8 +48,9 @@ class _NewTaskInputState extends State<NewTaskInput> {
|
||||||
),
|
),
|
||||||
builder: (_) => DateTimePicker(
|
builder: (_) => DateTimePicker(
|
||||||
initialDate: _selectedDate,
|
initialDate: _selectedDate,
|
||||||
onDone: (date) => setState(() => _selectedDate = date),
|
initialHasTime: _selectedHasTime,
|
||||||
onClear: () => setState(() => _selectedDate = null),
|
onDone: (date, hasTime) => setState(() { _selectedDate = date; _selectedHasTime = hasTime; }),
|
||||||
|
onClear: () => setState(() { _selectedDate = null; _selectedHasTime = false; }),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -60,8 +62,7 @@ class _NewTaskInputState extends State<NewTaskInput> {
|
||||||
final dayNames = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
final dayNames = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
||||||
final day = dayNames[d.weekday % 7];
|
final day = dayNames[d.weekday % 7];
|
||||||
final pad = (int n) => n.toString().padLeft(2, '0');
|
final pad = (int n) => n.toString().padLeft(2, '0');
|
||||||
final hasTime = d.hour != 0 || d.minute != 0;
|
final timePart = _selectedHasTime ? ', ${pad(d.hour)}:${pad(d.minute)}' : '';
|
||||||
final timePart = hasTime ? ', ${pad(d.hour)}:${pad(d.minute)}' : '';
|
|
||||||
if (taskDate == today) return 'Today$timePart';
|
if (taskDate == today) return 'Today$timePart';
|
||||||
return '$day, ${pad(d.day)}/${pad(d.month)}$timePart';
|
return '$day, ${pad(d.day)}/${pad(d.month)}$timePart';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ class _TaskDetailViewState extends State<TaskDetailView> with SingleTickerProvid
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _scheduleUpdate({String? dueDate}) {
|
void _scheduleUpdate({String? dueDate, bool? hasTime}) {
|
||||||
_debounce?.cancel();
|
_debounce?.cancel();
|
||||||
_debounce = Timer(const Duration(milliseconds: 400), () {
|
_debounce = Timer(const Duration(milliseconds: 400), () {
|
||||||
final state = context.read<AppState>();
|
final state = context.read<AppState>();
|
||||||
|
|
@ -65,6 +65,7 @@ class _TaskDetailViewState extends State<TaskDetailView> with SingleTickerProvid
|
||||||
description: _descController.text,
|
description: _descController.text,
|
||||||
status: widget.task.status,
|
status: widget.task.status,
|
||||||
dueDate: dueDate ?? widget.task.dueDate,
|
dueDate: dueDate ?? widget.task.dueDate,
|
||||||
|
hasTime: hasTime ?? widget.task.hasTime,
|
||||||
createdAt: widget.task.createdAt,
|
createdAt: widget.task.createdAt,
|
||||||
updatedAt: widget.task.updatedAt,
|
updatedAt: widget.task.updatedAt,
|
||||||
parentId: widget.task.parentId,
|
parentId: widget.task.parentId,
|
||||||
|
|
@ -72,7 +73,7 @@ class _TaskDetailViewState extends State<TaskDetailView> with SingleTickerProvid
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _updateDueDate(String? dueDate) {
|
void _updateDueDate(String? dueDate, {bool hasTime = false}) {
|
||||||
final state = context.read<AppState>();
|
final state = context.read<AppState>();
|
||||||
state.updateTask(api.TaskDto(
|
state.updateTask(api.TaskDto(
|
||||||
id: widget.task.id,
|
id: widget.task.id,
|
||||||
|
|
@ -80,6 +81,7 @@ class _TaskDetailViewState extends State<TaskDetailView> with SingleTickerProvid
|
||||||
description: _descController.text,
|
description: _descController.text,
|
||||||
status: widget.task.status,
|
status: widget.task.status,
|
||||||
dueDate: dueDate,
|
dueDate: dueDate,
|
||||||
|
hasTime: hasTime,
|
||||||
createdAt: widget.task.createdAt,
|
createdAt: widget.task.createdAt,
|
||||||
updatedAt: widget.task.updatedAt,
|
updatedAt: widget.task.updatedAt,
|
||||||
parentId: widget.task.parentId,
|
parentId: widget.task.parentId,
|
||||||
|
|
@ -96,7 +98,8 @@ class _TaskDetailViewState extends State<TaskDetailView> with SingleTickerProvid
|
||||||
),
|
),
|
||||||
builder: (_) => DateTimePicker(
|
builder: (_) => DateTimePicker(
|
||||||
initialDate: widget.task.dueDate != null ? DateTime.tryParse(widget.task.dueDate!) : null,
|
initialDate: widget.task.dueDate != null ? DateTime.tryParse(widget.task.dueDate!) : null,
|
||||||
onDone: (date) => _updateDueDate(date.toUtc().toIso8601String()),
|
initialHasTime: widget.task.hasTime,
|
||||||
|
onDone: (date, hasTime) => _updateDueDate(date.toUtc().toIso8601String(), hasTime: hasTime),
|
||||||
onClear: () => _updateDueDate(null),
|
onClear: () => _updateDueDate(null),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -112,8 +115,7 @@ class _TaskDetailViewState extends State<TaskDetailView> with SingleTickerProvid
|
||||||
final dayNames = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
final dayNames = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
||||||
final day = dayNames[local.weekday % 7];
|
final day = dayNames[local.weekday % 7];
|
||||||
final pad = (int n) => n.toString().padLeft(2, '0');
|
final pad = (int n) => n.toString().padLeft(2, '0');
|
||||||
final hasTime = local.hour != 0 || local.minute != 0;
|
final timePart = widget.task.hasTime ? ', ${pad(local.hour)}:${pad(local.minute)}' : '';
|
||||||
final timePart = hasTime ? ', ${pad(local.hour)}:${pad(local.minute)}' : '';
|
|
||||||
if (taskDate == today) return 'Today$timePart';
|
if (taskDate == today) return 'Today$timePart';
|
||||||
return '$day, ${pad(local.day)}/${pad(local.month)}$timePart';
|
return '$day, ${pad(local.day)}/${pad(local.month)}$timePart';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
1
apps/flutter/rust/Cargo.lock
generated
1
apps/flutter/rust/Cargo.lock
generated
|
|
@ -1194,6 +1194,7 @@ dependencies = [
|
||||||
"notify-debouncer-mini",
|
"notify-debouncer-mini",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"onyx-core",
|
"onyx-core",
|
||||||
|
"tokio",
|
||||||
"uuid",
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,3 +14,4 @@ chrono = { version = "0.4", features = ["serde"] }
|
||||||
once_cell = "1"
|
once_cell = "1"
|
||||||
notify = "7"
|
notify = "7"
|
||||||
notify-debouncer-mini = "0.5"
|
notify-debouncer-mini = "0.5"
|
||||||
|
tokio = { version = "1", features = ["full"] }
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,8 @@ use onyx_core::{
|
||||||
config::{AppConfig, WorkspaceConfig},
|
config::{AppConfig, WorkspaceConfig},
|
||||||
models::{Task, TaskStatus},
|
models::{Task, TaskStatus},
|
||||||
repository::TaskRepository,
|
repository::TaskRepository,
|
||||||
|
sync::{self, SyncMode},
|
||||||
|
webdav,
|
||||||
};
|
};
|
||||||
|
|
||||||
// ── State ───────────────────────────────────────────────────────────
|
// ── State ───────────────────────────────────────────────────────────
|
||||||
|
|
@ -44,11 +46,21 @@ pub struct TaskDto {
|
||||||
pub description: String,
|
pub description: String,
|
||||||
pub status: String,
|
pub status: String,
|
||||||
pub due_date: Option<String>,
|
pub due_date: Option<String>,
|
||||||
|
pub has_time: bool,
|
||||||
pub created_at: String,
|
pub created_at: String,
|
||||||
pub updated_at: String,
|
pub updated_at: String,
|
||||||
pub parent_id: Option<String>,
|
pub parent_id: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct SyncResultDto {
|
||||||
|
pub uploaded: u32,
|
||||||
|
pub downloaded: u32,
|
||||||
|
pub deleted_local: u32,
|
||||||
|
pub deleted_remote: u32,
|
||||||
|
pub conflicts: u32,
|
||||||
|
pub errors: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct TaskListDto {
|
pub struct TaskListDto {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub title: String,
|
pub title: String,
|
||||||
|
|
@ -79,6 +91,7 @@ fn task_to_dto(t: &Task) -> TaskDto {
|
||||||
TaskStatus::Completed => "completed".into(),
|
TaskStatus::Completed => "completed".into(),
|
||||||
},
|
},
|
||||||
due_date: t.due_date.map(|d| d.to_rfc3339()),
|
due_date: t.due_date.map(|d| d.to_rfc3339()),
|
||||||
|
has_time: t.has_time,
|
||||||
created_at: t.created_at.to_rfc3339(),
|
created_at: t.created_at.to_rfc3339(),
|
||||||
updated_at: t.updated_at.to_rfc3339(),
|
updated_at: t.updated_at.to_rfc3339(),
|
||||||
parent_id: t.parent_id.map(|id| id.to_string()),
|
parent_id: t.parent_id.map(|id| id.to_string()),
|
||||||
|
|
@ -218,6 +231,7 @@ pub fn update_task(list_id: String, task: TaskDto) -> Result<(), String> {
|
||||||
.as_deref()
|
.as_deref()
|
||||||
.and_then(|d| chrono::DateTime::parse_from_rfc3339(d).ok())
|
.and_then(|d| chrono::DateTime::parse_from_rfc3339(d).ok())
|
||||||
.map(|d| d.with_timezone(&chrono::Utc));
|
.map(|d| d.with_timezone(&chrono::Utc));
|
||||||
|
existing.has_time = task.has_time;
|
||||||
|
|
||||||
s.repo.as_mut().unwrap().update_task(lid, existing).map_err(|e| e.to_string())
|
s.repo.as_mut().unwrap().update_task(lid, existing).map_err(|e| e.to_string())
|
||||||
}
|
}
|
||||||
|
|
@ -295,6 +309,74 @@ pub fn get_group_by_due_date(list_id: String) -> Result<bool, String> {
|
||||||
s.repo.as_ref().unwrap().get_group_by_due_date(id).map_err(|e| e.to_string())
|
s.repo.as_ref().unwrap().get_group_by_due_date(id).map_err(|e| e.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Sync commands ──────────────────────────────────────────────────
|
||||||
|
|
||||||
|
pub fn store_credentials(domain: String, username: String, password: String) -> Result<(), String> {
|
||||||
|
webdav::store_credentials(&domain, &username, &password).map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_credentials(domain: String) -> Result<Vec<String>, String> {
|
||||||
|
let (u, p) = webdav::load_credentials(&domain).map_err(|e| e.to_string())?;
|
||||||
|
Ok(vec![u, p])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_webdav_config(workspace_name: String, webdav_url: String) -> Result<(), String> {
|
||||||
|
let mut s = STATE.lock().unwrap();
|
||||||
|
if let Some(ws) = s.config.workspaces.get_mut(&workspace_name) {
|
||||||
|
ws.webdav_url = Some(webdav_url);
|
||||||
|
}
|
||||||
|
let config_path = AppConfig::get_config_path();
|
||||||
|
s.config.save_to_file(&config_path).map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn test_webdav_connection(url: String, username: String, password: String) -> Result<(), String> {
|
||||||
|
let client = webdav::WebDavClient::new(&url, &username, &password);
|
||||||
|
client.test_connection().await.map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn sync_workspace_cmd(
|
||||||
|
workspace_name: String,
|
||||||
|
workspace_path: String,
|
||||||
|
webdav_url: String,
|
||||||
|
username: String,
|
||||||
|
password: String,
|
||||||
|
mode: String,
|
||||||
|
) -> Result<SyncResultDto, String> {
|
||||||
|
let sync_mode = match mode.as_str() {
|
||||||
|
"push" => SyncMode::Push,
|
||||||
|
"pull" => SyncMode::Pull,
|
||||||
|
_ => SyncMode::Full,
|
||||||
|
};
|
||||||
|
let result = sync::sync_workspace(
|
||||||
|
&PathBuf::from(&workspace_path),
|
||||||
|
&webdav_url,
|
||||||
|
&username,
|
||||||
|
&password,
|
||||||
|
sync_mode,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut s = STATE.lock().unwrap();
|
||||||
|
if let Some(ws) = s.config.workspaces.get_mut(&workspace_name) {
|
||||||
|
ws.last_sync = Some(chrono::Utc::now());
|
||||||
|
}
|
||||||
|
let config_path = AppConfig::get_config_path();
|
||||||
|
s.config.save_to_file(&config_path).map_err(|e| e.to_string())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(SyncResultDto {
|
||||||
|
uploaded: result.uploaded,
|
||||||
|
downloaded: result.downloaded,
|
||||||
|
deleted_local: result.deleted_local,
|
||||||
|
deleted_remote: result.deleted_remote,
|
||||||
|
conflicts: result.conflicts,
|
||||||
|
errors: result.errors,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// ── File watcher ───────────────────────────────────────────────────
|
// ── File watcher ───────────────────────────────────────────────────
|
||||||
|
|
||||||
static WATCHER: Mutex<Option<notify_debouncer_mini::Debouncer<notify::RecommendedWatcher>>> =
|
static WATCHER: Mutex<Option<notify_debouncer_mini::Debouncer<notify::RecommendedWatcher>>> =
|
||||||
|
|
|
||||||
|
|
@ -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.11.1";
|
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_VERSION: &str = "2.11.1";
|
||||||
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = -75020133;
|
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = -1094746925;
|
||||||
|
|
||||||
// Section: executor
|
// Section: executor
|
||||||
|
|
||||||
|
|
@ -411,6 +411,39 @@ fn wire__crate__api__list_tasks_impl(
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
fn wire__crate__api__load_credentials_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: "load_credentials",
|
||||||
|
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_domain = <String>::sse_decode(&mut deserializer);
|
||||||
|
deserializer.end();
|
||||||
|
move |context| {
|
||||||
|
transform_result_sse::<_, String>((move || {
|
||||||
|
let output_ok = crate::api::load_credentials(api_domain)?;
|
||||||
|
Ok(output_ok)
|
||||||
|
})())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
fn wire__crate__api__move_task_impl(
|
fn wire__crate__api__move_task_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,
|
||||||
|
|
@ -617,6 +650,166 @@ fn wire__crate__api__set_group_by_due_date_impl(
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
fn wire__crate__api__set_webdav_config_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: "set_webdav_config",
|
||||||
|
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_workspace_name = <String>::sse_decode(&mut deserializer);
|
||||||
|
let api_webdav_url = <String>::sse_decode(&mut deserializer);
|
||||||
|
deserializer.end();
|
||||||
|
move |context| {
|
||||||
|
transform_result_sse::<_, String>((move || {
|
||||||
|
let output_ok =
|
||||||
|
crate::api::set_webdav_config(api_workspace_name, api_webdav_url)?;
|
||||||
|
Ok(output_ok)
|
||||||
|
})())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
fn wire__crate__api__store_credentials_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: "store_credentials",
|
||||||
|
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_domain = <String>::sse_decode(&mut deserializer);
|
||||||
|
let api_username = <String>::sse_decode(&mut deserializer);
|
||||||
|
let api_password = <String>::sse_decode(&mut deserializer);
|
||||||
|
deserializer.end();
|
||||||
|
move |context| {
|
||||||
|
transform_result_sse::<_, String>((move || {
|
||||||
|
let output_ok =
|
||||||
|
crate::api::store_credentials(api_domain, api_username, api_password)?;
|
||||||
|
Ok(output_ok)
|
||||||
|
})())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
fn wire__crate__api__sync_workspace_cmd_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_async::<flutter_rust_bridge::for_generated::SseCodec, _, _, _>(
|
||||||
|
flutter_rust_bridge::for_generated::TaskInfo {
|
||||||
|
debug_name: "sync_workspace_cmd",
|
||||||
|
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_workspace_name = <String>::sse_decode(&mut deserializer);
|
||||||
|
let api_workspace_path = <String>::sse_decode(&mut deserializer);
|
||||||
|
let api_webdav_url = <String>::sse_decode(&mut deserializer);
|
||||||
|
let api_username = <String>::sse_decode(&mut deserializer);
|
||||||
|
let api_password = <String>::sse_decode(&mut deserializer);
|
||||||
|
let api_mode = <String>::sse_decode(&mut deserializer);
|
||||||
|
deserializer.end();
|
||||||
|
move |context| async move {
|
||||||
|
transform_result_sse::<_, String>(
|
||||||
|
(move || async move {
|
||||||
|
let output_ok = crate::api::sync_workspace_cmd(
|
||||||
|
api_workspace_name,
|
||||||
|
api_workspace_path,
|
||||||
|
api_webdav_url,
|
||||||
|
api_username,
|
||||||
|
api_password,
|
||||||
|
api_mode,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
Ok(output_ok)
|
||||||
|
})()
|
||||||
|
.await,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
fn wire__crate__api__test_webdav_connection_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_async::<flutter_rust_bridge::for_generated::SseCodec, _, _, _>(
|
||||||
|
flutter_rust_bridge::for_generated::TaskInfo {
|
||||||
|
debug_name: "test_webdav_connection",
|
||||||
|
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_url = <String>::sse_decode(&mut deserializer);
|
||||||
|
let api_username = <String>::sse_decode(&mut deserializer);
|
||||||
|
let api_password = <String>::sse_decode(&mut deserializer);
|
||||||
|
deserializer.end();
|
||||||
|
move |context| async move {
|
||||||
|
transform_result_sse::<_, String>(
|
||||||
|
(move || async move {
|
||||||
|
let output_ok =
|
||||||
|
crate::api::test_webdav_connection(api_url, api_username, api_password)
|
||||||
|
.await?;
|
||||||
|
Ok(output_ok)
|
||||||
|
})()
|
||||||
|
.await,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
fn wire__crate__api__toggle_task_impl(
|
fn wire__crate__api__toggle_task_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,
|
||||||
|
|
@ -770,6 +963,18 @@ impl SseDecode for bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl SseDecode for Vec<String> {
|
||||||
|
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||||
|
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
|
||||||
|
let mut len_ = <i32>::sse_decode(deserializer);
|
||||||
|
let mut ans_ = vec![];
|
||||||
|
for idx_ in 0..len_ {
|
||||||
|
ans_.push(<String>::sse_decode(deserializer));
|
||||||
|
}
|
||||||
|
return ans_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl SseDecode for Vec<u8> {
|
impl SseDecode for Vec<u8> {
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||||
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
|
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
|
||||||
|
|
@ -829,6 +1034,26 @@ impl SseDecode for Option<String> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl SseDecode for crate::api::SyncResultDto {
|
||||||
|
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||||
|
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
|
||||||
|
let mut var_uploaded = <u32>::sse_decode(deserializer);
|
||||||
|
let mut var_downloaded = <u32>::sse_decode(deserializer);
|
||||||
|
let mut var_deletedLocal = <u32>::sse_decode(deserializer);
|
||||||
|
let mut var_deletedRemote = <u32>::sse_decode(deserializer);
|
||||||
|
let mut var_conflicts = <u32>::sse_decode(deserializer);
|
||||||
|
let mut var_errors = <Vec<String>>::sse_decode(deserializer);
|
||||||
|
return crate::api::SyncResultDto {
|
||||||
|
uploaded: var_uploaded,
|
||||||
|
downloaded: var_downloaded,
|
||||||
|
deleted_local: var_deletedLocal,
|
||||||
|
deleted_remote: var_deletedRemote,
|
||||||
|
conflicts: var_conflicts,
|
||||||
|
errors: var_errors,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl SseDecode for crate::api::TaskDto {
|
impl SseDecode for crate::api::TaskDto {
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||||
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
|
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
|
||||||
|
|
@ -837,6 +1062,7 @@ impl SseDecode for crate::api::TaskDto {
|
||||||
let mut var_description = <String>::sse_decode(deserializer);
|
let mut var_description = <String>::sse_decode(deserializer);
|
||||||
let mut var_status = <String>::sse_decode(deserializer);
|
let mut var_status = <String>::sse_decode(deserializer);
|
||||||
let mut var_dueDate = <Option<String>>::sse_decode(deserializer);
|
let mut var_dueDate = <Option<String>>::sse_decode(deserializer);
|
||||||
|
let mut var_hasTime = <bool>::sse_decode(deserializer);
|
||||||
let mut var_createdAt = <String>::sse_decode(deserializer);
|
let mut var_createdAt = <String>::sse_decode(deserializer);
|
||||||
let mut var_updatedAt = <String>::sse_decode(deserializer);
|
let mut var_updatedAt = <String>::sse_decode(deserializer);
|
||||||
let mut var_parentId = <Option<String>>::sse_decode(deserializer);
|
let mut var_parentId = <Option<String>>::sse_decode(deserializer);
|
||||||
|
|
@ -846,6 +1072,7 @@ impl SseDecode for crate::api::TaskDto {
|
||||||
description: var_description,
|
description: var_description,
|
||||||
status: var_status,
|
status: var_status,
|
||||||
due_date: var_dueDate,
|
due_date: var_dueDate,
|
||||||
|
has_time: var_hasTime,
|
||||||
created_at: var_createdAt,
|
created_at: var_createdAt,
|
||||||
updated_at: var_updatedAt,
|
updated_at: var_updatedAt,
|
||||||
parent_id: var_parentId,
|
parent_id: var_parentId,
|
||||||
|
|
@ -933,15 +1160,20 @@ fn pde_ffi_dispatcher_primary_impl(
|
||||||
9 => wire__crate__api__greet_impl(port, ptr, rust_vec_len, data_len),
|
9 => wire__crate__api__greet_impl(port, ptr, rust_vec_len, data_len),
|
||||||
10 => wire__crate__api__init_workspace_impl(port, ptr, rust_vec_len, data_len),
|
10 => wire__crate__api__init_workspace_impl(port, ptr, rust_vec_len, data_len),
|
||||||
11 => wire__crate__api__list_tasks_impl(port, ptr, rust_vec_len, data_len),
|
11 => wire__crate__api__list_tasks_impl(port, ptr, rust_vec_len, data_len),
|
||||||
12 => wire__crate__api__move_task_impl(port, ptr, rust_vec_len, data_len),
|
12 => wire__crate__api__load_credentials_impl(port, ptr, rust_vec_len, data_len),
|
||||||
13 => wire__crate__api__remove_workspace_impl(port, ptr, rust_vec_len, data_len),
|
13 => wire__crate__api__move_task_impl(port, ptr, rust_vec_len, data_len),
|
||||||
14 => wire__crate__api__rename_list_impl(port, ptr, rust_vec_len, data_len),
|
14 => wire__crate__api__remove_workspace_impl(port, ptr, rust_vec_len, data_len),
|
||||||
15 => wire__crate__api__reorder_task_impl(port, ptr, rust_vec_len, data_len),
|
15 => wire__crate__api__rename_list_impl(port, ptr, rust_vec_len, data_len),
|
||||||
16 => wire__crate__api__set_current_workspace_impl(port, ptr, rust_vec_len, data_len),
|
16 => wire__crate__api__reorder_task_impl(port, ptr, rust_vec_len, data_len),
|
||||||
17 => wire__crate__api__set_group_by_due_date_impl(port, ptr, rust_vec_len, data_len),
|
17 => wire__crate__api__set_current_workspace_impl(port, ptr, rust_vec_len, data_len),
|
||||||
18 => wire__crate__api__toggle_task_impl(port, ptr, rust_vec_len, data_len),
|
18 => wire__crate__api__set_group_by_due_date_impl(port, ptr, rust_vec_len, data_len),
|
||||||
19 => wire__crate__api__update_task_impl(port, ptr, rust_vec_len, data_len),
|
19 => wire__crate__api__set_webdav_config_impl(port, ptr, rust_vec_len, data_len),
|
||||||
20 => wire__crate__api__watch_workspace_changes_impl(port, ptr, rust_vec_len, data_len),
|
20 => wire__crate__api__store_credentials_impl(port, ptr, rust_vec_len, data_len),
|
||||||
|
21 => wire__crate__api__sync_workspace_cmd_impl(port, ptr, rust_vec_len, data_len),
|
||||||
|
22 => wire__crate__api__test_webdav_connection_impl(port, ptr, rust_vec_len, data_len),
|
||||||
|
23 => wire__crate__api__toggle_task_impl(port, ptr, rust_vec_len, data_len),
|
||||||
|
24 => wire__crate__api__update_task_impl(port, ptr, rust_vec_len, data_len),
|
||||||
|
25 => wire__crate__api__watch_workspace_changes_impl(port, ptr, rust_vec_len, data_len),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -977,6 +1209,26 @@ impl flutter_rust_bridge::IntoIntoDart<crate::api::AppConfigDto> for crate::api:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||||
|
impl flutter_rust_bridge::IntoDart for crate::api::SyncResultDto {
|
||||||
|
fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi {
|
||||||
|
[
|
||||||
|
self.uploaded.into_into_dart().into_dart(),
|
||||||
|
self.downloaded.into_into_dart().into_dart(),
|
||||||
|
self.deleted_local.into_into_dart().into_dart(),
|
||||||
|
self.deleted_remote.into_into_dart().into_dart(),
|
||||||
|
self.conflicts.into_into_dart().into_dart(),
|
||||||
|
self.errors.into_into_dart().into_dart(),
|
||||||
|
]
|
||||||
|
.into_dart()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive for crate::api::SyncResultDto {}
|
||||||
|
impl flutter_rust_bridge::IntoIntoDart<crate::api::SyncResultDto> for crate::api::SyncResultDto {
|
||||||
|
fn into_into_dart(self) -> crate::api::SyncResultDto {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||||
impl flutter_rust_bridge::IntoDart for crate::api::TaskDto {
|
impl flutter_rust_bridge::IntoDart for crate::api::TaskDto {
|
||||||
fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi {
|
fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi {
|
||||||
[
|
[
|
||||||
|
|
@ -985,6 +1237,7 @@ impl flutter_rust_bridge::IntoDart for crate::api::TaskDto {
|
||||||
self.description.into_into_dart().into_dart(),
|
self.description.into_into_dart().into_dart(),
|
||||||
self.status.into_into_dart().into_dart(),
|
self.status.into_into_dart().into_dart(),
|
||||||
self.due_date.into_into_dart().into_dart(),
|
self.due_date.into_into_dart().into_dart(),
|
||||||
|
self.has_time.into_into_dart().into_dart(),
|
||||||
self.created_at.into_into_dart().into_dart(),
|
self.created_at.into_into_dart().into_dart(),
|
||||||
self.updated_at.into_into_dart().into_dart(),
|
self.updated_at.into_into_dart().into_dart(),
|
||||||
self.parent_id.into_into_dart().into_dart(),
|
self.parent_id.into_into_dart().into_dart(),
|
||||||
|
|
@ -1072,6 +1325,16 @@ impl SseEncode for bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl SseEncode for Vec<String> {
|
||||||
|
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||||
|
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
||||||
|
<i32>::sse_encode(self.len() as _, serializer);
|
||||||
|
for item in self {
|
||||||
|
<String>::sse_encode(item, serializer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl SseEncode for Vec<u8> {
|
impl SseEncode for Vec<u8> {
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||||
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
||||||
|
|
@ -1122,6 +1385,18 @@ impl SseEncode for Option<String> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl SseEncode for crate::api::SyncResultDto {
|
||||||
|
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||||
|
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
||||||
|
<u32>::sse_encode(self.uploaded, serializer);
|
||||||
|
<u32>::sse_encode(self.downloaded, serializer);
|
||||||
|
<u32>::sse_encode(self.deleted_local, serializer);
|
||||||
|
<u32>::sse_encode(self.deleted_remote, serializer);
|
||||||
|
<u32>::sse_encode(self.conflicts, serializer);
|
||||||
|
<Vec<String>>::sse_encode(self.errors, serializer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl SseEncode for crate::api::TaskDto {
|
impl SseEncode for crate::api::TaskDto {
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||||
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
||||||
|
|
@ -1130,6 +1405,7 @@ impl SseEncode for crate::api::TaskDto {
|
||||||
<String>::sse_encode(self.description, serializer);
|
<String>::sse_encode(self.description, serializer);
|
||||||
<String>::sse_encode(self.status, serializer);
|
<String>::sse_encode(self.status, serializer);
|
||||||
<Option<String>>::sse_encode(self.due_date, serializer);
|
<Option<String>>::sse_encode(self.due_date, serializer);
|
||||||
|
<bool>::sse_encode(self.has_time, serializer);
|
||||||
<String>::sse_encode(self.created_at, serializer);
|
<String>::sse_encode(self.created_at, serializer);
|
||||||
<String>::sse_encode(self.updated_at, serializer);
|
<String>::sse_encode(self.updated_at, serializer);
|
||||||
<Option<String>>::sse_encode(self.parent_id, serializer);
|
<Option<String>>::sse_encode(self.parent_id, serializer);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
//
|
||||||
|
// Generated file. Do not edit.
|
||||||
|
//
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
|
||||||
|
#include "generated_plugin_registrant.h"
|
||||||
|
|
||||||
|
#include <screen_retriever_windows/screen_retriever_windows_plugin_c_api.h>
|
||||||
|
#include <window_manager/window_manager_plugin.h>
|
||||||
|
|
||||||
|
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||||
|
ScreenRetrieverWindowsPluginCApiRegisterWithRegistrar(
|
||||||
|
registry->GetRegistrarForPlugin("ScreenRetrieverWindowsPluginCApi"));
|
||||||
|
WindowManagerPluginRegisterWithRegistrar(
|
||||||
|
registry->GetRegistrarForPlugin("WindowManagerPlugin"));
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
//
|
||||||
|
// Generated file. Do not edit.
|
||||||
|
//
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
|
||||||
|
#ifndef GENERATED_PLUGIN_REGISTRANT_
|
||||||
|
#define GENERATED_PLUGIN_REGISTRANT_
|
||||||
|
|
||||||
|
#include <flutter/plugin_registry.h>
|
||||||
|
|
||||||
|
// Registers Flutter plugins.
|
||||||
|
void RegisterPlugins(flutter::PluginRegistry* registry);
|
||||||
|
|
||||||
|
#endif // GENERATED_PLUGIN_REGISTRANT_
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
#
|
||||||
|
# Generated file, do not edit.
|
||||||
|
#
|
||||||
|
|
||||||
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
|
screen_retriever_windows
|
||||||
|
window_manager
|
||||||
|
)
|
||||||
|
|
||||||
|
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||||
|
)
|
||||||
|
|
||||||
|
set(PLUGIN_BUNDLED_LIBRARIES)
|
||||||
|
|
||||||
|
foreach(plugin ${FLUTTER_PLUGIN_LIST})
|
||||||
|
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin})
|
||||||
|
target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
|
||||||
|
list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
|
||||||
|
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
|
||||||
|
endforeach(plugin)
|
||||||
|
|
||||||
|
foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
|
||||||
|
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin})
|
||||||
|
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
|
||||||
|
endforeach(ffi_plugin)
|
||||||
Loading…
Reference in a new issue