Initial commit
This commit is contained in:
38
lib/pages/intro/flows/restore_backup_flow.dart
Normal file
38
lib/pages/intro/flows/restore_backup_flow.dart
Normal file
@@ -0,0 +1,38 @@
|
||||
// SPDX-FileCopyrightText: 2019-Present Christian Kußowski
|
||||
// SPDX-FileCopyrightText: 2019-Present Contributors to FluffyChat
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import 'package:tatchat/utils/file_selector.dart';
|
||||
import 'package:tatchat/widgets/future_loading_dialog.dart';
|
||||
import 'package:tatchat/widgets/matrix.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:material_ui/material_ui.dart';
|
||||
|
||||
Future<void> restoreBackupFlow(BuildContext context) async {
|
||||
final matrix = Matrix.of(context);
|
||||
final picked = await selectFiles(context);
|
||||
final file = picked.firstOrNull;
|
||||
if (file == null) return;
|
||||
|
||||
if (!context.mounted) return;
|
||||
final result = await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () async {
|
||||
final client = await matrix.getLoginClient();
|
||||
await client.database.importDump(
|
||||
String.fromCharCodes(await file.readAsBytes()),
|
||||
);
|
||||
await client.init(
|
||||
waitForFirstSync: false,
|
||||
waitUntilLoadCompletedLoaded: false,
|
||||
);
|
||||
matrix.initMatrix();
|
||||
return client.isLogged();
|
||||
},
|
||||
);
|
||||
|
||||
if (result.result == true && context.mounted) {
|
||||
context.go('/rooms');
|
||||
}
|
||||
}
|
||||
199
lib/pages/intro/intro_page.dart
Normal file
199
lib/pages/intro/intro_page.dart
Normal file
@@ -0,0 +1,199 @@
|
||||
// SPDX-FileCopyrightText: 2019-Present Christian Kußowski
|
||||
// SPDX-FileCopyrightText: 2019-Present Contributors to FluffyChat
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import 'package:tatchat/config/setting_keys.dart';
|
||||
import 'package:tatchat/l10n/l10n.dart';
|
||||
import 'package:tatchat/pages/intro/flows/restore_backup_flow.dart';
|
||||
import 'package:tatchat/utils/platform_infos.dart';
|
||||
import 'package:tatchat/widgets/layouts/login_scaffold.dart';
|
||||
import 'package:tatchat/widgets/matrix.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:material_ui/material_ui.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
class IntroPage extends StatelessWidget {
|
||||
final bool isLoading, hasPresetHomeserver;
|
||||
final String? loggingInToHomeserver, welcomeText;
|
||||
final VoidCallback login, register;
|
||||
|
||||
const IntroPage({
|
||||
required this.isLoading,
|
||||
required this.loggingInToHomeserver,
|
||||
super.key,
|
||||
required this.hasPresetHomeserver,
|
||||
required this.welcomeText,
|
||||
required this.login,
|
||||
required this.register,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final addMultiAccount = Matrix.of(
|
||||
context,
|
||||
).widget.clients.any((client) => client.isLogged());
|
||||
final loggingInToHomeserver = this.loggingInToHomeserver;
|
||||
|
||||
return LoginScaffold(
|
||||
appBar: AppBar(
|
||||
centerTitle: true,
|
||||
title: addMultiAccount
|
||||
? Text(L10n.of(context).addAccount)
|
||||
: Text(AppSettings.applicationName.value),
|
||||
actions: [
|
||||
PopupMenuButton(
|
||||
useRootNavigator: true,
|
||||
itemBuilder: (_) => [
|
||||
PopupMenuItem(
|
||||
onTap: isLoading ? null : () => restoreBackupFlow(context),
|
||||
child: Row(
|
||||
mainAxisSize: .min,
|
||||
spacing: 12,
|
||||
children: [
|
||||
const Icon(Icons.import_export_outlined),
|
||||
Expanded(
|
||||
child: Text(
|
||||
L10n.of(context).hydrate,
|
||||
maxLines: 1,
|
||||
overflow: .ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
PopupMenuItem(
|
||||
onTap: () => launchUrlString(AppSettings.privacyPolicy.value),
|
||||
child: Row(
|
||||
mainAxisSize: .min,
|
||||
spacing: 12,
|
||||
children: [
|
||||
const Icon(Icons.privacy_tip_outlined),
|
||||
Text(L10n.of(context).privacy),
|
||||
],
|
||||
),
|
||||
),
|
||||
PopupMenuItem(
|
||||
onTap: () => PlatformInfos.showDialog(context),
|
||||
child: Row(
|
||||
mainAxisSize: .min,
|
||||
spacing: 12,
|
||||
children: [
|
||||
const Icon(Icons.info_outlined),
|
||||
Text(L10n.of(context).about),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
body: isLoading
|
||||
? Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: .center,
|
||||
children: [
|
||||
CircularProgressIndicator.adaptive(),
|
||||
if (loggingInToHomeserver != null)
|
||||
Text(L10n.of(context).logInTo(loggingInToHomeserver)),
|
||||
],
|
||||
),
|
||||
)
|
||||
: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
return SingleChildScrollView(
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
minHeight: constraints.maxHeight,
|
||||
),
|
||||
child: IntrinsicHeight(
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
alignment: Alignment.center,
|
||||
padding: const EdgeInsets.all(32.0),
|
||||
child: Hero(
|
||||
tag: 'info-logo',
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(128),
|
||||
child: Image.asset(
|
||||
'./assets/logo/mini/logo_mini.png',
|
||||
width: 128,
|
||||
height: 128,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
L10n.of(context).appSubtitle,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(fontSize: 20),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
child: Text(
|
||||
welcomeText ?? L10n.of(context).appDescription,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 32),
|
||||
child: Column(
|
||||
mainAxisSize: .min,
|
||||
crossAxisAlignment: .stretch,
|
||||
children: [
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor:
|
||||
theme.colorScheme.secondary,
|
||||
foregroundColor:
|
||||
theme.colorScheme.onSecondary,
|
||||
),
|
||||
onPressed: hasPresetHomeserver
|
||||
? register
|
||||
: () => context.go(
|
||||
'${GoRouterState.of(context).uri.path}/sign_up',
|
||||
),
|
||||
child: Text(
|
||||
L10n.of(context).createNewAccount,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
ElevatedButton(
|
||||
onPressed: login,
|
||||
child: Text(L10n.of(context).signIn),
|
||||
),
|
||||
|
||||
if (!hasPresetHomeserver)
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
final client = await Matrix.of(
|
||||
context,
|
||||
).getLoginClient();
|
||||
if (!context.mounted) return;
|
||||
context.go(
|
||||
'${GoRouterState.of(context).uri.path}/login',
|
||||
extra: client,
|
||||
);
|
||||
},
|
||||
child: Text(
|
||||
L10n.of(context).loginWithMatrixId,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 36),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
144
lib/pages/intro/intro_page_presenter.dart
Normal file
144
lib/pages/intro/intro_page_presenter.dart
Normal file
@@ -0,0 +1,144 @@
|
||||
// SPDX-FileCopyrightText: 2019-Present Christian Kußowski
|
||||
// SPDX-FileCopyrightText: 2019-Present Contributors to FluffyChat
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:tatchat/config/setting_keys.dart';
|
||||
import 'package:tatchat/pages/intro/intro_page.dart';
|
||||
import 'package:tatchat/pages/sign_in/view_model/model/public_homeserver_data.dart';
|
||||
import 'package:tatchat/utils/localized_exception_extension.dart';
|
||||
import 'package:tatchat/utils/matrix_sdk_extensions/oidc_session_json_extension.dart';
|
||||
import 'package:tatchat/utils/sign_in_flows/check_homeserver.dart';
|
||||
import 'package:tatchat/widgets/matrix.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:material_ui/material_ui.dart';
|
||||
import 'package:matrix/matrix_api_lite/utils/logs.dart';
|
||||
import 'package:matrix/msc_extensions/msc_2964_oidc_login_flow/msc_2964_oidc_login_flow.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:universal_html/universal_html.dart' as web;
|
||||
|
||||
class IntroPagePresenter extends StatefulWidget {
|
||||
const IntroPagePresenter({super.key});
|
||||
|
||||
@override
|
||||
State<IntroPagePresenter> createState() => _IntroPagePresenterState();
|
||||
}
|
||||
|
||||
class _IntroPagePresenterState extends State<IntroPagePresenter> {
|
||||
bool isLoading = kIsWeb;
|
||||
String? loggingInToHomeserver;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
if (kIsWeb) _finishOidcLogin();
|
||||
}
|
||||
|
||||
Future<void> _finishOidcLogin() async {
|
||||
final store = await SharedPreferences.getInstance();
|
||||
final storedHomeserverString = store.getString(
|
||||
OidcSessionJsonExtension.homeserverStoreKey,
|
||||
);
|
||||
final homeserverUrl = storedHomeserverString == null
|
||||
? null
|
||||
: Uri.tryParse(storedHomeserverString);
|
||||
|
||||
final oidcSessionString = store.getString(
|
||||
OidcSessionJsonExtension.storeKey,
|
||||
);
|
||||
final session = oidcSessionString == null
|
||||
? null
|
||||
: OidcSessionJsonExtension.fromJson(jsonDecode(oidcSessionString));
|
||||
|
||||
await store.remove(OidcSessionJsonExtension.storeKey);
|
||||
await store.remove(OidcSessionJsonExtension.homeserverStoreKey);
|
||||
if (!mounted) return;
|
||||
|
||||
if (homeserverUrl == null || session == null) {
|
||||
setState(() {
|
||||
isLoading = false;
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
loggingInToHomeserver = homeserverUrl.origin;
|
||||
});
|
||||
|
||||
try {
|
||||
final returnUrl = Uri.parse(web.window.location.href);
|
||||
final queryParameters = returnUrl.hasFragment
|
||||
? Uri.parse(returnUrl.fragment).queryParameters
|
||||
: returnUrl.queryParameters;
|
||||
final code = queryParameters['code'] as String;
|
||||
final state = queryParameters['state'] as String;
|
||||
|
||||
final client = await Matrix.of(context).getLoginClient();
|
||||
await client.checkHomeserver(homeserverUrl);
|
||||
await client.oidcLogin(session: session, code: code, state: state);
|
||||
if (!mounted) return;
|
||||
context.go('/backup');
|
||||
} catch (e, s) {
|
||||
Logs().w('Unable to login via OIDC', e, s);
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text(e.toLocalizedString(context))));
|
||||
}
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _login() {
|
||||
final presetHomeserver = AppSettings.presetHomeserver.value;
|
||||
if (presetHomeserver.isEmpty) {
|
||||
context.go('${GoRouterState.of(context).uri.path}/sign_in');
|
||||
return;
|
||||
}
|
||||
|
||||
connectToHomeserverFlow(
|
||||
PublicHomeserverData(name: presetHomeserver),
|
||||
context,
|
||||
(snapshot) {},
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
void _signUp() {
|
||||
final presetHomeserver = AppSettings.presetHomeserver.value;
|
||||
if (presetHomeserver.isEmpty) {
|
||||
context.go('${GoRouterState.of(context).uri.path}/sign_up');
|
||||
return;
|
||||
}
|
||||
|
||||
connectToHomeserverFlow(
|
||||
PublicHomeserverData(name: presetHomeserver),
|
||||
context,
|
||||
(snapshot) {},
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return IntroPage(
|
||||
isLoading: isLoading,
|
||||
loggingInToHomeserver: loggingInToHomeserver,
|
||||
hasPresetHomeserver: AppSettings.presetHomeserver.value.isNotEmpty,
|
||||
welcomeText: AppSettings.welcomeText.value.isEmpty
|
||||
? null
|
||||
: AppSettings.welcomeText.value,
|
||||
login: _login,
|
||||
register: _signUp,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user