import 'package:comunic/helpers/preferences_helper.dart'; import 'package:comunic/helpers/push_notifications_helper.dart'; import 'package:comunic/helpers/server_config_helper.dart'; import 'package:comunic/helpers/users_helper.dart'; import 'package:comunic/models/config.dart'; import 'package:comunic/models/user.dart'; import 'package:comunic/ui/routes/push_notifications_route.dart'; import 'package:comunic/ui/widgets/async_screen_widget.dart'; import 'package:comunic/ui/widgets/login_routes_theme.dart'; import 'package:comunic/ui/widgets/tour/account_image_tour_pane.dart'; import 'package:comunic/ui/widgets/tour/first_pane.dart'; import 'package:comunic/ui/widgets/tour/last_pane.dart'; import 'package:comunic/ui/widgets/tour/presentation_pane.dart'; import 'package:comunic/ui/widgets/tour/tour_notifications_pane.dart'; import 'package:comunic/utils/account_utils.dart'; import 'package:comunic/utils/intl_utils.dart'; import 'package:comunic/utils/log_utils.dart'; import 'package:flutter/material.dart'; /// Tour route /// /// The tour is shown when the client sign into the application /// /// @author Pierre Hubert Future showTour(BuildContext context) async => await Navigator.of(context) .push(MaterialPageRoute(builder: (c) => TourRoute())); typedef TourEntriesBuilder = List Function(TourRouteState); class TourRoute extends StatefulWidget { @override TourRouteState createState() => TourRouteState(); } class TourRouteState extends State { final _key = GlobalKey(); final pushNotificationsKey = GlobalKey(); final Map pubKeys = Map(); late User currUser; int _defaultIndex = 0; bool areNotificationsConfigured = false; Future _init() async { currUser = await UsersHelper().getSingleWithThrow(userID(), forceDownload: true); // Pre-configure notifications final notificationsStatus = await PushNotificationsHelper.getLocalStatus(); if (!PushNotificationsHelper.arePushNotificationsAvailable || notificationsStatus != PushNotificationsStatus.UNDEFINED) areNotificationsConfigured = true; // Attempt to automatically register to FCM else if (srvConfig!.notificationsPolicy.hasFirebase) { try { await PushNotificationsHelper.configure( context, PushNotificationsStatus.FIREBASE); areNotificationsConfigured = true; } catch (e, s) { logError(e, s); } } } void rebuild() => setState(() {}); void setStateKeepCurrentIndex(BuildContext cxt) async { _defaultIndex = DefaultTabController.of(cxt)!.index; await _key.currentState!.refresh(); } List get _list => (config().toursEntriesBuilder != null ? config().toursEntriesBuilder!(this) : [ FirstTourPane(), // Account image AccountImageTourPane( user: currUser, onUpdated: setStateKeepCurrentIndex, ), // Notifications TourNotificationsPane( pushNotificationsKey: pushNotificationsKey, onConfigured: () => setState(() {}), onChanged: () => setState(() {}), visible: !areNotificationsConfigured, ), PresentationPane( icon: Icons.group_add, title: tr("Friends")!, text: "${tr("You can search the people you know and ask them to become your friends!")}\n\n${tr("This will help you to reach them to exchange information!")}", ), PresentationPane( icon: Icons.question_answer, title: tr("Conversations")!, text: "${tr("With Comunic, you can have conversations with all your friends.")}\n\n${tr("It is also possible to make video calls!")}", ), PresentationPane( icon: Icons.group, title: tr("Groups")!, text: "${tr("You can join groups where people share the same interests as you!")}\n\n${tr("It is also easy to create your own groups!")}", ), PresentationPane( icon: Icons.lock, title: tr("Privacy")!, text: "${tr("Your data is YOUR DATA. We will never use it or sell it.")}\n\n${tr("If you do not trust us, you can always check out our source code to verify it!")}", ), LastTourPane(), ]) ..removeWhere((pane) { if (pane is PresentationPane) { PresentationPane p = pane; return !(p.visible); } return false; }); @override Widget build(BuildContext context) => LoginRoutesTheme( child: Scaffold( body: SafeArea( child: AsyncScreenWidget( key: _key, onReload: _init, errorMessage: tr("Failed to load tour!")!, onBuild: () => DefaultTabController( initialIndex: _defaultIndex, length: _list.length, child: _RouteBody( panes: _list, ), ), ), ), ), ); } class _RouteBody extends StatefulWidget { final List? panes; const _RouteBody({Key? key, this.panes}) : super(key: key); @override __RouteBodyState createState() => __RouteBodyState(); } class __RouteBodyState extends State<_RouteBody> { TabController? _controller; bool get _isLastPane => _controller!.index >= widget.panes!.length - 1; PresentationPane? get _currPane => widget.panes![_controller!.index] is PresentationPane ? widget.panes![_controller!.index] as PresentationPane? : null; bool get _canGoNext => _currPane?.canGoNext ?? true; @override void didUpdateWidget(_RouteBody oldWidget) { super.didUpdateWidget(oldWidget); _updateController(); } @override void didChangeDependencies() { super.didChangeDependencies(); _updateController(); } void _updateController() { _controller = DefaultTabController.of(context); _controller!.addListener(() => setState(() {})); } @override Widget build(BuildContext context) => Padding( padding: const EdgeInsets.all(8.0), child: Column( children: [ Spacer(flex: 1), Expanded( child: TabBarView( children: widget.panes!, physics: NeverScrollableScrollPhysics(), ), flex: 10), Spacer(flex: 1), TabPageSelector(selectedColor: Colors.white), Spacer(flex: 1), OutlinedButton( onPressed: _canGoNext ? _nextOrFinish : null, child: Text(!_isLastPane ? tr("Next")! : tr("Let's go!")!), ), Spacer(flex: 1), ], ), ); void _nextOrFinish() async { if (_controller!.indexIsChanging) return; if (!_isLastPane) { // Check if the next click has to be captured if (_currPane?.onTapNext != null) { if (!await _currPane!.onTapNext!(context)) return; } _controller!.animateTo(_controller!.index + 1); } else { (await PreferencesHelper.getInstance()) .setBool(PreferencesKeyList.IS_TOUR_SEEN, true); Navigator.of(context).pop(); } } }