twonly-app/lib/src/visual/views/onboarding/recover.view.dart
2026-06-05 01:17:42 +02:00

254 lines
9.5 KiB
Dart

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<BackupRecoveryView> createState() => _BackupRecoveryViewState();
}
class _BackupRecoveryViewState extends State<BackupRecoveryView> {
bool obscureText = true;
bool isLoading = false;
final TextEditingController usernameCtrl = TextEditingController();
final TextEditingController passwordCtrl = TextEditingController();
Future<void> _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<void>(
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),
],
),
),
),
);
},
),
),
),
);
}
}