From e5f06f650a64008923bd5436b920dc143f893e72 Mon Sep 17 00:00:00 2001 From: Hank Grabowski Date: Tue, 9 May 2023 16:40:21 -0400 Subject: [PATCH 1/4] Reorder packages to be alphabetical --- pubspec.yaml | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/pubspec.yaml b/pubspec.yaml index 8bb9726..3fb0a2a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,8 +11,12 @@ dependencies: flutter: sdk: flutter cached_network_image: ^3.2.2 + color_blindness: ^0.1.2 cupertino_icons: ^1.0.2 + carousel_slider: ^4.2.1 desktop_window: ^0.4.0 + device_info_plus: ^8.0.0 + device_preview: ^1.1.0 file_picker: ^5.2.4 flutter_dotenv: ^5.0.2 flutter_file_dialog: ^3.0.0 @@ -22,6 +26,7 @@ dependencies: get_it: ^7.2.0 get_it_mixin: ^4.1.1 go_router: ^6.5.0 + html: ^0.15.2 image: ^4.0.15 image_picker: ^0.8.6 logging: ^1.1.0 @@ -36,7 +41,10 @@ dependencies: media_kit_libs_linux: ^1.0.2 # GNU/Linux dependency package. metadata_fetch: ^0.4.1 multi_trigger_autocomplete: ^0.1.1 network_to_file_image: ^4.0.1 + objectbox: ^2.0.0 + objectbox_flutter_libs: ^2.0.0 path: ^1.8.2 + path_provider: ^2.0.11 provider: ^6.0.4 result_monad: git: @@ -44,20 +52,13 @@ dependencies: scrollable_positioned_list: ^0.3.5 shared_preferences: ^2.0.15 sqlite3: ^1.9.1 + stack_trace: ^1.11.0 + string_validator: ^0.3.0 time_machine: ^0.9.17 url_launcher: ^6.1.6 uuid: ^3.0.6 video_player: ^2.4.10 - objectbox: ^2.0.0 - objectbox_flutter_libs: ^2.0.0 - path_provider: ^2.0.11 - carousel_slider: ^4.2.1 - device_info_plus: ^8.0.0 - string_validator: ^0.3.0 - device_preview: ^1.1.0 - color_blindness: ^0.1.2 - stack_trace: ^1.11.0 - html: ^0.15.2 + dev_dependencies: flutter_test: From fbf95350e40fcd82490150959ef88c5492c6398b Mon Sep 17 00:00:00 2001 From: Hank Grabowski Date: Tue, 9 May 2023 16:42:36 -0400 Subject: [PATCH 2/4] Fix updates timer should only be configured once per profile and do nothing if not the active one --- lib/update_timer_initialization.dart | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/update_timer_initialization.dart b/lib/update_timer_initialization.dart index dd8580f..e5973da 100644 --- a/lib/update_timer_initialization.dart +++ b/lib/update_timer_initialization.dart @@ -29,11 +29,15 @@ Future executeUpdatesForProfile(Profile profile) async { return; } + if (getIt().currentProfile != profile) { + return; + } + await getIt>() .getForProfile(profile) .withResultAsync((info) async { final dt = DateTime.now().difference(info.lastMyConnectionsUpdate); - _logger.finer('Time since last connections update: $dt'); + _logger.finer('Time since last update for ${profile.id}: $dt'); if (dt >= _connectionsRefreshInterval) { await getIt>() .getForProfile(profile) From 97f48240b31bc39602ac10f536c2bc022a2c3f5e Mon Sep 17 00:00:00 2001 From: Hank Grabowski Date: Tue, 9 May 2023 16:42:53 -0400 Subject: [PATCH 3/4] Add timeouts to the API calls --- lib/friendica_client/friendica_client.dart | 28 ++++++++++--------- lib/globals.dart | 1 + lib/models/exec_error.dart | 9 ++++++- lib/services/auth_service.dart | 4 --- lib/utils/network_utils.dart | 31 +++++++++++++++++++--- 5 files changed, 51 insertions(+), 22 deletions(-) diff --git a/lib/friendica_client/friendica_client.dart b/lib/friendica_client/friendica_client.dart index 456b673..4fcfc73 100644 --- a/lib/friendica_client/friendica_client.dart +++ b/lib/friendica_client/friendica_client.dart @@ -1082,26 +1082,28 @@ abstract class FriendicaClient { FutureResult>, ExecError> _getApiListRequest( Uri url) async { - return (await getUrl(url, headers: _headers).andThenSuccessAsync( - (response) async => - response.map((data) => jsonDecode(data) as List), - )) - .mapError((error) => error as ExecError); + return await getUrl(url, headers: _headers).transformAsync( + (response) async { + return response.map((data) => jsonDecode(data) as List); + }, + ).execErrorCastAsync(); } FutureResult, ExecError> _getApiPagedRequest( Uri url) async { - return (await getUrl(url, headers: _headers).andThenSuccessAsync( - (response) async => response.map((data) => jsonDecode(data)), - )) - .mapError((error) => error as ExecError); + return await getUrl(url, headers: _headers).transformAsync( + (response) async { + return response.map((data) => jsonDecode(data)); + }, + ).execErrorCastAsync(); } FutureResult _getApiRequest(Uri url) async { - return (await getUrl(url, headers: _headers).andThenSuccessAsync( - (response) async => jsonDecode(response.data), - )) - .execErrorCastAsync(); + return await getUrl(url, headers: _headers).transformAsync( + (response) async { + return jsonDecode(response.data); + }, + ).execErrorCastAsync(); } Map get _headers => { diff --git a/lib/globals.dart b/lib/globals.dart index a1ddb3b..55b0510 100644 --- a/lib/globals.dart +++ b/lib/globals.dart @@ -22,6 +22,7 @@ const maxViewPortalWidth = 750.0; const maxProcessingMillis = 3; const processingSleep = Duration(milliseconds: 1); +const apiCallTimeout = Duration(seconds: 30); Future showConfirmDialog(BuildContext context, String caption) { return showDialog( diff --git a/lib/models/exec_error.dart b/lib/models/exec_error.dart index d13b723..8276942 100644 --- a/lib/models/exec_error.dart +++ b/lib/models/exec_error.dart @@ -56,7 +56,14 @@ extension ExecErrorExtension on Result { ? error : ExecError(type: ErrorType.localError, message: error.toString())); - FutureResult execErrorCastAsync() async => execErrorCast(); + FutureResult execErrorCastAsync() async { + return this.execErrorCast(); + } +} + +extension ExecErrorExtensionFuture on FutureResult { + FutureResult execErrorCastAsync() async => + (await this).execErrorCast(); } void logError(ExecError error, Logger logger, diff --git a/lib/services/auth_service.dart b/lib/services/auth_service.dart index 2b392a1..63eb52b 100644 --- a/lib/services/auth_service.dart +++ b/lib/services/auth_service.dart @@ -157,10 +157,6 @@ class AccountsService extends ChangeNotifier { Future setActiveProfile(Profile profile, {bool withNotification = true}) async { _currentProfile = profile; - Future.delayed( - const Duration(seconds: 10), - () async => await executeUpdatesForProfile(profile), - ); if (withNotification) { notifyListeners(); } diff --git a/lib/utils/network_utils.dart b/lib/utils/network_utils.dart index da82022..b3f3540 100644 --- a/lib/utils/network_utils.dart +++ b/lib/utils/network_utils.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:logging/logging.dart'; +import '../globals.dart'; import 'package:result_monad/result_monad.dart'; import '../friendica_client/paged_response.dart'; @@ -9,17 +10,24 @@ import '../models/exec_error.dart'; final _logger = Logger('NetworkUtils'); +http.Response requestTimeout() => http.Response('Client side timeout', 408); + FutureResult, ExecError> getUrl( Uri url, { Map? headers, }) async { _logger.finer('GET: $url'); try { - final response = await http.get( + final request = http.get( url, headers: headers, ); + final response = await request.timeout( + apiCallTimeout, + onTimeout: requestTimeout, + ); + if (response.statusCode != 200) { return Result.error(ExecError( type: ErrorType.authentication, @@ -42,12 +50,17 @@ FutureResult postUrl( }) async { _logger.finer('POST: $url \n Body: $body'); try { - final response = await http.post( + final request = http.post( url, headers: headers, body: jsonEncode(body), ); + final response = await request.timeout( + apiCallTimeout, + onTimeout: requestTimeout, + ); + if (response.statusCode != 200) { return Result.error(ExecError( type: ErrorType.authentication, @@ -67,12 +80,17 @@ FutureResult putUrl( }) async { _logger.finer('PUT: $url \n Body: $body'); try { - final response = await http.put( + final request = http.put( url, headers: headers, body: jsonEncode(body), ); + final response = await request.timeout( + apiCallTimeout, + onTimeout: requestTimeout, + ); + if (response.statusCode != 200) { return Result.error(ExecError( type: ErrorType.authentication, @@ -92,12 +110,17 @@ FutureResult deleteUrl( }) async { _logger.finer('DELETE: $url'); try { - final response = await http.delete( + final request = http.delete( url, headers: headers, body: jsonEncode(body), ); + final response = await request.timeout( + apiCallTimeout, + onTimeout: requestTimeout, + ); + if (response.statusCode != 200) { return Result.error(ExecError( type: ErrorType.authentication, From 1978819c9e18f06fe116c12ddad13426ead18fc4 Mon Sep 17 00:00:00 2001 From: Hank Grabowski Date: Wed, 10 May 2023 11:21:33 -0400 Subject: [PATCH 4/4] Show splash screen until 1st account logged in then allow rest to load while app in use. --- lib/di_initialization.dart | 2 +- lib/routes.dart | 5 +++++ lib/screens/splash.dart | 18 ++++++++++++++- lib/services/auth_service.dart | 6 +++++ pubspec.lock | 40 ++++++++++++++++++++++++++++++++++ pubspec.yaml | 7 +++--- 6 files changed, 73 insertions(+), 5 deletions(-) diff --git a/lib/di_initialization.dart b/lib/di_initialization.dart index 29cbd28..1648b12 100644 --- a/lib/di_initialization.dart +++ b/lib/di_initialization.dart @@ -86,7 +86,7 @@ Future dependencyInjectionInitialization() async { if (serviceInit.isFailure) { _logger.severe('Error initializing credentials'); } else { - await accountsService.initialize(); + accountsService.initialize(); } getIt.registerSingleton(accountsService); getIt>().subscribeToProfileSwaps(); diff --git a/lib/routes.dart b/lib/routes.dart index a51555c..e71d5b7 100644 --- a/lib/routes.dart +++ b/lib/routes.dart @@ -67,6 +67,11 @@ final appRouter = GoRouter( refreshListenable: _authService, redirect: (context, state) async { final loggedIn = _authService.loggedIn; + + if (!loggedIn && _authService.initializing) { + return ScreenPaths.splash; + } + if (!loggedIn && !allowedLoggedOut.contains(state.location)) { return ScreenPaths.signin; } diff --git a/lib/screens/splash.dart b/lib/screens/splash.dart index 52889d8..a39eb41 100644 --- a/lib/screens/splash.dart +++ b/lib/screens/splash.dart @@ -1,4 +1,9 @@ import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +import '../controls/padding.dart'; +import '../globals.dart'; +import '../services/auth_service.dart'; class SplashScreen extends StatelessWidget { @override @@ -9,7 +14,18 @@ class SplashScreen extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ - const Text('Relatica'), + SvgPicture.asset('icon/relatica_logo.svg', width: 128), + const VerticalPadding(), + Text( + 'Relatica', + style: Theme.of(context).textTheme.headlineLarge, + ), + const VerticalPadding(), + if (getIt().initializing) ...[ + const CircularProgressIndicator(), + const VerticalPadding(), + const Text('Logging in accounts...'), + ], ], )), ); diff --git a/lib/services/auth_service.dart b/lib/services/auth_service.dart index 63eb52b..2e1f107 100644 --- a/lib/services/auth_service.dart +++ b/lib/services/auth_service.dart @@ -18,6 +18,7 @@ class AccountsService extends ChangeNotifier { Profile? _currentProfile; final _loggedInProfiles = {}; final _loggedOutProfiles = {}; + var _initializing = false; final SecretsService secretsService; @@ -25,6 +26,8 @@ class AccountsService extends ChangeNotifier { bool get loggedIn => _currentProfile != null; + bool get initializing => _initializing; + List get loggedInProfiles => UnmodifiableListView(_loggedInProfiles); List get loggedOutProfiles => @@ -35,6 +38,7 @@ class AccountsService extends ChangeNotifier { FutureResult initialize() async { final lastActiveProfile = await _getStoredLoginState(); + _initializing = true; final result = await runCatchingAsync(() async { final initialProfiles = secretsService.profiles; for (final p in initialProfiles) { @@ -60,9 +64,11 @@ class AccountsService extends ChangeNotifier { await setActiveProfile(_loggedInProfiles.first); } + notifyListeners(); return Result.ok(loggedIn); }); + _initializing = false; return result.execErrorCast(); } diff --git a/pubspec.lock b/pubspec.lock index e48430d..579b28c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -451,6 +451,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.0" + flutter_svg: + dependency: "direct main" + description: + name: flutter_svg + sha256: f991fdb1533c3caeee0cdc14b04f50f0c3916f0dbcbc05237ccbe4e3c6b93f3f + url: "https://pub.dev" + source: hosted + version: "2.0.5" flutter_test: dependency: "direct dev" description: flutter @@ -861,6 +869,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.8.2" + path_parsing: + dependency: transitive + description: + name: path_parsing + sha256: e3e67b1629e6f7e8100b367d3db6ba6af4b1f0bb80f64db18ef1fbabd2fa9ccf + url: "https://pub.dev" + source: hosted + version: "1.0.1" path_provider: dependency: "direct main" description: @@ -1307,6 +1323,30 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.7" + vector_graphics: + dependency: transitive + description: + name: vector_graphics + sha256: ea8d3fc7b2e0f35de38a7465063ecfcf03d8217f7962aa2a6717132cb5d43a79 + url: "https://pub.dev" + source: hosted + version: "1.1.5" + vector_graphics_codec: + dependency: transitive + description: + name: vector_graphics_codec + sha256: a5eaa5d19e123ad4f61c3718ca1ed921c4e6254238d9145f82aa214955d9aced + url: "https://pub.dev" + source: hosted + version: "1.1.5" + vector_graphics_compiler: + dependency: transitive + description: + name: vector_graphics_compiler + sha256: "15edc42f7eaa478ce854eaf1fbb9062a899c0e4e56e775dd73b7f4709c97c4ca" + url: "https://pub.dev" + source: hosted + version: "1.1.5" vector_math: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 3fb0a2a..28dbd07 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -58,6 +58,7 @@ dependencies: url_launcher: ^6.1.6 uuid: ^3.0.6 video_player: ^2.4.10 + flutter_svg: ^2.0.5 dev_dependencies: @@ -69,9 +70,9 @@ dev_dependencies: flutter: uses-material-design: true - # assets: - # Add assets from the images directory to the application. - #- .env + assets: + - icon/relatica_logo.svg + parts: uet-lms: source: .