import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:restart_app/restart_app.dart'; import 'package:twonly/src/services/backup.service.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/visual/components/snackbar.dart'; import 'package:twonly/src/visual/elements/my_button.element.dart'; import 'package:twonly/src/visual/elements/my_input.element.dart'; import 'package:twonly/src/visual/views/onboarding/components/link_logo_animation.dart'; class BackupRecoveryView extends StatefulWidget { const BackupRecoveryView({super.key}); @override State createState() => _BackupRecoveryViewState(); } class _BackupRecoveryViewState extends State { bool obscureText = true; bool isLoading = false; final TextEditingController usernameCtrl = TextEditingController(); final TextEditingController passwordCtrl = TextEditingController(); Future _recoverTwonlySafe() async { setState(() { isLoading = true; }); final error = await BackupService.startFullBackupRecovery( usernameCtrl.text, passwordCtrl.text, ); if (!mounted) return; if (error != null) { String errorMessage; switch (error) { case RecoveryError.noInternet: errorMessage = context.lang.recoverErrorNoInternet; case RecoveryError.usernameNotValid: errorMessage = context.lang.recoverErrorUsernameNotValid; case RecoveryError.passwordInvalid: errorMessage = context.lang.recoverErrorPasswordInvalid; case RecoveryError.tryAgainLater: errorMessage = context.lang.recoverErrorTryAgainLater; case RecoveryError.unkownError: errorMessage = context.lang.recoverErrorUnknown; } setState(() { isLoading = false; }); return showSnackbar(context, errorMessage); } await Restart.restartApp( notificationTitle: context.lang.recoverSuccessTitle, notificationBody: context.lang.recoverSuccessBody, forceKill: true, ); setState(() { isLoading = false; }); } void _showBackupExplanation(BuildContext context) { final isDark = isDarkMode(context); final backgroundColor = Theme.of(context).scaffoldBackgroundColor; final textColor = isDark ? Colors.white : Colors.black87; final subtitleColor = isDark ? Colors.white70 : Colors.black54; showModalBottomSheet( context: context, backgroundColor: backgroundColor, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical( top: Radius.circular(28), ), ), isScrollControlled: true, builder: (context) { return SafeArea( child: Padding( padding: const EdgeInsets.fromLTRB(24, 12, 24, 24), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Center( child: Container( width: 40, height: 5, decoration: BoxDecoration( color: isDark ? Colors.white24 : Colors.black12, borderRadius: BorderRadius.circular(2.5), ), ), ), const SizedBox(height: 24), Text( 'twonly Backup', style: TextStyle( fontSize: 22, fontWeight: FontWeight.bold, color: textColor, ), textAlign: TextAlign.center, ), const SizedBox(height: 16), Text( context.lang.backupTwonlySafeLongDesc, style: TextStyle( fontSize: 16, height: 1.5, color: subtitleColor, ), ), const SizedBox(height: 32), MyButton( onPressed: () => Navigator.pop(context), child: const Text('Got it'), ), ], ), ), ); }, ); } @override Widget build(BuildContext context) { final isDark = isDarkMode(context); final titleColor = isDark ? Colors.white : Colors.black87; final iconColor = isDark ? Colors.white70 : Colors.black54; return GestureDetector( onTap: () => FocusManager.instance.primaryFocus?.unfocus(), behavior: HitTestBehavior.opaque, child: Scaffold( backgroundColor: Theme.of(context).scaffoldBackgroundColor, body: SafeArea( child: LayoutBuilder( builder: (context, constraints) { return SingleChildScrollView( padding: const EdgeInsets.symmetric(horizontal: 24), child: ConstrainedBox( constraints: BoxConstraints( minHeight: constraints.maxHeight, ), child: IntrinsicHeight( child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Row( children: [ IconButton( onPressed: () => Navigator.of(context).pop(), icon: const Icon( Icons.arrow_back_ios_new_rounded, ), color: iconColor, iconSize: 20, ), const Spacer(), IconButton( onPressed: () => _showBackupExplanation(context), icon: const FaIcon(FontAwesomeIcons.circleInfo), color: iconColor, iconSize: 20, ), ], ), const SizedBox(height: 20), Center( child: Padding( padding: const EdgeInsets.all(20), child: LinkLogoAnimation( color: isDark ? Colors.white : Colors.black, ), ), ), const SizedBox(height: 16), Padding( padding: const EdgeInsets.symmetric(horizontal: 20), child: Text( context.lang.twonlySafeRecoverTitle, textAlign: TextAlign.center, style: TextStyle( fontSize: 24, fontWeight: FontWeight.w800, color: titleColor, letterSpacing: -0.5, ), ), ), const SizedBox(height: 48), MyInput( controller: usernameCtrl, onChanged: (value) => setState(() {}), hintText: context.lang.registerUsernameDecoration, prefixIcon: const Icon(Icons.alternate_email), ), const SizedBox(height: 16), MyInput( controller: passwordCtrl, onChanged: (value) => setState(() {}), obscureText: obscureText, hintText: context.lang.password, prefixIcon: const Icon(Icons.lock_outline_rounded), suffixIcon: IconButton( onPressed: () { setState(() { obscureText = !obscureText; }); }, icon: FaIcon( obscureText ? FontAwesomeIcons.eye : FontAwesomeIcons.eyeSlash, size: 16, ), ), ), const SizedBox(height: 32), MyButton( onPressed: (!isLoading) ? _recoverTwonlySafe : null, child: isLoading ? const SizedBox( height: 24, width: 24, child: CircularProgressIndicator.adaptive( valueColor: AlwaysStoppedAnimation( Colors.white, ), strokeWidth: 3, ), ) : Text(context.lang.twonlySafeRecoverBtn), ), const Spacer(), const SizedBox(height: 40), ], ), ), ), ); }, ), ), ), ); } }