mirror of
https://gitlab.com/nkming2/nc-photos.git
synced 2025-02-24 18:38:48 +01:00
Restore legacy sign in in debug mode for quick dev access
This commit is contained in:
parent
34066215c0
commit
dbe74cf2d5
5 changed files with 842 additions and 0 deletions
136
app/lib/legacy/app_password_exchange_bloc.dart
Normal file
136
app/lib/legacy/app_password_exchange_bloc.dart
Normal file
|
@ -0,0 +1,136 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:nc_photos/account.dart';
|
||||
import 'package:nc_photos/api/api.dart';
|
||||
import 'package:nc_photos/exception.dart';
|
||||
|
||||
abstract class AppPasswordExchangeBlocEvent {
|
||||
const AppPasswordExchangeBlocEvent();
|
||||
}
|
||||
|
||||
class AppPasswordExchangeBlocConnect extends AppPasswordExchangeBlocEvent {
|
||||
const AppPasswordExchangeBlocConnect(this.account);
|
||||
|
||||
@override
|
||||
toString() {
|
||||
return "$runtimeType {"
|
||||
"account: $account, "
|
||||
"}";
|
||||
}
|
||||
|
||||
final Account account;
|
||||
}
|
||||
|
||||
abstract class AppPasswordExchangeBlocState {
|
||||
const AppPasswordExchangeBlocState();
|
||||
}
|
||||
|
||||
class AppPasswordExchangeBlocInit extends AppPasswordExchangeBlocState {
|
||||
const AppPasswordExchangeBlocInit();
|
||||
}
|
||||
|
||||
class AppPasswordExchangeBlocSuccess extends AppPasswordExchangeBlocState {
|
||||
const AppPasswordExchangeBlocSuccess(this.password);
|
||||
|
||||
@override
|
||||
toString() {
|
||||
return "$runtimeType {"
|
||||
"password: ${kDebugMode ? password : '***'}, "
|
||||
"}";
|
||||
}
|
||||
|
||||
final String password;
|
||||
}
|
||||
|
||||
class AppPasswordExchangeBlocFailure extends AppPasswordExchangeBlocState {
|
||||
const AppPasswordExchangeBlocFailure(this.exception);
|
||||
|
||||
@override
|
||||
toString() {
|
||||
return "$runtimeType {"
|
||||
"exception: $exception, "
|
||||
"}";
|
||||
}
|
||||
|
||||
final dynamic exception;
|
||||
}
|
||||
|
||||
/// Legacy sign in support, may be removed any time in the future
|
||||
class AppPasswordExchangeBloc
|
||||
extends Bloc<AppPasswordExchangeBlocEvent, AppPasswordExchangeBlocState> {
|
||||
AppPasswordExchangeBloc() : super(const AppPasswordExchangeBlocInit()) {
|
||||
on<AppPasswordExchangeBlocEvent>(_onEvent);
|
||||
}
|
||||
|
||||
Future<void> _onEvent(AppPasswordExchangeBlocEvent event,
|
||||
Emitter<AppPasswordExchangeBlocState> emit) async {
|
||||
_log.info("[_onEvent] $event");
|
||||
if (event is AppPasswordExchangeBlocConnect) {
|
||||
await _onEventConnect(event, emit);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onEventConnect(AppPasswordExchangeBlocConnect ev,
|
||||
Emitter<AppPasswordExchangeBlocState> emit) async {
|
||||
final account = ev.account;
|
||||
try {
|
||||
final appPwd = await _exchangePassword(account);
|
||||
emit(AppPasswordExchangeBlocSuccess(appPwd));
|
||||
} on InvalidBaseUrlException catch (e) {
|
||||
_log.warning("[_onEventConnect] Invalid base url");
|
||||
emit(AppPasswordExchangeBlocFailure(e));
|
||||
} on HandshakeException catch (e) {
|
||||
_log.info("[_onEventConnect] Self-signed cert");
|
||||
emit(AppPasswordExchangeBlocFailure(e));
|
||||
} catch (e, stacktrace) {
|
||||
if (e is ApiException && e.response.statusCode == 401) {
|
||||
// wrong password, normal
|
||||
_log.warning("[_onEventConnect] Server response 401, wrong password?");
|
||||
} else {
|
||||
_log.shout("[_onEventConnect] Failed while exchanging password", e,
|
||||
stacktrace);
|
||||
}
|
||||
emit(AppPasswordExchangeBlocFailure(e));
|
||||
}
|
||||
}
|
||||
|
||||
/// Query the app password for [account]
|
||||
static Future<String> _exchangePassword(Account account) async {
|
||||
final response = await Api(account).request(
|
||||
"GET",
|
||||
"ocs/v2.php/core/getapppassword",
|
||||
header: {
|
||||
"OCS-APIRequest": "true",
|
||||
},
|
||||
);
|
||||
if (response.isGood) {
|
||||
try {
|
||||
final appPwdRegex = RegExp(r"<apppassword>(.*)</apppassword>");
|
||||
final appPwdMatch = appPwdRegex.firstMatch(response.body);
|
||||
return appPwdMatch!.group(1)!;
|
||||
} catch (_) {
|
||||
// this happens when the address is not the base URL and so Nextcloud
|
||||
// returned the login page
|
||||
throw InvalidBaseUrlException();
|
||||
}
|
||||
} else if (response.statusCode == 403) {
|
||||
// If the client is authenticated with an app password a 403 will be
|
||||
// returned
|
||||
_log.info("[_exchangePassword] Already an app password");
|
||||
return account.password;
|
||||
} else {
|
||||
_log.severe(
|
||||
"[_exchangePassword] Failed while requesting app password: $response");
|
||||
throw ApiException(
|
||||
response: response,
|
||||
message:
|
||||
"Server responed with an error: HTTP ${response.statusCode}");
|
||||
}
|
||||
}
|
||||
|
||||
static final _log =
|
||||
Logger("legacy.app_password_exchange_bloc.AppPasswordExchangeBloc");
|
||||
}
|
319
app/lib/legacy/connect.dart
Normal file
319
app/lib/legacy/connect.dart
Normal file
|
@ -0,0 +1,319 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:kiwi/kiwi.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:nc_photos/account.dart';
|
||||
import 'package:nc_photos/app_localizations.dart';
|
||||
import 'package:nc_photos/ci_string.dart';
|
||||
import 'package:nc_photos/di_container.dart';
|
||||
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
||||
import 'package:nc_photos/exception.dart';
|
||||
import 'package:nc_photos/exception_util.dart' as exception_util;
|
||||
import 'package:nc_photos/help_utils.dart' as help_util;
|
||||
import 'package:nc_photos/k.dart' as k;
|
||||
import 'package:nc_photos/legacy/app_password_exchange_bloc.dart';
|
||||
import 'package:nc_photos/mobile/self_signed_cert_manager.dart';
|
||||
import 'package:nc_photos/platform/features.dart' as features;
|
||||
import 'package:nc_photos/snack_bar_manager.dart';
|
||||
import 'package:nc_photos/string_extension.dart';
|
||||
import 'package:nc_photos/url_launcher_util.dart';
|
||||
import 'package:nc_photos/use_case/ls_single_file.dart';
|
||||
|
||||
class ConnectArguments {
|
||||
ConnectArguments(this.account);
|
||||
|
||||
final Account account;
|
||||
}
|
||||
|
||||
/// Legacy sign in support, may be removed any time in the future
|
||||
class Connect extends StatefulWidget {
|
||||
static const routeName = "/connect-legacy";
|
||||
|
||||
static Route buildRoute(ConnectArguments args) => MaterialPageRoute<Account>(
|
||||
builder: (context) => Connect.fromArgs(args),
|
||||
);
|
||||
|
||||
const Connect({
|
||||
Key? key,
|
||||
required this.account,
|
||||
}) : super(key: key);
|
||||
|
||||
Connect.fromArgs(ConnectArguments args, {Key? key})
|
||||
: this(
|
||||
key: key,
|
||||
account: args.account,
|
||||
);
|
||||
|
||||
@override
|
||||
createState() => _ConnectState();
|
||||
|
||||
final Account account;
|
||||
}
|
||||
|
||||
class _ConnectState extends State<Connect> {
|
||||
@override
|
||||
initState() {
|
||||
super.initState();
|
||||
_initBloc();
|
||||
}
|
||||
|
||||
@override
|
||||
build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: BlocListener<AppPasswordExchangeBloc, AppPasswordExchangeBlocState>(
|
||||
bloc: _bloc,
|
||||
listener: (context, state) => _onStateChange(context, state),
|
||||
child: Builder(builder: (context) => _buildContent(context)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _initBloc() {
|
||||
_log.info("[_initBloc] Initialize bloc");
|
||||
_connect();
|
||||
}
|
||||
|
||||
Widget _buildContent(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Center(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.cloud,
|
||||
size: 128,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
Text(
|
||||
L10n.global().connectingToServer(widget.account.url),
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.headline6,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _onStateChange(
|
||||
BuildContext context, AppPasswordExchangeBlocState state) {
|
||||
if (state is AppPasswordExchangeBlocSuccess) {
|
||||
final newAccount = widget.account.copyWith(password: state.password);
|
||||
_log.info("[_onStateChange] Password exchanged: $newAccount");
|
||||
_checkWebDavUrl(context, newAccount);
|
||||
} else if (state is AppPasswordExchangeBlocFailure) {
|
||||
if (features.isSupportSelfSignedCert &&
|
||||
state.exception is HandshakeException) {
|
||||
_onSelfSignedCert(context);
|
||||
} else if (state.exception is ApiException &&
|
||||
(state.exception as ApiException).response.statusCode == 401) {
|
||||
SnackBarManager().showSnackBar(SnackBar(
|
||||
content: Text(L10n.global().errorWrongPassword),
|
||||
duration: k.snackBarDurationNormal,
|
||||
));
|
||||
Navigator.of(context).pop(null);
|
||||
} else {
|
||||
SnackBarManager().showSnackBar(SnackBar(
|
||||
content: Text(exception_util.toUserString(state.exception)),
|
||||
duration: k.snackBarDurationNormal,
|
||||
));
|
||||
Navigator.of(context).pop(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _onSelfSignedCert(BuildContext context) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text(L10n.global().serverCertErrorDialogTitle),
|
||||
content: Text(L10n.global().serverCertErrorDialogContent),
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Text(MaterialLocalizations.of(context).closeButtonLabel),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(true);
|
||||
},
|
||||
child: Text(L10n.global().advancedButtonLabel),
|
||||
),
|
||||
],
|
||||
),
|
||||
).then((value) {
|
||||
if (value != true) {
|
||||
Navigator.of(context).pop(null);
|
||||
return;
|
||||
}
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text(L10n.global().whitelistCertDialogTitle),
|
||||
content: Text(L10n.global().whitelistCertDialogContent(
|
||||
SelfSignedCertManager().getLastBadCertHost(),
|
||||
SelfSignedCertManager().getLastBadCertFingerprint())),
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Text(MaterialLocalizations.of(context).cancelButtonLabel),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(true);
|
||||
},
|
||||
child: Text(L10n.global().whitelistCertButtonLabel),
|
||||
),
|
||||
],
|
||||
),
|
||||
).then((value) {
|
||||
if (value != true) {
|
||||
Navigator.of(context).pop(null);
|
||||
return;
|
||||
}
|
||||
SelfSignedCertManager().whitelistLastBadCert().then((value) {
|
||||
Navigator.of(context).pop(null);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void _connect() {
|
||||
_bloc.add(AppPasswordExchangeBlocConnect(widget.account));
|
||||
}
|
||||
|
||||
Future<void> _onCheckWebDavUrlFailed(
|
||||
BuildContext context, Account account) async {
|
||||
final userId = await _askWebDavUrl(context, account);
|
||||
if (userId != null) {
|
||||
final newAccount = account.copyWith(
|
||||
userId: userId.toCi(),
|
||||
);
|
||||
return _checkWebDavUrl(context, newAccount);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _checkWebDavUrl(BuildContext context, Account account) async {
|
||||
// check the WebDAV URL
|
||||
try {
|
||||
final c = KiwiContainer().resolve<DiContainer>();
|
||||
await LsSingleFile(c.withRemoteFileRepo())(
|
||||
account, file_util.unstripPath(account, ""));
|
||||
_log.info("[_checkWebDavUrl] Account is good: $account");
|
||||
Navigator.of(context).pop(account);
|
||||
} on ApiException catch (e) {
|
||||
if (e.response.statusCode == 404) {
|
||||
return _onCheckWebDavUrlFailed(context, account);
|
||||
}
|
||||
SnackBarManager().showSnackBar(SnackBar(
|
||||
content: Text(exception_util.toUserString(e)),
|
||||
duration: k.snackBarDurationNormal,
|
||||
));
|
||||
Navigator.of(context).pop(null);
|
||||
} on StateError catch (_) {
|
||||
// Nextcloud for some reason doesn't return HTTP error when listing home
|
||||
// dir of other users
|
||||
return _onCheckWebDavUrlFailed(context, account);
|
||||
} catch (e, stackTrace) {
|
||||
_log.shout("[_checkWebDavUrl] Failed", e, stackTrace);
|
||||
SnackBarManager().showSnackBar(SnackBar(
|
||||
content: Text(exception_util.toUserString(e)),
|
||||
duration: k.snackBarDurationNormal,
|
||||
));
|
||||
Navigator.of(context).pop(null);
|
||||
}
|
||||
}
|
||||
|
||||
Future<String?> _askWebDavUrl(BuildContext context, Account account) {
|
||||
return showDialog<String>(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (context) => _WebDavUrlDialog(account: account),
|
||||
);
|
||||
}
|
||||
|
||||
final _bloc = AppPasswordExchangeBloc();
|
||||
|
||||
static final _log = Logger("widget.connect._ConnectState");
|
||||
}
|
||||
|
||||
class _WebDavUrlDialog extends StatefulWidget {
|
||||
const _WebDavUrlDialog({
|
||||
Key? key,
|
||||
required this.account,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
createState() => _WebDavUrlDialogState();
|
||||
|
||||
final Account account;
|
||||
}
|
||||
|
||||
class _WebDavUrlDialogState extends State<_WebDavUrlDialog> {
|
||||
@override
|
||||
build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(L10n.global().homeFolderNotFoundDialogTitle),
|
||||
content: Form(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(L10n.global().homeFolderNotFoundDialogContent),
|
||||
const SizedBox(height: 16),
|
||||
Text("${widget.account.url}/remote.php/dav/files/"),
|
||||
TextFormField(
|
||||
validator: (value) {
|
||||
if (value?.trimAny("/").isNotEmpty == true) {
|
||||
return null;
|
||||
}
|
||||
return L10n.global().homeFolderInputInvalidEmpty;
|
||||
},
|
||||
onSaved: (value) {
|
||||
_formValue.userId = value!.trimAny("/");
|
||||
},
|
||||
initialValue: widget.account.userId.toString(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: _onHelpPressed,
|
||||
child: Text(L10n.global().helpButtonLabel),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: _onOkPressed,
|
||||
child: Text(MaterialLocalizations.of(context).okButtonLabel),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
void _onOkPressed() {
|
||||
if (_formKey.currentState?.validate() == true) {
|
||||
_formKey.currentState!.save();
|
||||
Navigator.of(context).pop(_formValue.userId);
|
||||
}
|
||||
}
|
||||
|
||||
void _onHelpPressed() {
|
||||
launch(help_util.homeFolderNotFoundUrl);
|
||||
}
|
||||
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
final _formValue = _FormValue();
|
||||
}
|
||||
|
||||
class _FormValue {
|
||||
late String userId;
|
||||
}
|
356
app/lib/legacy/sign_in.dart
Normal file
356
app/lib/legacy/sign_in.dart
Normal file
|
@ -0,0 +1,356 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:kiwi/kiwi.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:nc_photos/account.dart';
|
||||
import 'package:nc_photos/app_localizations.dart';
|
||||
import 'package:nc_photos/ci_string.dart';
|
||||
import 'package:nc_photos/di_container.dart';
|
||||
import 'package:nc_photos/entity/sqlite_table_extension.dart' as sql;
|
||||
import 'package:nc_photos/help_utils.dart' as help_utils;
|
||||
import 'package:nc_photos/iterable_extension.dart';
|
||||
import 'package:nc_photos/legacy/connect.dart';
|
||||
import 'package:nc_photos/platform/k.dart' as platform_k;
|
||||
import 'package:nc_photos/pref.dart';
|
||||
import 'package:nc_photos/pref_util.dart' as pref_util;
|
||||
import 'package:nc_photos/string_extension.dart';
|
||||
import 'package:nc_photos/theme.dart';
|
||||
import 'package:nc_photos/url_launcher_util.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-legacy";
|
||||
|
||||
const SignIn({
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
createState() => _SignInState();
|
||||
}
|
||||
|
||||
class _SignInState extends State<SignIn> {
|
||||
@override
|
||||
build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Builder(builder: (context) => _buildContent(context)),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildContent(BuildContext context) {
|
||||
if (_isConnecting) {
|
||||
return Stack(
|
||||
children: const [
|
||||
Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 64,
|
||||
child: Center(
|
||||
child: SizedBox(
|
||||
width: 24,
|
||||
height: 24,
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
} else {
|
||||
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(
|
||||
L10n.global().signInHeaderText,
|
||||
style: Theme.of(context).textTheme.headline5,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.center,
|
||||
child: Container(
|
||||
constraints: BoxConstraints(
|
||||
maxWidth:
|
||||
Theme.of(context).widthLimitedContentMaxWidth,
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 32),
|
||||
child: _buildForm(context),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
constraints: BoxConstraints(
|
||||
maxWidth:
|
||||
Theme.of(context).widthLimitedContentMaxWidth,
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
launch(help_utils.twoFactorAuthUrl);
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16, vertical: 16),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Icon(Icons.help_outline, size: 16),
|
||||
const SizedBox(width: 4),
|
||||
Expanded(
|
||||
child:
|
||||
Text(L10n.global().signIn2faHintText),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (!platform_k.isWeb) Expanded(child: Container()),
|
||||
Container(
|
||||
constraints: BoxConstraints(
|
||||
maxWidth:
|
||||
Theme.of(context).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(L10n.global().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: [
|
||||
SizedBox(
|
||||
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: EdgeInsets.symmetric(horizontal: 4),
|
||||
child: Text("://"),
|
||||
),
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
decoration: InputDecoration(
|
||||
hintText: L10n.global().serverAddressInputHint,
|
||||
),
|
||||
keyboardType: TextInputType.url,
|
||||
validator: (value) {
|
||||
if (value!.trim().trimRightAny("/").isEmpty) {
|
||||
return L10n.global().serverAddressInputInvalidEmpty;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
onSaved: (value) {
|
||||
_formValue.address = value!.trim().trimRightAny("/");
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
TextFormField(
|
||||
decoration: InputDecoration(
|
||||
hintText: L10n.global().usernameInputHint,
|
||||
),
|
||||
validator: (value) {
|
||||
if (value!.trim().isEmpty) {
|
||||
return L10n.global().usernameInputInvalidEmpty;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
onSaved: (value) {
|
||||
_formValue.username = value!;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
TextFormField(
|
||||
decoration: InputDecoration(
|
||||
hintText: L10n.global().passwordInputHint,
|
||||
),
|
||||
obscureText: true,
|
||||
validator: (value) {
|
||||
if (value!.trim().isEmpty) {
|
||||
return L10n.global().passwordInputInvalidEmpty;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
onSaved: (value) {
|
||||
_formValue.password = value!;
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _connect() async {
|
||||
_formKey.currentState!.save();
|
||||
Account? account = Account(
|
||||
Account.newId(),
|
||||
_formValue.scheme,
|
||||
_formValue.address,
|
||||
_formValue.username.toCi(),
|
||||
_formValue.username,
|
||||
_formValue.password,
|
||||
[""],
|
||||
);
|
||||
_log.info("[_connect] Try connecting with account: $account");
|
||||
account = await Navigator.pushNamed<Account>(context, Connect.routeName,
|
||||
arguments: ConnectArguments(account));
|
||||
if (account == null) {
|
||||
// connection failed
|
||||
return;
|
||||
}
|
||||
account = await Navigator.pushNamed<Account>(context, RootPicker.routeName,
|
||||
arguments: RootPickerArguments(account));
|
||||
if (account == null) {
|
||||
// ???
|
||||
return;
|
||||
}
|
||||
// we've got a good account
|
||||
setState(() {
|
||||
_isConnecting = true;
|
||||
});
|
||||
try {
|
||||
await _persistAccount(account);
|
||||
unawaited(
|
||||
Navigator.pushNamedAndRemoveUntil(
|
||||
context, Home.routeName, (route) => false,
|
||||
arguments: HomeArguments(account)),
|
||||
);
|
||||
} catch (_) {
|
||||
setState(() {
|
||||
_isConnecting = false;
|
||||
});
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _persistAccount(Account account) async {
|
||||
final c = KiwiContainer().resolve<DiContainer>();
|
||||
await c.sqliteDb.use((db) async {
|
||||
await db.insertAccountOf(account);
|
||||
});
|
||||
// only signing in with app password would trigger distinct
|
||||
final accounts = (Pref().getAccounts3Or([])..add(account)).distinct();
|
||||
try {
|
||||
AccountPref.setGlobalInstance(
|
||||
account, await pref_util.loadAccountPref(account));
|
||||
} catch (e, stackTrace) {
|
||||
_log.shout("[_connect] Failed reading pref for account: $account", e,
|
||||
stackTrace);
|
||||
}
|
||||
unawaited(Pref().setAccounts3(accounts));
|
||||
unawaited(Pref().setCurrentAccountIndex(accounts.indexOf(account)));
|
||||
}
|
||||
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
var _scheme = _Scheme.https;
|
||||
var _isConnecting = false;
|
||||
|
||||
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;
|
||||
}
|
|
@ -4,6 +4,8 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
|||
import 'package:logging/logging.dart';
|
||||
import 'package:nc_photos/event/event.dart';
|
||||
import 'package:nc_photos/language_util.dart' as language_util;
|
||||
import 'package:nc_photos/legacy/connect.dart' as legacy;
|
||||
import 'package:nc_photos/legacy/sign_in.dart' as legacy;
|
||||
import 'package:nc_photos/navigation_manager.dart';
|
||||
import 'package:nc_photos/pref.dart';
|
||||
import 'package:nc_photos/snack_bar_manager.dart';
|
||||
|
@ -137,6 +139,7 @@ class _MyAppState extends State<MyApp>
|
|||
Setup.routeName: (context) => const Setup(),
|
||||
SignIn.routeName: (context) => const SignIn(),
|
||||
Splash.routeName: (context) => const Splash(),
|
||||
legacy.SignIn.routeName: (_) => const legacy.SignIn(),
|
||||
};
|
||||
|
||||
Route<dynamic>? _onGenerateRoute(RouteSettings settings) {
|
||||
|
@ -145,6 +148,7 @@ class _MyAppState extends State<MyApp>
|
|||
route ??= _handleBasicRoute(settings);
|
||||
route ??= _handleViewerRoute(settings);
|
||||
route ??= _handleConnectRoute(settings);
|
||||
route ??= _handleConnectLegacyRoute(settings);
|
||||
route ??= _handleHomeRoute(settings);
|
||||
route ??= _handleRootPickerRoute(settings);
|
||||
route ??= _handleAlbumBrowserRoute(settings);
|
||||
|
@ -221,6 +225,19 @@ class _MyAppState extends State<MyApp>
|
|||
return null;
|
||||
}
|
||||
|
||||
Route<dynamic>? _handleConnectLegacyRoute(RouteSettings settings) {
|
||||
try {
|
||||
if (settings.name == legacy.Connect.routeName &&
|
||||
settings.arguments != null) {
|
||||
final args = settings.arguments as legacy.ConnectArguments;
|
||||
return legacy.Connect.buildRoute(args);
|
||||
}
|
||||
} catch (e) {
|
||||
_log.severe("[_handleConnectLegacyRoute] Failed while handling route", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Route<dynamic>? _handleHomeRoute(RouteSettings settings) {
|
||||
try {
|
||||
if (settings.name == Home.routeName && settings.arguments != null) {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:kiwi/kiwi.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
@ -8,6 +9,7 @@ import 'package:nc_photos/app_localizations.dart';
|
|||
import 'package:nc_photos/di_container.dart';
|
||||
import 'package:nc_photos/entity/sqlite_table_extension.dart' as sql;
|
||||
import 'package:nc_photos/iterable_extension.dart';
|
||||
import 'package:nc_photos/legacy/sign_in.dart' as legacy;
|
||||
import 'package:nc_photos/platform/k.dart' as platform_k;
|
||||
import 'package:nc_photos/pref.dart';
|
||||
import 'package:nc_photos/pref_util.dart' as pref_util;
|
||||
|
@ -191,6 +193,18 @@ class _SignInState extends State<SignIn> {
|
|||
),
|
||||
],
|
||||
),
|
||||
if (kDebugMode) ...[
|
||||
const SizedBox(height: 8),
|
||||
InkWell(
|
||||
onTap: () {
|
||||
Navigator.pushReplacementNamed(context, legacy.SignIn.routeName);
|
||||
},
|
||||
child: const Text(
|
||||
"Legacy sign in",
|
||||
style: TextStyle(decoration: TextDecoration.underline),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue