import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:logging/logging.dart'; import 'package:nc_photos/account.dart'; import 'package:nc_photos/list_extension.dart'; import 'package:nc_photos/platform/k.dart' as platform_k; import 'package:nc_photos/pref.dart'; import 'package:nc_photos/string_extension.dart'; import 'package:nc_photos/theme.dart'; import 'package:nc_photos/widget/connect.dart'; import 'package:nc_photos/widget/home.dart'; import 'package:nc_photos/widget/root_picker.dart'; class SignIn extends StatefulWidget { static const routeName = "/sign-in"; SignIn({Key? key}) : super(key: key); @override createState() => _SignInState(); } class _SignInState extends State { @override build(BuildContext context) { return AppTheme( child: Scaffold( body: Builder(builder: (context) => _buildContent(context)), ), ); } Widget _buildContent(BuildContext context) { return SafeArea( child: LayoutBuilder( builder: (BuildContext context, BoxConstraints viewportConstraints) { return Form( key: _formKey, child: SingleChildScrollView( child: ConstrainedBox( constraints: BoxConstraints( minHeight: viewportConstraints.maxHeight, ), child: IntrinsicHeight( child: Column( children: [ Padding( padding: const EdgeInsets.all(24), child: Text( AppLocalizations.of(context)!.signInHeaderText, style: Theme.of(context).textTheme.headline5, textAlign: TextAlign.center, ), ), Align( alignment: Alignment.center, child: Container( constraints: const BoxConstraints( maxWidth: AppTheme.widthLimitedContentMaxWidth), padding: const EdgeInsets.symmetric(horizontal: 32), child: _buildForm(context), ), ), Container( alignment: AlignmentDirectional.centerStart, constraints: const BoxConstraints( maxWidth: AppTheme.widthLimitedContentMaxWidth), padding: const EdgeInsets.symmetric( horizontal: 32, vertical: 16), child: Text( AppLocalizations.of(context)!.signIn2faHintText, style: TextStyle(fontStyle: FontStyle.italic), ), ), if (!platform_k.isWeb) Expanded(child: Container()), Container( constraints: const BoxConstraints( maxWidth: AppTheme.widthLimitedContentMaxWidth), padding: const EdgeInsets.all(16), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ if (!ModalRoute.of(context)!.isFirst) TextButton( onPressed: () { Navigator.pop(context); }, child: Text(MaterialLocalizations.of(context) .cancelButtonLabel), ) else Container(), ElevatedButton( onPressed: () { if (_formKey.currentState?.validate() == true) { _connect(); } }, child: Text(AppLocalizations.of(context)! .connectButtonLabel), ), ], ), ), ], ), ), ), ), ); }, ), ); } Widget _buildForm(BuildContext context) { return Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Center( child: Icon( Icons.cloud, color: Theme.of(context).colorScheme.primary, size: 72, ), ), const SizedBox(height: 8), Row( children: [ Container( width: 64, child: DropdownButtonHideUnderline( child: DropdownButtonFormField<_Scheme>( value: _scheme, items: [_Scheme.http, _Scheme.https] .map((e) => DropdownMenuItem<_Scheme>( value: e, child: Text(e.toValueString()), )) .toList(), onChanged: (newValue) { setState(() { _scheme = newValue!; }); }, onSaved: (value) { _formValue.scheme = value!.toValueString(); }, ), ), ), const Padding( padding: const EdgeInsets.symmetric(horizontal: 4), child: const Text("://"), ), Expanded( child: TextFormField( decoration: InputDecoration( hintText: AppLocalizations.of(context)!.serverAddressInputHint, ), keyboardType: TextInputType.url, validator: (value) { if (value!.trim().trimRightAny("/").isEmpty) { return AppLocalizations.of(context)! .serverAddressInputInvalidEmpty; } return null; }, onSaved: (value) { _formValue.address = value!.trim().trimRightAny("/"); }, ), ), ], ), const SizedBox(height: 8), TextFormField( decoration: InputDecoration( hintText: AppLocalizations.of(context)!.usernameInputHint, ), validator: (value) { if (value!.trim().isEmpty) { return AppLocalizations.of(context)!.usernameInputInvalidEmpty; } return null; }, onSaved: (value) { _formValue.username = value!; }, ), const SizedBox(height: 8), TextFormField( decoration: InputDecoration( hintText: AppLocalizations.of(context)!.passwordInputHint, ), obscureText: true, validator: (value) { if (value!.trim().isEmpty) { return AppLocalizations.of(context)!.passwordInputInvalidEmpty; } return null; }, onSaved: (value) { _formValue.password = value!; }, ), ], ); } void _connect() { _formKey.currentState!.save(); final account = Account(_formValue.scheme, _formValue.address, _formValue.username, _formValue.password, [""]); _log.info("[_connect] Try connecting with account: $account"); Navigator.pushNamed(context, Connect.routeName, arguments: ConnectArguments(account)) .then((result) { return result != null ? Navigator.pushNamed(context, RootPicker.routeName, arguments: RootPickerArguments(result)) : Future.value(null); }).then((result) { if (result != null) { // we've got a good account // only signing in with app password would trigger distinct final accounts = (Pref.inst().getAccountsOr([])..add(result)).distinct(); Pref.inst() ..setAccounts(accounts) ..setCurrentAccountIndex(accounts.indexOf(result)); Navigator.pushNamedAndRemoveUntil( context, Home.routeName, (route) => false, arguments: HomeArguments(result)); } }); } final _formKey = GlobalKey(); var _scheme = _Scheme.https; final _formValue = _FormValue(); static final _log = Logger("widget.sign_in._SignInState"); } enum _Scheme { http, https, } extension on _Scheme { String toValueString() { switch (this) { case _Scheme.http: return "http"; case _Scheme.https: return "https"; default: throw StateError("Unknown value: $this"); } } } class _FormValue { late String scheme; late String address; late String username; late String password; }