diff --git a/lib/content_wrapper.dart b/lib/content_wrapper.dart index 645c7b54..ae36f911 100644 --- a/lib/content_wrapper.dart +++ b/lib/content_wrapper.dart @@ -5,6 +5,7 @@ import 'package:flutter/material.dart'; import 'models/destination.dart'; import 'models/user/user.dart'; import 'widgets/bottom_app_bar.dart'; +import 'widgets/verification_banner.dart'; import 'package:fsek_mobile/l10n/app_localizations.dart'; import 'package:audioplayers/audioplayers.dart'; @@ -201,6 +202,7 @@ class _ContentWrapperState extends State view); //move offstage to ensure they aren't painted when not visible } }).toList())), + VerificationBanner(), ])), bottomNavigationBar: BottomAppBar( shape: CircularNotchedRectangle(), diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 261aa78c..eb36009d 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -14,6 +14,12 @@ "other" : "Other", "introduction" : "Introduction", + "verifyEmailBanner": "Your account is not verified. Verify your email to get full access.", + "verifyEmailAction": "Send email", + "verifyEmailSent": "A verification email has been sent.", + "verifyEmailSentShort": "Sent", + "verifyEmailFailed": "Could not send verification email, please try again later.", + "loginRequiredField": "Required field", "loginEmail": "Email", "loginPassword": "Password", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 1de7f910..1409eef9 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -164,6 +164,36 @@ abstract class AppLocalizations { /// **'Nollning'** String get introduction; + /// No description provided for @verifyEmailBanner. + /// + /// In sv, this message translates to: + /// **'Ditt konto är inte verifierat. Verifiera din e-post för att få full åtkomst.'** + String get verifyEmailBanner; + + /// No description provided for @verifyEmailAction. + /// + /// In sv, this message translates to: + /// **'Skicka mejl'** + String get verifyEmailAction; + + /// No description provided for @verifyEmailSent. + /// + /// In sv, this message translates to: + /// **'Ett verifieringsmejl har skickats.'** + String get verifyEmailSent; + + /// No description provided for @verifyEmailSentShort. + /// + /// In sv, this message translates to: + /// **'Skickat'** + String get verifyEmailSentShort; + + /// No description provided for @verifyEmailFailed. + /// + /// In sv, this message translates to: + /// **'Kunde inte skicka verifieringsmejl, försök igen senare.'** + String get verifyEmailFailed; + /// No description provided for @loginRequiredField. /// /// In sv, this message translates to: diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index fc814a4d..374ca121 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -41,6 +41,23 @@ class AppLocalizationsEn extends AppLocalizations { @override String get introduction => 'Introduction'; + @override + String get verifyEmailBanner => + 'Your account is not verified. Verify your email to get full access.'; + + @override + String get verifyEmailAction => 'Send email'; + + @override + String get verifyEmailSent => 'A verification email has been sent.'; + + @override + String get verifyEmailSentShort => 'Sent'; + + @override + String get verifyEmailFailed => + 'Could not send verification email, please try again later.'; + @override String get loginRequiredField => 'Required field'; diff --git a/lib/l10n/app_localizations_sv.dart b/lib/l10n/app_localizations_sv.dart index feb09ede..1f85faf0 100644 --- a/lib/l10n/app_localizations_sv.dart +++ b/lib/l10n/app_localizations_sv.dart @@ -41,6 +41,23 @@ class AppLocalizationsSv extends AppLocalizations { @override String get introduction => 'Nollning'; + @override + String get verifyEmailBanner => + 'Ditt konto är inte verifierat. Verifiera din e-post för att få full åtkomst.'; + + @override + String get verifyEmailAction => 'Skicka mejl'; + + @override + String get verifyEmailSent => 'Ett verifieringsmejl har skickats.'; + + @override + String get verifyEmailSentShort => 'Skickat'; + + @override + String get verifyEmailFailed => + 'Kunde inte skicka verifieringsmejl, försök igen senare.'; + @override String get loginRequiredField => 'Obligatoriskt fält'; diff --git a/lib/l10n/app_sv.arb b/lib/l10n/app_sv.arb index f67986e4..540b735a 100644 --- a/lib/l10n/app_sv.arb +++ b/lib/l10n/app_sv.arb @@ -14,6 +14,12 @@ "other" : "Övrigt", "introduction" : "Nollning", + "verifyEmailBanner": "Ditt konto är inte verifierat. Verifiera din e-post för att få full åtkomst.", + "verifyEmailAction": "Skicka mejl", + "verifyEmailSent": "Ett verifieringsmejl har skickats.", + "verifyEmailSentShort": "Skickat", + "verifyEmailFailed": "Kunde inte skicka verifieringsmejl, försök igen senare.", + "loginRequiredField": "Obligatoriskt fält", "loginEmail": "E-postadress", "loginPassword": "Lösenord", diff --git a/lib/screens/nollning/schedule.dart b/lib/screens/nollning/schedule.dart index 49d3f127..453756cf 100644 --- a/lib/screens/nollning/schedule.dart +++ b/lib/screens/nollning/schedule.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:fsek_mobile/l10n/app_localizations.dart'; import 'package:fsek_mobile/screens/nollning/englishSchedule.dart'; import 'package:fsek_mobile/util/app_exception.dart'; @@ -16,6 +17,15 @@ class _ScheduleScreenState extends State { @override void initState(){ super.initState(); + // hide the android navigation bar while the schedule is showing + SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky); + } + + @override + void dispose() { + // before leaving, show the android navigation bar again + SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); + super.dispose(); } @override diff --git a/lib/widgets/verification_banner.dart b/lib/widgets/verification_banner.dart new file mode 100644 index 00000000..92ceb95b --- /dev/null +++ b/lib/widgets/verification_banner.dart @@ -0,0 +1,140 @@ +import 'package:flutter/material.dart'; +import 'package:fsek_mobile/api_client/lib/api_client.dart'; +import 'package:fsek_mobile/services/api.service.dart'; +import 'package:fsek_mobile/l10n/app_localizations.dart'; + +// Banner shown above the navbar when the user has not verified their email. +class VerificationBanner extends StatefulWidget { + const VerificationBanner({Key? key}) : super(key: key); + + @override + _VerificationBannerState createState() => _VerificationBannerState(); +} + +class _VerificationBannerState extends State + with WidgetsBindingObserver { + AdminUserRead? user; + bool sending = false; + bool sent = false; + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addObserver(this); + _loadUser(); + } + + @override + void dispose() { + WidgetsBinding.instance.removeObserver(this); + super.dispose(); + } + + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + // Re-check when coming back to the app + if (state == AppLifecycleState.resumed) { + _loadUser(); + } + } + + Future _loadUser() async { + try { + final response = await ApiService.apiClient.getUsersApi().usersGetMe(); + if (!mounted) return; + setState(() { + user = response.data; + }); + } catch (error) { + // If we can't fetch the user we simply don't show the banner + } + } + + Future _requestVerification() async { + final email = user?.email; + if (email == null || sending) return; + setState(() { + sending = true; + }); + var t = AppLocalizations.of(context)!; + try { + await ApiService.apiClient.getAuthApi().authVerifyRequestToken( + bodyAuthVerifyRequestToken: BodyAuthVerifyRequestToken( + (b) => b..email = email, + ), + ); + if (!mounted) return; + setState(() { + sending = false; + sent = true; + }); + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text(t.verifyEmailSent))); + } catch (error) { + if (!mounted) return; + setState(() { + sending = false; + }); + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text(t.verifyEmailFailed))); + } + } + + @override + Widget build(BuildContext context) { + // Only show the banner once we know the user isn't verified + if (user == null || user!.isVerified != false) { + return SizedBox.shrink(); + } + + var t = AppLocalizations.of(context)!; + ColorScheme colors = Theme.of(context).colorScheme; + + return Material( + color: colors.errorContainer, + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 12, vertical: 8), + child: Row( + children: [ + Icon( + Icons.warning_amber_rounded, + color: colors.onErrorContainer, + size: 22, + ), + SizedBox(width: 10), + Expanded( + child: Text( + t.verifyEmailBanner, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: colors.onErrorContainer, + ), + ), + ), + SizedBox(width: 8), + TextButton( + onPressed: (sending || sent) ? null : _requestVerification, + style: TextButton.styleFrom( + foregroundColor: colors.onPrimary, + padding: EdgeInsets.symmetric(horizontal: 8), + minimumSize: Size(0, 32), + tapTargetSize: MaterialTapTargetSize.shrinkWrap, + ), + child: sending + ? SizedBox( + width: 16, + height: 16, + child: CircularProgressIndicator( + strokeWidth: 2, + color: colors.onErrorContainer, + ), + ) + : Text(sent ? t.verifyEmailSentShort : t.verifyEmailAction), + ), + ], + ), + ), + ); + } +}