diff --git a/lib/main.dart b/lib/main.dart index 2f14ac9..93d5eb4 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -13,6 +13,7 @@ import 'package:solian/utils/timeago.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:solian/utils/video_player.dart'; import 'package:solian/widgets/chat/call/call_overlay.dart'; +import 'package:solian/widgets/provider_init.dart'; void main() { WidgetsFlutterBinding.ensureInitialized(); @@ -52,7 +53,9 @@ class SolianApp extends StatelessWidget { OverlayEntry(builder: (context) { return ScaffoldMessenger( child: Scaffold( - body: child ?? Container(), + body: ProviderInitializer( + child: child ?? Container(), + ), ), ); }), diff --git a/lib/providers/chat.dart b/lib/providers/chat.dart index f565e29..3dce94c 100644 --- a/lib/providers/chat.dart +++ b/lib/providers/chat.dart @@ -31,6 +31,8 @@ class ChatProvider extends ChangeNotifier { PagingController? historyPagingController; + WebSocketChannel? _channel; + Future connect(AuthProvider auth) async { if (auth.client == null) await auth.loadClient(); if (!await auth.isAuthorized()) return null; @@ -46,11 +48,12 @@ class ChatProvider extends ChangeNotifier { queryParameters: {'tk': Uri.encodeComponent(auth.client!.currentToken!)}, ); - final channel = WebSocketChannel.connect(uri); - isOpened = true; - channel.stream.listen( + _channel = WebSocketChannel.connect(uri); + await _channel!.ready; + + _channel!.stream.listen( (event) { final result = NetworkPackage.fromJson(jsonDecode(event)); if (focusChannel == null || historyPagingController == null) return; @@ -90,11 +93,16 @@ class ChatProvider extends ChangeNotifier { } notifyListeners(); }, - onError: (_, __) => connect(auth), - onDone: () => connect(auth), + onError: (_, __) => Future.delayed(const Duration(seconds: 3), () => connect(auth)), + onDone: () => Future.delayed(const Duration(seconds: 1), () => connect(auth)), ); - return channel; + return _channel!; + } + + void disconnect() { + _channel = null; + isOpened = false; } Future fetchMessages(int pageKey, BuildContext context) async { diff --git a/lib/providers/notify.dart b/lib/providers/notify.dart index b684e64..48eb380 100644 --- a/lib/providers/notify.dart +++ b/lib/providers/notify.dart @@ -64,6 +64,8 @@ class NotifyProvider extends ChangeNotifier { notifyListeners(); } + WebSocketChannel? _channel; + Future connect( AuthProvider auth, { Keypair? Function(String id)? onKexRequest, @@ -83,10 +85,12 @@ class NotifyProvider extends ChangeNotifier { queryParameters: {'tk': Uri.encodeComponent(auth.client!.currentToken!)}, ); - final channel = WebSocketChannel.connect(uri); - await channel.ready; + isOpened = true; - channel.stream.listen( + _channel = WebSocketChannel.connect(uri); + await _channel!.ready; + + _channel!.stream.listen( (event) { final result = NetworkPackage.fromJson(jsonDecode(event)); switch (result.method) { @@ -101,7 +105,7 @@ class NotifyProvider extends ChangeNotifier { if (onKexRequest == null || result.payload == null) break; final resp = onKexRequest(result.payload!['keypair_id']); if (resp == null) break; - channel.sink.add(jsonEncode( + _channel!.sink.add(jsonEncode( NetworkPackage(method: 'kex.provide', payload: { 'request_id': result.payload!['request_id'], 'keypair_id': resp.id, @@ -121,11 +125,16 @@ class NotifyProvider extends ChangeNotifier { break; } }, - onError: (_, __) => connect(auth), - onDone: () => connect(auth), + onError: (_, __) => Future.delayed(const Duration(seconds: 3), () => connect(auth)), + onDone: () => Future.delayed(const Duration(seconds: 1), () => connect(auth)), ); - return channel; + return _channel!; + } + + void disconnect() { + _channel = null; + isOpened = false; } void notifyMessage(String title, String body) { diff --git a/lib/screens/account.dart b/lib/screens/account.dart index 2ec8d3b..d0e3cf4 100644 --- a/lib/screens/account.dart +++ b/lib/screens/account.dart @@ -1,7 +1,9 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:solian/providers/auth.dart'; +import 'package:solian/providers/chat.dart'; import 'package:solian/providers/keypair.dart'; +import 'package:solian/providers/notify.dart'; import 'package:solian/router.dart'; import 'package:solian/utils/theme.dart'; import 'package:solian/widgets/account/account_avatar.dart'; @@ -82,6 +84,8 @@ class _AccountScreenWidgetState extends State { onTap: () { auth.signoff(); keypair.clearKeys(); + context.read().disconnect(); + context.read().disconnect(); setState(() { _isAuthorized = false; }); diff --git a/lib/screens/auth/signin.dart b/lib/screens/auth/signin.dart index 5bf557a..a4d06b1 100644 --- a/lib/screens/auth/signin.dart +++ b/lib/screens/auth/signin.dart @@ -2,6 +2,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:provider/provider.dart'; import 'package:solian/providers/auth.dart'; +import 'package:solian/providers/chat.dart'; +import 'package:solian/providers/notify.dart'; import 'package:solian/router.dart'; import 'package:solian/utils/services_url.dart'; import 'package:solian/widgets/exts.dart'; @@ -21,6 +23,8 @@ class SignInScreen extends StatelessWidget { final password = _passwordController.value.text; if (username.isEmpty || password.isEmpty) return; auth.signin(context, username, password).then((_) { + context.read().connect(auth); + context.read().connect(auth); SolianRouter.router.pop(true); }).catchError((e) { List messages = e.toString().split('\n'); diff --git a/lib/screens/explore.dart b/lib/screens/explore.dart index 5383c84..455672f 100644 --- a/lib/screens/explore.dart +++ b/lib/screens/explore.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'package:flutter/material.dart'; +import 'package:flutter_animate/flutter_animate.dart'; import 'package:provider/provider.dart'; import 'package:solian/models/pagination.dart'; import 'package:solian/models/post.dart'; @@ -133,14 +134,12 @@ class _ExplorePostWidgetState extends State { return Container(); } - return Container( - height: 120, - decoration: BoxDecoration( - border: Border( - bottom: BorderSide(width: 0.3, color: Theme.of(context).dividerColor), - ), - ), - child: const RealmShortcuts(), + return Padding( + padding: const EdgeInsets.only(bottom: 8), + child: const Material( + elevation: 8, + child: SizedBox(height: 120, child: RealmShortcuts()), + ).animate().fade().slideY(begin: -1, end: 0, curve: Curves.fastEaseInToSlowEaseOut), ); }, ), diff --git a/lib/widgets/notification_notifier.dart b/lib/widgets/notification_notifier.dart index d5af2fb..94ce195 100644 --- a/lib/widgets/notification_notifier.dart +++ b/lib/widgets/notification_notifier.dart @@ -1,57 +1,12 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; -import 'package:solian/providers/auth.dart'; -import 'package:solian/providers/keypair.dart'; import 'package:solian/providers/notify.dart'; import 'package:solian/router.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:badges/badges.dart' as badge; -class NotificationButton extends StatefulWidget { +class NotificationButton extends StatelessWidget { const NotificationButton({super.key}); - @override - State createState() => _NotificationButtonState(); -} - -class _NotificationButtonState extends State { - void connect() async { - final auth = context.read(); - final nty = context.read(); - final keypair = context.read(); - - if (nty.isOpened) return; - - final notify = ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(AppLocalizations.of(context)!.connectingServer), - duration: const Duration(minutes: 1), - ), - ); - - if (await auth.isAuthorized()) { - if (auth.client == null) { - await auth.loadClient(); - } - - nty.fetch(auth); - keypair.channel = await nty.connect( - auth, - onKexRequest: keypair.provideKeypair, - onKexProvide: keypair.receiveKeypair, - ); - } - - notify.close(); - } - - @override - void initState() { - super.initState(); - - Future.delayed(Duration.zero, () => connect()); - } - @override Widget build(BuildContext context) { final nty = context.watch(); diff --git a/lib/widgets/provider_init.dart b/lib/widgets/provider_init.dart new file mode 100644 index 0000000..e490a2d --- /dev/null +++ b/lib/widgets/provider_init.dart @@ -0,0 +1,59 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:solian/providers/auth.dart'; +import 'package:solian/providers/chat.dart'; +import 'package:solian/providers/keypair.dart'; +import 'package:solian/providers/notify.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +class ProviderInitializer extends StatefulWidget { + final Widget child; + + const ProviderInitializer({super.key, required this.child}); + + @override + State createState() => _ProviderInitializerState(); +} + +class _ProviderInitializerState extends State { + void connect() async { + final auth = context.read(); + final nty = context.read(); + final chat = context.read(); + final keypair = context.read(); + + final notify = ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(AppLocalizations.of(context)!.connectingServer), + duration: const Duration(minutes: 1), + ), + ); + + if (await auth.isAuthorized()) { + if (auth.client == null) { + await auth.loadClient(); + } + + nty.fetch(auth); + chat.connect(auth); + keypair.channel = await nty.connect( + auth, + onKexRequest: keypair.provideKeypair, + onKexProvide: keypair.receiveKeypair, + ); + } + + notify.close(); + } + + @override + void initState() { + super.initState(); + Future.delayed(Duration.zero, () => connect()); + } + + @override + Widget build(BuildContext context) { + return widget.child; + } +} \ No newline at end of file