Initial commit
This commit is contained in:
247
lib/pages/sign_in/sign_in_page.dart
Normal file
247
lib/pages/sign_in/sign_in_page.dart
Normal file
@@ -0,0 +1,247 @@
|
||||
// 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/app_config.dart';
|
||||
import 'package:tatchat/config/themes.dart';
|
||||
import 'package:tatchat/l10n/l10n.dart';
|
||||
import 'package:tatchat/pages/sign_in/view_model/model/public_homeserver_data.dart';
|
||||
import 'package:tatchat/pages/sign_in/view_model/sign_in_view_model.dart';
|
||||
import 'package:tatchat/utils/localized_exception_extension.dart';
|
||||
import 'package:tatchat/utils/sign_in_flows/check_homeserver.dart';
|
||||
import 'package:tatchat/widgets/layouts/login_scaffold.dart';
|
||||
import 'package:tatchat/widgets/matrix.dart';
|
||||
import 'package:tatchat/widgets/view_model_builder.dart';
|
||||
import 'package:material_ui/material_ui.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
class SignInPage extends StatelessWidget {
|
||||
final bool signUp;
|
||||
const SignInPage({required this.signUp, super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
return ViewModelBuilder(
|
||||
create: () => SignInViewModel(Matrix.of(context), signUp: signUp),
|
||||
builder: (context, viewModel, _) {
|
||||
final state = viewModel.value;
|
||||
final publicHomeservers = state.filteredPublicHomeservers;
|
||||
final selectedHomserver = state.selectedHomeserver;
|
||||
return LoginScaffold(
|
||||
appBar: AppBar(
|
||||
leading:
|
||||
state.loginLoading.connectionState == ConnectionState.waiting
|
||||
? CloseButton(
|
||||
onPressed: () =>
|
||||
viewModel.setLoginLoading(AsyncSnapshot.nothing()),
|
||||
)
|
||||
: BackButton(onPressed: Navigator.of(context).pop),
|
||||
backgroundColor: theme.colorScheme.surface,
|
||||
surfaceTintColor: theme.colorScheme.surface,
|
||||
scrolledUnderElevation: 0,
|
||||
centerTitle: true,
|
||||
title: Text(
|
||||
signUp
|
||||
? L10n.of(context).createNewAccount
|
||||
: L10n.of(context).login,
|
||||
),
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: Column(
|
||||
spacing: 16,
|
||||
children: [
|
||||
SelectableText(
|
||||
signUp
|
||||
? L10n.of(context).signUpGreeting
|
||||
: L10n.of(context).signInGreeting,
|
||||
textAlign: .center,
|
||||
),
|
||||
TextField(
|
||||
readOnly:
|
||||
state.publicHomeservers.connectionState ==
|
||||
ConnectionState.waiting ||
|
||||
state.loginLoading.connectionState ==
|
||||
ConnectionState.waiting,
|
||||
controller: viewModel.filterTextController,
|
||||
autocorrect: false,
|
||||
keyboardType: TextInputType.url,
|
||||
decoration: InputDecoration(
|
||||
filled: true,
|
||||
fillColor: theme.colorScheme.secondaryContainer,
|
||||
border: OutlineInputBorder(
|
||||
borderSide: BorderSide.none,
|
||||
borderRadius: BorderRadius.circular(99),
|
||||
),
|
||||
errorText: state.publicHomeservers.error?.toLocalizedString(
|
||||
context,
|
||||
),
|
||||
prefixIcon: const Icon(Icons.search_outlined),
|
||||
hintText: L10n.of(context).searchOrEnterHomeserverAddress,
|
||||
),
|
||||
),
|
||||
if (state.publicHomeservers.connectionState ==
|
||||
ConnectionState.done)
|
||||
Expanded(
|
||||
child: Material(
|
||||
borderRadius: BorderRadius.circular(
|
||||
AppConfig.borderRadius,
|
||||
),
|
||||
clipBehavior: Clip.hardEdge,
|
||||
color: theme.colorScheme.surfaceContainerLow,
|
||||
child: RadioGroup<PublicHomeserverData>(
|
||||
groupValue: state.selectedHomeserver,
|
||||
onChanged: viewModel.selectHomeserver,
|
||||
child: ListView.builder(
|
||||
itemCount: publicHomeservers.length,
|
||||
itemBuilder: (context, i) {
|
||||
final server = publicHomeservers[i];
|
||||
final website = server.website;
|
||||
return RadioListTile(
|
||||
value: server,
|
||||
enabled:
|
||||
state.loginLoading.connectionState !=
|
||||
ConnectionState.waiting,
|
||||
title: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(server.name ?? 'Unknown'),
|
||||
),
|
||||
if (website != null)
|
||||
SizedBox.square(
|
||||
dimension: 32,
|
||||
child: IconButton(
|
||||
tooltip: website,
|
||||
icon: const Icon(
|
||||
Icons.open_in_new_outlined,
|
||||
size: 16,
|
||||
),
|
||||
onPressed: () =>
|
||||
launchUrlString(website),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
subtitle: Column(
|
||||
spacing: 4.0,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (server.features?.isNotEmpty == true)
|
||||
Wrap(
|
||||
spacing: 4.0,
|
||||
runSpacing: 4.0,
|
||||
children: [
|
||||
...?server.languages?.map(
|
||||
(language) => Material(
|
||||
borderRadius: BorderRadius.circular(
|
||||
AppConfig.borderRadius,
|
||||
),
|
||||
color: theme
|
||||
.colorScheme
|
||||
.tertiaryContainer,
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(
|
||||
horizontal: 6.0,
|
||||
vertical: 3.0,
|
||||
),
|
||||
child: Text(
|
||||
language,
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
color: theme
|
||||
.colorScheme
|
||||
.onTertiaryContainer,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
...server.features!.map(
|
||||
(feature) => Material(
|
||||
borderRadius: BorderRadius.circular(
|
||||
AppConfig.borderRadius,
|
||||
),
|
||||
color: theme
|
||||
.colorScheme
|
||||
.secondaryContainer,
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(
|
||||
horizontal: 6.0,
|
||||
vertical: 3.0,
|
||||
),
|
||||
child: Text(
|
||||
feature,
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
color: theme
|
||||
.colorScheme
|
||||
.onSecondaryContainer,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Text(
|
||||
server.description ?? 'A matrix homeserver',
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
else
|
||||
Center(child: CircularProgressIndicator.adaptive()),
|
||||
],
|
||||
),
|
||||
),
|
||||
bottomNavigationBar: AnimatedSize(
|
||||
duration: FluffyThemes.animationDuration,
|
||||
curve: FluffyThemes.animationCurve,
|
||||
child:
|
||||
selectedHomserver == null ||
|
||||
!publicHomeservers.contains(selectedHomserver)
|
||||
? const SizedBox.shrink()
|
||||
: Material(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: SafeArea(
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: theme.colorScheme.primary,
|
||||
foregroundColor: theme.colorScheme.onPrimary,
|
||||
),
|
||||
onPressed:
|
||||
state.loginLoading.connectionState ==
|
||||
ConnectionState.waiting
|
||||
? null
|
||||
: () => connectToHomeserverFlow(
|
||||
selectedHomserver,
|
||||
context,
|
||||
viewModel.setLoginLoading,
|
||||
signUp,
|
||||
),
|
||||
child:
|
||||
state.loginLoading.connectionState ==
|
||||
ConnectionState.waiting
|
||||
? const CircularProgressIndicator.adaptive()
|
||||
: Text(L10n.of(context).continueText),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
// SPDX-FileCopyrightText: 2019-Present Christian Kußowski
|
||||
// SPDX-FileCopyrightText: 2019-Present Contributors to FluffyChat
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
class PublicHomeserverData {
|
||||
final String? name;
|
||||
final String? clientDomain;
|
||||
final String? website;
|
||||
final String? isp;
|
||||
final String? staffJur;
|
||||
final String? rules;
|
||||
final String? privacy;
|
||||
final bool? usingVanillaReg;
|
||||
final String? description;
|
||||
final String? regMethod;
|
||||
final String? regLink;
|
||||
final String? software;
|
||||
final String? version;
|
||||
final bool? captcha;
|
||||
final bool? email;
|
||||
final List<String>? languages;
|
||||
final List<String>? features;
|
||||
final int? onlineStatus;
|
||||
final String? serverDomain;
|
||||
final int? verStatus;
|
||||
final int? roomDirectory;
|
||||
final bool? slidingSync;
|
||||
final bool? ipv6;
|
||||
|
||||
PublicHomeserverData({
|
||||
this.name,
|
||||
this.clientDomain,
|
||||
this.website,
|
||||
this.isp,
|
||||
this.staffJur,
|
||||
this.rules,
|
||||
this.privacy,
|
||||
this.usingVanillaReg,
|
||||
this.description,
|
||||
this.regMethod,
|
||||
this.regLink,
|
||||
this.software,
|
||||
this.version,
|
||||
this.captcha,
|
||||
this.email,
|
||||
this.languages,
|
||||
this.features,
|
||||
this.onlineStatus,
|
||||
this.serverDomain,
|
||||
this.verStatus,
|
||||
this.roomDirectory,
|
||||
this.slidingSync,
|
||||
this.ipv6,
|
||||
});
|
||||
|
||||
factory PublicHomeserverData.fromJson(Map<String, dynamic> json) {
|
||||
return PublicHomeserverData(
|
||||
name: json['name'],
|
||||
clientDomain: json['client_domain'],
|
||||
website: json['website'],
|
||||
isp: json['isp'],
|
||||
staffJur: json['staff_jur'],
|
||||
rules: json['rules'],
|
||||
privacy: json['privacy'],
|
||||
usingVanillaReg: json['using_vanilla_reg'],
|
||||
description: json['description'],
|
||||
regMethod: json['reg_method'],
|
||||
regLink: json['reg_link'],
|
||||
software: json['software'],
|
||||
version: json['version'],
|
||||
captcha: json['captcha'],
|
||||
email: json['email'],
|
||||
languages: List<String>.from(json['languages'] ?? []),
|
||||
features: List<String>.from(json['features'] ?? []),
|
||||
onlineStatus: json['online_status'],
|
||||
serverDomain: json['server_domain'],
|
||||
verStatus: json['ver_status'],
|
||||
roomDirectory: json['room_directory'],
|
||||
slidingSync: json['sliding_sync'],
|
||||
ipv6: json['ipv6'],
|
||||
);
|
||||
}
|
||||
}
|
||||
15
lib/pages/sign_in/view_model/sign_in_state.dart
Normal file
15
lib/pages/sign_in/view_model/sign_in_state.dart
Normal file
@@ -0,0 +1,15 @@
|
||||
// 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/pages/sign_in/view_model/model/public_homeserver_data.dart';
|
||||
import 'package:material_ui/material_ui.dart';
|
||||
|
||||
class SignInState {
|
||||
PublicHomeserverData? selectedHomeserver;
|
||||
AsyncSnapshot<List<PublicHomeserverData>> publicHomeservers =
|
||||
const AsyncSnapshot.nothing();
|
||||
List<PublicHomeserverData> filteredPublicHomeservers = [];
|
||||
AsyncSnapshot<bool> loginLoading = const AsyncSnapshot.nothing();
|
||||
}
|
||||
114
lib/pages/sign_in/view_model/sign_in_view_model.dart
Normal file
114
lib/pages/sign_in/view_model/sign_in_view_model.dart
Normal file
@@ -0,0 +1,114 @@
|
||||
// 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:collection/collection.dart';
|
||||
import 'package:tatchat/config/app_config.dart';
|
||||
import 'package:tatchat/config/setting_keys.dart';
|
||||
import 'package:tatchat/pages/sign_in/view_model/model/public_homeserver_data.dart';
|
||||
import 'package:tatchat/pages/sign_in/view_model/sign_in_state.dart';
|
||||
import 'package:tatchat/widgets/matrix.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:matrix/matrix_api_lite/utils/logs.dart';
|
||||
|
||||
class SignInViewModel extends ValueNotifier<SignInState> {
|
||||
final MatrixState matrixService;
|
||||
final bool signUp;
|
||||
final TextEditingController filterTextController = TextEditingController();
|
||||
|
||||
SignInViewModel(this.matrixService, {required this.signUp})
|
||||
: super(SignInState()) {
|
||||
refreshPublicHomeservers();
|
||||
filterTextController.addListener(_filterHomeservers);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
filterTextController.removeListener(_filterHomeservers);
|
||||
filterTextController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _filterHomeservers() {
|
||||
final filterText = filterTextController.text.trim().toLowerCase();
|
||||
final filteredPublicHomeservers =
|
||||
value.publicHomeservers.data
|
||||
?.where(
|
||||
(homeserver) =>
|
||||
homeserver.name?.toLowerCase().contains(filterText) ?? false,
|
||||
)
|
||||
.toList() ??
|
||||
[];
|
||||
if (filterText.length >= 3 &&
|
||||
(filterText.contains('.') || filterText.endsWith('localhost')) &&
|
||||
Uri.tryParse(filterText) != null &&
|
||||
!filteredPublicHomeservers.any(
|
||||
(homeserver) => homeserver.name == filterText,
|
||||
)) {
|
||||
filteredPublicHomeservers.add(PublicHomeserverData(name: filterText));
|
||||
}
|
||||
value.filteredPublicHomeservers = filteredPublicHomeservers;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> refreshPublicHomeservers() async {
|
||||
notifyListeners();
|
||||
value.publicHomeservers = AsyncSnapshot.waiting();
|
||||
final defaultHomeserverData = PublicHomeserverData(
|
||||
name: AppSettings.defaultHomeserver.value,
|
||||
);
|
||||
try {
|
||||
final client = await matrixService.getLoginClient();
|
||||
final response = await client.httpClient.get(AppConfig.homeserverList);
|
||||
final json = jsonDecode(response.body) as Map<String, dynamic>;
|
||||
final homeserverJsonList = json['public_servers'] as List;
|
||||
|
||||
final publicHomeservers = homeserverJsonList
|
||||
.map((json) => PublicHomeserverData.fromJson(json))
|
||||
.toList();
|
||||
|
||||
if (signUp) {
|
||||
publicHomeservers.removeWhere((server) {
|
||||
return server.regMethod == null;
|
||||
});
|
||||
}
|
||||
|
||||
final defaultServer = publicHomeservers.singleWhereOrNull(
|
||||
(server) => server.name == AppSettings.defaultHomeserver.value,
|
||||
);
|
||||
|
||||
if (defaultServer == null) {
|
||||
publicHomeservers.insert(0, defaultHomeserverData);
|
||||
}
|
||||
|
||||
value.selectedHomeserver =
|
||||
value.selectedHomeserver ?? publicHomeservers.first;
|
||||
value.publicHomeservers = AsyncSnapshot.withData(
|
||||
ConnectionState.done,
|
||||
publicHomeservers,
|
||||
);
|
||||
notifyListeners();
|
||||
} catch (e, s) {
|
||||
Logs().w('Unable to fetch public homeservers...', e, s);
|
||||
value.selectedHomeserver = defaultHomeserverData;
|
||||
value.publicHomeservers = AsyncSnapshot.withData(ConnectionState.done, [
|
||||
defaultHomeserverData,
|
||||
]);
|
||||
notifyListeners();
|
||||
}
|
||||
_filterHomeservers();
|
||||
}
|
||||
|
||||
void selectHomeserver(PublicHomeserverData? publicHomeserverData) {
|
||||
value.selectedHomeserver = publicHomeserverData;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void setLoginLoading(AsyncSnapshot<bool> loginLoading) {
|
||||
value.loginLoading = loginLoading;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user