diff --git a/lib/di_initialization.dart b/lib/di_initialization.dart index 4e0120c..a97affc 100644 --- a/lib/di_initialization.dart +++ b/lib/di_initialization.dart @@ -32,6 +32,7 @@ import 'services/persistent_info_service.dart'; import 'services/reshared_via_service.dart'; import 'services/secrets_service.dart'; import 'services/setting_service.dart'; +import 'services/status_service.dart'; import 'services/timeline_entry_filter_service.dart'; import 'services/timeline_manager.dart'; import 'update_timer_initialization.dart'; @@ -40,6 +41,8 @@ import 'utils/active_profile_selector.dart'; final _logger = Logger('DI_Init'); Future dependencyInjectionInitialization() async { + getIt.registerSingleton(StatusService()); + final appSupportdir = await getApplicationSupportDirectory(); getIt.registerSingleton>( ActiveProfileSelector( diff --git a/lib/friendica_client/friendica_client.dart b/lib/friendica_client/friendica_client.dart index 6229df0..e089c88 100644 --- a/lib/friendica_client/friendica_client.dart +++ b/lib/friendica_client/friendica_client.dart @@ -781,7 +781,7 @@ class ProfileClient extends FriendicaClient { _logger.finest(() => 'Getting logged in user profile'); final request = Uri.parse('https://$serverName/api/v1/accounts/verify_credentials'); - return (await _getApiRequest(request)) + return (await _getApiRequest(request, timeout: oauthTimeout)) .mapValue((json) => ConnectionMastodonExtensions.fromJson( json, defaultServerName: serverName, @@ -1083,25 +1083,40 @@ abstract class FriendicaClient { } FutureResult>, ExecError> _getApiListRequest( - Uri url) async { - return await getUrl(url, headers: _headers).transformAsync( + Uri url, { + Duration? timeout, + }) async { + return await getUrl( + url, + headers: _headers, + timeout: timeout, + ).transformAsync( (response) async { return response.map((data) => jsonDecode(data) as List); }, ).execErrorCastAsync(); } - FutureResult, ExecError> _getApiPagedRequest( - Uri url) async { - return await getUrl(url, headers: _headers).transformAsync( + FutureResult, ExecError> _getApiPagedRequest(Uri url, + {Duration? timeout}) async { + return await getUrl( + url, + headers: _headers, + timeout: timeout, + ).transformAsync( (response) async { return response.map((data) => jsonDecode(data)); }, ).execErrorCastAsync(); } - FutureResult _getApiRequest(Uri url) async { - return await getUrl(url, headers: _headers).transformAsync( + FutureResult _getApiRequest(Uri url, + {Duration? timeout}) async { + return await getUrl( + url, + headers: _headers, + timeout: timeout, + ).transformAsync( (response) async { return jsonDecode(response.data); }, @@ -1111,6 +1126,5 @@ abstract class FriendicaClient { Map get _headers => { 'Authorization': _profile.credentials.authHeaderValue, 'Content-Type': 'application/json; charset=UTF-8', - if (usePhpDebugging) 'Cookie': 'XDEBUG_SESSION=PHPSTORM;path=/', }; } diff --git a/lib/globals.dart b/lib/globals.dart index 0befc53..9f99be3 100644 --- a/lib/globals.dart +++ b/lib/globals.dart @@ -23,6 +23,7 @@ const maxViewPortalWidth = 750.0; const maxProcessingMillis = 3; const processingSleep = Duration(milliseconds: 1); const apiCallTimeout = Duration(seconds: 60); +const oauthTimeout = Duration(seconds: 15); Future showConfirmDialog(BuildContext context, String caption) { return showDialog( diff --git a/lib/main.dart b/lib/main.dart index 0348e9c..7643ec5 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -21,6 +21,7 @@ import 'services/hashtag_service.dart'; import 'services/interactions_manager.dart'; import 'services/notifications_manager.dart'; import 'services/setting_service.dart'; +import 'services/status_service.dart'; import 'services/timeline_entry_filter_service.dart'; import 'services/timeline_manager.dart'; import 'utils/active_profile_selector.dart'; @@ -62,6 +63,10 @@ class App extends StatelessWidget { return Portal( child: MultiProvider( providers: [ + ChangeNotifierProvider( + create: (_) => getIt(), + lazy: true, + ), ChangeNotifierProvider( create: (_) => getIt(), lazy: true, diff --git a/lib/models/auth/oauth_credentials.dart b/lib/models/auth/oauth_credentials.dart index 12fc110..dd392fc 100644 --- a/lib/models/auth/oauth_credentials.dart +++ b/lib/models/auth/oauth_credentials.dart @@ -6,6 +6,8 @@ import 'package:logging/logging.dart'; import 'package:result_monad/result_monad.dart'; import 'package:uuid/uuid.dart'; +import '../../globals.dart'; +import '../../services/status_service.dart'; import '../../utils/network_utils.dart'; import '../exec_error.dart'; import 'credentials_intf.dart'; @@ -91,20 +93,28 @@ class OAuthCredentials implements ICredentials { 'Client ID and Client Secret are already set, skipping ID fetching'); return Result.ok(true); } + getIt().setStatus('Attempting to contact $serverName...'); final idEndpoint = Uri.parse('https://$serverName/api/v1/apps'); - final response = await postUrl(idEndpoint, { - 'client_name': 'Relatica', - 'redirect_uris': redirectUrl, - 'scopes': 'read write follow push', - 'website': 'https://myportal.social', - }); + final response = await postUrl( + idEndpoint, + { + 'client_name': 'Relatica', + 'redirect_uris': redirectUrl, + 'scopes': 'read write follow push', + 'website': 'https://myportal.social', + }, + timeout: oauthTimeout, + ); response.match(onSuccess: (body) { + getIt().setStatus('Connected to $serverName...'); final json = jsonDecode(body); clientId = json['client_id']; clientSecret = json['client_secret']; }, onError: (error) { - _logger.severe('Error logging in: $error'); + getIt() + .setStatus('Error contacting $serverName...: $error'); + _logger.severe('Error contacting $serverName...: $error'); }); return response.mapValue((_) => true); @@ -112,6 +122,7 @@ class OAuthCredentials implements ICredentials { FutureResult _login() async { if (accessToken.isNotEmpty) { + getIt().setStatus('Logged into $serverName'); _logger.info('Already have access token, skipping'); return Result.ok(true); } @@ -124,12 +135,20 @@ class OAuthCredentials implements ICredentials { }); try { + getIt() + .setStatus('Attempting getting authorization to $serverName'); + final result = await FlutterWebAuth2.authenticate( - url: url.toString(), callbackUrlScheme: redirectScheme); + url: url.toString(), + callbackUrlScheme: redirectScheme, + ); final code = Uri.parse(result).queryParameters['code']; if (code == null) { _logger.severe( 'Error code was not returned with the query parameters: $result'); + getIt().setStatus( + 'Error getting the response code during authentication to $serverName'); + return buildErrorResult( type: ErrorType.serverError, message: 'Error getting the response code during authentication', @@ -145,11 +164,16 @@ class OAuthCredentials implements ICredentials { }; final response = await postUrl(url2, body); response.match(onSuccess: (body) { + getIt().setStatus('Logged into $serverName'); accessToken = jsonDecode(body)['access_token']; }, onError: (error) { + getIt() + .setStatus('Error getting authorization to $serverName'); _logger.severe('Error doing OAUth processing: $error'); }); } catch (e) { + getIt() + .setStatus('Error getting authorization to $serverName'); _logger.severe('Exception while Doing OAuth Process: $e'); return buildErrorResult( type: ErrorType.serverError, diff --git a/lib/screens/splash.dart b/lib/screens/splash.dart index a62adb5..c25fc9c 100644 --- a/lib/screens/splash.dart +++ b/lib/screens/splash.dart @@ -1,15 +1,25 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:go_router/go_router.dart'; +import 'package:provider/provider.dart'; import '../controls/padding.dart'; import '../globals.dart'; +import '../routes.dart'; import '../services/auth_service.dart'; +import '../services/status_service.dart'; class SplashScreen extends StatelessWidget { const SplashScreen({super.key}); @override Widget build(BuildContext context) { + final statusService = context.watch(); + final accountsService = context.watch(); + if (!accountsService.initializing && !accountsService.loggedIn) { + context.pushNamed(ScreenPaths.signin); + } + return Scaffold( body: Center( child: Column( @@ -27,7 +37,13 @@ class SplashScreen extends StatelessWidget { const CircularProgressIndicator(), const VerticalPadding(), const Text('Logging in accounts...'), + const VerticalPadding(), ], + Text( + statusService.status, + softWrap: true, + textAlign: TextAlign.center, + ), ], )), ); diff --git a/lib/services/auth_service.dart b/lib/services/auth_service.dart index 2e1f107..e15ca1a 100644 --- a/lib/services/auth_service.dart +++ b/lib/services/auth_service.dart @@ -7,11 +7,13 @@ import 'package:shared_preferences/shared_preferences.dart'; import '../di_initialization.dart'; import '../friendica_client/friendica_client.dart'; +import '../globals.dart'; import '../models/auth/credentials_intf.dart'; import '../models/auth/profile.dart'; import '../models/exec_error.dart'; import '../update_timer_initialization.dart'; import 'secrets_service.dart'; +import 'status_service.dart'; class AccountsService extends ChangeNotifier { static final _logger = Logger('$AccountsService'); @@ -80,6 +82,8 @@ class AccountsService extends ChangeNotifier { final client = ProfileClient(Profile.credentialsOnly(signedInCredentials)); credentialsCache = signedInCredentials; + getIt().setStatus( + 'Getting user profile from ${signedInCredentials.serverName}'); return await client.getMyProfile(); }).andThenAsync((profileData) async { final loginProfile = Profile( @@ -91,6 +95,8 @@ class AccountsService extends ChangeNotifier { loggedIn: true, ); + getIt() + .setStatus('Loaded user profile ${profileData.handle}'); if (_loggedInProfiles.isEmpty) { await setActiveProfile(loginProfile, withNotification: withNotification); @@ -106,6 +112,7 @@ class AccountsService extends ChangeNotifier { }); if (result.isFailure) { + getIt().setStatus('Error signing in: ${result.error}'); _logger.severe('Error signing in: ${result.error}'); } diff --git a/lib/services/status_service.dart b/lib/services/status_service.dart new file mode 100644 index 0000000..0dbe036 --- /dev/null +++ b/lib/services/status_service.dart @@ -0,0 +1,16 @@ +import 'package:flutter/widgets.dart'; + +class StatusService extends ChangeNotifier { + String _lastStatus = 'None'; + DateTime _lastStatusTime = DateTime.now(); + + String get status => _lastStatus; + + DateTime get statusTime => _lastStatusTime; + + void setStatus(String status) { + _lastStatus = status; + _lastStatusTime = DateTime.now(); + notifyListeners(); + } +} diff --git a/lib/update_timer_initialization.dart b/lib/update_timer_initialization.dart index 41ef0c5..0337994 100644 --- a/lib/update_timer_initialization.dart +++ b/lib/update_timer_initialization.dart @@ -18,6 +18,12 @@ final _logger = Logger('UpdateTimer'); void setupUpdateTimers() { Timer.periodic(_timerRefresh, (_) async { + final service = getIt(); + if (!service.loggedIn) { + _logger + .info('Trying to do update when no logged in accounts. Skipping...'); + return; + } executeUpdatesForProfile(getIt().currentProfile); }); } diff --git a/lib/utils/network_utils.dart b/lib/utils/network_utils.dart index b3f3540..dcded55 100644 --- a/lib/utils/network_utils.dart +++ b/lib/utils/network_utils.dart @@ -2,10 +2,10 @@ 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'; +import '../globals.dart'; import '../models/exec_error.dart'; final _logger = Logger('NetworkUtils'); @@ -15,16 +15,21 @@ http.Response requestTimeout() => http.Response('Client side timeout', 408); FutureResult, ExecError> getUrl( Uri url, { Map? headers, + Duration? timeout, }) async { _logger.finer('GET: $url'); + final requestHeaders = headers ?? {}; + if (usePhpDebugging) { + requestHeaders['Cookie'] = 'XDEBUG_SESSION=PHPSTORM;path=/'; + } try { final request = http.get( url, - headers: headers, + headers: requestHeaders, ); final response = await request.timeout( - apiCallTimeout, + timeout ?? apiCallTimeout, onTimeout: requestTimeout, ); @@ -47,17 +52,22 @@ FutureResult postUrl( Uri url, Map body, { Map? headers, + Duration? timeout, }) async { _logger.finer('POST: $url \n Body: $body'); + final requestHeaders = headers ?? {}; + if (usePhpDebugging) { + requestHeaders['Cookie'] = 'XDEBUG_SESSION=PHPSTORM;path=/'; + } try { final request = http.post( url, - headers: headers, + headers: requestHeaders, body: jsonEncode(body), ); final response = await request.timeout( - apiCallTimeout, + timeout ?? apiCallTimeout, onTimeout: requestTimeout, );