feat: New splash screen

This commit is contained in:
Krille 2023-12-22 09:40:38 +01:00
parent ea790f43ac
commit a4bbef19f2
No known key found for this signature in database
GPG key ID: E067ECD60F1A0652
6 changed files with 149 additions and 104 deletions

View file

@ -2550,6 +2550,7 @@
"wrongRecoveryKey": "Sorry... this does not seem to be the correct recovery key.",
"startConversation": "Start conversation",
"commandHint_sendraw": "Send raw json",
"databaseMigrationTitle": "Database is optimized",
"databaseMigrationBody": "Please wait. This may take a moment."
"databaseMigrationTitle": "Optimizing your chats... This can take a few moments!",
"loadingChats": "Loading your chats...",
"reportError": "Report error"
}

View file

@ -1,6 +1,5 @@
import 'package:flutter/material.dart';
import 'package:collection/collection.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:matrix/matrix.dart';
import 'package:shared_preferences/shared_preferences.dart';
@ -22,15 +21,23 @@ void main() async {
WidgetsFlutterBinding.ensureInitialized();
Logs().nativeColors = !PlatformInfos.isIOS;
// Do not send online presences when app is in background fetch mode.
final store = await SharedPreferences.getInstance();
final clients = await ClientManager.getClients(store: store);
var isMigratingDatabase = false;
final clientsFuture = ClientManager.getClients(
store: store,
onMigration: () {
isMigratingDatabase = true;
},
);
// If the app starts in detached mode, we assume that it is in
// background fetch mode for processing push notifications. This is
// currently only supported on Android.
if (PlatformInfos.isAndroid &&
AppLifecycleState.detached == WidgetsBinding.instance.lifecycleState) {
// Do not send online presences when app is in background fetch mode.
final clients = await clientsFuture;
for (final client in clients) {
client.syncPresence = PresenceType.offline;
}
@ -46,15 +53,26 @@ void main() async {
return;
}
if (!isMigratingDatabase) {
final clients = await clientsFuture;
// Preload first client
final firstClient = clients.firstOrNull;
await firstClient?.roomsLoading;
await firstClient?.accountDataLoading;
}
// Started in foreground mode.
Logs().i(
'${AppConfig.applicationName} started in foreground mode. Rendering GUI...',
);
await startGui(clients, store);
await startGui(clientsFuture, store);
}
/// Fetch the pincode for the applock and start the flutter engine.
Future<void> startGui(List<Client> clients, SharedPreferences store) async {
Future<void> startGui(
Future<List<Client>> clientsFuture,
SharedPreferences store,
) async {
// Fetch the pin for the applock if existing for mobile applications.
String? pin;
if (PlatformInfos.isMobile) {
@ -66,13 +84,14 @@ Future<void> startGui(List<Client> clients, SharedPreferences store) async {
}
}
// Preload first client
final firstClient = clients.firstOrNull;
await firstClient?.roomsLoading;
await firstClient?.accountDataLoading;
ErrorWidget.builder = (details) => FluffyChatErrorWidget(details);
runApp(FluffyChatApp(clients: clients, pincode: pin, store: store));
runApp(
FluffyChatApp(
pincode: pin,
clientsFuture: clientsFuture,
store: store,
),
);
}
/// Watches the lifecycle changes to start the application when it
@ -96,7 +115,7 @@ class AppStarter with WidgetsBindingObserver {
for (final client in clients) {
client.syncPresence = PresenceType.online;
}
startGui(clients, store);
startGui(Future.value(clients), store);
// We must make sure that the GUI is only started once.
guiStarted = true;
}

View file

@ -1,19 +1,11 @@
import 'dart:io';
import 'dart:ui';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:matrix/encryption/utils/key_verification.dart';
import 'package:matrix/matrix.dart';
import 'package:path_provider/path_provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:universal_html/html.dart' as html;
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/utils/custom_http_client.dart';
import 'package:fluffychat/utils/custom_image_resizer.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/flutter_hive_collections_database.dart';
@ -23,8 +15,8 @@ import 'matrix_sdk_extensions/flutter_matrix_sdk_database_builder.dart';
abstract class ClientManager {
static const String clientNamespace = 'im.fluffychat.store.clients';
static Future<List<Client>> getClients({
bool initialize = true,
required SharedPreferences store,
void Function()? onMigration,
}) async {
if (PlatformInfos.isLinux) {
Hive.init((await getApplicationSupportDirectory()).path);
@ -44,28 +36,20 @@ abstract class ClientManager {
await store.setStringList(clientNamespace, clientNames.toList());
}
final clients = clientNames.map(createClient).toList();
if (initialize) {
FlutterLocalNotificationsPlugin? flutterLocalNotificationsPlugin;
await Future.wait(
clients.map(
(client) => client
.init(
waitForFirstSync: false,
waitUntilLoadCompletedLoaded: false,
onMigration: () {
sendMigrationNotification(
flutterLocalNotificationsPlugin ??=
FlutterLocalNotificationsPlugin(),
);
},
)
.catchError(
(e, s) => Logs().e('Unable to initialize client', e, s),
),
),
);
flutterLocalNotificationsPlugin?.cancel(0);
}
await Future.wait(
clients.map(
(client) => client
.init(
waitForFirstSync: false,
waitUntilLoadCompletedLoaded: false,
onMigration: onMigration,
)
.catchError(
(e, s) => Logs().e('Unable to initialize client', e, s),
),
),
);
if (clients.length > 1 && clients.any((c) => !c.isLogged())) {
final loggedOutClients = clients.where((c) => !c.isLogged()).toList();
for (final client in loggedOutClients) {
@ -130,42 +114,4 @@ abstract class ClientManager {
enableDehydratedDevices: true,
);
}
static void sendMigrationNotification(
FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin,
) async {
final l10n = lookupL10n(Locale(Platform.localeName));
if (kIsWeb) {
html.Notification(
l10n.databaseMigrationTitle,
body: l10n.databaseMigrationBody,
);
}
await flutterLocalNotificationsPlugin.initialize(
const InitializationSettings(
android: AndroidInitializationSettings('notifications_icon'),
iOS: DarwinInitializationSettings(),
),
);
flutterLocalNotificationsPlugin.show(
0,
l10n.databaseMigrationTitle,
l10n.databaseMigrationBody,
const NotificationDetails(
android: AndroidNotificationDetails(
AppConfig.pushNotificationsChannelId,
AppConfig.pushNotificationsChannelName,
channelDescription: AppConfig.pushNotificationsChannelDescription,
importance: Importance.max,
priority: Priority.max,
fullScreenIntent: true, // To show notification popup
showProgress: true,
),
iOS: DarwinNotificationDetails(),
),
);
}
}

View file

@ -18,9 +18,13 @@ class ErrorReporter {
const ErrorReporter(this.context, [this.message]);
void onErrorCallback(Object error, [StackTrace? stackTrace]) async {
void onErrorCallback(
Object error, [
StackTrace? stackTrace,
OkCancelResult? consent,
]) async {
Logs().e(message ?? 'Error caught', error, stackTrace);
final consent = await showOkCancelAlertDialog(
consent ??= await showOkCancelAlertDialog(
context: context,
title: error.toLocalizedString(context),
message: L10n.of(context)!.reportErrorDescription,

View file

@ -101,7 +101,6 @@ Future<void> _tryPushHelper(
);
client ??= (await ClientManager.getClients(
initialize: false,
store: await SharedPreferences.getInstance(),
))
.first;

View file

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:adaptive_dialog/adaptive_dialog.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:go_router/go_router.dart';
import 'package:matrix/matrix.dart';
@ -7,24 +8,25 @@ import 'package:shared_preferences/shared_preferences.dart';
import 'package:fluffychat/config/routes.dart';
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/utils/error_reporter.dart';
import 'package:fluffychat/widgets/app_lock.dart';
import 'package:fluffychat/widgets/theme_builder.dart';
import '../config/app_config.dart';
import '../utils/custom_scroll_behaviour.dart';
import 'matrix.dart';
class FluffyChatApp extends StatelessWidget {
class FluffyChatApp extends StatefulWidget {
final Widget? testWidget;
final List<Client> clients;
final String? pincode;
final Future<List<Client>> clientsFuture;
final SharedPreferences store;
const FluffyChatApp({
super.key,
this.testWidget,
required this.clients,
required this.store,
this.pincode,
required this.clientsFuture,
required this.store,
});
/// getInitialLink may rereturn the value multiple times if this view is
@ -36,6 +38,18 @@ class FluffyChatApp extends StatelessWidget {
// the current path.
static final GoRouter router = GoRouter(routes: AppRoutes.routes);
@override
State<FluffyChatApp> createState() => _FluffyChatAppState();
}
class _FluffyChatAppState extends State<FluffyChatApp> {
List<Client>? clients;
bool isMigratingDatabase = false;
Future<List<Client>> loadClient() async {
return clients ??= await widget.clientsFuture;
}
@override
Widget build(BuildContext context) {
return ThemeBuilder(
@ -48,21 +62,83 @@ class FluffyChatApp extends StatelessWidget {
scrollBehavior: CustomScrollBehavior(),
localizationsDelegates: L10n.localizationsDelegates,
supportedLocales: L10n.supportedLocales,
routerConfig: router,
builder: (context, child) => AppLockWidget(
pincode: pincode,
clients: clients,
// Need a navigator above the Matrix widget for
// displaying dialogs
child: Navigator(
onGenerateRoute: (_) => MaterialPageRoute(
builder: (_) => Matrix(
clients: clients,
store: store,
child: testWidget ?? child,
routerConfig: FluffyChatApp.router,
builder: (context, child) => FutureBuilder(
future: loadClient(),
builder: (context, snapshot) {
final data = snapshot.data;
if (data == null) {
final error = snapshot.error;
final label = error != null
? L10n.of(context)!.reportErrorDescription
: L10n.of(context)!.databaseMigrationTitle;
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
if (error == null)
Image.asset(
'assets/logo.png',
width: 64,
height: 64,
)
else
const Text('😭', style: TextStyle(fontSize: 100)),
Padding(
padding: const EdgeInsets.all(16.0),
child: SizedBox(
width: FluffyThemes.columnWidth,
child: Text(
label,
style: TextStyle(
fontSize: error == null ? 20 : 16,
color: error == null
? null
: Theme.of(context).colorScheme.error,
),
textAlign: TextAlign.center,
),
),
),
if (error != null)
OutlinedButton.icon(
onPressed: () =>
ErrorReporter(context, 'INITIALIZATION ERROR')
.onErrorCallback(
error,
snapshot.stackTrace,
OkCancelResult.ok,
),
label: Text(L10n.of(context)!.reportError),
icon: const Icon(Icons.favorite),
),
if (error == null)
const CircularProgressIndicator.adaptive(
strokeWidth: 2,
),
],
),
),
);
}
return AppLockWidget(
pincode: widget.pincode,
clients: data,
// Need a navigator above the Matrix widget for
// displaying dialogs
child: Navigator(
onGenerateRoute: (_) => MaterialPageRoute(
builder: (_) => Matrix(
clients: data,
store: widget.store,
child: widget.testWidget ?? child,
),
),
),
),
),
);
},
),
),
);