123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371 |
- import 'dart:ui';
- import 'package:flutter/material.dart';
- import 'package:logging/logging.dart';
- import 'package:pedantic/pedantic.dart';
- import 'package:photos/core/configuration.dart';
- import 'package:photos/ente_theme_data.dart';
- import 'package:photos/services/local_authentication_service.dart';
- import 'package:photos/services/user_remote_flag_service.dart';
- import 'package:photos/theme/colors.dart';
- import 'package:photos/theme/ente_theme.dart';
- import 'package:photos/ui/account/password_entry_page.dart';
- import 'package:photos/ui/common/gradient_button.dart';
- import 'package:photos/ui/home_widget.dart';
- import 'package:photos/utils/dialog_util.dart';
- import 'package:photos/utils/navigation_util.dart';
- class PasswordReminder extends StatefulWidget {
- const PasswordReminder({Key? key}) : super(key: key);
- @override
- State<PasswordReminder> createState() => _PasswordReminderState();
- }
- class _PasswordReminderState extends State<PasswordReminder> {
- final _passwordController = TextEditingController();
- final Logger _logger = Logger((_PasswordReminderState).toString());
- bool _password2Visible = false;
- bool _incorrectPassword = false;
- Future<void> _verifyRecoveryKey() async {
- final dialog = createProgressDialog(context, "Verifying password...");
- await dialog.show();
- try {
- final String inputKey = _passwordController.text;
- await Configuration.instance.verifyPassword(inputKey);
- await dialog.hide();
- UserRemoteFlagService.instance.stopPasswordReminder().ignore();
- // todo: change this as per figma once the component is ready
- await showErrorDialog(
- context,
- "Password verified",
- "Great! Thank you for verifying.\n"
- "\nPlease"
- " remember to keep your recovery key safely backed up.",
- );
- unawaited(
- Navigator.of(context).pushAndRemoveUntil(
- MaterialPageRoute(
- builder: (BuildContext context) {
- return const HomeWidget();
- },
- ),
- (route) => false,
- ),
- );
- } catch (e, s) {
- _logger.severe("failed to verify password", e, s);
- await dialog.hide();
- _incorrectPassword = true;
- if (mounted) {
- setState(() => {});
- }
- }
- }
- Future<void> _onChangePasswordClick() async {
- try {
- final hasAuthenticated =
- await LocalAuthenticationService.instance.requestLocalAuthentication(
- context,
- "Please authenticate to change your password",
- );
- if (hasAuthenticated) {
- UserRemoteFlagService.instance.stopPasswordReminder().ignore();
- await routeToPage(
- context,
- const PasswordEntryPage(
- mode: PasswordEntryMode.update,
- ),
- forceCustomPageRoute: true,
- );
- unawaited(
- Navigator.of(context).pushAndRemoveUntil(
- MaterialPageRoute(
- builder: (BuildContext context) {
- return const HomeWidget();
- },
- ),
- (route) => false,
- ),
- );
- }
- } catch (e) {
- showGenericErrorDialog(context: context);
- return;
- }
- }
- Future<void> _onSkipClick() async {
- final enteTextTheme = getEnteTextTheme(context);
- final enteColor = getEnteColorScheme(context);
- final content = Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- mainAxisSize: MainAxisSize.min,
- children: [
- Text(
- "You will not be able to access your photos if you forget "
- "your password.\n\nIf you do not remember your password, "
- "now is a good time to change it.",
- style: enteTextTheme.body.copyWith(
- color: enteColor.textMuted,
- ),
- ),
- const Padding(padding: EdgeInsets.all(8)),
- SizedBox(
- width: double.infinity,
- height: 52,
- child: OutlinedButton(
- style: Theme.of(context).outlinedButtonTheme.style?.copyWith(
- textStyle: MaterialStateProperty.resolveWith<TextStyle>(
- (Set<MaterialState> states) {
- return enteTextTheme.bodyBold;
- },
- ),
- ),
- onPressed: () async {
- Navigator.of(context, rootNavigator: true).pop('dialog');
- _onChangePasswordClick();
- },
- child: const Text(
- "Change password",
- ),
- ),
- ),
- const Padding(padding: EdgeInsets.all(8)),
- SizedBox(
- width: double.infinity,
- height: 52,
- child: OutlinedButton(
- style: Theme.of(context).outlinedButtonTheme.style?.copyWith(
- textStyle: MaterialStateProperty.resolveWith<TextStyle>(
- (Set<MaterialState> states) {
- return enteTextTheme.bodyBold;
- },
- ),
- backgroundColor: MaterialStateProperty.resolveWith<Color>(
- (Set<MaterialState> states) {
- return enteColor.fillFaint;
- },
- ),
- foregroundColor: MaterialStateProperty.resolveWith<Color>(
- (Set<MaterialState> states) {
- return Theme.of(context).colorScheme.defaultTextColor;
- },
- ),
- ),
- onPressed: () async {
- Navigator.of(context, rootNavigator: true).pop('dialog');
- },
- child: Text(
- "Cancel",
- style: enteTextTheme.bodyBold,
- ),
- ),
- )
- ],
- );
- return showDialog(
- context: context,
- builder: (BuildContext context) {
- return AlertDialog(
- backgroundColor: enteColor.backgroundElevated,
- title: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Icon(
- Icons.report_outlined,
- size: 36,
- color: getEnteColorScheme(context).strokeBase,
- ),
- ],
- ),
- content: content,
- );
- },
- barrierColor: enteColor.backdropFaint,
- );
- }
- @override
- void dispose() {
- _passwordController.dispose();
- super.dispose();
- }
- @override
- Widget build(BuildContext context) {
- final enteTheme = Theme.of(context).colorScheme.enteTheme;
- final List<Widget> actions = <Widget>[];
- actions.add(
- PopupMenuButton(
- itemBuilder: (context) {
- return [
- PopupMenuItem(
- value: 1,
- child: SizedBox(
- width: 120,
- height: 32,
- child: Row(
- mainAxisAlignment: MainAxisAlignment.start,
- crossAxisAlignment: CrossAxisAlignment.center,
- children: [
- const Icon(
- Icons.report_outlined,
- color: warning500,
- size: 20,
- ),
- const Padding(padding: EdgeInsets.symmetric(horizontal: 6)),
- Text(
- "Skip",
- style: getEnteTextTheme(context)
- .bodyBold
- .copyWith(color: warning500),
- ),
- ],
- ),
- ),
- ),
- ];
- },
- onSelected: (value) async {
- _onSkipClick();
- },
- ),
- );
- return Scaffold(
- appBar: AppBar(
- elevation: 0,
- leading: null,
- automaticallyImplyLeading: false,
- actions: actions,
- ),
- body: Padding(
- padding: const EdgeInsets.symmetric(horizontal: 20.0),
- child: LayoutBuilder(
- builder: (context, constraints) {
- return SingleChildScrollView(
- child: ConstrainedBox(
- constraints: BoxConstraints(
- minWidth: constraints.maxWidth,
- minHeight: constraints.maxHeight,
- ),
- child: IntrinsicHeight(
- child: Column(
- mainAxisAlignment: MainAxisAlignment.start,
- children: [
- SizedBox(
- width: double.infinity,
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- mainAxisAlignment: MainAxisAlignment.start,
- children: [
- Text(
- 'Password reminder',
- style: enteTheme.textTheme.h3Bold,
- ),
- Text(
- Configuration.instance.getEmail()!,
- style: enteTheme.textTheme.small.copyWith(
- color: enteTheme.colorScheme.textMuted,
- ),
- ),
- ],
- ),
- ),
- const SizedBox(height: 18),
- Text(
- "Enter your password to ensure you remember it."
- "\n\nThe developer account we use to publish ente on App Store will change in the next version, so you will need to login again when the next version is released.",
- style: enteTheme.textTheme.small
- .copyWith(color: enteTheme.colorScheme.textMuted),
- ),
- const SizedBox(height: 24),
- TextFormField(
- autofillHints: const [AutofillHints.password],
- decoration: InputDecoration(
- filled: true,
- hintText: "Password",
- suffixIcon: IconButton(
- icon: Icon(
- _password2Visible
- ? Icons.visibility
- : Icons.visibility_off,
- color: Theme.of(context).iconTheme.color,
- size: 20,
- ),
- onPressed: () {
- setState(() {
- _password2Visible = !_password2Visible;
- });
- },
- ),
- contentPadding: const EdgeInsets.all(20),
- border: UnderlineInputBorder(
- borderSide: BorderSide.none,
- borderRadius: BorderRadius.circular(6),
- ),
- ),
- style: const TextStyle(
- fontSize: 14,
- fontFeatures: [FontFeature.tabularFigures()],
- ),
- controller: _passwordController,
- autofocus: false,
- autocorrect: false,
- obscureText: !_password2Visible,
- keyboardType: TextInputType.visiblePassword,
- onChanged: (_) {
- _incorrectPassword = false;
- setState(() {});
- },
- ),
- _incorrectPassword
- ? const SizedBox(height: 2)
- : const SizedBox.shrink(),
- _incorrectPassword
- ? Align(
- alignment: Alignment.centerLeft,
- child: Text(
- "Incorrect password",
- style: enteTheme.textTheme.small.copyWith(
- color: enteTheme.colorScheme.warning700,
- ),
- ),
- )
- : const SizedBox.shrink(),
- const SizedBox(height: 12),
- Expanded(
- child: Container(
- alignment: Alignment.bottomCenter,
- width: double.infinity,
- padding: const EdgeInsets.fromLTRB(0, 12, 0, 40),
- child: Column(
- mainAxisAlignment: MainAxisAlignment.end,
- crossAxisAlignment: CrossAxisAlignment.stretch,
- children: [
- GradientButton(
- onTap: _verifyRecoveryKey,
- text: "Verify",
- ),
- const SizedBox(height: 8),
- ],
- ),
- ),
- ),
- const SizedBox(height: 20)
- ],
- ),
- ),
- ),
- );
- },
- ),
- ),
- );
- }
- }
|