diff --git a/assets/i18n/strings.i18n.json b/assets/i18n/strings.i18n.json index 7a79ab8..6e5f703 100644 --- a/assets/i18n/strings.i18n.json +++ b/assets/i18n/strings.i18n.json @@ -1,8 +1,26 @@ { - "appTitle": "RustSend", + "appTitle": { + "parta": "LocalSend", + "partb": "_RS" + }, "home": { "title": "Home Page" }, + "mission": { + "accept": "Accept", + "cancel": "Cancel", + "complete": "Complete", + "finished": "Finished", + "tranfer": "Transfering", + "pending": "Pending", + "failed": "Failed", + "skip": "Skip", + "advance": "Advance" + }, + "common": { + "file": "File", + "size": "Size" + }, "setting": { "title": "Settings", "common": "Common", @@ -19,6 +37,13 @@ "title": "Language", "subTitle": "Current language: $language" }, + "receive": { + "title": "Receive", + "quickSave": "Quick Save", + "quickSaveHint": "Start tranfer without accept", + "saveFolder": "Save Folder", + "selectSaveFolder": "Select" + }, "core": { "title": "core setting", "server": { diff --git a/assets/i18n/strings_zh.i18n.json b/assets/i18n/strings_zh.i18n.json index 30290df..500128d 100644 --- a/assets/i18n/strings_zh.i18n.json +++ b/assets/i18n/strings_zh.i18n.json @@ -1,8 +1,26 @@ { - "appTitle": "锈船", + "appTitle": { + "parta": "快传", + "partb": "锈" + }, "home": { "title": "主页" }, + "mission": { + "accept": "接收", + "cancel": "取消", + "complete": "完成", + "finished": "已完成", + "tranfer": "传输中", + "pending": "等待中", + "failed": "失败", + "skip": "跳过", + "advance": "高级" + }, + "common": { + "file": "文件", + "size": "大小" + }, "setting": { "title": "设置", "common": "通用", @@ -19,6 +37,13 @@ "title": "语言", "subTitle": "当前语言: $language" }, + "receive": { + "title": "接收设置", + "quickSave": "快速保存", + "quickSaveHint": "不需要等待确认直接接受", + "saveFolder": "保存目录", + "selectSaveFolder": "选择" + }, "core": { "title": "核心设置", "server": { diff --git a/lib/common/device_info_utils.dart b/lib/common/device_info_utils.dart index 85be3c0..afd4204 100644 --- a/lib/common/device_info_utils.dart +++ b/lib/common/device_info_utils.dart @@ -48,7 +48,7 @@ int randomPort() { return 10000 + Random().nextInt(65535 - 10000); } -Future newDevice() async { +Future getDevice() async { final deviceInfo = await getDeviceInfo(); final addressList = await getInterface(); final alias = diff --git a/lib/common/utils.dart b/lib/common/utils.dart index c36ce65..de8ad37 100644 --- a/lib/common/utils.dart +++ b/lib/common/utils.dart @@ -1,7 +1,19 @@ import 'dart:io'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:localsend_rs/core/rust/actor/model.dart'; +import 'package:localsend_rs/core/store/config_store.dart'; +import 'package:logger/logger.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:window_manager/window_manager.dart'; + +import '../core/rust/actor/core.dart'; +import '../core/rust/actor/mission.dart'; +import '../core/rust/bridge.dart'; +import '../i18n/strings.g.dart'; +import 'constants.dart'; Future sleepAsync(int millis) { return Future.delayed(Duration(milliseconds: millis), () {}); @@ -37,7 +49,7 @@ Future updateSystemOverlayStyleWithBrightness( } } -Locale stringToLocale(String value){ +Locale stringToLocale(String value) { if (value.isEmpty) { value = Platform.localeName; } @@ -60,3 +72,114 @@ Locale stringToLocale(String value){ } return const Locale("en"); } + +Future getConfig(int port) async { + if (!ConfigStore().storePathSet()) { + final path = await getDownloadPath(); + ConfigStore().setStorePath(path); + } + final storePath = ConfigStore().storePath(); + + return CoreConfig( + port: port, + interfaceAddr: "0.0.0.0", + multicastAddr: "224.0.0.167", + multicastPort: 53317, + storePath: storePath, + ); +} + +Future getDownloadPath() async { + String storePath; + if (Platform.isAndroid) { + storePath = "/storage/emulated/0/Download"; + if (kDebugMode) { + createLogStream().listen((event) { + debugPrint( + '${event.level} ${event.tag} ${event.msg} ${event.timeMillis}'); + }); + } + } else { + storePath = (await getDownloadsDirectory())!.absolute.path; + } + return storePath; +} + +const levelList = [ + Level.off, + Level.error, + Level.warning, + Level.info, + Level.debug, + Level.trace +]; + +void initLogger() { + var logger = Logger(); + + if (Platform.isAndroid) { + if (kDebugMode) { + createLogStream().listen((event) { + logger.log( + levelList[event.level], + event.msg, + time: DateTime.fromMillisecondsSinceEpoch(event.timeMillis), + ); + }); + } + } +} + +void initLocale() { + final localeMode = ConfigStore().localeMode(); + + if (localeMode == LocaleMode.system) { + LocaleSettings.useDeviceLocale(); + } else { + final locale = ConfigStore().locale(); + LocaleSettings.setLocaleRaw(stringToLocale(locale).languageCode); + } + + if (Platform.isWindows) { + windowManager.setTitle(t.appTitle.parta + t.appTitle.partb); + } +} + +extension MissionStateName on MissionState { + String getName() { + switch (this) { + case MissionState.idle: + // TODO: Handle this case. + case MissionState.pending: + return t.mission.pending; + case MissionState.transfering: + return t.mission.tranfer; + case MissionState.finished: + return t.mission.finished; + case MissionState.failed: + return t.mission.failed; + case MissionState.canceled: + return t.mission.cancel; + case MissionState.busy: + // TODO: Handle this case. + } + return "unknown"; + } +} + +extension FileStateName on FileState { + String getName() { + switch (this) { + case FileState_Pending(): + return t.mission.pending; + case FileState_Transfer(): + return t.mission.tranfer; + case FileState_Finish(): + return t.mission.complete; + case FileState_Skip(): + return t.mission.skip; + default: + return "unknown"; + } + } +} diff --git a/lib/common/widgets.dart b/lib/common/widgets.dart deleted file mode 100644 index 798cab2..0000000 --- a/lib/common/widgets.dart +++ /dev/null @@ -1,24 +0,0 @@ -import 'package:flutter/material.dart'; - -class AppTitle extends StatelessWidget { - const AppTitle({super.key}); - - @override - Widget build(BuildContext context) { - return Text.rich( - TextSpan( - text: 'LocalSend', - children: [ - TextSpan( - text: '_RS', - style: Theme.of(context).textTheme.displaySmall!.copyWith( - color: const Color(0xfff74c00), - fontWeight: FontWeight.bold, - ), - ), - ], - ), - style: Theme.of(context).textTheme.displaySmall, - ); - } -} diff --git a/lib/core/providers/locale_provider.dart b/lib/core/providers/locale_provider.dart index 60eafb3..9709419 100644 --- a/lib/core/providers/locale_provider.dart +++ b/lib/core/providers/locale_provider.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:window_manager/window_manager.dart'; import '../../common/constants.dart'; import '../../common/utils.dart'; @@ -46,6 +47,9 @@ class LocaleState extends _$LocaleState { } else { LocaleSettings.setLocaleRaw(state.customLocale.languageCode); } + if (Platform.isWindows) { + windowManager.setTitle(t.appTitle.parta + t.appTitle.partb); + } } void setLocale(Locale locale) { diff --git a/lib/core/providers/locale_provider.g.dart b/lib/core/providers/locale_provider.g.dart index 4fcfd9c..100b0ef 100644 --- a/lib/core/providers/locale_provider.g.dart +++ b/lib/core/providers/locale_provider.g.dart @@ -6,7 +6,7 @@ part of 'locale_provider.dart'; // RiverpodGenerator // ************************************************************************** -String _$localeStateHash() => r'66232fdd0418e099b18338bffc760af0e9652a9d'; +String _$localeStateHash() => r'e639ad5930410f9b4d38fd1d65777d2836014665'; /// See also [LocaleState]. @ProviderFor(LocaleState) diff --git a/lib/core/rust/actor/core.dart b/lib/core/rust/actor/core.dart index e905600..ff7c232 100644 --- a/lib/core/rust/actor/core.dart +++ b/lib/core/rust/actor/core.dart @@ -6,5 +6,37 @@ import '../frb_generated.dart'; import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; -// Rust type: RustOpaqueMoi> -abstract class CoreConfig implements RustOpaqueInterface {} +class CoreConfig { + final int port; + final String interfaceAddr; + final String multicastAddr; + final int multicastPort; + final String storePath; + + const CoreConfig({ + required this.port, + required this.interfaceAddr, + required this.multicastAddr, + required this.multicastPort, + required this.storePath, + }); + + @override + int get hashCode => + port.hashCode ^ + interfaceAddr.hashCode ^ + multicastAddr.hashCode ^ + multicastPort.hashCode ^ + storePath.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is CoreConfig && + runtimeType == other.runtimeType && + port == other.port && + interfaceAddr == other.interfaceAddr && + multicastAddr == other.multicastAddr && + multicastPort == other.multicastPort && + storePath == other.storePath; +} diff --git a/lib/core/rust/bridge.dart b/lib/core/rust/bridge.dart index 86498c6..d437f15 100644 --- a/lib/core/rust/bridge.dart +++ b/lib/core/rust/bridge.dart @@ -15,8 +15,8 @@ import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; // These types are ignored because they are not used by any `pub` functions: `CORE` // These function are ignored because they are on traits that is not defined in current crate (put an empty `#[frb]` on it to unignore): `deref`, `initialize` -Future setup({required NodeDevice device}) => - RustLib.instance.api.crateBridgeSetup(device: device); +Future setup({required NodeDevice device, required CoreConfig config}) => + RustLib.instance.api.crateBridgeSetup(device: device, config: config); Stream listenServerState() => RustLib.instance.api.crateBridgeListenServerState(); @@ -26,8 +26,10 @@ Future startServer() => RustLib.instance.api.crateBridgeStartServer(); Future shutdownServer() => RustLib.instance.api.crateBridgeShutdownServer(); -Future changeAddress({required String addr}) => - RustLib.instance.api.crateBridgeChangeAddress(addr: addr); +Future restartServer() => RustLib.instance.api.crateBridgeRestartServer(); + +Future changePath({required String path}) => + RustLib.instance.api.crateBridgeChangePath(path: path); Future changeConfig({required CoreConfig config}) => RustLib.instance.api.crateBridgeChangeConfig(config: config); diff --git a/lib/core/rust/frb_generated.dart b/lib/core/rust/frb_generated.dart index f1c743c..eed64a9 100644 --- a/lib/core/rust/frb_generated.dart +++ b/lib/core/rust/frb_generated.dart @@ -61,7 +61,7 @@ class RustLib extends BaseEntrypoint { String get codegenVersion => '2.1.0'; @override - int get rustContentHash => 2101766676; + int get rustContentHash => -1293166539; static const kDefaultExternalLibraryLoaderConfig = ExternalLibraryLoaderConfig( @@ -78,10 +78,10 @@ abstract class RustLibApi extends BaseApi { Future crateBridgeCancelPending({required String id}); - Future crateBridgeChangeAddress({required String addr}); - Future crateBridgeChangeConfig({required CoreConfig config}); + Future crateBridgeChangePath({required String path}); + Future crateBridgeClearMission(); Stream crateBridgeCreateLogStream(); @@ -92,19 +92,14 @@ abstract class RustLibApi extends BaseApi { Stream crateBridgeListenServerState(); - Future crateBridgeSetup({required NodeDevice device}); + Future crateBridgeRestartServer(); + + Future crateBridgeSetup( + {required NodeDevice device, required CoreConfig config}); Future crateBridgeShutdownServer(); Future crateBridgeStartServer(); - - RustArcIncrementStrongCountFnType - get rust_arc_increment_strong_count_CoreConfig; - - RustArcDecrementStrongCountFnType - get rust_arc_decrement_strong_count_CoreConfig; - - CrossPlatformFinalizerArg get rust_arc_decrement_strong_count_CoreConfigPtr; } class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { @@ -187,11 +182,11 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { ); @override - Future crateBridgeChangeAddress({required String addr}) { + Future crateBridgeChangeConfig({required CoreConfig config}) { return handler.executeNormal(NormalTask( callFfi: (port_) { final serializer = SseSerializer(generalizedFrbRustBinding); - sse_encode_String(addr, serializer); + sse_encode_box_autoadd_core_config(config, serializer); pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 4, port: port_); }, @@ -199,24 +194,23 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { decodeSuccessData: sse_decode_unit, decodeErrorData: null, ), - constMeta: kCrateBridgeChangeAddressConstMeta, - argValues: [addr], + constMeta: kCrateBridgeChangeConfigConstMeta, + argValues: [config], apiImpl: this, )); } - TaskConstMeta get kCrateBridgeChangeAddressConstMeta => const TaskConstMeta( - debugName: "change_address", - argNames: ["addr"], + TaskConstMeta get kCrateBridgeChangeConfigConstMeta => const TaskConstMeta( + debugName: "change_config", + argNames: ["config"], ); @override - Future crateBridgeChangeConfig({required CoreConfig config}) { + Future crateBridgeChangePath({required String path}) { return handler.executeNormal(NormalTask( callFfi: (port_) { final serializer = SseSerializer(generalizedFrbRustBinding); - sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCoreConfig( - config, serializer); + sse_encode_String(path, serializer); pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 5, port: port_); }, @@ -224,15 +218,15 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { decodeSuccessData: sse_decode_unit, decodeErrorData: null, ), - constMeta: kCrateBridgeChangeConfigConstMeta, - argValues: [config], + constMeta: kCrateBridgeChangePathConstMeta, + argValues: [path], apiImpl: this, )); } - TaskConstMeta get kCrateBridgeChangeConfigConstMeta => const TaskConstMeta( - debugName: "change_config", - argNames: ["config"], + TaskConstMeta get kCrateBridgeChangePathConstMeta => const TaskConstMeta( + debugName: "change_path", + argNames: ["path"], ); @override @@ -364,11 +358,10 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { ); @override - Future crateBridgeSetup({required NodeDevice device}) { + Future crateBridgeRestartServer() { return handler.executeNormal(NormalTask( callFfi: (port_) { final serializer = SseSerializer(generalizedFrbRustBinding); - sse_encode_box_autoadd_node_device(device, serializer); pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 11, port: port_); }, @@ -376,15 +369,41 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { decodeSuccessData: sse_decode_unit, decodeErrorData: null, ), + constMeta: kCrateBridgeRestartServerConstMeta, + argValues: [], + apiImpl: this, + )); + } + + TaskConstMeta get kCrateBridgeRestartServerConstMeta => const TaskConstMeta( + debugName: "restart_server", + argNames: [], + ); + + @override + Future crateBridgeSetup( + {required NodeDevice device, required CoreConfig config}) { + return handler.executeNormal(NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_box_autoadd_node_device(device, serializer); + sse_encode_box_autoadd_core_config(config, serializer); + pdeCallFfi(generalizedFrbRustBinding, serializer, + funcId: 12, port: port_); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_unit, + decodeErrorData: null, + ), constMeta: kCrateBridgeSetupConstMeta, - argValues: [device], + argValues: [device, config], apiImpl: this, )); } TaskConstMeta get kCrateBridgeSetupConstMeta => const TaskConstMeta( debugName: "setup", - argNames: ["device"], + argNames: ["device", "config"], ); @override @@ -393,7 +412,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { callFfi: (port_) { final serializer = SseSerializer(generalizedFrbRustBinding); pdeCallFfi(generalizedFrbRustBinding, serializer, - funcId: 12, port: port_); + funcId: 13, port: port_); }, codec: SseCodec( decodeSuccessData: sse_decode_unit, @@ -416,7 +435,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { callFfi: (port_) { final serializer = SseSerializer(generalizedFrbRustBinding); pdeCallFfi(generalizedFrbRustBinding, serializer, - funcId: 13, port: port_); + funcId: 14, port: port_); }, codec: SseCodec( decodeSuccessData: sse_decode_unit, @@ -433,36 +452,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { argNames: [], ); - RustArcIncrementStrongCountFnType - get rust_arc_increment_strong_count_CoreConfig => wire - .rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCoreConfig; - - RustArcDecrementStrongCountFnType - get rust_arc_decrement_strong_count_CoreConfig => wire - .rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCoreConfig; - @protected AnyhowException dco_decode_AnyhowException(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs return AnyhowException(raw as String); } - @protected - CoreConfig - dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCoreConfig( - dynamic raw) { - // Codec=Dco (DartCObject based), see doc to use other codecs - return CoreConfigImpl.frbInternalDcoDecode(raw as List); - } - - @protected - CoreConfig - dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCoreConfig( - dynamic raw) { - // Codec=Dco (DartCObject based), see doc to use other codecs - return CoreConfigImpl.frbInternalDcoDecode(raw as List); - } - @protected RustStreamSink dco_decode_StreamSink_bool_Sse(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs @@ -501,6 +496,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return raw as bool; } + @protected + CoreConfig dco_decode_box_autoadd_core_config(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return dco_decode_core_config(raw); + } + @protected MissionInfo dco_decode_box_autoadd_mission_info(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs @@ -513,6 +514,21 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return dco_decode_node_device(raw); } + @protected + CoreConfig dco_decode_core_config(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + final arr = raw as List; + if (arr.length != 5) + throw Exception('unexpected arr length: expect 5 but see ${arr.length}'); + return CoreConfig( + port: dco_decode_u_16(arr[0]), + interfaceAddr: dco_decode_String(arr[1]), + multicastAddr: dco_decode_String(arr[2]), + multicastPort: dco_decode_u_16(arr[3]), + storePath: dco_decode_String(arr[4]), + ); + } + @protected FileInfo dco_decode_file_info(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs @@ -683,12 +699,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return; } - @protected - BigInt dco_decode_usize(dynamic raw) { - // Codec=Dco (DartCObject based), see doc to use other codecs - return dcoDecodeU64(raw); - } - @protected AnyhowException sse_decode_AnyhowException(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -696,24 +706,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return AnyhowException(inner); } - @protected - CoreConfig - sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCoreConfig( - SseDeserializer deserializer) { - // Codec=Sse (Serialization based), see doc to use other codecs - return CoreConfigImpl.frbInternalSseDecode( - sse_decode_usize(deserializer), sse_decode_i_32(deserializer)); - } - - @protected - CoreConfig - sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCoreConfig( - SseDeserializer deserializer) { - // Codec=Sse (Serialization based), see doc to use other codecs - return CoreConfigImpl.frbInternalSseDecode( - sse_decode_usize(deserializer), sse_decode_i_32(deserializer)); - } - @protected RustStreamSink sse_decode_StreamSink_bool_Sse( SseDeserializer deserializer) { @@ -756,6 +748,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return deserializer.buffer.getUint8() != 0; } + @protected + CoreConfig sse_decode_box_autoadd_core_config(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return (sse_decode_core_config(deserializer)); + } + @protected MissionInfo sse_decode_box_autoadd_mission_info( SseDeserializer deserializer) { @@ -769,6 +767,22 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return (sse_decode_node_device(deserializer)); } + @protected + CoreConfig sse_decode_core_config(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var var_port = sse_decode_u_16(deserializer); + var var_interfaceAddr = sse_decode_String(deserializer); + var var_multicastAddr = sse_decode_String(deserializer); + var var_multicastPort = sse_decode_u_16(deserializer); + var var_storePath = sse_decode_String(deserializer); + return CoreConfig( + port: var_port, + interfaceAddr: var_interfaceAddr, + multicastAddr: var_multicastAddr, + multicastPort: var_multicastPort, + storePath: var_storePath); + } + @protected FileInfo sse_decode_file_info(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -972,12 +986,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { // Codec=Sse (Serialization based), see doc to use other codecs } - @protected - BigInt sse_decode_usize(SseDeserializer deserializer) { - // Codec=Sse (Serialization based), see doc to use other codecs - return deserializer.buffer.getBigUint64(); - } - @protected void sse_encode_AnyhowException( AnyhowException self, SseSerializer serializer) { @@ -985,24 +993,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { sse_encode_String(self.message, serializer); } - @protected - void - sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCoreConfig( - CoreConfig self, SseSerializer serializer) { - // Codec=Sse (Serialization based), see doc to use other codecs - sse_encode_usize( - (self as CoreConfigImpl).frbInternalSseEncode(move: true), serializer); - } - - @protected - void - sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCoreConfig( - CoreConfig self, SseSerializer serializer) { - // Codec=Sse (Serialization based), see doc to use other codecs - sse_encode_usize( - (self as CoreConfigImpl).frbInternalSseEncode(move: null), serializer); - } - @protected void sse_encode_StreamSink_bool_Sse( RustStreamSink self, SseSerializer serializer) { @@ -1067,6 +1057,13 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { serializer.buffer.putUint8(self ? 1 : 0); } + @protected + void sse_encode_box_autoadd_core_config( + CoreConfig self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_core_config(self, serializer); + } + @protected void sse_encode_box_autoadd_mission_info( MissionInfo self, SseSerializer serializer) { @@ -1081,6 +1078,16 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { sse_encode_node_device(self, serializer); } + @protected + void sse_encode_core_config(CoreConfig self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_u_16(self.port, serializer); + sse_encode_String(self.interfaceAddr, serializer); + sse_encode_String(self.multicastAddr, serializer); + sse_encode_u_16(self.multicastPort, serializer); + sse_encode_String(self.storePath, serializer); + } + @protected void sse_encode_file_info(FileInfo self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -1248,30 +1255,4 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { void sse_encode_unit(void self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs } - - @protected - void sse_encode_usize(BigInt self, SseSerializer serializer) { - // Codec=Sse (Serialization based), see doc to use other codecs - serializer.buffer.putBigUint64(self); - } -} - -@sealed -class CoreConfigImpl extends RustOpaque implements CoreConfig { - // Not to be used by end users - CoreConfigImpl.frbInternalDcoDecode(List wire) - : super.frbInternalDcoDecode(wire, _kStaticData); - - // Not to be used by end users - CoreConfigImpl.frbInternalSseDecode(BigInt ptr, int externalSizeOnNative) - : super.frbInternalSseDecode(ptr, externalSizeOnNative, _kStaticData); - - static final _kStaticData = RustArcStaticData( - rustArcIncrementStrongCount: - RustLib.instance.api.rust_arc_increment_strong_count_CoreConfig, - rustArcDecrementStrongCount: - RustLib.instance.api.rust_arc_decrement_strong_count_CoreConfig, - rustArcDecrementStrongCountPtr: - RustLib.instance.api.rust_arc_decrement_strong_count_CoreConfigPtr, - ); } diff --git a/lib/core/rust/frb_generated.io.dart b/lib/core/rust/frb_generated.io.dart index a5315ef..e4fa3c5 100644 --- a/lib/core/rust/frb_generated.io.dart +++ b/lib/core/rust/frb_generated.io.dart @@ -23,22 +23,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { required super.portManager, }); - CrossPlatformFinalizerArg get rust_arc_decrement_strong_count_CoreConfigPtr => - wire._rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCoreConfigPtr; - @protected AnyhowException dco_decode_AnyhowException(dynamic raw); - @protected - CoreConfig - dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCoreConfig( - dynamic raw); - - @protected - CoreConfig - dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCoreConfig( - dynamic raw); - @protected RustStreamSink dco_decode_StreamSink_bool_Sse(dynamic raw); @@ -59,12 +46,18 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected bool dco_decode_bool(dynamic raw); + @protected + CoreConfig dco_decode_box_autoadd_core_config(dynamic raw); + @protected MissionInfo dco_decode_box_autoadd_mission_info(dynamic raw); @protected NodeDevice dco_decode_box_autoadd_node_device(dynamic raw); + @protected + CoreConfig dco_decode_core_config(dynamic raw); + @protected FileInfo dco_decode_file_info(dynamic raw); @@ -119,22 +112,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void dco_decode_unit(dynamic raw); - @protected - BigInt dco_decode_usize(dynamic raw); - @protected AnyhowException sse_decode_AnyhowException(SseDeserializer deserializer); - @protected - CoreConfig - sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCoreConfig( - SseDeserializer deserializer); - - @protected - CoreConfig - sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCoreConfig( - SseDeserializer deserializer); - @protected RustStreamSink sse_decode_StreamSink_bool_Sse( SseDeserializer deserializer); @@ -158,12 +138,18 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected bool sse_decode_bool(SseDeserializer deserializer); + @protected + CoreConfig sse_decode_box_autoadd_core_config(SseDeserializer deserializer); + @protected MissionInfo sse_decode_box_autoadd_mission_info(SseDeserializer deserializer); @protected NodeDevice sse_decode_box_autoadd_node_device(SseDeserializer deserializer); + @protected + CoreConfig sse_decode_core_config(SseDeserializer deserializer); + @protected FileInfo sse_decode_file_info(SseDeserializer deserializer); @@ -220,23 +206,10 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void sse_decode_unit(SseDeserializer deserializer); - @protected - BigInt sse_decode_usize(SseDeserializer deserializer); - @protected void sse_encode_AnyhowException( AnyhowException self, SseSerializer serializer); - @protected - void - sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCoreConfig( - CoreConfig self, SseSerializer serializer); - - @protected - void - sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCoreConfig( - CoreConfig self, SseSerializer serializer); - @protected void sse_encode_StreamSink_bool_Sse( RustStreamSink self, SseSerializer serializer); @@ -259,6 +232,10 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void sse_encode_bool(bool self, SseSerializer serializer); + @protected + void sse_encode_box_autoadd_core_config( + CoreConfig self, SseSerializer serializer); + @protected void sse_encode_box_autoadd_mission_info( MissionInfo self, SseSerializer serializer); @@ -267,6 +244,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { void sse_encode_box_autoadd_node_device( NodeDevice self, SseSerializer serializer); + @protected + void sse_encode_core_config(CoreConfig self, SseSerializer serializer); + @protected void sse_encode_file_info(FileInfo self, SseSerializer serializer); @@ -326,9 +306,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void sse_encode_unit(void self, SseSerializer serializer); - - @protected - void sse_encode_usize(BigInt self, SseSerializer serializer); } // Section: wire_class @@ -344,36 +321,4 @@ class RustLibWire implements BaseWire { /// The symbols are looked up in [dynamicLibrary]. RustLibWire(ffi.DynamicLibrary dynamicLibrary) : _lookup = dynamicLibrary.lookup; - - void - rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCoreConfig( - ffi.Pointer ptr, - ) { - return _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCoreConfig( - ptr, - ); - } - - late final _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCoreConfigPtr = - _lookup)>>( - 'frbgen_localsend_rs_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCoreConfig'); - late final _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCoreConfig = - _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCoreConfigPtr - .asFunction)>(); - - void - rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCoreConfig( - ffi.Pointer ptr, - ) { - return _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCoreConfig( - ptr, - ); - } - - late final _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCoreConfigPtr = - _lookup)>>( - 'frbgen_localsend_rs_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCoreConfig'); - late final _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCoreConfig = - _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCoreConfigPtr - .asFunction)>(); } diff --git a/lib/core/store/config_store.dart b/lib/core/store/config_store.dart index d9d21c6..870db11 100644 --- a/lib/core/store/config_store.dart +++ b/lib/core/store/config_store.dart @@ -73,4 +73,30 @@ class ConfigStore { Future updateLocaleMode(LocaleMode mode) async { await _prefs!.setInt(_localeKey, mode.index); } + + static const _storePathKey = 'storePath'; + + bool storePathSet() { + return _prefs!.containsKey(_storePathKey); + } + + String storePath() { + final value = _prefs!.getString(_storePathKey) ?? "./"; + return value; + } + + Future setStorePath(String path) async { + await _prefs!.setString(_storePathKey, path); + } + + static const _quickSaveKey = 'quickSave'; + + bool quickSave() { + final value = _prefs!.getBool(_quickSaveKey) ?? false; + return value; + } + + Future setQuickSave(bool value) async { + await _prefs!.setBool(_quickSaveKey, value); + } } diff --git a/lib/i18n/strings.g.dart b/lib/i18n/strings.g.dart index 04be741..d25f24b 100644 --- a/lib/i18n/strings.g.dart +++ b/lib/i18n/strings.g.dart @@ -4,9 +4,9 @@ /// To regenerate, run: `dart run slang` /// /// Locales: 2 -/// Strings: 26 (13 per locale) +/// Strings: 60 (30 per locale) /// -/// Built on 2024-07-07 at 04:40 UTC +/// Built on 2024-07-07 at 14:19 UTC // coverage:ignore-file // ignore_for_file: type=lint @@ -148,11 +148,24 @@ class Translations implements BaseTranslations { late final Translations _root = this; // ignore: unused_field // Translations - String get appTitle => 'RustSend'; + late final _StringsAppTitleEn appTitle = _StringsAppTitleEn._(_root); late final _StringsHomeEn home = _StringsHomeEn._(_root); + late final _StringsMissionEn mission = _StringsMissionEn._(_root); + late final _StringsCommonEn common = _StringsCommonEn._(_root); late final _StringsSettingEn setting = _StringsSettingEn._(_root); } +// Path: appTitle +class _StringsAppTitleEn { + _StringsAppTitleEn._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + String get parta => 'LocalSend'; + String get partb => '_RS'; +} + // Path: home class _StringsHomeEn { _StringsHomeEn._(this._root); @@ -163,6 +176,35 @@ class _StringsHomeEn { String get title => 'Home Page'; } +// Path: mission +class _StringsMissionEn { + _StringsMissionEn._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + String get accept => 'Accept'; + String get cancel => 'Cancel'; + String get complete => 'Complete'; + String get finished => 'Finished'; + String get tranfer => 'Transfering'; + String get pending => 'Pending'; + String get failed => 'Failed'; + String get skip => 'Skip'; + String get advance => 'Advance'; +} + +// Path: common +class _StringsCommonEn { + _StringsCommonEn._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + String get file => 'File'; + String get size => 'Size'; +} + // Path: setting class _StringsSettingEn { _StringsSettingEn._(this._root); @@ -174,6 +216,7 @@ class _StringsSettingEn { String get common => 'Common'; late final _StringsSettingBrightnessEn brightness = _StringsSettingBrightnessEn._(_root); late final _StringsSettingLanguageEn language = _StringsSettingLanguageEn._(_root); + late final _StringsSettingReceiveEn receive = _StringsSettingReceiveEn._(_root); late final _StringsSettingCoreEn core = _StringsSettingCoreEn._(_root); } @@ -200,6 +243,20 @@ class _StringsSettingLanguageEn { String subTitle({required Object language}) => 'Current language: ${language}'; } +// Path: setting.receive +class _StringsSettingReceiveEn { + _StringsSettingReceiveEn._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + String get title => 'Receive'; + String get quickSave => 'Quick Save'; + String get quickSaveHint => 'Start tranfer without accept'; + String get saveFolder => 'Save Folder'; + String get selectSaveFolder => 'Select'; +} + // Path: setting.core class _StringsSettingCoreEn { _StringsSettingCoreEn._(this._root); @@ -257,11 +314,24 @@ class _StringsZh implements Translations { @override late final _StringsZh _root = this; // ignore: unused_field // Translations - @override String get appTitle => '锈船'; + @override late final _StringsAppTitleZh appTitle = _StringsAppTitleZh._(_root); @override late final _StringsHomeZh home = _StringsHomeZh._(_root); + @override late final _StringsMissionZh mission = _StringsMissionZh._(_root); + @override late final _StringsCommonZh common = _StringsCommonZh._(_root); @override late final _StringsSettingZh setting = _StringsSettingZh._(_root); } +// Path: appTitle +class _StringsAppTitleZh implements _StringsAppTitleEn { + _StringsAppTitleZh._(this._root); + + @override final _StringsZh _root; // ignore: unused_field + + // Translations + @override String get parta => '快传'; + @override String get partb => '锈'; +} + // Path: home class _StringsHomeZh implements _StringsHomeEn { _StringsHomeZh._(this._root); @@ -272,6 +342,35 @@ class _StringsHomeZh implements _StringsHomeEn { @override String get title => '主页'; } +// Path: mission +class _StringsMissionZh implements _StringsMissionEn { + _StringsMissionZh._(this._root); + + @override final _StringsZh _root; // ignore: unused_field + + // Translations + @override String get accept => '接收'; + @override String get cancel => '取消'; + @override String get complete => '完成'; + @override String get finished => '已完成'; + @override String get tranfer => '传输中'; + @override String get pending => '等待中'; + @override String get failed => '失败'; + @override String get skip => '跳过'; + @override String get advance => '高级'; +} + +// Path: common +class _StringsCommonZh implements _StringsCommonEn { + _StringsCommonZh._(this._root); + + @override final _StringsZh _root; // ignore: unused_field + + // Translations + @override String get file => '文件'; + @override String get size => '大小'; +} + // Path: setting class _StringsSettingZh implements _StringsSettingEn { _StringsSettingZh._(this._root); @@ -283,6 +382,7 @@ class _StringsSettingZh implements _StringsSettingEn { @override String get common => '通用'; @override late final _StringsSettingBrightnessZh brightness = _StringsSettingBrightnessZh._(_root); @override late final _StringsSettingLanguageZh language = _StringsSettingLanguageZh._(_root); + @override late final _StringsSettingReceiveZh receive = _StringsSettingReceiveZh._(_root); @override late final _StringsSettingCoreZh core = _StringsSettingCoreZh._(_root); } @@ -309,6 +409,20 @@ class _StringsSettingLanguageZh implements _StringsSettingLanguageEn { @override String subTitle({required Object language}) => '当前语言: ${language}'; } +// Path: setting.receive +class _StringsSettingReceiveZh implements _StringsSettingReceiveEn { + _StringsSettingReceiveZh._(this._root); + + @override final _StringsZh _root; // ignore: unused_field + + // Translations + @override String get title => '接收设置'; + @override String get quickSave => '快速保存'; + @override String get quickSaveHint => '不需要等待确认直接接受'; + @override String get saveFolder => '保存目录'; + @override String get selectSaveFolder => '选择'; +} + // Path: setting.core class _StringsSettingCoreZh implements _StringsSettingCoreEn { _StringsSettingCoreZh._(this._root); @@ -348,8 +462,20 @@ class _StringsSettingCoreServerZh implements _StringsSettingCoreServerEn { extension on Translations { dynamic _flatMapFunction(String path) { switch (path) { - case 'appTitle': return 'RustSend'; + case 'appTitle.parta': return 'LocalSend'; + case 'appTitle.partb': return '_RS'; case 'home.title': return 'Home Page'; + case 'mission.accept': return 'Accept'; + case 'mission.cancel': return 'Cancel'; + case 'mission.complete': return 'Complete'; + case 'mission.finished': return 'Finished'; + case 'mission.tranfer': return 'Transfering'; + case 'mission.pending': return 'Pending'; + case 'mission.failed': return 'Failed'; + case 'mission.skip': return 'Skip'; + case 'mission.advance': return 'Advance'; + case 'common.file': return 'File'; + case 'common.size': return 'Size'; case 'setting.title': return 'Settings'; case 'setting.common': return 'Common'; case 'setting.brightness.title': return 'Brightness'; @@ -359,6 +485,11 @@ extension on Translations { case 'setting.brightness.themeMode.dark': return 'Dark mode'; case 'setting.language.title': return 'Language'; case 'setting.language.subTitle': return ({required Object language}) => 'Current language: ${language}'; + case 'setting.receive.title': return 'Receive'; + case 'setting.receive.quickSave': return 'Quick Save'; + case 'setting.receive.quickSaveHint': return 'Start tranfer without accept'; + case 'setting.receive.saveFolder': return 'Save Folder'; + case 'setting.receive.selectSaveFolder': return 'Select'; case 'setting.core.title': return 'core setting'; case 'setting.core.server.title': return 'server'; default: return null; @@ -369,8 +500,20 @@ extension on Translations { extension on _StringsZh { dynamic _flatMapFunction(String path) { switch (path) { - case 'appTitle': return '锈船'; + case 'appTitle.parta': return '快传'; + case 'appTitle.partb': return '锈'; case 'home.title': return '主页'; + case 'mission.accept': return '接收'; + case 'mission.cancel': return '取消'; + case 'mission.complete': return '完成'; + case 'mission.finished': return '已完成'; + case 'mission.tranfer': return '传输中'; + case 'mission.pending': return '等待中'; + case 'mission.failed': return '失败'; + case 'mission.skip': return '跳过'; + case 'mission.advance': return '高级'; + case 'common.file': return '文件'; + case 'common.size': return '大小'; case 'setting.title': return '设置'; case 'setting.common': return '通用'; case 'setting.brightness.title': return '明暗'; @@ -380,6 +523,11 @@ extension on _StringsZh { case 'setting.brightness.themeMode.dark': return '深色模式'; case 'setting.language.title': return '语言'; case 'setting.language.subTitle': return ({required Object language}) => '当前语言: ${language}'; + case 'setting.receive.title': return '接收设置'; + case 'setting.receive.quickSave': return '快速保存'; + case 'setting.receive.quickSaveHint': return '不需要等待确认直接接受'; + case 'setting.receive.saveFolder': return '保存目录'; + case 'setting.receive.selectSaveFolder': return '选择'; case 'setting.core.title': return '核心设置'; case 'setting.core.server.title': return '服务器'; default: return null; diff --git a/lib/main.dart b/lib/main.dart index 9044e2c..ea3487a 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,15 +1,14 @@ import 'dart:async'; import 'dart:io'; -import 'package:flutter/foundation.dart'; +import 'package:chinese_font_library/chinese_font_library.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:google_fonts/google_fonts.dart'; -import 'package:path_provider/path_provider.dart'; +import 'package:window_manager/window_manager.dart'; import 'common/device_info_utils.dart'; +import 'common/utils.dart'; import 'core/providers/locale_provider.dart'; import 'core/providers/theme_provider.dart'; import 'core/rust/bridge.dart'; @@ -21,60 +20,67 @@ import 'view/pages/frame_page.dart'; Future main() async { WidgetsFlutterBinding.ensureInitialized(); await RustLib.init(); - String storePath; - if (Platform.isAndroid) { - SystemUiOverlayStyle systemUiOverlayStyle = - const SystemUiOverlayStyle(statusBarColor: Colors.transparent); - SystemChrome.setSystemUIOverlayStyle(systemUiOverlayStyle); - storePath = "/storage/emulated/0/Download"; - if (kDebugMode) { - createLogStream().listen((event) { - debugPrint( - '${event.level} ${event.tag} ${event.msg} ${event.timeMillis}'); - }); - } - } else { - storePath = (await getDownloadsDirectory())!.absolute.path; - } + initLogger(); await ConfigStore.ensureInitialized(); - final locale = ConfigStore().locale(); - final String defaultLocale = Platform.localeName; - final countryCode = LocaleSettings.currentLocale.countryCode; - if (countryCode == null || countryCode != locale) { - LocaleSettings.setLocaleRaw(defaultLocale); + + final device = await getDevice(); + final config = await getConfig(device.port); + await setup(device: device, config: config); + + if (Platform.isWindows) { + // Must add this line. + await windowManager.ensureInitialized(); + + WindowOptions windowOptions = const WindowOptions( + size: Size(1080, 960), + center: true, + ); + windowManager.waitUntilReadyToShow(windowOptions, () async { + await windowManager.show(); + await windowManager.focus(); + }); } - final device = await newDevice(); - await setup(device: device); + + initLocale(); + runApp(ProviderScope(child: TranslationProvider(child: const MyApp()))); } class MyApp extends ConsumerWidget { const MyApp({super.key}); - ThemeData _buildTheme(Brightness brightness) { + ThemeData _buildTheme(Brightness brightness, Locale locale) { var baseTheme = ThemeData( useMaterial3: true, colorSchemeSeed: const Color(0xfff74c00), brightness: brightness); - return baseTheme.copyWith( - textTheme: GoogleFonts.notoSansTextTheme(baseTheme.textTheme), - ); + if (locale.languageCode == "zh") { + return baseTheme.useSystemChineseFont(brightness); + } + return baseTheme; } @override Widget build(BuildContext context, WidgetRef ref) { final themeMode = ref.watch(themeStateProvider); final localeConfig = ref.watch(localeStateProvider); + final locale = localeConfig.getLocale(); return MaterialApp( - title: t.appTitle, - locale: localeConfig.getLocale(), + title: t.appTitle.parta + t.appTitle.partb, + locale: locale, // use provider supportedLocales: AppLocaleUtils.supportedLocales, localizationsDelegates: GlobalMaterialLocalizations.delegates, - theme: _buildTheme(Brightness.light), - darkTheme: _buildTheme(Brightness.dark), + theme: _buildTheme( + Brightness.light, + locale, + ), + darkTheme: _buildTheme( + Brightness.dark, + locale, + ), themeMode: themeMode, home: const FramePage(), ); diff --git a/lib/view/pages/frame_page.dart b/lib/view/pages/frame_page.dart index f02f17c..a6736bb 100644 --- a/lib/view/pages/frame_page.dart +++ b/lib/view/pages/frame_page.dart @@ -1,14 +1,13 @@ +import 'package:animations/animations.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:localsend_rs/view/pages/mission_page.dart'; import '../../common/utils.dart'; -import '../../common/widgets.dart'; import '../../core/providers/mission_provider.dart'; -import '../../core/rust/actor/model.dart'; -import '../../core/rust/bridge.dart'; import '../../i18n/strings.g.dart'; +import '../widget/common_widget.dart'; import 'home_page.dart'; import 'setting_page.dart'; @@ -27,6 +26,7 @@ class FramePage extends ConsumerStatefulWidget { class _FramePageState extends ConsumerState { int index = 0; + int lastIndex = 0; List pages = [ const HomePage(), @@ -34,9 +34,9 @@ class _FramePageState extends ConsumerState { ]; FrameType getFrameType(double width) { - if (width < 960) { + if (width < 800) { return FrameType.compact; - } else if (width < 1440) { + } else if (width < 1200) { return FrameType.normal; } else { return FrameType.wide; @@ -52,6 +52,16 @@ class _FramePageState extends ConsumerState { updateSystemOverlayStyle(brightness); } + void changeIndex(int value) { + if (value == index) { + return; + } + setState(() { + lastIndex = index; + index = value; + }); + } + @override Widget build(BuildContext context) { if (!init) { @@ -70,20 +80,18 @@ class _FramePageState extends ConsumerState { return Scaffold( body: SafeArea(child: getView(frameType)), bottomNavigationBar: frameType == FrameType.compact - ? BottomNavigationBar( - currentIndex: index, - onTap: (index) { - setState(() { - this.index = index; - }); - }, - items: [ - BottomNavigationBarItem( - icon: const Icon(Icons.home), + ? NavigationBar( + selectedIndex: index, + onDestinationSelected: changeIndex, + destinations: [ + NavigationDestination( + icon: const Icon(Icons.home_outlined), + selectedIcon: const Icon(Icons.home), label: context.t.home.title, ), - BottomNavigationBarItem( - icon: const Icon(Icons.settings), + NavigationDestination( + icon: const Icon(Icons.settings_outlined), + selectedIcon: const Icon(Icons.settings), label: context.t.setting.title, ), ], @@ -95,11 +103,7 @@ class _FramePageState extends ConsumerState { Widget getSideNavigation(FrameType frameType) { if (frameType == FrameType.wide) { return NavigationDrawer( - onDestinationSelected: (index) { - setState(() { - this.index = index; - }); - }, + onDestinationSelected: changeIndex, selectedIndex: index, children: [ const SizedBox( @@ -124,11 +128,7 @@ class _FramePageState extends ConsumerState { if (frameType == FrameType.normal) { return NavigationRail( backgroundColor: Theme.of(context).colorScheme.surfaceContainerLow, - onDestinationSelected: (value) { - setState(() { - index = value; - }); - }, + onDestinationSelected: changeIndex, labelType: NavigationRailLabelType.selected, destinations: [ NavigationRailDestination( @@ -160,14 +160,23 @@ class _FramePageState extends ConsumerState { child: Row( children: [ Expanded( - child: Container( - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surface, - borderRadius: BorderRadius.circular(24), - ), - child: IndexedStack( - index: index, - children: pages, + child: ClipRRect( + borderRadius: BorderRadius.circular(24), + child: PageTransitionSwitcher( + reverse: lastIndex > index, + transitionBuilder: ( + Widget child, + Animation animation, + Animation secondaryAnimation, + ) { + return SharedAxisTransition( + animation: animation, + secondaryAnimation: secondaryAnimation, + transitionType: SharedAxisTransitionType.vertical, + child: child, + ); + }, + child: pages.elementAt(index), ), ), ), @@ -196,9 +205,21 @@ class _FramePageState extends ConsumerState { Widget getView(FrameType frameType) { if (frameType == FrameType.compact) { - return IndexedStack( - index: index, - children: pages, + return PageTransitionSwitcher( + reverse: lastIndex > index, + transitionBuilder: ( + Widget child, + Animation animation, + Animation secondaryAnimation, + ) { + return SharedAxisTransition( + animation: animation, + secondaryAnimation: secondaryAnimation, + transitionType: SharedAxisTransitionType.horizontal, + child: child, + ); + }, + child: pages.elementAt(index), ); } return getParalleView(frameType); diff --git a/lib/view/pages/home_page.dart b/lib/view/pages/home_page.dart index d6cb906..4b2dd6f 100644 --- a/lib/view/pages/home_page.dart +++ b/lib/view/pages/home_page.dart @@ -29,14 +29,15 @@ class _HomePageState extends State { @override Widget build(BuildContext context) { return Scaffold( - backgroundColor: Colors.transparent, body: Center( child: Container( padding: const EdgeInsets.symmetric(horizontal: 16), constraints: const BoxConstraints(maxWidth: 800), child: Column( children: [ - // StaticAppbar(title: context.t.home.title), + SizedBox( + height: 8, + ), Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0), child: Row( diff --git a/lib/view/pages/mission_page.dart b/lib/view/pages/mission_page.dart index 296dcb4..e7064d5 100644 --- a/lib/view/pages/mission_page.dart +++ b/lib/view/pages/mission_page.dart @@ -1,11 +1,14 @@ +import 'package:filesize/filesize.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:localsend_rs/common/utils.dart'; import 'package:localsend_rs/core/rust/actor/mission.dart'; -import '../../common/widgets.dart'; import '../../core/providers/mission_provider.dart'; import '../../core/rust/actor/model.dart'; import '../../core/rust/bridge.dart'; +import '../../i18n/strings.g.dart'; +import '../widget/common_widget.dart'; import '../widget/device_widget.dart'; class IdlePage extends StatelessWidget { @@ -24,27 +27,54 @@ class IdlePage extends StatelessWidget { } } -class TransferPage extends StatelessWidget { +class TransferPage extends StatefulWidget { final MissionInfo mission; final bool isParalle; - TransferPage({super.key, required this.mission, required this.isParalle}); + const TransferPage( + {super.key, required this.mission, required this.isParalle}); + @override + State createState() => _TransferPageState(); +} + +class _TransferPageState extends State { final ButtonStyle style = ElevatedButton.styleFrom( padding: const EdgeInsets.fromLTRB(16, 16, 24, 16), ); + bool showAdvaned = false; + + List advanceMessage(MissionInfo mission) { + int totalNum = mission.files.length; + int totalSize = 0; + int finishedNum = 0; + int finishedSize = 0; + for (var file in mission.files) { + totalSize += file.info.size; + if (file.state == const FileState.finish()) { + finishedNum += 1; + finishedSize += file.info.size; + } + } + return [ + Text("${t.common.file}: $finishedNum / $totalNum"), + Text( + "${t.common.size}: ${filesize(finishedSize)} / ${filesize(totalSize)}") + ]; + } + @override Widget build(BuildContext context) { return Scaffold( - backgroundColor: isParalle ? Colors.transparent : null, + backgroundColor: widget.isParalle ? Colors.transparent : null, body: Column( children: [ Expanded( child: ListView.builder( - itemCount: mission.files.length, + itemCount: widget.mission.files.length, itemBuilder: (context, index) { - final file = mission.files.elementAt(index); + final file = widget.mission.files.elementAt(index); return Padding( padding: const EdgeInsets.symmetric( horizontal: 16, vertical: 8), @@ -57,13 +87,16 @@ class TransferPage extends StatelessWidget { ), Container( decoration: BoxDecoration( - color: Theme.of(context).colorScheme.secondary, + color: Theme.of(context) + .colorScheme + .secondaryContainer, borderRadius: BorderRadius.circular(12), ), height: 48, width: 48, child: Icon( Icons.file_present, + color: Theme.of(context).colorScheme.secondary, size: 36, ), ), @@ -75,9 +108,18 @@ class TransferPage extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ - Text(file.info.fileName), - Text(file.state.toString()), - LinearProgressIndicator(value: 0.3), + Text( + "${file.info.fileName} (${filesize(file.info.size)})"), + Text( + file.state.getName(), + style: TextStyle( + color: Theme.of(context) + .colorScheme + .secondary, + ), + ), + if (file.state == const FileState.transfer()) + LinearProgressIndicator(value: 0.3), ], ), ), @@ -89,53 +131,101 @@ class TransferPage extends StatelessWidget { ), ); })), - Container( + AnimatedContainer( + curve: Curves.ease, margin: EdgeInsets.symmetric( horizontal: 16, ), - padding: EdgeInsets.symmetric( - vertical: 8, - horizontal: 16, - ), decoration: BoxDecoration( color: Theme.of(context).colorScheme.secondaryContainer, borderRadius: BorderRadius.circular(12), ), - height: 96, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + duration: Durations.medium1, + height: showAdvaned ? 144 : 96, + child: Stack( children: [ - Text( - mission.state.toString(), - style: Theme.of(context).textTheme.titleMedium, - ), - SizedBox( - height: 8, - ), - LinearProgressIndicator( - value: 0.3, - minHeight: 8, - borderRadius: BorderRadius.circular(12), - ), - SizedBox( - height: 8, + Padding( + padding: EdgeInsets.symmetric( + vertical: 8, + horizontal: 16, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + widget.mission.state.getName(), + style: Theme.of(context).textTheme.titleMedium, + ), + SizedBox( + height: 8, + ), + if (widget.mission.state == MissionState.finished) + LinearProgressIndicator( + value: 1, + minHeight: 8, + borderRadius: BorderRadius.circular(12), + ) + else + LinearProgressIndicator( + value: 0.3, + minHeight: 8, + borderRadius: BorderRadius.circular(12), + ), + SizedBox( + height: 8, + ), + Container( + height: 48, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: advanceMessage(widget.mission), + ), + ), + ], + ), ), - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - TextButton.icon( - onPressed: () {}, - label: Text("高级"), - icon: Icon(Icons.info), + Align( + alignment: Alignment.bottomCenter, + child: Container( + padding: EdgeInsets.symmetric( + vertical: 8, + horizontal: 16, ), - TextButton.icon( - onPressed: () { - clearMission(); - }, - label: Text("取消"), - icon: Icon(Icons.info), - ) - ], + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.secondaryContainer, + borderRadius: BorderRadius.circular(12), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton.icon( + onPressed: () { + setState(() { + showAdvaned = !showAdvaned; + }); + }, + label: Text(context.t.mission.advance), + icon: Icon(Icons.info), + ), + if (widget.mission.state == MissionState.finished) + FilledButton.icon( + onPressed: () { + clearMission(); + }, + label: Text(context.t.mission.complete), + icon: Icon(Icons.info), + ) + else + TextButton.icon( + onPressed: () { + clearMission(); + }, + label: Text(context.t.mission.cancel), + icon: Icon(Icons.cancel), + ) + ], + ), + ), ), ], ), diff --git a/lib/view/pages/setting_page.dart b/lib/view/pages/setting_page.dart index cd05ad6..de359a6 100644 --- a/lib/view/pages/setting_page.dart +++ b/lib/view/pages/setting_page.dart @@ -1,10 +1,8 @@ import 'package:flutter/material.dart'; +import 'package:localsend_rs/core/store/config_store.dart'; -import '../../common/widgets.dart'; -import '../../core/rust/bridge.dart'; import '../../i18n/strings.g.dart'; import '../widget/common_widget.dart'; -import '../widget/network_widget.dart'; import '../widget/setting_widgets.dart'; class SettingPage extends StatelessWidget { @@ -13,7 +11,6 @@ class SettingPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( - backgroundColor: Colors.transparent, body: CustomScrollView( slivers: [ SliverToBoxAdapter( @@ -36,25 +33,20 @@ class SettingPage extends StatelessWidget { height: 16, ), SettingTileGroup( - title: t.setting.core.title, + title: t.setting.receive.title, children: [ - // core status - const ServerTile(), // core log - NetworkWidget( - onPressed: (addr) { - changeAddress(addr: addr); - }, - ), + const QuickSaveWidget(), + const StorePathWIdget() ], ), const SizedBox( height: 16, ), - const SettingTileGroup( - title: "behavior", + SettingTileGroup( + title: t.setting.core.title, children: [ - // receive without accept - // save dir + // core status + const ServerTile(), // core log ], ), const SizedBox( diff --git a/lib/view/widget/common_widget.dart b/lib/view/widget/common_widget.dart index d8e5100..2f936b6 100644 --- a/lib/view/widget/common_widget.dart +++ b/lib/view/widget/common_widget.dart @@ -1,5 +1,30 @@ import 'package:flutter/material.dart'; +import '../../i18n/strings.g.dart'; + +class AppTitle extends StatelessWidget { + const AppTitle({super.key}); + + @override + Widget build(BuildContext context) { + return Text.rich( + TextSpan( + text: t.appTitle.parta, + children: [ + TextSpan( + text: t.appTitle.partb, + style: Theme.of(context).textTheme.displaySmall!.copyWith( + color: const Color(0xfff74c00), + fontWeight: FontWeight.bold, + ), + ), + ], + ), + style: Theme.of(context).textTheme.displaySmall, + ); + } +} + class Tag extends StatelessWidget { final String title; diff --git a/lib/view/widget/device_widget.dart b/lib/view/widget/device_widget.dart index b493399..37d9a49 100644 --- a/lib/view/widget/device_widget.dart +++ b/lib/view/widget/device_widget.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:localsend_rs/core/rust/actor/model.dart'; +import 'package:simple_icons/simple_icons.dart'; import 'common_widget.dart'; @@ -54,6 +55,35 @@ class DeviceWidget extends StatelessWidget { const DeviceWidget({super.key, required this.device}); + Widget getDeviceBadge(BuildContext context) { + IconData? icon = null; + if (SimpleIcons.values.containsKey(device.deviceModel.toLowerCase())) { + icon = SimpleIcons.values[device.deviceModel.toLowerCase()]; + } else if (SimpleIcons.values + .containsKey(device.deviceType.toLowerCase())) { + icon = SimpleIcons.values[device.deviceType.toLowerCase()]; + } + + return icon == null + ? const SizedBox() + : Align( + alignment: Alignment.bottomRight, + child: Container( + margin: const EdgeInsets.all(8), + padding: const EdgeInsets.all(4), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.primaryContainer, + borderRadius: BorderRadius.circular(12), + ), + child: Icon( + icon, + color: Theme.of(context).colorScheme.primary, + size: 18, + ), + ), + ); + } + @override Widget build(BuildContext context) { return Padding( @@ -66,12 +96,20 @@ class DeviceWidget extends StatelessWidget { height: 80, child: Row( children: [ - const SizedBox( + SizedBox( height: 80, width: 80, - child: Icon( - Icons.smartphone, - size: 48, + child: Stack( + children: [ + Align( + alignment: Alignment.center, + child: Icon( + Icons.smartphone, + size: 48, + ), + ), + getDeviceBadge(context), + ], ), ), Column( diff --git a/lib/view/widget/setting_widgets.dart b/lib/view/widget/setting_widgets.dart index ea3ee5b..0da615a 100644 --- a/lib/view/widget/setting_widgets.dart +++ b/lib/view/widget/setting_widgets.dart @@ -1,5 +1,6 @@ import 'dart:io'; +import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -9,6 +10,7 @@ import '../../core/providers/core_provider.dart'; import '../../core/providers/locale_provider.dart'; import '../../core/providers/theme_provider.dart'; import '../../core/rust/bridge.dart'; +import '../../core/store/config_store.dart'; import '../../i18n/strings.g.dart'; class SettingTileGroup extends StatelessWidget { @@ -74,9 +76,9 @@ class ThemeTile extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final themeMode = ref.watch(themeStateProvider); return ListTile( - title: Text(t.setting.brightness.title), - subtitle: - Text(t.setting.brightness.subTitle(mode: getThemeName(themeMode))), + title: Text(context.t.setting.brightness.title), + subtitle: Text( + context.t.setting.brightness.subTitle(mode: getThemeName(themeMode))), trailing: OverflowBar( children: [ IconButton( @@ -131,8 +133,9 @@ class LocaleTile extends ConsumerWidget { supportLanguages[locale.languageCode]?.name ?? "unknown"; return ListTile( - title: Text(t.setting.language.title), - subtitle: Text(t.setting.language.subTitle(language: currentLocaleName)), + title: Text(context.t.setting.language.title), + subtitle: Text( + context.t.setting.language.subTitle(language: currentLocaleName)), trailing: FilledButton( onPressed: () { showDialog( @@ -143,7 +146,7 @@ class LocaleTile extends ConsumerWidget { supportLanguages[systemLocale.languageCode]?.name ?? "unknown"; return SimpleDialog( - title: Text(t.setting.language.title), + title: Text(context.t.setting.language.title), children: [ ListTile( title: Text("系统默认: $systemLocaleName"), @@ -184,7 +187,7 @@ class ServerTile extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final core = ref.watch(coreStateProvider); return ListTile( - title: Text(t.setting.core.server.title), + title: Text(context.t.setting.core.server.title), trailing: OverflowBar( children: [ IconButton( @@ -208,3 +211,60 @@ class ServerTile extends ConsumerWidget { ); } } + +class QuickSaveWidget extends StatefulWidget { + const QuickSaveWidget({ + super.key, + }); + + @override + State createState() => _QuickSaveWidgetState(); +} + +class _QuickSaveWidgetState extends State { + @override + Widget build(BuildContext context) { + return ListTile( + title: Text(context.t.setting.receive.quickSave), + subtitle: Text(context.t.setting.receive.quickSaveHint), + trailing: Switch( + onChanged: (value) { + ConfigStore().setQuickSave(value); + setState(() {}); + }, + value: ConfigStore().quickSave(), + ), + ); + } +} + +class StorePathWIdget extends StatefulWidget { + const StorePathWIdget({ + super.key, + }); + + @override + State createState() => _StorePathWIdgetState(); +} + +class _StorePathWIdgetState extends State { + @override + Widget build(BuildContext context) { + return ListTile( + title: Text(context.t.setting.receive.saveFolder), + subtitle: Text(ConfigStore().storePath()), + trailing: FilledButton( + onPressed: () async { + String? selectedDirectory = + await FilePicker.platform.getDirectoryPath(); + + if (selectedDirectory != null) { + ConfigStore().setStorePath(selectedDirectory); + } + setState(() {}); + }, + child: Text(context.t.setting.receive.selectSaveFolder), + ), + ); + } +} diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index e71a16d..b7a6d08 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -6,6 +6,14 @@ #include "generated_plugin_registrant.h" +#include +#include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) screen_retriever_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverPlugin"); + screen_retriever_plugin_register_with_registrar(screen_retriever_registrar); + g_autoptr(FlPluginRegistrar) window_manager_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "WindowManagerPlugin"); + window_manager_plugin_register_with_registrar(window_manager_registrar); } diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index b263c67..f2eb5a6 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -3,6 +3,8 @@ # list(APPEND FLUTTER_PLUGIN_LIST + screen_retriever + window_manager ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 251527a..210e9df 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -7,10 +7,14 @@ import Foundation import device_info_plus import path_provider_foundation +import screen_retriever import shared_preferences_foundation +import window_manager func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) + ScreenRetrieverPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) + WindowManagerPlugin.register(with: registry.registrar(forPlugin: "WindowManagerPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index a79ff1a..49d3858 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -25,6 +25,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.11.3" + animations: + dependency: "direct main" + description: + name: animations + sha256: d3d6dcfb218225bbe68e87ccf6378bbb2e32a94900722c5f81611dad089911cb + url: "https://pub.dev" + source: hosted + version: "2.0.11" archive: dependency: transitive description: @@ -145,6 +153,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.3" + chinese_font_library: + dependency: "direct main" + description: + name: chinese_font_library + sha256: f6c18eb58c1514a95e4ed9a7e2f6702be1333b67e4226976b333e5918021c1df + url: "https://pub.dev" + source: hosted + version: "1.2.0" ci: dependency: transitive description: @@ -193,6 +209,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.1" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "55d7b444feb71301ef6b8838dbc1ae02e63dd48c8773f3810ff53bb1e2945b32" + url: "https://pub.dev" + source: hosted + version: "0.3.4+1" crypto: dependency: transitive description: @@ -321,6 +345,22 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.0" + file_picker: + dependency: "direct main" + description: + name: file_picker + sha256: "824f5b9f389bfc4dddac3dea76cd70c51092d9dff0b2ece7ef4f53db8547d258" + url: "https://pub.dev" + source: hosted + version: "8.0.6" + filesize: + dependency: "direct main" + description: + name: filesize + sha256: f53df1f27ff60e466eefcd9df239e02d4722d5e2debee92a87dfd99ac66de2af + url: "https://pub.dev" + source: hosted + version: "2.0.1" fixnum: dependency: transitive description: @@ -360,6 +400,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + sha256: c6b0b4c05c458e1c01ad9bcc14041dd7b1f6783d487be4386f793f47a8a4d03e + url: "https://pub.dev" + source: hosted + version: "2.0.20" flutter_riverpod: dependency: "direct main" description: @@ -423,14 +471,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.2" - google_fonts: - dependency: "direct main" - description: - name: google_fonts - sha256: b1ac0fe2832c9cc95e5e88b57d627c5e68c223b9657f4b96e1487aa9098c7b82 - url: "https://pub.dev" - source: hosted - version: "6.2.1" graphs: dependency: transitive description: @@ -564,6 +604,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.0" + logger: + dependency: "direct main" + description: + name: logger + sha256: af05cc8714f356fd1f3888fb6741cbe9fbe25cdb6eedbab80e1a6db21047d4a4 + url: "https://pub.dev" + source: hosted + version: "2.3.0" logging: dependency: transitive description: @@ -716,6 +764,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + pubspec: + dependency: transitive + description: + name: pubspec + sha256: f534a50a2b4d48dc3bc0ec147c8bd7c304280fff23b153f3f11803c4d49d927e + url: "https://pub.dev" + source: hosted + version: "2.3.0" + pubspec_dependency_sorter: + dependency: "direct dev" + description: + name: pubspec_dependency_sorter + sha256: d2114e92f003195de74a9ed3aeb9c59425ae9b7d637b802bb225b3fe3a4ba605 + url: "https://pub.dev" + source: hosted + version: "1.0.5" pubspec_parse: dependency: transitive description: @@ -787,6 +851,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.27.7" + screen_retriever: + dependency: transitive + description: + name: screen_retriever + sha256: "6ee02c8a1158e6dae7ca430da79436e3b1c9563c8cf02f524af997c201ac2b90" + url: "https://pub.dev" + source: hosted + version: "0.1.9" shared_preferences: dependency: "direct main" description: @@ -859,6 +931,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.0" + simple_icons: + dependency: "direct main" + description: + name: simple_icons + sha256: "30067d70a9d72923fbc80e142e17fa46085dfa970e66bc4bede3be4819d05901" + url: "https://pub.dev" + source: hosted + version: "10.1.3" sky_engine: dependency: transitive description: flutter @@ -992,6 +1072,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.2" + uri: + dependency: transitive + description: + name: uri + sha256: "889eea21e953187c6099802b7b4cf5219ba8f3518f604a1033064d45b1b8268a" + url: "https://pub.dev" + source: hosted + version: "1.0.0" uuid: dependency: "direct main" description: @@ -1080,6 +1168,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.3" + window_manager: + dependency: "direct main" + description: + name: window_manager + sha256: "8699323b30da4cdbe2aa2e7c9de567a6abd8a97d9a5c850a3c86dcd0b34bbfbf" + url: "https://pub.dev" + source: hosted + version: "0.3.9" xdg_directories: dependency: transitive description: @@ -1112,6 +1208,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.1" + yaml_writer: + dependency: transitive + description: + name: yaml_writer + sha256: f182931a598f9a3fd29ff528f0caab98fffa713583e30c12c7a27ce0b66c1308 + url: "https://pub.dev" + source: hosted + version: "2.0.0" sdks: dart: ">=3.4.0 <4.0.0" flutter: ">=3.22.0" diff --git a/pubspec.yaml b/pubspec.yaml index 1d2011c..8f3d6f4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,120 +1,54 @@ -name: localsend_rs -description: "A Rust Implementation of LocalSend protocl v2 for better performance" -# The following line prevents the package from being accidentally published to -# pub.dev using `flutter pub publish`. This is preferred for private packages. -publish_to: "none" # Remove this line if you wish to publish to pub.dev - -# The following defines the version and build number for your application. -# A version number is three numbers separated by dots, like 1.2.43 -# followed by an optional build number separated by a +. -# Both the version and the builder number may be overridden in flutter -# build by specifying --build-name and --build-number, respectively. -# In Android, build-name is used as versionName while build-number used as versionCode. -# Read more about Android versioning at https://developer.android.com/studio/publish/versioning -# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. -# Read more about iOS versioning at -# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -# In Windows, build-name is used as the major, minor, and patch parts -# of the product and file versions while build-number is used as the build suffix. -version: 1.0.0+1 - -environment: - sdk: ">=3.2.0 <4.0.0" - -# Dependencies specify other packages that your package needs in order to work. -# To automatically upgrade your package dependencies to the latest versions -# consider running `flutter pub upgrade --major-versions`. Alternatively, -# dependencies can be manually updated by changing the version numbers below to -# the latest version available on pub.dev. To see which dependencies have newer -# versions available, run `flutter pub outdated`. -dependencies: - flutter: - sdk: flutter - flutter_localizations: # add this - sdk: flutter - - # The following adds the Cupertino Icons font to your application. - # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^1.0.2 - rust_builder: - path: rust_builder - flutter_rust_bridge: ^2.1.0 - dio: ^5.4.0 - freezed_annotation: ^2.4.2 - path_provider: ^2.1.1 - # permission_handler: ^11.1.0 - flutter_riverpod: ^2.4.9 - riverpod_annotation: ^2.3.3 - shared_preferences: ^2.2.2 - slang: ^3.31.0 - slang_flutter: ^3.31.0 - device_info_plus: ^10.1.0 - uuid: ^4.4.0 - equatable: ^2.0.5 - google_fonts: ^6.2.1 - -dev_dependencies: - flutter_test: - sdk: flutter - - # The "flutter_lints" package below contains a set of recommended lints to - # encourage good coding practices. The lint set provided by the package is - # activated in the `analysis_options.yaml` file located at the root of your - # package. See that file for information about deactivating specific lint - # rules and activating additional ones. - flutter_lints: ^4.0.0 - ffigen: ^12.0.0 - integration_test: - sdk: flutter - build_runner: ^2.4.8 - freezed: ^2.4.6 - riverpod_generator: ^2.3.9 - custom_lint: ^0.6.4 - riverpod_lint: ^2.3.7 - slang_build_runner: ^3.31.0 - flutter_launcher_icons: "^0.13.1" - innosetup: ^0.1.3 - - # For information on the generic Dart part of this file, see the - # following page: https://dart.dev/tools/pub/pubspec - - # The following section is specific to Flutter packages. - version: any -flutter: - # The following line ensures that the Material Icons font is - # included with your application, so that you can use the icons in - # the material Icons class. - uses-material-design: true - - # To add assets to your application, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg - assets: - - assets/icon/ - - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/assets-and-images/#resolution-aware - - # For details regarding adding assets from package dependencies, see - # https://flutter.dev/assets-and-images/#from-packages - - # To add custom fonts to your application, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts from package dependencies, - # see https://flutter.dev/custom-fonts/#from-packages +name: 'localsend_rs' +version: '1.0.0+1' +publish_to: none +environment: + sdk: '>=3.2.0 <4.0.0' +description: A Rust Implementation of LocalSend protocl v2 for better performance +dependencies: + animations: ^2.0.11 + cupertino_icons: '^1.0.2' + chinese_font_library: ^1.2.0 + device_info_plus: '^10.1.0' + dio: '^5.4.0' + equatable: '^2.0.5' + file_picker: ^8.0.6 + filesize: ^2.0.1 + flutter: + sdk: flutter + flutter_localizations: + sdk: flutter + flutter_riverpod: '^2.4.9' + flutter_rust_bridge: '^2.1.0' + freezed_annotation: '^2.4.2' + logger: '^2.3.0' + path_provider: '^2.1.1' + riverpod_annotation: '^2.3.3' + rust_builder: + path: 'rust_builder' + shared_preferences: '^2.2.2' + slang: '^3.31.0' + slang_flutter: '^3.31.0' + uuid: '^4.4.0' + window_manager: '^0.3.9' + simple_icons: ^10.1.3 +dev_dependencies: + build_runner: '^2.4.8' + custom_lint: '^0.6.4' + ffigen: '^12.0.0' + flutter_launcher_icons: '^0.13.1' + flutter_lints: '^4.0.0' + flutter_test: + sdk: flutter + freezed: '^2.4.6' + innosetup: '^0.1.3' + integration_test: + sdk: flutter + pubspec_dependency_sorter: '^1.0.5' + riverpod_generator: '^2.3.9' + riverpod_lint: '^2.3.7' + slang_build_runner: '^3.31.0' + version: any +flutter: + uses-material-design: true + assets: + - assets/icon/ diff --git a/rust/src/actor/core.rs b/rust/src/actor/core.rs index 3bb2274..14c6bc2 100644 --- a/rust/src/actor/core.rs +++ b/rust/src/actor/core.rs @@ -10,9 +10,10 @@ use super::{ #[derive(Clone)] pub struct CoreConfig { pub port: u16, - pub interface_addr: Ipv4Addr, - pub multicast_addr: Ipv4Addr, + pub interface_addr: String, + pub multicast_addr: String, pub multicast_port: u16, + pub store_path: String, } struct AppContext { @@ -23,9 +24,10 @@ impl CoreConfig { fn default() -> Self { CoreConfig { port: 8080, - interface_addr: Ipv4Addr::new(0, 0, 0, 0), - multicast_addr: Ipv4Addr::new(224, 0, 0, 167), + interface_addr: "0.0.0.0".to_string(), + multicast_addr: "224.0.0.167".to_string(), multicast_port: 53317, + store_path: "./".to_string(), } } } @@ -58,15 +60,16 @@ enum CoreMessage { } impl CoreActor { - fn new(receiver: mpsc::Receiver, device: NodeDevice) -> Self { + fn new( + receiver: mpsc::Receiver, + device: NodeDevice, + mut config: CoreConfig, + ) -> Self { let (tx, rx) = watch::channel(false); - let mut core_config = CoreConfig::default(); - core_config.port = device.port; + config.port = device.port; CoreActor { receiver, - context: AppContext { - config: core_config, - }, + context: AppContext { config }, server: None, server_state_sender: tx, server_state_listener: rx, @@ -126,9 +129,9 @@ pub struct CoreActorHandle { } impl CoreActorHandle { - pub fn new(device: NodeDevice) -> Self { + pub fn new(device: NodeDevice, config: CoreConfig) -> Self { let (sender, receiver) = mpsc::channel(8); - let actor = CoreActor::new(receiver, device.clone()); + let actor = CoreActor::new(receiver, device.clone(), config); tokio::spawn(run_context_actor(actor)); let device = DeviceActorHandle::new(device); @@ -178,9 +181,9 @@ impl CoreActorHandle { self.change_config(value).await; } - pub async fn change_address(&self, addr: String) { + pub async fn change_path(&self, path: String) { let mut value = self.get_config().await; - value.interface_addr = Ipv4Addr::from_str(&addr).unwrap(); + value.store_path = path; self.change_config(value).await; } diff --git a/rust/src/actor/discovery.rs b/rust/src/actor/discovery.rs index b5bb1a3..1fe51e6 100644 --- a/rust/src/actor/discovery.rs +++ b/rust/src/actor/discovery.rs @@ -1,5 +1,7 @@ use std::net::IpAddr; +use std::net::Ipv4Addr; use std::net::SocketAddr; +use std::str::FromStr; use log::{debug, info}; use tokio::sync::mpsc; @@ -47,8 +49,8 @@ async fn register(current: NodeDevice, target: NodeDevice) -> bool { } async fn announce(config: CoreConfig, current: String) { - let interface_addr = config.interface_addr; - let multicast_addr = config.multicast_addr; + let interface_addr = Ipv4Addr::from_str(&config.interface_addr).unwrap(); + let multicast_addr = Ipv4Addr::from_str(&config.multicast_addr).unwrap(); let multicast_port = config.multicast_port; let send_socket: UdpSocket = UdpSocket::bind((interface_addr, multicast_port + 2)) @@ -72,8 +74,8 @@ async fn announce(config: CoreConfig, current: String) { async fn run_udp_actor(mut actor: DiscoverActor, shutdown_callback: watch::Sender) { let config = actor.core.get_config().await; - let interface_addr = config.interface_addr; - let multicast_addr = config.multicast_addr; + let interface_addr = Ipv4Addr::from_str(&config.interface_addr).unwrap(); + let multicast_addr = Ipv4Addr::from_str(&config.multicast_addr).unwrap(); let multicast_port = config.multicast_port; info!("udp service {} started", multicast_port); diff --git a/rust/src/actor/mission/transfer.rs b/rust/src/actor/mission/transfer.rs index f8786db..6ca9292 100644 --- a/rust/src/actor/mission/transfer.rs +++ b/rust/src/actor/mission/transfer.rs @@ -20,16 +20,10 @@ enum Message { respond_to: oneshot::Sender, String>>, }, StartTask { - id: String, token: String, respond_to: oneshot::Sender, FileInfo), String>>, }, - Finish { - id: String, - respond_to: oneshot::Sender<()>, - }, StateTask { - id: String, token: String, state: FileState, respond_to: oneshot::Sender<()>, @@ -86,6 +80,43 @@ impl Actor { }; Actor { receiver, store } } + fn check_finish(&self) -> bool { + for (_, file) in self.store.mission.clone().unwrap().files { + match file.state { + FileState::Finish => {} + FileState::Skip => {} + _ => { + return false; + } + } + } + return true; + } + fn change_file_state(&mut self, token: String, state: FileState) { + let mut file = self + .store + .mission + .clone() + .unwrap() + .files + .get(&token) + .unwrap() + .clone(); + file.state = state; + self.store + .mission + .as_mut() + .unwrap() + .files + .insert(token, file.clone()); + } + async fn finish_mission(&mut self, state: MissionState) { + let mut mission = self.store.mission.take().unwrap(); + mission.state = state; + MISSION_NOTIFY + .notify(Some(MissionInfo::from_transfer_mission(mission))) + .await; + } async fn handle_message(&mut self, msg: Message) { match msg { Message::Add { @@ -99,14 +130,14 @@ impl Actor { } let mut files = HashMap::new(); - let token_map = mission.token_map.clone(); + let token_map = mission.id_token_map.clone(); let info_map = mission.info_map.clone(); for (k, v) in token_map { let info = MissionFileInfo { state: FileState::Pending, - info: info_map.get(&v).unwrap().clone(), + info: info_map.get(&k).unwrap().clone(), }; - files.insert(k, info); + files.insert(v, info); } let transfer_mission = TransferMission { @@ -122,16 +153,8 @@ impl Actor { .await; let _ = respond_to.send(Ok(())); } - Message::StartTask { - id, - token, - respond_to, - } => { + Message::StartTask { token, respond_to } => { let mission = self.store.mission.as_mut().unwrap(); - if mission.id != id { - let _ = respond_to.send(Err("mission not exist".to_string())); - return; - } if !mission.files.contains_key(&token) { let _ = respond_to.send(Err("task token not exist".to_string())); return; @@ -150,41 +173,38 @@ impl Actor { }; self.store.task.replace(task); - + MISSION_NOTIFY + .notify(Some(MissionInfo::from_transfer_mission( + self.store.mission.clone().unwrap(), + ))) + .await; let _ = respond_to.send(Ok((tx, file.info))); } - Message::Finish { id, respond_to } => { - match &self.store.mission { - Some(mission) => { - if mission.id == id { - let mut mission = self.store.mission.take().unwrap(); - mission.state = MissionState::Finished; - MISSION_NOTIFY - .notify(Some(MissionInfo::from_transfer_mission(mission))) - .await; - } - } - None => {} - } - - let _ = respond_to.send(()); - } Message::StateTask { - id, - token: _, - state: _, + token, + state, respond_to, } => { - match &self.store.mission { - Some(mission) => { - if mission.id == id { - let mut mission = self.store.mission.take().unwrap(); - mission.state = MissionState::Canceled; + if self.store.mission.is_some() { + self.change_file_state(token, state.clone()); + match state { + FileState::Skip | FileState::Finish => { + let finish = self.check_finish(); + if finish { + self.finish_mission(MissionState::Finished).await; + } + } + FileState::Fail { msg: _ } => { + self.finish_mission(MissionState::Failed).await; + } + _ => { MISSION_NOTIFY - .notify(Some(MissionInfo::from_transfer_mission(mission))); + .notify(Some(MissionInfo::from_transfer_mission( + self.store.mission.clone().unwrap(), + ))) + .await; } } - None => {} } let _ = respond_to.send(()); @@ -196,7 +216,8 @@ impl Actor { let mut mission = self.store.mission.take().unwrap(); mission.state = MissionState::Canceled; MISSION_NOTIFY - .notify(Some(MissionInfo::from_transfer_mission(mission))); + .notify(Some(MissionInfo::from_transfer_mission(mission))) + .await; } } None => {} @@ -250,12 +271,10 @@ impl Handle { pub async fn start_task( &self, - id: String, token: String, ) -> Result<(watch::Sender, FileInfo), String> { let (send, recv) = oneshot::channel(); let msg = Message::StartTask { - id, token, respond_to: send, }; @@ -279,10 +298,9 @@ impl Handle { recv.await.expect("Actor task has been killed") } - pub async fn fail_file(&self, id: String, token: String, state: FileState) { + pub async fn state_task(&self, token: String, state: FileState) { let (send, recv) = oneshot::channel(); let msg = Message::StateTask { - id, token, state, respond_to: send, @@ -292,18 +310,6 @@ impl Handle { recv.await.expect("Actor task has been killed"); } - pub async fn finish(&self, id: String) { - let (send, recv) = oneshot::channel(); - let msg = Message::Finish { - id: id, - respond_to: send, - }; - - let _ = self.sender.send(msg).await; - - recv.await.expect("Actor task has been killed"); - } - pub async fn add(&self, mission: Mission) -> Result<(), MissionState> { let (send, recv) = oneshot::channel(); let msg = Message::Add { diff --git a/rust/src/actor/model.rs b/rust/src/actor/model.rs index 84bc18c..d0e21ce 100644 --- a/rust/src/actor/model.rs +++ b/rust/src/actor/model.rs @@ -72,27 +72,27 @@ impl NodeDevice { pub struct Mission { pub id: String, pub sender: NodeDevice, - pub token_map: HashMap, - pub reverse_token_map: HashMap, + pub id_token_map: HashMap, + pub token_id_map: HashMap, pub info_map: HashMap, } impl Mission { pub fn new(info_map: HashMap, sender: NodeDevice) -> Self { let id = uuid::Uuid::new_v4().to_string(); - let mut token_map = HashMap::new(); - let mut reverse_token_map = HashMap::new(); - info_map.iter().for_each(|(key, _value)| { + let mut id_token_map = HashMap::new(); + let mut token_id_map = HashMap::new(); + info_map.iter().for_each(|(id, _value)| { let token = uuid::Uuid::new_v4().to_string(); - reverse_token_map.insert(key.clone(), token.clone()); - token_map.insert(token.clone(), key.clone()); + id_token_map.insert(id.clone(), token.clone()); + token_id_map.insert(token.clone(), id.clone()); }); Mission { id: id.clone(), sender, - token_map, - reverse_token_map, + id_token_map, + token_id_map, info_map: info_map.clone(), } } diff --git a/rust/src/api/v2.rs b/rust/src/api/v2.rs index ecf39f4..7ae1c75 100644 --- a/rust/src/api/v2.rs +++ b/rust/src/api/v2.rs @@ -22,6 +22,7 @@ use tokio_util::io::StreamReader; use crate::{ actor::{ core::CoreActorHandle, + mission::FileState, model::{Mission, MissionState, NodeAnnounce, NodeDevice}, }, util::ProgressWriteAdapter, @@ -56,7 +57,8 @@ impl Drop for Guard { } async fn stream_to_file( - path: &str, + dir: &str, + file_name: &str, stream: S, progress: watch::Sender, ) -> Result<(), (StatusCode, String)> @@ -72,13 +74,13 @@ where futures::pin_mut!(body_reader); // Create the file. `File` implements `AsyncWrite`. - let path = std::path::Path::new("./").join(path); - let dir = path.parent().unwrap(); - if dir.exists() == false { - tokio::fs::create_dir_all(dir).await?; + let file_path = std::path::Path::new(dir).join(file_name); + let store_dir = file_path.parent().unwrap(); + if store_dir.exists() == false { + tokio::fs::create_dir_all(store_dir).await?; } - let file = BufWriter::new(File::create(path).await?); + let file = BufWriter::new(File::create(file_path).await?); let mut writer = ProgressWriteAdapter::new(file, progress); // Copy the body into the file. @@ -99,8 +101,9 @@ async fn handle_upload( debug!("handle_upload {:?}", task); let handle = state.core.mission.transfer.clone(); + let store_path = state.core.get_config().await.store_path; - let res = handle.start_task(task.session_id, task.token).await; + let res = handle.start_task(task.token.clone()).await; match res { Ok((tx, file)) => { @@ -108,11 +111,21 @@ async fn handle_upload( // ... let body_stream = request.into_body().into_data_stream(); - let res = stream_to_file(&file_name, body_stream, tx).await; + let res = stream_to_file(&store_path, &file_name, body_stream, tx).await; match res { - Ok(_) => Ok(()), - Err(e) => Err(e), + Ok(_) => { + handle + .state_task(task.token.clone(), FileState::Finish) + .await; + Ok(()) + } + Err(e) => { + handle + .state_task(task.token, FileState::Fail { msg: e.1.clone() }) + .await; + Err(e) + } } } Err(_) => todo!(), @@ -177,7 +190,7 @@ async fn pending_mission( let result = match *state_rx.borrow_and_update() { MissionState::Transfering => Ok(Json(FileResponse { session_id: mission.id, - files: mission.reverse_token_map, + files: mission.id_token_map, })), MissionState::Busy => { debug!("core is resolving another mission"); diff --git a/rust/src/bridge.rs b/rust/src/bridge.rs index 2ace3d8..5624cf1 100644 --- a/rust/src/bridge.rs +++ b/rust/src/bridge.rs @@ -1,5 +1,9 @@ -use std::net::{IpAddr, SocketAddr}; +use std::{ + net::{IpAddr, Ipv4Addr, SocketAddr}, + str::FromStr as _, +}; +use axum::extract::path; use lazy_static::lazy_static; use log::debug; use tokio::{net::UdpSocket, sync::OnceCell}; @@ -22,9 +26,9 @@ fn _get_core() -> CoreActorHandle { CORE.get().unwrap().clone() } -pub async fn setup(device: NodeDevice) { +pub async fn setup(device: NodeDevice, config: CoreConfig) { logger::init_logger(true); - let _ = CORE.set(CoreActorHandle::new(device)); + let _ = CORE.set(CoreActorHandle::new(device, config)); _get_core().start().await; } @@ -45,12 +49,15 @@ pub async fn shutdown_server() { _get_core().shutdown().await; } -pub async fn change_address(addr: String) { +pub async fn restart_server() { _get_core().shutdown().await; - _get_core().change_address(addr).await; _get_core().start().await; } +pub async fn change_path(path: String) { + _get_core().change_path(path).await; +} + pub async fn change_config(config: CoreConfig) { _get_core().change_config(config).await; } @@ -92,8 +99,8 @@ pub fn create_log_stream(s: StreamSink) { pub async fn announce() { let config = _get_core().get_config().await; - let interface_addr = config.interface_addr; - let multicast_addr = config.multicast_addr; + let interface_addr = Ipv4Addr::from_str(&config.interface_addr).unwrap(); + let multicast_addr = Ipv4Addr::from_str(&config.multicast_addr).unwrap(); let multicast_port = config.multicast_port; _get_core().device.clear_devices().await; diff --git a/rust/src/frb_generated.rs b/rust/src/frb_generated.rs index ee7c465..792e690 100644 --- a/rust/src/frb_generated.rs +++ b/rust/src/frb_generated.rs @@ -25,7 +25,6 @@ // Section: imports -use crate::actor::core::*; use flutter_rust_bridge::for_generated::byteorder::{NativeEndian, ReadBytesExt, WriteBytesExt}; use flutter_rust_bridge::for_generated::{transform_result_dco, Lifetimeable, Lockable}; use flutter_rust_bridge::{Handler, IntoIntoDart}; @@ -38,7 +37,7 @@ flutter_rust_bridge::frb_generated_boilerplate!( default_rust_auto_opaque = RustAutoOpaqueMoi, ); pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_VERSION: &str = "2.1.0"; -pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = 2101766676; +pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = -1293166539; // Section: executor @@ -159,7 +158,7 @@ fn wire__crate__bridge__cancel_pending_impl( }, ) } -fn wire__crate__bridge__change_address_impl( +fn wire__crate__bridge__change_config_impl( port_: flutter_rust_bridge::for_generated::MessagePort, ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, rust_vec_len_: i32, @@ -167,7 +166,7 @@ fn wire__crate__bridge__change_address_impl( ) { FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( flutter_rust_bridge::for_generated::TaskInfo { - debug_name: "change_address", + debug_name: "change_config", port: Some(port_), mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, }, @@ -181,13 +180,13 @@ fn wire__crate__bridge__change_address_impl( }; let mut deserializer = flutter_rust_bridge::for_generated::SseDeserializer::new(message); - let api_addr = ::sse_decode(&mut deserializer); + let api_config = ::sse_decode(&mut deserializer); deserializer.end(); move |context| async move { transform_result_sse::<_, ()>( (move || async move { let output_ok = Result::<_, ()>::Ok({ - crate::bridge::change_address(api_addr).await; + crate::bridge::change_config(api_config).await; })?; Ok(output_ok) })() @@ -197,7 +196,7 @@ fn wire__crate__bridge__change_address_impl( }, ) } -fn wire__crate__bridge__change_config_impl( +fn wire__crate__bridge__change_path_impl( port_: flutter_rust_bridge::for_generated::MessagePort, ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, rust_vec_len_: i32, @@ -205,7 +204,7 @@ fn wire__crate__bridge__change_config_impl( ) { FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( flutter_rust_bridge::for_generated::TaskInfo { - debug_name: "change_config", + debug_name: "change_path", port: Some(port_), mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, }, @@ -219,13 +218,13 @@ fn wire__crate__bridge__change_config_impl( }; let mut deserializer = flutter_rust_bridge::for_generated::SseDeserializer::new(message); - let api_config = ::sse_decode(&mut deserializer); + let api_path = ::sse_decode(&mut deserializer); deserializer.end(); move |context| async move { transform_result_sse::<_, ()>( (move || async move { let output_ok = Result::<_, ()>::Ok({ - crate::bridge::change_config(api_config).await; + crate::bridge::change_path(api_path).await; })?; Ok(output_ok) })() @@ -433,6 +432,43 @@ fn wire__crate__bridge__listen_server_state_impl( }, ) } +fn wire__crate__bridge__restart_server_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::TaskInfo { + debug_name: "restart_server", + 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); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, ()>( + (move || async move { + let output_ok = Result::<_, ()>::Ok({ + crate::bridge::restart_server().await; + })?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} fn wire__crate__bridge__setup_impl( port_: flutter_rust_bridge::for_generated::MessagePort, ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, @@ -456,12 +492,13 @@ fn wire__crate__bridge__setup_impl( let mut deserializer = flutter_rust_bridge::for_generated::SseDeserializer::new(message); let api_device = ::sse_decode(&mut deserializer); + let api_config = ::sse_decode(&mut deserializer); deserializer.end(); move |context| async move { transform_result_sse::<_, ()>( (move || async move { let output_ok = Result::<_, ()>::Ok({ - crate::bridge::setup(api_device).await; + crate::bridge::setup(api_device, api_config).await; })?; Ok(output_ok) })() @@ -546,12 +583,6 @@ fn wire__crate__bridge__start_server_impl( ) } -// Section: related_funcs - -flutter_rust_bridge::frb_generated_moi_arc_impl_value!( - flutter_rust_bridge::for_generated::RustAutoOpaqueInner -); - // Section: dart2rust impl SseDecode for flutter_rust_bridge::for_generated::anyhow::Error { @@ -562,26 +593,6 @@ impl SseDecode for flutter_rust_bridge::for_generated::anyhow::Error { } } -impl SseDecode for CoreConfig { - // Codec=Sse (Serialization based), see doc to use other codecs - fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { - let mut inner = , - >>::sse_decode(deserializer); - return flutter_rust_bridge::for_generated::rust_auto_opaque_decode_owned(inner); - } -} - -impl SseDecode - for RustOpaqueMoi> -{ - // Codec=Sse (Serialization based), see doc to use other codecs - fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { - let mut inner = ::sse_decode(deserializer); - return decode_rust_opaque_moi(inner); - } -} - impl SseDecode for StreamSink { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { @@ -641,6 +652,24 @@ impl SseDecode for bool { } } +impl SseDecode for crate::actor::core::CoreConfig { + // 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_port = ::sse_decode(deserializer); + let mut var_interfaceAddr = ::sse_decode(deserializer); + let mut var_multicastAddr = ::sse_decode(deserializer); + let mut var_multicastPort = ::sse_decode(deserializer); + let mut var_storePath = ::sse_decode(deserializer); + return crate::actor::core::CoreConfig { + port: var_port, + interface_addr: var_interfaceAddr, + multicast_addr: var_multicastAddr, + multicast_port: var_multicastPort, + store_path: var_storePath, + }; + } +} + impl SseDecode for crate::api::model::FileInfo { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { @@ -886,13 +915,6 @@ impl SseDecode for () { fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {} } -impl SseDecode for usize { - // Codec=Sse (Serialization based), see doc to use other codecs - fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { - deserializer.cursor.read_u64::().unwrap() as _ - } -} - fn pde_ffi_dispatcher_primary_impl( func_id: i32, port: flutter_rust_bridge::for_generated::MessagePort, @@ -905,16 +927,17 @@ fn pde_ffi_dispatcher_primary_impl( 1 => wire__crate__bridge__accept_pending_impl(port, ptr, rust_vec_len, data_len), 2 => wire__crate__bridge__announce_impl(port, ptr, rust_vec_len, data_len), 3 => wire__crate__bridge__cancel_pending_impl(port, ptr, rust_vec_len, data_len), - 4 => wire__crate__bridge__change_address_impl(port, ptr, rust_vec_len, data_len), - 5 => wire__crate__bridge__change_config_impl(port, ptr, rust_vec_len, data_len), + 4 => wire__crate__bridge__change_config_impl(port, ptr, rust_vec_len, data_len), + 5 => wire__crate__bridge__change_path_impl(port, ptr, rust_vec_len, data_len), 6 => wire__crate__bridge__clear_mission_impl(port, ptr, rust_vec_len, data_len), 7 => wire__crate__bridge__create_log_stream_impl(port, ptr, rust_vec_len, data_len), 8 => wire__crate__bridge__listen_device_impl(port, ptr, rust_vec_len, data_len), 9 => wire__crate__bridge__listen_mission_impl(port, ptr, rust_vec_len, data_len), 10 => wire__crate__bridge__listen_server_state_impl(port, ptr, rust_vec_len, data_len), - 11 => wire__crate__bridge__setup_impl(port, ptr, rust_vec_len, data_len), - 12 => wire__crate__bridge__shutdown_server_impl(port, ptr, rust_vec_len, data_len), - 13 => wire__crate__bridge__start_server_impl(port, ptr, rust_vec_len, data_len), + 11 => wire__crate__bridge__restart_server_impl(port, ptr, rust_vec_len, data_len), + 12 => wire__crate__bridge__setup_impl(port, ptr, rust_vec_len, data_len), + 13 => wire__crate__bridge__shutdown_server_impl(port, ptr, rust_vec_len, data_len), + 14 => wire__crate__bridge__start_server_impl(port, ptr, rust_vec_len, data_len), _ => unreachable!(), } } @@ -934,20 +957,29 @@ fn pde_ffi_dispatcher_sync_impl( // Section: rust2dart // Codec=Dco (DartCObject based), see doc to use other codecs -impl flutter_rust_bridge::IntoDart for FrbWrapper { +impl flutter_rust_bridge::IntoDart for crate::actor::core::CoreConfig { fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { - flutter_rust_bridge::for_generated::rust_auto_opaque_encode::<_, MoiArc<_>>(self.0) - .into_dart() + [ + self.port.into_into_dart().into_dart(), + self.interface_addr.into_into_dart().into_dart(), + self.multicast_addr.into_into_dart().into_dart(), + self.multicast_port.into_into_dart().into_dart(), + self.store_path.into_into_dart().into_dart(), + ] + .into_dart() } } -impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive for FrbWrapper {} - -impl flutter_rust_bridge::IntoIntoDart> for CoreConfig { - fn into_into_dart(self) -> FrbWrapper { - self.into() +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive + for crate::actor::core::CoreConfig +{ +} +impl flutter_rust_bridge::IntoIntoDart + for crate::actor::core::CoreConfig +{ + fn into_into_dart(self) -> crate::actor::core::CoreConfig { + self } } - // Codec=Dco (DartCObject based), see doc to use other codecs impl flutter_rust_bridge::IntoDart for crate::api::model::FileInfo { fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { @@ -1124,24 +1156,6 @@ impl SseEncode for flutter_rust_bridge::for_generated::anyhow::Error { } } -impl SseEncode for CoreConfig { - // Codec=Sse (Serialization based), see doc to use other codecs - fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { - >>::sse_encode(flutter_rust_bridge::for_generated::rust_auto_opaque_encode::<_, MoiArc<_>>(self), serializer); - } -} - -impl SseEncode - for RustOpaqueMoi> -{ - // Codec=Sse (Serialization based), see doc to use other codecs - fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { - let (ptr, size) = self.sse_encode_raw(); - ::sse_encode(ptr, serializer); - ::sse_encode(size, serializer); - } -} - impl SseEncode for StreamSink { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { @@ -1196,6 +1210,17 @@ impl SseEncode for bool { } } +impl SseEncode for crate::actor::core::CoreConfig { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.port, serializer); + ::sse_encode(self.interface_addr, serializer); + ::sse_encode(self.multicast_addr, serializer); + ::sse_encode(self.multicast_port, serializer); + ::sse_encode(self.store_path, serializer); + } +} + impl SseEncode for crate::api::model::FileInfo { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { @@ -1394,16 +1419,6 @@ impl SseEncode for () { fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {} } -impl SseEncode for usize { - // Codec=Sse (Serialization based), see doc to use other codecs - fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { - serializer - .cursor - .write_u64::(self as _) - .unwrap(); - } -} - #[cfg(not(target_family = "wasm"))] mod io { // This file is automatically generated, so please do not edit it. @@ -1412,7 +1427,6 @@ mod io { // Section: imports use super::*; - use crate::actor::core::*; use flutter_rust_bridge::for_generated::byteorder::{ NativeEndian, ReadBytesExt, WriteBytesExt, }; @@ -1422,20 +1436,6 @@ mod io { // Section: boilerplate flutter_rust_bridge::frb_generated_boilerplate_io!(); - - #[no_mangle] - pub extern "C" fn frbgen_localsend_rs_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCoreConfig( - ptr: *const std::ffi::c_void, - ) { - MoiArc::>::increment_strong_count(ptr as _); - } - - #[no_mangle] - pub extern "C" fn frbgen_localsend_rs_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCoreConfig( - ptr: *const std::ffi::c_void, - ) { - MoiArc::>::decrement_strong_count(ptr as _); - } } #[cfg(not(target_family = "wasm"))] pub use io::*; diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 8b6d468..d6b86fa 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -6,6 +6,12 @@ #include "generated_plugin_registrant.h" +#include +#include void RegisterPlugins(flutter::PluginRegistry* registry) { + ScreenRetrieverPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("ScreenRetrieverPlugin")); + WindowManagerPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("WindowManagerPlugin")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 7f4127b..94b9088 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -3,6 +3,8 @@ # list(APPEND FLUTTER_PLUGIN_LIST + screen_retriever + window_manager ) list(APPEND FLUTTER_FFI_PLUGIN_LIST