feat: set initial L2 via cached space code course target language if available (#4264)
This commit is contained in:
parent
9790d2e56d
commit
a44e378f4f
13 changed files with 160 additions and 561 deletions
|
|
@ -135,7 +135,7 @@ abstract class AppRoutes {
|
|||
),
|
||||
// #Pangea
|
||||
GoRoute(
|
||||
path: 'signup',
|
||||
path: 'language',
|
||||
pageBuilder: (context, state) => defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
|
|
@ -143,13 +143,11 @@ abstract class AppRoutes {
|
|||
),
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: ':langcode',
|
||||
path: 'signup',
|
||||
pageBuilder: (context, state) => defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
SignupPage(
|
||||
langCode: state.pathParameters['langcode']!,
|
||||
),
|
||||
const SignupPage(),
|
||||
),
|
||||
routes: [
|
||||
GoRoute(
|
||||
|
|
@ -157,9 +155,8 @@ abstract class AppRoutes {
|
|||
pageBuilder: (context, state) => defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
SignupPage(
|
||||
const SignupPage(
|
||||
withEmail: true,
|
||||
langCode: state.pathParameters['langcode']!,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
@ -196,6 +193,14 @@ abstract class AppRoutes {
|
|||
),
|
||||
redirect: PAuthGaurd.onboardingRedirect,
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: 'create',
|
||||
pageBuilder: (context, state) => defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
const CreatePangeaAccountPage(),
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
path: 'course',
|
||||
pageBuilder: (context, state) => defaultPageBuilder(
|
||||
|
|
@ -271,16 +276,6 @@ abstract class AppRoutes {
|
|||
),
|
||||
],
|
||||
),
|
||||
GoRoute(
|
||||
path: ':langcode',
|
||||
pageBuilder: (context, state) => defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
CreatePangeaAccountPage(
|
||||
langCode: state.pathParameters['langcode']!,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
GoRoute(
|
||||
|
|
|
|||
|
|
@ -27,12 +27,7 @@ class PAuthGaurd {
|
|||
// If user hasn't set their L2,
|
||||
// and their URL doesn’t include ‘course,’ redirect
|
||||
final bool hasSetL2 = await pController!.userController.isUserL2Set;
|
||||
final langCode = state.pathParameters['langcode'];
|
||||
return !hasSetL2
|
||||
? langCode != null
|
||||
? '/registration/$langCode'
|
||||
: '/registration'
|
||||
: '/rooms';
|
||||
return !hasSetL2 ? '/registration/create' : '/rooms';
|
||||
}
|
||||
|
||||
/// Redirect for /rooms routes
|
||||
|
|
@ -53,13 +48,7 @@ class PAuthGaurd {
|
|||
// If user hasn't set their L2,
|
||||
// and their URL doesn’t include ‘course,’ redirect
|
||||
final bool hasSetL2 = await pController!.userController.isUserL2Set;
|
||||
|
||||
final langCode = state.pathParameters['langcode'];
|
||||
return !hasSetL2
|
||||
? langCode != null
|
||||
? '/registration/$langCode'
|
||||
: '/registration'
|
||||
: null;
|
||||
return !hasSetL2 ? '/registration/create' : null;
|
||||
}
|
||||
|
||||
/// Redirect for onboarding routes
|
||||
|
|
|
|||
|
|
@ -10,14 +10,15 @@ import 'package:matrix/matrix.dart';
|
|||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/common/widgets/error_indicator.dart';
|
||||
import 'package:fluffychat/pangea/course_plans/course_plan_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/course_plans/course_plans_repo.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/utils/p_language_store.dart';
|
||||
import 'package:fluffychat/pangea/login/utils/lang_code_repo.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
class CreatePangeaAccountPage extends StatefulWidget {
|
||||
final String langCode;
|
||||
const CreatePangeaAccountPage({
|
||||
super.key,
|
||||
required this.langCode,
|
||||
});
|
||||
|
||||
@override
|
||||
|
|
@ -25,8 +26,10 @@ class CreatePangeaAccountPage extends StatefulWidget {
|
|||
}
|
||||
|
||||
class CreatePangeaAccountPageState extends State<CreatePangeaAccountPage> {
|
||||
bool _loadingProfile = true;
|
||||
bool _loading = true;
|
||||
|
||||
Object? _profileError;
|
||||
Object? _courseError;
|
||||
|
||||
final List<String> avatarPaths = const [
|
||||
"assets/pangea/Avatar_1.png",
|
||||
|
|
@ -39,7 +42,69 @@ class CreatePangeaAccountPageState extends State<CreatePangeaAccountPage> {
|
|||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_createUserInPangea();
|
||||
_createProfile();
|
||||
}
|
||||
|
||||
String? _spaceId;
|
||||
String? _courseLangCode;
|
||||
|
||||
String? get _cachedLangCode => LangCodeRepo.get();
|
||||
|
||||
String? get _langCode => _courseLangCode ?? _cachedLangCode;
|
||||
|
||||
String? get _cachedSpaceCode =>
|
||||
MatrixState.pangeaController.spaceCodeController.cachedSpaceCode;
|
||||
|
||||
Future<void> _createProfile() async {
|
||||
setState(() {
|
||||
_loading = true;
|
||||
_profileError = null;
|
||||
_courseError = null;
|
||||
});
|
||||
|
||||
await _joinCachedCourse();
|
||||
if (mounted) {
|
||||
await _createUserInPangea();
|
||||
}
|
||||
if (mounted) {
|
||||
setState(() => _loading = false);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _joinCachedCourse() async {
|
||||
await MatrixState.pangeaController.spaceCodeController.initCompleter.future;
|
||||
if (_cachedSpaceCode == null) return;
|
||||
|
||||
try {
|
||||
final spaceId = await MatrixState.pangeaController.spaceCodeController
|
||||
.joinCachedSpaceCode(context);
|
||||
|
||||
if (spaceId == null) {
|
||||
throw Exception('Failed to join space with code $_cachedSpaceCode');
|
||||
}
|
||||
|
||||
final client = Matrix.of(context).client;
|
||||
Room? room = client.getRoomById(spaceId);
|
||||
if (room == null || room.membership != Membership.join) {
|
||||
await client.waitForRoomInSync(spaceId, join: true);
|
||||
room = client.getRoomById(spaceId);
|
||||
if (room == null || room.membership != Membership.join) {
|
||||
throw Exception('Failed to join space with code $_cachedSpaceCode');
|
||||
}
|
||||
}
|
||||
|
||||
final courseId = room.coursePlan?.uuid;
|
||||
if (courseId == null) {
|
||||
throw Exception('No course plan associated with space $spaceId');
|
||||
}
|
||||
|
||||
final course = await CoursePlansRepo.get(courseId);
|
||||
|
||||
_spaceId = spaceId;
|
||||
_courseLangCode = course.targetLanguage;
|
||||
} catch (err) {
|
||||
_courseError = err;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _setAvatar() async {
|
||||
|
|
@ -68,7 +133,7 @@ class CreatePangeaAccountPageState extends State<CreatePangeaAccountPage> {
|
|||
Future<void> _updateTargetLanguage() async {
|
||||
await MatrixState.pangeaController.userController.updateProfile(
|
||||
(profile) {
|
||||
profile.userSettings.targetLanguage = widget.langCode;
|
||||
profile.userSettings.targetLanguage = _langCode;
|
||||
return profile;
|
||||
},
|
||||
waitForDataInSync: true,
|
||||
|
|
@ -76,11 +141,6 @@ class CreatePangeaAccountPageState extends State<CreatePangeaAccountPage> {
|
|||
}
|
||||
|
||||
Future<void> _createUserInPangea() async {
|
||||
setState(() {
|
||||
_loadingProfile = true;
|
||||
_profileError = null;
|
||||
});
|
||||
|
||||
final l2Set = await MatrixState.pangeaController.userController.isUserL2Set;
|
||||
if (l2Set) {
|
||||
await _updateTargetLanguage();
|
||||
|
|
@ -89,6 +149,10 @@ class CreatePangeaAccountPageState extends State<CreatePangeaAccountPage> {
|
|||
}
|
||||
|
||||
try {
|
||||
if (_langCode == null) {
|
||||
throw Exception('No language selected');
|
||||
}
|
||||
|
||||
final updateFuture = [
|
||||
_setAvatar(),
|
||||
MatrixState.pangeaController.userController.updateProfile(
|
||||
|
|
@ -100,14 +164,14 @@ class CreatePangeaAccountPageState extends State<CreatePangeaAccountPage> {
|
|||
profile.userSettings.sourceLanguage = systemLang;
|
||||
}
|
||||
|
||||
profile.userSettings.targetLanguage = widget.langCode;
|
||||
profile.userSettings.targetLanguage = _langCode;
|
||||
profile.userSettings.createdAt = DateTime.now();
|
||||
return profile;
|
||||
},
|
||||
waitForDataInSync: true,
|
||||
),
|
||||
MatrixState.pangeaController.userController.updateAnalyticsProfile(
|
||||
targetLanguage: PLanguageStore.byLangCode(widget.langCode),
|
||||
targetLanguage: PLanguageStore.byLangCode(_langCode!),
|
||||
baseLanguage:
|
||||
MatrixState.pangeaController.languageController.systemLanguage,
|
||||
level: 1,
|
||||
|
|
@ -129,32 +193,26 @@ class CreatePangeaAccountPageState extends State<CreatePangeaAccountPage> {
|
|||
} else {
|
||||
_profileError = err;
|
||||
}
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() => _loadingProfile = false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onProfileCreated() async {
|
||||
final joinedSpaceId = await MatrixState.pangeaController.spaceCodeController
|
||||
.joinCachedSpaceCode(context);
|
||||
if (joinedSpaceId != null) return;
|
||||
context.go('/registration/course');
|
||||
await LangCodeRepo.remove();
|
||||
context.go(
|
||||
_spaceId != null
|
||||
? '/rooms/spaces/$_spaceId/details'
|
||||
: '/registration/course',
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (_loadingProfile && _profileError != null) {
|
||||
_onProfileCreated();
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
body: SafeArea(
|
||||
child: Center(
|
||||
child: _loadingProfile
|
||||
child: _loading
|
||||
? const CircularProgressIndicator.adaptive()
|
||||
: _profileError != null
|
||||
: _profileError != null || _courseError != null
|
||||
? Column(
|
||||
spacing: 8.0,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
|
|
@ -162,9 +220,19 @@ class CreatePangeaAccountPageState extends State<CreatePangeaAccountPage> {
|
|||
ErrorIndicator(
|
||||
message: L10n.of(context).oopsSomethingWentWrong,
|
||||
),
|
||||
TextButton(
|
||||
onPressed: _createUserInPangea,
|
||||
child: Text(L10n.of(context).tryAgain),
|
||||
Row(
|
||||
spacing: 8.0,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: _createUserInPangea,
|
||||
child: Text(L10n.of(context).tryAgain),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: Navigator.of(context).pop,
|
||||
child: Text(L10n.of(context).cancel),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import 'package:go_router/go_router.dart';
|
|||
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/models/language_model.dart';
|
||||
import 'package:fluffychat/pangea/login/utils/lang_code_repo.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
class LanguageSelectionPage extends StatefulWidget {
|
||||
|
|
@ -25,6 +26,17 @@ class LanguageSelectionPageState extends State<LanguageSelectionPage> {
|
|||
setState(() => _selectedLanguage = l);
|
||||
}
|
||||
|
||||
Future<void> _submit() async {
|
||||
if (_selectedLanguage == null) return;
|
||||
|
||||
await LangCodeRepo.set(_selectedLanguage!.langCode);
|
||||
context.go(
|
||||
GoRouterState.of(context).fullPath?.contains('home') == true
|
||||
? '/home/language/signup'
|
||||
: '/registration/create',
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
|
|
@ -114,18 +126,7 @@ class LanguageSelectionPageState extends State<LanguageSelectionPage> {
|
|||
),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: _selectedLanguage != null
|
||||
? () {
|
||||
context.go(
|
||||
GoRouterState.of(context)
|
||||
.fullPath
|
||||
?.contains('home') ==
|
||||
true
|
||||
? '/home/signup/${_selectedLanguage!.langCode}'
|
||||
: '/registration/${_selectedLanguage!.langCode}',
|
||||
);
|
||||
}
|
||||
: null,
|
||||
onPressed: _selectedLanguage != null ? _submit : null,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: theme.colorScheme.primaryContainer,
|
||||
foregroundColor: theme.colorScheme.onPrimaryContainer,
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import 'package:fluffychat/l10n/l10n.dart';
|
|||
import 'package:fluffychat/pangea/common/config/environment.dart';
|
||||
import 'package:fluffychat/pangea/common/widgets/pangea_logo_svg.dart';
|
||||
import 'package:fluffychat/pangea/login/widgets/app_config_dialog.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
class LoginOrSignupView extends StatefulWidget {
|
||||
const LoginOrSignupView({super.key});
|
||||
|
|
@ -24,6 +25,9 @@ class LoginOrSignupViewState extends State<LoginOrSignupView> {
|
|||
_loadOverrides();
|
||||
}
|
||||
|
||||
String? get _cachedSpaceCode =>
|
||||
MatrixState.pangeaController.spaceCodeController.cachedSpaceCode;
|
||||
|
||||
Future<void> _loadOverrides() async {
|
||||
final overrides = await Environment.getAppConfigOverrides();
|
||||
if (mounted) {
|
||||
|
|
@ -93,7 +97,11 @@ class LoginOrSignupViewState extends State<LoginOrSignupView> {
|
|||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ElevatedButton(
|
||||
onPressed: () => context.go('/home/signup'),
|
||||
onPressed: () => context.go(
|
||||
_cachedSpaceCode != null
|
||||
? '/home/language/signup'
|
||||
: '/home/language',
|
||||
),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: theme.colorScheme.primaryContainer,
|
||||
foregroundColor: theme.colorScheme.onPrimaryContainer,
|
||||
|
|
|
|||
|
|
@ -14,10 +14,8 @@ import 'package:fluffychat/widgets/matrix.dart';
|
|||
|
||||
class SignupPage extends StatefulWidget {
|
||||
final bool withEmail;
|
||||
final String langCode;
|
||||
|
||||
const SignupPage({
|
||||
required this.langCode,
|
||||
this.withEmail = false,
|
||||
super.key,
|
||||
});
|
||||
|
|
@ -178,7 +176,7 @@ class SignupPageController extends State<SignupPage> {
|
|||
},
|
||||
);
|
||||
|
||||
if (!resp.isError) context.go('/registration/${widget.langCode}');
|
||||
if (!resp.isError) context.go('/registration/create');
|
||||
}
|
||||
|
||||
Future<void> _signupFuture() async {
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ class SignupPageView extends StatelessWidget {
|
|||
),
|
||||
ElevatedButton(
|
||||
onPressed: () => context.go(
|
||||
'/home/signup/${controller.widget.langCode}/email',
|
||||
'/home/language/signup/email',
|
||||
),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: theme.colorScheme.primaryContainer,
|
||||
|
|
|
|||
|
|
@ -1,278 +0,0 @@
|
|||
import 'dart:async';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pangea/common/controllers/pangea_controller.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/enums/language_level_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/models/language_model.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/utils/p_language_store.dart';
|
||||
import 'package:fluffychat/pangea/login/pages/user_settings_view.dart';
|
||||
import 'package:fluffychat/utils/file_selector.dart';
|
||||
import 'package:fluffychat/utils/localized_exception_extension.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
class UserSettingsPage extends StatefulWidget {
|
||||
const UserSettingsPage({super.key});
|
||||
|
||||
@override
|
||||
UserSettingsState createState() => UserSettingsState();
|
||||
}
|
||||
|
||||
class UserSettingsState extends State<UserSettingsPage> {
|
||||
PangeaController get _pangeaController => MatrixState.pangeaController;
|
||||
|
||||
LanguageModel? selectedTargetLanguage;
|
||||
LanguageModel? selectedBaseLanguage;
|
||||
bool showBaseLanguageDropdown = false;
|
||||
|
||||
LanguageLevelTypeEnum selectedCefrLevel = LanguageLevelTypeEnum.a1;
|
||||
|
||||
String? selectedLanguageError;
|
||||
String? profileCreationError;
|
||||
String? tncError;
|
||||
|
||||
bool loading = false;
|
||||
|
||||
Uint8List? avatar;
|
||||
String? _selectedFilePath;
|
||||
|
||||
List<String> avatarPaths = const [
|
||||
"assets/pangea/Avatar_1.png",
|
||||
"assets/pangea/Avatar_2.png",
|
||||
"assets/pangea/Avatar_3.png",
|
||||
"assets/pangea/Avatar_4.png",
|
||||
"assets/pangea/Avatar_5.png",
|
||||
];
|
||||
String? selectedAvatarPath;
|
||||
|
||||
LanguageModel? get _systemLanguage {
|
||||
final systemLangCode =
|
||||
_pangeaController.languageController.systemLanguage?.langCode;
|
||||
return systemLangCode == null
|
||||
? null
|
||||
: PLanguageStore.byLangCode(systemLangCode);
|
||||
}
|
||||
|
||||
TextEditingController displayNameController = TextEditingController();
|
||||
final GlobalKey<FormState> formKey = GlobalKey<FormState>();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
selectedBaseLanguage =
|
||||
_pangeaController.languageController.userL1 ?? _systemLanguage;
|
||||
selectedTargetLanguage = _pangeaController.languageController.userL2;
|
||||
|
||||
displayNameController.text = Matrix.of(context).client.userID?.localpart ??
|
||||
Matrix.of(context).client.userID ??
|
||||
"";
|
||||
|
||||
final random = Random();
|
||||
selectedAvatarPath = avatarPaths[random.nextInt(avatarPaths.length)];
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
displayNameController.dispose();
|
||||
loading = false;
|
||||
selectedLanguageError = null;
|
||||
profileCreationError = null;
|
||||
tncError = null;
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void setSelectedBaseLanguage(LanguageModel? language) {
|
||||
setState(() {
|
||||
selectedBaseLanguage = language;
|
||||
selectedLanguageError = null;
|
||||
});
|
||||
}
|
||||
|
||||
void setSelectedTargetLanguage(LanguageModel? language) {
|
||||
setState(() {
|
||||
selectedTargetLanguage = language;
|
||||
selectedLanguageError = null;
|
||||
if (!showBaseLanguageDropdown && _hasIdenticalLanguages) {
|
||||
showBaseLanguageDropdown = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void setSelectedCefrLevel(LanguageLevelTypeEnum? cefrLevel) {
|
||||
setState(() {
|
||||
selectedCefrLevel = cefrLevel ?? LanguageLevelTypeEnum.a1;
|
||||
});
|
||||
}
|
||||
|
||||
void setSelectedAvatarPath(int index) {
|
||||
if (index < 0 || index >= avatarPaths.length) return;
|
||||
setState(() {
|
||||
avatar = null;
|
||||
selectedAvatarPath = avatarPaths[index];
|
||||
});
|
||||
}
|
||||
|
||||
int get selectedAvatarIndex {
|
||||
if (selectedAvatarPath == null) return -1;
|
||||
return avatarPaths.indexOf(selectedAvatarPath!);
|
||||
}
|
||||
|
||||
void uploadAvatar() async {
|
||||
final photo = await selectFiles(
|
||||
context,
|
||||
type: FileSelectorType.images,
|
||||
allowMultiple: false,
|
||||
);
|
||||
final selectedFile = photo.singleOrNull;
|
||||
final bytes = await selectedFile?.readAsBytes();
|
||||
final path = selectedFile?.path;
|
||||
|
||||
if (bytes == null || path == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
selectedAvatarPath = null;
|
||||
avatar = bytes;
|
||||
_selectedFilePath = path;
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _setAvatar() async {
|
||||
final client = Matrix.of(context).client;
|
||||
try {
|
||||
MatrixFile? file;
|
||||
if (avatar != null && _selectedFilePath != null) {
|
||||
file = MatrixFile(
|
||||
bytes: avatar!,
|
||||
name: _selectedFilePath!,
|
||||
);
|
||||
} else if (selectedAvatarPath != null) {
|
||||
final ByteData byteData = await rootBundle.load(selectedAvatarPath!);
|
||||
final Uint8List bytes = byteData.buffer.asUint8List();
|
||||
file = MatrixFile(
|
||||
bytes: bytes,
|
||||
name: selectedAvatarPath!,
|
||||
);
|
||||
}
|
||||
if (file != null) await client.setAvatar(file);
|
||||
} catch (err, s) {
|
||||
ErrorHandler.logError(
|
||||
e: err,
|
||||
s: s,
|
||||
data: {
|
||||
"avatar": avatar.toString(),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _setDisplayName() async {
|
||||
final displayName = displayNameController.text.trim();
|
||||
if (displayName.isEmpty) return;
|
||||
|
||||
final client = Matrix.of(context).client;
|
||||
if (client.userID == null) return;
|
||||
await client.setDisplayName(client.userID!, displayName);
|
||||
}
|
||||
|
||||
Future<void> createUserInPangea() async {
|
||||
setState(() {
|
||||
profileCreationError = null;
|
||||
selectedLanguageError = null;
|
||||
tncError = null;
|
||||
});
|
||||
|
||||
if (selectedTargetLanguage == null) {
|
||||
setState(() {
|
||||
selectedLanguageError = L10n.of(context).pleaseSelectALanguage;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (_hasIdenticalLanguages) {
|
||||
setState(() {
|
||||
selectedLanguageError = L10n.of(context).noIdenticalLanguages;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!formKey.currentState!.validate()) return;
|
||||
setState(() => loading = true);
|
||||
|
||||
try {
|
||||
final updateFuture = [
|
||||
_setDisplayName(),
|
||||
_setAvatar(),
|
||||
_pangeaController.subscriptionController.reinitialize(),
|
||||
_pangeaController.userController.updateProfile(
|
||||
(profile) {
|
||||
profile.userSettings.sourceLanguage =
|
||||
selectedBaseLanguage?.langCode ?? _systemLanguage?.langCode;
|
||||
profile.userSettings.targetLanguage =
|
||||
selectedTargetLanguage!.langCode;
|
||||
profile.userSettings.cefrLevel = selectedCefrLevel;
|
||||
profile.userSettings.createdAt = DateTime.now();
|
||||
return profile;
|
||||
},
|
||||
waitForDataInSync: true,
|
||||
),
|
||||
_pangeaController.userController.updateAnalyticsProfile(
|
||||
targetLanguage: selectedTargetLanguage,
|
||||
baseLanguage: _systemLanguage,
|
||||
level: 1,
|
||||
),
|
||||
];
|
||||
|
||||
await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () => Future.wait(updateFuture).timeout(
|
||||
const Duration(seconds: 30),
|
||||
onTimeout: () {
|
||||
throw TimeoutException(L10n.of(context).oopsSomethingWentWrong);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
await _pangeaController.subscriptionController.reinitialize();
|
||||
context.go(
|
||||
_pangeaController.spaceCodeController.cachedSpaceCode == null
|
||||
? '/user_age/join_space'
|
||||
: '/rooms',
|
||||
);
|
||||
} catch (err) {
|
||||
if (err is MatrixException) {
|
||||
profileCreationError = err.errorMessage;
|
||||
} else {
|
||||
profileCreationError = err.toLocalizedString(context);
|
||||
}
|
||||
if (mounted) setState(() {});
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() => loading = false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<LanguageModel> get targetOptions =>
|
||||
_pangeaController.pLanguageStore.targetOptions;
|
||||
|
||||
List<LanguageModel> get baseOptions =>
|
||||
MatrixState.pangeaController.pLanguageStore.baseOptions;
|
||||
|
||||
bool get _hasIdenticalLanguages =>
|
||||
selectedBaseLanguage != null &&
|
||||
selectedTargetLanguage?.langCodeShort ==
|
||||
selectedBaseLanguage?.langCodeShort;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => UserSettingsView(controller: this);
|
||||
}
|
||||
|
|
@ -1,198 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pangea/chat_settings/widgets/language_level_dropdown.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/widgets/p_language_dropdown.dart';
|
||||
import 'package:fluffychat/pangea/login/pages/pangea_login_scaffold.dart';
|
||||
import 'package:fluffychat/pangea/login/pages/user_settings.dart';
|
||||
import 'package:fluffychat/pangea/login/widgets/full_width_button.dart';
|
||||
import 'package:fluffychat/pangea/user/utils/p_logout.dart';
|
||||
|
||||
class UserSettingsView extends StatelessWidget {
|
||||
final UserSettingsState controller;
|
||||
|
||||
const UserSettingsView({
|
||||
required this.controller,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final double avatarSize = 55.0;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final List<Widget> avatarOptions = controller.avatarPaths
|
||||
.mapIndexed((index, path) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(5),
|
||||
child: AvatarOption(
|
||||
onTap: () => controller.setSelectedAvatarPath(index),
|
||||
path: path,
|
||||
selected: controller.selectedAvatarIndex == index,
|
||||
size: avatarSize,
|
||||
),
|
||||
);
|
||||
})
|
||||
.cast<Widget>()
|
||||
.toList();
|
||||
|
||||
avatarOptions.add(
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(5),
|
||||
child: InkWell(
|
||||
onTap: controller.uploadAvatar,
|
||||
child: Container(
|
||||
width: avatarSize,
|
||||
height: avatarSize,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Colors.white,
|
||||
border: Border.all(
|
||||
color: controller.avatar != null
|
||||
? AppConfig.activeToggleColor
|
||||
: Theme.of(context).colorScheme.primary,
|
||||
width: 2,
|
||||
),
|
||||
),
|
||||
child: Icon(
|
||||
Icons.upload,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
size: 30,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return Form(
|
||||
key: controller.formKey,
|
||||
child: PangeaLoginScaffold(
|
||||
customAppBar: AppBar(
|
||||
leading: BackButton(
|
||||
onPressed: () => pLogoutAction(
|
||||
context,
|
||||
bypassWarning: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
showAppName: false,
|
||||
mainAssetPath: controller.selectedAvatarPath ?? "",
|
||||
mainAssetBytes: controller.avatar,
|
||||
children: [
|
||||
Opacity(
|
||||
opacity: 0.9,
|
||||
child: Text(
|
||||
L10n.of(context).chooseYourAvatar,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w100,
|
||||
fontStyle: FontStyle.italic,
|
||||
),
|
||||
),
|
||||
),
|
||||
Wrap(
|
||||
alignment: WrapAlignment.center,
|
||||
children: avatarOptions,
|
||||
),
|
||||
FullWidthTextField(
|
||||
labelText: L10n.of(context).displayName,
|
||||
hintText: L10n.of(context).username,
|
||||
validator: (username) {
|
||||
if (username == null || username.isEmpty) {
|
||||
return L10n.of(context).pleaseChooseAUsername;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
controller: controller.displayNameController,
|
||||
),
|
||||
AnimatedSize(
|
||||
duration: FluffyThemes.animationDuration,
|
||||
child: controller.showBaseLanguageDropdown
|
||||
? Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: PLanguageDropdown(
|
||||
languages: controller.baseOptions,
|
||||
onChange: controller.setSelectedBaseLanguage,
|
||||
initialLanguage: controller.selectedBaseLanguage,
|
||||
hasError: controller.selectedLanguageError != null,
|
||||
decorationText: L10n.of(context).myBaseLanguage,
|
||||
),
|
||||
)
|
||||
: const SizedBox(),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: PLanguageDropdown(
|
||||
languages: controller.targetOptions,
|
||||
onChange: controller.setSelectedTargetLanguage,
|
||||
initialLanguage: controller.selectedTargetLanguage,
|
||||
isL2List: true,
|
||||
error: controller.selectedLanguageError,
|
||||
decorationText: L10n.of(context).iWantToLearn,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: LanguageLevelDropdown(
|
||||
onChanged: controller.setSelectedCefrLevel,
|
||||
initialLevel: controller.selectedCefrLevel,
|
||||
),
|
||||
),
|
||||
FullWidthButton(
|
||||
title: L10n.of(context).letsStart,
|
||||
onPressed: controller.selectedTargetLanguage != null
|
||||
? controller.createUserInPangea
|
||||
: null,
|
||||
error: controller.profileCreationError,
|
||||
loading: controller.loading,
|
||||
enabled: controller.selectedTargetLanguage != null,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AvatarOption extends StatelessWidget {
|
||||
final VoidCallback onTap;
|
||||
final String path; // Path or URL of the SVG file
|
||||
final double size; // Diameter of the circle
|
||||
final bool selected;
|
||||
|
||||
const AvatarOption({
|
||||
super.key,
|
||||
required this.onTap,
|
||||
required this.path,
|
||||
this.size = 40.0,
|
||||
this.selected = false,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
width: size,
|
||||
height: size,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Colors.white,
|
||||
border: Border.all(
|
||||
color: selected
|
||||
? AppConfig.activeToggleColor
|
||||
: Theme.of(context).colorScheme.primary,
|
||||
width: 2,
|
||||
),
|
||||
),
|
||||
child: ClipOval(
|
||||
child: Image.asset(
|
||||
path,
|
||||
fit: BoxFit.cover, // scale properly without warping
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
17
lib/pangea/login/utils/lang_code_repo.dart
Normal file
17
lib/pangea/login/utils/lang_code_repo.dart
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import 'package:get_storage/get_storage.dart';
|
||||
|
||||
class LangCodeRepo {
|
||||
static final GetStorage _storage = GetStorage('lang_code_storage');
|
||||
|
||||
static String? get() {
|
||||
return _storage.read('lang_code');
|
||||
}
|
||||
|
||||
static Future<void> set(String langcode) async {
|
||||
await _storage.write('lang_code', langcode);
|
||||
}
|
||||
|
||||
static Future<void> remove() async {
|
||||
await _storage.remove('lang_code');
|
||||
}
|
||||
}
|
||||
|
|
@ -54,9 +54,6 @@ Future<void> pangeaSSOLoginAction(
|
|||
final token = Uri.parse(result).queryParameters['loginToken'];
|
||||
if (token?.isEmpty ?? false) return;
|
||||
|
||||
final langCode = FluffyChatApp.router.state.pathParameters['langcode'];
|
||||
final path = langCode != null ? '/registration/$langCode' : '/registration';
|
||||
|
||||
final redirect = client.onLoginStateChanged.stream
|
||||
.where((state) => state == LoginState.loggedIn)
|
||||
.first
|
||||
|
|
@ -65,7 +62,7 @@ Future<void> pangeaSSOLoginAction(
|
|||
final route = FluffyChatApp.router.state.fullPath;
|
||||
if (route == null ||
|
||||
(!route.contains("/rooms") && !route.contains('registration'))) {
|
||||
context.go(path);
|
||||
context.go('/registration/create');
|
||||
}
|
||||
},
|
||||
).timeout(const Duration(seconds: 30));
|
||||
|
|
@ -80,7 +77,7 @@ Future<void> pangeaSSOLoginAction(
|
|||
final route = FluffyChatApp.router.state.fullPath;
|
||||
if (route == null ||
|
||||
(!route.contains("/rooms") && !route.contains('registration'))) {
|
||||
context.go(path);
|
||||
context.go('/registration/create');
|
||||
}
|
||||
} else {
|
||||
await redirect;
|
||||
|
|
|
|||
|
|
@ -21,8 +21,13 @@ class SpaceCodeController extends BaseController {
|
|||
late PangeaController _pangeaController;
|
||||
static final GetStorage _spaceStorage = GetStorage('class_storage');
|
||||
|
||||
Completer<void> initCompleter = Completer<void>();
|
||||
|
||||
SpaceCodeController(PangeaController pangeaController) : super() {
|
||||
_pangeaController = pangeaController;
|
||||
GetStorage.init('class_storage').then(
|
||||
(_) => initCompleter.complete(),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> cacheSpaceCode(String code) async {
|
||||
|
|
|
|||
|
|
@ -380,14 +380,11 @@ class MatrixState extends State<Matrix> with WidgetsBindingObserver {
|
|||
} else {
|
||||
// #Pangea
|
||||
final isL2Set = await pangeaController.userController.isUserL2Set;
|
||||
final langCode = FluffyChatApp.router.state.pathParameters['langcode'];
|
||||
final registrationRedirect =
|
||||
langCode != null ? '/registration/$langCode' : '/registration';
|
||||
FluffyChatApp.router.go(
|
||||
state == LoginState.loggedIn
|
||||
? isL2Set
|
||||
? '/rooms'
|
||||
: registrationRedirect
|
||||
: '/registration/create'
|
||||
: '/home',
|
||||
);
|
||||
// FluffyChatApp.router
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue