diff --git a/lib/common/consts.dart b/lib/common/consts.dart index a428b68..4170031 100644 --- a/lib/common/consts.dart +++ b/lib/common/consts.dart @@ -83,6 +83,7 @@ const defaultSortDirection = HabitDisplaySortDirection.asc; const defaultHabitsRecordScrollBehavior = HabitsRecordScrollBehavior.scrollable; const defaultFirstDay = DateTime.monday; const defaultAppReminder = AppReminderConfig.off; +const defaultAppSyncTimeout = Duration(seconds: 60); //#endregion //#region habit-field diff --git a/lib/model/app_sync_server.dart b/lib/model/app_sync_server.dart index 350d883..59a39d1 100644 --- a/lib/model/app_sync_server.dart +++ b/lib/model/app_sync_server.dart @@ -25,12 +25,13 @@ part 'app_sync_server.g.dart'; enum AppSyncServerType implements EnumWithDBCode { unknown(code: 0), webdav( - code: 1, - includePathField: true, - includeUsernameField: true, - includePasswordField: true, - includeIgnoreSSLField: true, - ), + code: 1, + includePathField: true, + includeUsernameField: true, + includePasswordField: true, + includeIgnoreSSLField: true, + includeConnTimeoutField: true, + includeRetryCountFIeld: true), fake(code: 99); final int code; @@ -38,14 +39,17 @@ enum AppSyncServerType implements EnumWithDBCode { final bool includeUsernameField; final bool includePasswordField; final bool includeIgnoreSSLField; - - const AppSyncServerType({ - required this.code, - this.includePathField = false, - this.includeUsernameField = false, - this.includePasswordField = false, - this.includeIgnoreSSLField = false, - }); + final bool includeConnTimeoutField; + final bool includeRetryCountFIeld; + + const AppSyncServerType( + {required this.code, + this.includePathField = false, + this.includeUsernameField = false, + this.includePasswordField = false, + this.includeIgnoreSSLField = false, + this.includeConnTimeoutField = false, + this.includeRetryCountFIeld = false}); @override int get dbCode => code; @@ -115,7 +119,7 @@ abstract interface class AppSyncServer implements JsonAdaptor { Iterable get syncMobileNetworks; bool get syncInLowData; bool get ignoreSSL; - DateTime? get timeout; + Duration? get timeout; bool get verified; bool get configed; @@ -143,7 +147,7 @@ final class AppWebDavSyncServer implements AppSyncServer { @override final bool syncInLowData; @override - final DateTime? timeout; + final Duration? timeout; @override final bool verified; @override @@ -155,7 +159,7 @@ final class AppWebDavSyncServer implements AppSyncServer { final String username; final String password; final int? maxRetryCount; - final DateTime? connectTimeout; + final Duration? connectTimeout; const AppWebDavSyncServer({ required this.identity, @@ -183,8 +187,8 @@ final class AppWebDavSyncServer implements AppSyncServer { Iterable? syncMobileNetworks, bool syncInLowData = true, bool ignoreSSL = false, - DateTime? timeout, - DateTime? connectTimeout, + Duration? timeout, + Duration? connectTimeout, int? maxRetryCount, }) { final now = DateTime.now(); @@ -246,14 +250,17 @@ final class AppWebDavSyncServer implements AppSyncServer { @override AppSyncServerForm toForm() => AppSyncServerForm( - uuid: UuidValue.fromString(identity), - createTime: createTime, - modifyTime: modifyTime, - type: type, - path: path.toString(), - username: username, - password: password, - ); + uuid: UuidValue.fromString(identity), + createTime: createTime, + modifyTime: modifyTime, + type: type, + path: path.toString(), + username: username, + password: password, + ignoreSSL: ignoreSSL, + timeout: timeout, + connectTimeout: connectTimeout, + retryCount: maxRetryCount); @override String toDebugString() { @@ -298,7 +305,7 @@ final class AppFakeSyncServer implements AppSyncServer { @override final bool syncInLowData; @override - final DateTime? timeout; + final Duration? timeout; @override final bool verified; @override @@ -327,8 +334,8 @@ final class AppFakeSyncServer implements AppSyncServer { List? syncMobileNetworks, bool syncInLowData = true, bool ignoreSSL = false, - DateTime? timeout, - DateTime? connectTimeout, + Duration? timeout, + Duration? connectTimeout, int? maxRetryCount, }) { final now = DateTime.now(); @@ -379,11 +386,17 @@ final class AppFakeSyncServer implements AppSyncServer { @override AppSyncServerForm toForm() => AppSyncServerForm( - uuid: UuidValue.fromString(identity), - createTime: createTime, - modifyTime: modifyTime, - type: type, - ); + uuid: UuidValue.fromString(identity), + createTime: createTime, + modifyTime: modifyTime, + type: type, + path: null, + username: null, + password: null, + ignoreSSL: ignoreSSL, + timeout: timeout, + connectTimeout: null, + retryCount: null); @override Map toJson() => _$AppFakeSyncServerToJson(this); @@ -401,21 +414,28 @@ class AppSyncServerForm { String? username; String? password; bool? ignoreSSL; + Duration? timeout; + Duration? connectTimeout; + int? retryCount; AppSyncServerForm({ required this.uuid, required this.type, required this.createTime, required this.modifyTime, - this.path, - this.username, - this.password, - this.ignoreSSL, + required this.path, + required this.username, + required this.password, + required this.ignoreSSL, + required this.timeout, + required this.connectTimeout, + required this.retryCount, }); @override String toString() => 'AppSyncServerForm(uuid=$uuid,type=$type,' 'createTime=$createTime,modifyTime=$modifyTime,' 'path=$path,username=$username,password=$password,' - 'ignoreSSL=$ignoreSSL)'; + 'ignoreSSL=$ignoreSSL,timeout=$timeout,' + 'connectTimeout=$connectTimeout,retryCount=$retryCount)'; } diff --git a/lib/model/app_sync_server.g.dart b/lib/model/app_sync_server.g.dart index 9a925ee..e31d31b 100644 --- a/lib/model/app_sync_server.g.dart +++ b/lib/model/app_sync_server.g.dart @@ -20,9 +20,9 @@ abstract class _$AppWebDavSyncServerCWProxy { Uri? path, String? username, String? password, - DateTime? timeout, + Duration? timeout, int? maxRetryCount, - DateTime? connectTimeout, + Duration? connectTimeout, bool? verified, bool? configed, Iterable? syncMobileNetworks, @@ -91,7 +91,7 @@ class _$AppWebDavSyncServerCWProxyImpl implements _$AppWebDavSyncServerCWProxy { timeout: timeout == const $CopyWithPlaceholder() ? _value.timeout // ignore: cast_nullable_to_non_nullable - : timeout as DateTime?, + : timeout as Duration?, maxRetryCount: maxRetryCount == const $CopyWithPlaceholder() ? _value.maxRetryCount // ignore: cast_nullable_to_non_nullable @@ -99,7 +99,7 @@ class _$AppWebDavSyncServerCWProxyImpl implements _$AppWebDavSyncServerCWProxy { connectTimeout: connectTimeout == const $CopyWithPlaceholder() ? _value.connectTimeout // ignore: cast_nullable_to_non_nullable - : connectTimeout as DateTime?, + : connectTimeout as Duration?, verified: verified == const $CopyWithPlaceholder() || verified == null ? _value.verified // ignore: cast_nullable_to_non_nullable @@ -147,7 +147,7 @@ abstract class _$AppFakeSyncServerCWProxy { DateTime? modifyTime, bool? ignoreSSL, bool? syncInLowData, - DateTime? timeout, + Duration? timeout, bool? verified, bool? configed, List? syncMobileNetworks, @@ -211,7 +211,7 @@ class _$AppFakeSyncServerCWProxyImpl implements _$AppFakeSyncServerCWProxy { timeout: timeout == const $CopyWithPlaceholder() ? _value.timeout // ignore: cast_nullable_to_non_nullable - : timeout as DateTime?, + : timeout as Duration?, verified: verified == const $CopyWithPlaceholder() || verified == null ? _value.verified // ignore: cast_nullable_to_non_nullable @@ -252,6 +252,9 @@ abstract class _$AppSyncServerFormCWProxy { String? username, String? password, bool? ignoreSSL, + Duration? timeout, + Duration? connectTimeout, + int? retryCount, }); } @@ -278,6 +281,9 @@ class _$AppSyncServerFormCWProxyImpl implements _$AppSyncServerFormCWProxy { Object? username = const $CopyWithPlaceholder(), Object? password = const $CopyWithPlaceholder(), Object? ignoreSSL = const $CopyWithPlaceholder(), + Object? timeout = const $CopyWithPlaceholder(), + Object? connectTimeout = const $CopyWithPlaceholder(), + Object? retryCount = const $CopyWithPlaceholder(), }) { return AppSyncServerForm( uuid: uuid == const $CopyWithPlaceholder() || uuid == null @@ -314,6 +320,18 @@ class _$AppSyncServerFormCWProxyImpl implements _$AppSyncServerFormCWProxy { ? _value.ignoreSSL // ignore: cast_nullable_to_non_nullable : ignoreSSL as bool?, + timeout: timeout == const $CopyWithPlaceholder() + ? _value.timeout + // ignore: cast_nullable_to_non_nullable + : timeout as Duration?, + connectTimeout: connectTimeout == const $CopyWithPlaceholder() + ? _value.connectTimeout + // ignore: cast_nullable_to_non_nullable + : connectTimeout as Duration?, + retryCount: retryCount == const $CopyWithPlaceholder() + ? _value.retryCount + // ignore: cast_nullable_to_non_nullable + : retryCount as int?, ); } } @@ -339,11 +357,11 @@ AppWebDavSyncServer _$AppWebDavSyncServerFromJson(Map json) => password: json['password'] as String, timeout: json['timeout'] == null ? null - : DateTime.parse(json['timeout'] as String), + : Duration(microseconds: (json['timeout'] as num).toInt()), maxRetryCount: (json['maxRetryCount'] as num?)?.toInt(), connectTimeout: json['connectTimeout'] == null ? null - : DateTime.parse(json['connectTimeout'] as String), + : Duration(microseconds: (json['connectTimeout'] as num).toInt()), verified: json['verified'] as bool, configed: json['configed'] as bool, syncMobileNetworks: (json['syncMobileNetworks'] as List) @@ -362,14 +380,14 @@ Map _$AppWebDavSyncServerToJson( 'type_': _$AppSyncServerTypeEnumMap[instance.type]!, 'ignoreSSL': instance.ignoreSSL, 'syncInLowData': instance.syncInLowData, - 'timeout': instance.timeout?.toIso8601String(), + 'timeout': instance.timeout?.inMicroseconds, 'verified': instance.verified, 'configed': instance.configed, 'path': instance.path.toString(), 'username': instance.username, 'password': instance.password, 'maxRetryCount': instance.maxRetryCount, - 'connectTimeout': instance.connectTimeout?.toIso8601String(), + 'connectTimeout': instance.connectTimeout?.inMicroseconds, 'syncMobileNetworks': instance.syncMobileNetworks .map((e) => _$AppSyncServerMobileNetworkEnumMap[e]!) .toList(), @@ -397,7 +415,7 @@ AppFakeSyncServer _$AppFakeSyncServerFromJson(Map json) => syncInLowData: json['syncInLowData'] as bool, timeout: json['timeout'] == null ? null - : DateTime.parse(json['timeout'] as String), + : Duration(microseconds: (json['timeout'] as num).toInt()), verified: json['verified'] as bool, configed: json['configed'] as bool, syncMobileNetworks: (json['syncMobileNetworks'] as List) @@ -414,7 +432,7 @@ Map _$AppFakeSyncServerToJson(AppFakeSyncServer instance) => 'type_': _$AppSyncServerTypeEnumMap[instance.type]!, 'ignoreSSL': instance.ignoreSSL, 'syncInLowData': instance.syncInLowData, - 'timeout': instance.timeout?.toIso8601String(), + 'timeout': instance.timeout?.inMicroseconds, 'verified': instance.verified, 'configed': instance.configed, 'syncMobileNetworks': instance.syncMobileNetworks diff --git a/lib/provider/app_sync_server_form.dart b/lib/provider/app_sync_server_form.dart index 91117ed..6e6eaf7 100644 --- a/lib/provider/app_sync_server_form.dart +++ b/lib/provider/app_sync_server_form.dart @@ -119,6 +119,17 @@ class AppSyncServerFormViewModel extends ChangeNotifier notifyListeners(); } + Duration? get timeout => _form.timeout; + set timeout(Duration? value) { + assert((value?.inSeconds ?? 0) >= 0); + if (value == timeout) return; + final oldValue = timeout; + _form.timeout = value; + appLog.value.info('$runtimeType.timeout', + beforeVal: oldValue?.inSeconds, afterVal: value?.inSeconds); + notifyListeners(); + } + Future<(String, String?)> getPassword({ Duration timeout = const Duration(seconds: 1), bool changeController = true, diff --git a/lib/view/for_app_sync_server_editor/_widget.dart b/lib/view/for_app_sync_server_editor/_widget.dart index 956406a..eb674bb 100644 --- a/lib/view/for_app_sync_server_editor/_widget.dart +++ b/lib/view/for_app_sync_server_editor/_widget.dart @@ -15,6 +15,7 @@ export './app_sync_server_ignoressl.dart'; export './app_sync_server_password.dart'; export './app_sync_server_path.dart'; +export './app_sync_server_timeout.dart'; export './app_sync_server_type.dart'; export './app_sync_server_username.dart'; export './page_providers.dart'; diff --git a/lib/view/for_app_sync_server_editor/app_sync_server_ignoressl.dart b/lib/view/for_app_sync_server_editor/app_sync_server_ignoressl.dart index 436d09e..419366b 100644 --- a/lib/view/for_app_sync_server_editor/app_sync_server_ignoressl.dart +++ b/lib/view/for_app_sync_server_editor/app_sync_server_ignoressl.dart @@ -18,10 +18,10 @@ import 'package:provider/provider.dart'; import '../../model/app_sync_server.dart'; import '../../provider/app_sync_server_form.dart'; -class AppSyncServerIgnoreSSL extends StatelessWidget { +class AppSyncServerIgnoreSSLTile extends StatelessWidget { final EdgeInsetsGeometry? contentPadding; - const AppSyncServerIgnoreSSL({super.key, this.contentPadding}); + const AppSyncServerIgnoreSSLTile({super.key, this.contentPadding}); @override Widget build(BuildContext context) { diff --git a/lib/view/for_app_sync_server_editor/app_sync_server_timeout.dart b/lib/view/for_app_sync_server_editor/app_sync_server_timeout.dart new file mode 100644 index 0000000..2102aa9 --- /dev/null +++ b/lib/view/for_app_sync_server_editor/app_sync_server_timeout.dart @@ -0,0 +1,53 @@ +// Copyright 2025 Fries_I23 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import 'dart:math' as math; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:provider/provider.dart'; + +import '../../common/consts.dart'; +import '../../provider/app_sync_server_form.dart'; + +class AppSyncServerTimeoutTile extends StatelessWidget { + final EdgeInsetsGeometry? contentPadding; + + const AppSyncServerTimeoutTile({super.key, this.contentPadding}); + + @override + Widget build(BuildContext context) { + final vm = context.read(); + return ListTile( + contentPadding: contentPadding, + title: TextField( + controller: TextEditingController.fromValue( + TextEditingValue(text: vm.timeout?.inSeconds.toString() ?? '')), + decoration: InputDecoration( + labelText: 'Sync Timeout Second', + hintText: 'Default: ${defaultAppSyncTimeout.inSeconds}s', + ), + keyboardType: const TextInputType.numberWithOptions( + signed: false, decimal: false), + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly, + LengthLimitingTextInputFormatter(4) + ], + onChanged: (value) => vm.timeout = value.isNotEmpty + ? Duration(seconds: math.max(int.parse(value), 0)) + : null, + ), + ); + } +} diff --git a/lib/view/page_app_sync_server_editor.dart b/lib/view/page_app_sync_server_editor.dart index d6a3b15..0c4d95e 100644 --- a/lib/view/page_app_sync_server_editor.dart +++ b/lib/view/page_app_sync_server_editor.dart @@ -139,7 +139,8 @@ class _AppSyncServerEditorFsDialog extends StatelessWidget { const AppSyncServerPathTile(), const AppSyncServerUsernameTile(), const AppSyncServerPasswordTile(), - const AppSyncServerIgnoreSSL(), + const AppSyncServerIgnoreSSLTile(), + const AppSyncServerTimeoutTile(), const _DebuggerTile(), ], ), @@ -176,7 +177,8 @@ class _AppSyncServerEditorDialog extends StatelessWidget { const Expanded(child: AppSyncServerPasswordTile()), ], ), - const AppSyncServerIgnoreSSL(), + const AppSyncServerIgnoreSSLTile(), + const AppSyncServerTimeoutTile(), const _DebuggerTile(), ], ), @@ -247,6 +249,7 @@ class _DebuggerTileState extends State<_DebuggerTile> { Text("Username: ${vm.usernameInputController.text}"), Text("Password: ${vm.passwordInputController.text}"), Text("IgnoreSSL: ${vm.ignoreSSL}"), + Text("Timeout: ${vm.timeout?.inSeconds}"), Text("Form: ${vm.formSnapshot}"), ], ),