Added translations using l10n
Translations are provided in *.arb* format. Some keys have descriptions (indicated by leading @-symbol). Descriptions should not be copied into the translation itself. Currently only English is supported (app_en.arb), but German is planned. Apparently weblate merged .arb support at some time, so it would be nice to enable community translations at some point.
This commit is contained in:
parent
90adcc6bb1
commit
8fffafde47
24 changed files with 947 additions and 618 deletions
|
@ -1,3 +0,0 @@
|
|||
# Official Outbag App
|
||||
Source code of the official outbag app,
|
||||
written in flutter.
|
15
README.org
Normal file
15
README.org
Normal file
|
@ -0,0 +1,15 @@
|
|||
* Official Outbag App
|
||||
Source code of the official outbag app,
|
||||
written in flutter.
|
||||
|
||||
** Translating
|
||||
This app uses /l10n/ according to the official flutter
|
||||
[[https://docs.flutter.dev/development/accessibility-and-localization/internationalization][internationalization guide]].
|
||||
|
||||
1. Check if there is a ~.arb~ file available for your language,
|
||||
and add missing translations in there,
|
||||
if the file exists
|
||||
2. Otherwise copy the ~app_en.arb~ file
|
||||
and paste it as ~arb_<your-language>.arb~
|
||||
3. Edit the translations in the file
|
||||
4. Run ~flutter gen-l10n~ to generate the required ~.dart~ files
|
3
l10n.yaml
Normal file
3
l10n.yaml
Normal file
|
@ -0,0 +1,3 @@
|
|||
arb-dir: lib/l10n
|
||||
template-arb-file: app_en.arb
|
||||
output-localization-file: app_localizations.dart
|
|
@ -1,30 +1,32 @@
|
|||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
/*
|
||||
* Tool to automatically generate english text from error
|
||||
*/
|
||||
String errorAsString(Map<String, dynamic> json) {
|
||||
String errorAsString(Map<String, dynamic> json, AppLocalizations trans) {
|
||||
switch (json['data']) {
|
||||
case 'notfound':
|
||||
return 'Endpoint not found';
|
||||
return trans.errorNotFound;
|
||||
case 'wrongstate':
|
||||
return 'Missing data';
|
||||
return trans.errorDataIncomplete;
|
||||
case 'data':
|
||||
return 'Invalid data';
|
||||
return trans.errorDataInvalid;
|
||||
case 'roomAdmin':
|
||||
case 'right':
|
||||
return 'You are not allowed to perform this action';
|
||||
return trans.errorPermissions;
|
||||
case 'server':
|
||||
return 'Server error';
|
||||
return trans.errorServer;
|
||||
case 'closed':
|
||||
return 'Server cannot be reached';
|
||||
return trans.errorUnreachable;
|
||||
case 'auth':
|
||||
return 'Username or password wrong';
|
||||
return trans.errorAuth;
|
||||
case 'ota':
|
||||
return 'Invalid OTA';
|
||||
return trans.errorInvalidOTA;
|
||||
case 'existence':
|
||||
return 'Username unavailable';
|
||||
return trans.errorUsernameUnavailable;
|
||||
case 'config':
|
||||
return 'Server reached user limit';
|
||||
return trans.errorServerLimit;
|
||||
}
|
||||
|
||||
return "Unknown Error";
|
||||
return trans.errorUnknown;
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
// implementation of permission types
|
||||
// according to the server permissions.ts
|
||||
// https://codeberg.org/outbag/server/src/branch/dev/src/server/permissions.ts
|
||||
|
@ -100,45 +102,49 @@ class RoomPermission {
|
|||
];
|
||||
}
|
||||
|
||||
static String name(int permission) {
|
||||
static String name(int permission, BuildContext context) {
|
||||
final trans = AppLocalizations.of(context);
|
||||
|
||||
switch (permission) {
|
||||
case 1:
|
||||
return 'Add Articles';
|
||||
return trans!.roomPermissionAddArticles;
|
||||
case 2:
|
||||
return 'Remove Articles';
|
||||
return trans!.roomPermissionRemoveArticles;
|
||||
case 4:
|
||||
return 'List Groups and Items';
|
||||
return trans!.roomPermissionList;
|
||||
case 8:
|
||||
return 'Change Room Metadata';
|
||||
return trans!.roomPermissionChangeMeta;
|
||||
case 16:
|
||||
return 'Manage OTAs';
|
||||
return trans!.roomPermissionManageOTA;
|
||||
case 32:
|
||||
return 'Manage Admins';
|
||||
return trans!.roomPermissionManageAdmins;
|
||||
case 64:
|
||||
return 'Manage Members';
|
||||
return trans!.roomPermissionManageMembers;
|
||||
}
|
||||
|
||||
return "Unknown permission";
|
||||
return trans!.roomPermissionUnknown;
|
||||
}
|
||||
|
||||
static String describe(int permission) {
|
||||
static String describe(int permission, BuildContext context) {
|
||||
final trans = AppLocalizations.of(context);
|
||||
|
||||
switch (permission) {
|
||||
case 1:
|
||||
return 'Allows users to add items to the shopping list';
|
||||
return trans!.roomPermissionAddArticlesSubtitle;
|
||||
case 2:
|
||||
return 'Allows users to remove items from the shopping list';
|
||||
return trans!.roomPermissionRemoveArticlesSubtitle;
|
||||
case 4:
|
||||
return 'Allows the user to view groups and products';
|
||||
return trans!.roomPermissionListSubtitle;
|
||||
case 8:
|
||||
return 'Allows the user to edit the room name, description and icon';
|
||||
return trans!.roomPermissionChangeMetaSubtitle;
|
||||
case 16:
|
||||
return 'Alloww the user to create, share and delete authentification tokens';
|
||||
return trans!.roomPermissionManageOTASubtitle;
|
||||
case 32:
|
||||
return 'Allows the user to change the admin status of other members';
|
||||
return trans!.roomPermissionManageAdminsSubtitle;
|
||||
case 64:
|
||||
return 'Allows the user to invite and kick room members';
|
||||
return trans!.roomPermissionManageMembersSubtitle;
|
||||
}
|
||||
|
||||
return "No description available";
|
||||
return trans!.roomPermissionUnknownSubtitle;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:localstore/localstore.dart';
|
||||
import 'package:outbag_app/tools/assets.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
class RoomVisibility {
|
||||
final int type;
|
||||
|
@ -28,14 +29,16 @@ class RoomVisibility {
|
|||
return Icons.lock;
|
||||
}
|
||||
|
||||
String get text {
|
||||
String text(BuildContext context) {
|
||||
final trans = AppLocalizations.of(context);
|
||||
|
||||
if (type == 2) {
|
||||
return "Global";
|
||||
return trans!.roomVisibilityGlobal;
|
||||
} else if (type == 1) {
|
||||
return "Local";
|
||||
return trans!.roomVisibilityLocal;
|
||||
}
|
||||
|
||||
return "Private";
|
||||
return trans!.roomVisibilityPrivate;
|
||||
}
|
||||
|
||||
static List<RoomVisibility> list() {
|
||||
|
|
|
@ -1,20 +1,23 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:localstore/localstore.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
class AppTheme {
|
||||
ThemeMode mode;
|
||||
|
||||
AppTheme(this.mode);
|
||||
|
||||
String get name {
|
||||
if (mode == ThemeMode.light) {
|
||||
return 'Light';
|
||||
String name(BuildContext context) {
|
||||
final trans = AppLocalizations.of(context);
|
||||
|
||||
if (mode == ThemeMode.light) {
|
||||
return trans!.themeLight;
|
||||
}
|
||||
if (mode == ThemeMode.dark) {
|
||||
return 'Dark';
|
||||
return trans!.themeDark;
|
||||
}
|
||||
|
||||
return 'System';
|
||||
return trans!.themeSystem;
|
||||
}
|
||||
|
||||
IconData get icon {
|
||||
|
@ -80,5 +83,4 @@ class AppTheme {
|
|||
|
||||
@override
|
||||
int get hashCode => mode.index;
|
||||
|
||||
}
|
||||
|
|
287
lib/l10n/app_en.arb
Normal file
287
lib/l10n/app_en.arb
Normal file
|
@ -0,0 +1,287 @@
|
|||
{
|
||||
"helloWorld": "Hello World!",
|
||||
"@helloWorld": {
|
||||
"description": "The conventional newborn programmer greeting"
|
||||
},
|
||||
|
||||
"welcomeTitle": "Welcome to Outbag",
|
||||
"@welcomeTitle": {
|
||||
"description": "Title shown on welcome screen"
|
||||
},
|
||||
"welcomeSubtitle": "Shopping lists made easy",
|
||||
"@welcomeSubtitle": {
|
||||
"description": "Subtitle shown on welcome screen"
|
||||
},
|
||||
"userHasAnAccount": "I already have an account",
|
||||
"@userHasAnAccount": {
|
||||
"description": "Button displayed on welcome screen (bottom-center) used to launch sign in screen"
|
||||
},
|
||||
"letsGo": "Let's go",
|
||||
"@letsGo": {
|
||||
"description": "Text for button on welcome screen used to move the page viewer the first time"
|
||||
},
|
||||
"page2Title": "Open. Decentralized",
|
||||
"page2Subtitle": "One account, multiple servers",
|
||||
|
||||
"next": "Next",
|
||||
|
||||
"page3Title": "Made to share",
|
||||
"page3Subtitle": "Collaborate on your shopping lists in real time",
|
||||
|
||||
"continueTour": "Continue Tour",
|
||||
"takeTour": "Take Tour",
|
||||
|
||||
"page4Title": "Pocket-size",
|
||||
"page4Subtitle": "Always have your shopping lists with you",
|
||||
"signUp": "Sign Up",
|
||||
|
||||
|
||||
"signIn": "Sign In",
|
||||
"logIntoAccount": "Log into account",
|
||||
"createNewAccount": "Create new account",
|
||||
|
||||
"inputServerLabel": "Server",
|
||||
"inputServerHint": "Your homeserver URL",
|
||||
"inputServerHelp": "Your data will be stored on your homeserver",
|
||||
|
||||
"inputUsernameLabel": "Username",
|
||||
"inputUsernameHint": "Your username",
|
||||
"inputUsernameHelp": "Your username and server-tag allow others to identify you",
|
||||
|
||||
"inputPasswordLabel": "Password",
|
||||
"inputPasswordHint": "Your password",
|
||||
"inputPasswordHelp": "Password has to be at least six characters long",
|
||||
|
||||
"inputPasswordRepeatLabel": "Repeat Password",
|
||||
"inputPasswordRepeatHint": "Type your password again",
|
||||
"inputPasswordRepeatHelp": "Make sure to type the correct password",
|
||||
|
||||
"inputOTALabel": "OTA",
|
||||
"inputOTAHint": "One-Time-Authorization token",
|
||||
"inputOTAHelp": "This token might be required if the server is rate limited",
|
||||
|
||||
"errorPasswordLength": "Password has to be at least six characters long",
|
||||
"errorPasswordsDoNotMatch": "Passwords do not match",
|
||||
|
||||
"errorInvalidServer": "Unable to find outbag server on {server}",
|
||||
"@errorInvalidServer": {
|
||||
"description": "Error shown when there is no outbag server on the given url",
|
||||
"placeholders": {
|
||||
"server": {
|
||||
"type": "String",
|
||||
"example": "outbag.example.com"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"search": "Search",
|
||||
"settings": "Settings",
|
||||
"about": "About",
|
||||
"serverDashboard": "Server Dashboard",
|
||||
|
||||
"addRoom": "Add Room",
|
||||
"addRoomHint": "Add a new room",
|
||||
|
||||
"noNewRoomsFound": "No new rooms found",
|
||||
"joinRoom": "Join Room",
|
||||
"refresh": "Refresh",
|
||||
"joinRoomInvite": "Join invite-only room",
|
||||
|
||||
"newRoom": "New Room",
|
||||
"newRoomShort": "New",
|
||||
"createRoom": "Create Room",
|
||||
"createRoomShort": "Create",
|
||||
|
||||
"changeRoomIcon": "Change room icon",
|
||||
"chooseRoomIcon": "Choose a room icon",
|
||||
|
||||
"inputRoomIdLabel": "Room ID",
|
||||
"inputRoomIdHint": "Unique room id",
|
||||
"inputRoomIdHelp": "The room id and server tag allow the room to be identified",
|
||||
|
||||
"inputRoomNameLabel": "Room Name",
|
||||
"inputRoomNameHint": "Give your room a name",
|
||||
"inputRoomNameHelp": "Choose a human-readable name to easily identify a room",
|
||||
|
||||
"inputRoomDescriptionLabel": "Room Description",
|
||||
"inputRoomDescriptionHint": "Briefly describe your room",
|
||||
"inputRoomDescriptionHelp": "Make it easier for others to know what this room is used for",
|
||||
|
||||
"roomVisibilityTitle": "Visibility",
|
||||
"roomVisibilitySubtitle": "Specify who has access to your room",
|
||||
"roomVisibilityPrivate": "Private",
|
||||
"roomVisibilityLocal": "Local",
|
||||
"roomVisibilityGlobal": "Global",
|
||||
|
||||
"errorNoRoomId": "Please specify a room ID",
|
||||
"errorRoomIdLength": "Room ID has to be at least three characters long",
|
||||
"errorNoRoomName": "Please specify a room name",
|
||||
|
||||
"errorNetwork": "Network error",
|
||||
"errorUnknown": "Unknown error",
|
||||
"errorServer":"Server error",
|
||||
|
||||
"errorNotFound":"Not found",
|
||||
"errorDataIncomplete":"Missing data",
|
||||
"errorDataInvalid":"Invalid data",
|
||||
"errorPermissions":"You are not allowed to perform this action",
|
||||
"errorUnreachable":"Server cannot be reached",
|
||||
"errorAuth":"Username or password wrong",
|
||||
"errorInvalidOTA":"Invalid OTA",
|
||||
"errorUsernameUnavailable":"A user with that name already exists",
|
||||
"errorServerLimit":"Server reached user limit",
|
||||
|
||||
"themeLight": "Light",
|
||||
"themeDark": "Dark",
|
||||
"themeSystem": "System",
|
||||
|
||||
"roomListTitle": "List",
|
||||
"roomListSubtitle": "View shopping list",
|
||||
"roomProductsTitle": "Products",
|
||||
"roomProductsSubtitle": "View saved items",
|
||||
"roomCategoriesTitle": "Categories",
|
||||
"roomCategoriesSubtitle": "View categories",
|
||||
"roomAboutTitle": "About",
|
||||
"roomAboutSubtitle": "View room info",
|
||||
|
||||
"changeRoomVisibilityTitle": "Change Room Visibility",
|
||||
"changeRoomVisibilitySubtitle": "Do you really want to change the room visibility to: {visibility}",
|
||||
"@changeRoomVisibilitySubtitle": {
|
||||
"placeholders": {
|
||||
"visibility": {
|
||||
"type": "String",
|
||||
"example": "Local"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"editRoomMetadata": "Edit Metadata",
|
||||
"editRoomMetadataShort": "Edit Room",
|
||||
"editRoomMetadataSubtitle": "Edit the room name, description and icon",
|
||||
"showRoomMembers": "Members",
|
||||
"showRoomMembersSubtitle": "Show Member list",
|
||||
"editRoomPermissions": "Edit Permissions",
|
||||
"editRoomPermissionsSubtitle": "Change the default permission-set for all members",
|
||||
"manageRoomOTA": "OTA",
|
||||
"manageRoomOTASubtitle": "Add and delete OTAs",
|
||||
"manageRoomInvites": "Invites",
|
||||
"manageRoomInvitesSubtitle": "Invite people to this room",
|
||||
|
||||
"leaveRoom": "Leave Room",
|
||||
"leaveRoomShort": "Leave",
|
||||
"leaveRoomConfirm": "Do you really want to leave this room?",
|
||||
"deleteRoom": "Delete Room",
|
||||
"deleteRoomShort": "Delete",
|
||||
"deleteRoomConfirm": "Do you really want to delete this room?",
|
||||
|
||||
"updateRoomPermissions": "Edit",
|
||||
"updateRoomPermissionsHint": "Update default permission set",
|
||||
"roomDefaultPermissions": "Default Permissions",
|
||||
|
||||
"roomPermissionAddArticles": "Add Articles",
|
||||
"roomPermissionAddArticlesSubtitle": "Allows users to add items to the shopping list",
|
||||
"roomPermissionRemoveArticles": "Remove Articles",
|
||||
"roomPermissionRemoveArticlesSubtitle": "Allows users to remove items from the shopping list",
|
||||
"roomPermissionList": "List Groups and Items",
|
||||
"roomPermissionListSubtitle": "Allows the user to view groups and products",
|
||||
"roomPermissionChangeMeta": "Change Room Metadata",
|
||||
"roomPermissionChangeMetaSubtitle": "Allows the user to edit the room name, description and icon",
|
||||
"roomPermissionManageOTA": "Manage OTAs",
|
||||
"roomPermissionManageOTASubtitle": "Alloww the user to create, share and delete authentification tokens",
|
||||
"roomPermissionManageAdmins": "Manage Admins",
|
||||
"roomPermissionManageAdminsSubtitle": "Allows the user to change the admin status of other members",
|
||||
"roomPermissionManageMembers": "Manage Members",
|
||||
"roomPermissionManageMembersSubtitle": "Allows the user to invite and kick room members",
|
||||
"roomPermissionUnknown": "Unknown Permission",
|
||||
"roomPermissionUnknownSubtitle": "No information available",
|
||||
|
||||
"roomMembersTitle": "Room Members ({count})",
|
||||
"@roomMembersTitle": {
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"roleOwner": "Owner",
|
||||
"roleAdmin": "Admin",
|
||||
"roleMember": "Member",
|
||||
|
||||
"makeAdminTitle": "Make Admin",
|
||||
"makeAdminSubtitle": "Grants the user the permission to do everything",
|
||||
"makeAdminConfirm": "Do you really want to make {user} admin?",
|
||||
"@makeAdminConfirm": {
|
||||
"placeholders": {
|
||||
"user": {
|
||||
"type": "String",
|
||||
"example": "ash@example.com"
|
||||
}
|
||||
}
|
||||
},
|
||||
"removeAdminTitle": "Remove admin privileges",
|
||||
"removeAdminSubtitle": "Revokes admin privileges from the user",
|
||||
"removeAdminConfirm": "Do you really want to remove {user}'s admin privileges",
|
||||
"@removeAdminConfirm": {
|
||||
"placeholders": {
|
||||
"user": {
|
||||
"type": "String",
|
||||
"example": "ash@example.com"
|
||||
}
|
||||
}
|
||||
},
|
||||
"kickUserTitle": "Kich User",
|
||||
"kickUserSubtitle": "Temporarily remove user from server (they'll be able to join the room again)",
|
||||
"kichUserConfirm": "Do you really want to kick {user}?",
|
||||
"@kickUserConfirm": {
|
||||
"placeholders": {
|
||||
"user": {
|
||||
"type": "String",
|
||||
"example": "ash@example.com"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"limitRoomCount": "Room count limit",
|
||||
"limitRoomCountSubtitle": "How many rooms you are allowed to own",
|
||||
"limitRoomSize": "Room size limit",
|
||||
"limitRoomSizeSubtitle": "How many items/products/categories each room may contain",
|
||||
"limitRoomMemberCount": "Room member limit",
|
||||
"limitRoomMemberCountSubtitle": "How many members each of your rooms may have",
|
||||
"userDiscoverable": "Discoverable",
|
||||
"userDiscoverableSubtitle": "Determines if your account can be discovered by users from other servers",
|
||||
|
||||
"changeThemeTitle": "Change Theme",
|
||||
"changeThemeSubtitle": "Choose your preferred color-scheme",
|
||||
"changePasswordTitle": "Change Password",
|
||||
"changePasswordSubtitle": "Choose a new password for your account",
|
||||
"exportAccountTitle": "Export Account",
|
||||
"exportAccountSubtitle": "Export account data",
|
||||
"deleteAccountTitle": "Delete Account",
|
||||
"deleteAccountSubtitle": "Delete your account from your homeserver",
|
||||
"deleteAccountConfirm": "Do you really want to delete your account?",
|
||||
|
||||
"logOut": "Log out",
|
||||
"logOutConfirm": "Do you really want to log out?",
|
||||
|
||||
"inputOldPasswordLabel": "Old Password",
|
||||
"inputOldPasswordHint": "Your current password",
|
||||
"inputOldPasswordHelp": "Type your current password here",
|
||||
"inputNewPasswordLabel": "New Password",
|
||||
"inputNewPasswordHint": "Your new password",
|
||||
"inputNewPasswordHelp": "Password has to be at least six characters long",
|
||||
"inputNewPasswordRepeatLabel": "Repeat new Password",
|
||||
"inputNewPasswordRepeatHint": "Type your new password again",
|
||||
"inputNewPasswordRepeatHelp": "Make sure this matches your new password",
|
||||
|
||||
"errorPasswordsDontMatch": "New passwords do not match",
|
||||
"errorOldPasswordWrong": "Your old password is wrong",
|
||||
|
||||
"yes": "Yes",
|
||||
"loading": "Loading",
|
||||
"dismiss": "Dismiss",
|
||||
"cancel": "Cancel",
|
||||
"ok": "OK",
|
||||
"close": "Close",
|
||||
"update": "Update"
|
||||
}
|
|
@ -10,6 +10,8 @@ import 'package:outbag_app/screens/room/new.dart';
|
|||
import 'package:outbag_app/screens/settings/main.dart';
|
||||
import 'package:outbag_app/tools/fetch_wrapper.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import './screens/home.dart';
|
||||
import './screens/welcome.dart';
|
||||
import './screens/room/main.dart';
|
||||
|
@ -142,6 +144,13 @@ class _OutbagAppState extends State {
|
|||
],
|
||||
child: MaterialApp.router(
|
||||
title: "Outbag",
|
||||
localizationsDelegates: const [
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
AppLocalizations.delegate
|
||||
],
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
themeMode: theme.mode,
|
||||
theme: ThemeData(useMaterial3: true, brightness: Brightness.light),
|
||||
darkTheme: ThemeData(useMaterial3: true, brightness: Brightness.dark),
|
||||
|
@ -223,10 +232,6 @@ class _OutbagAppState extends State {
|
|||
GoRoute(
|
||||
name: 'room',
|
||||
path: 'r/:server/:id',
|
||||
redirect: (context, state) {
|
||||
print(state.subloc);
|
||||
return null;
|
||||
},
|
||||
builder: (context, state) => RoomPage(
|
||||
state.params['server'] ?? '',
|
||||
state.params['id'] ?? ''),
|
||||
|
|
|
@ -4,6 +4,7 @@ import 'package:outbag_app/backend/request.dart';
|
|||
import 'package:outbag_app/backend/user.dart';
|
||||
import 'package:outbag_app/tools/fetch_wrapper.dart';
|
||||
import 'package:outbag_app/tools/snackbar.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import '../backend/resolve_url.dart';
|
||||
|
||||
enum Mode {
|
||||
|
@ -31,13 +32,13 @@ class _AuthPageState extends State<AuthPage> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
String modeName = "Sign In";
|
||||
String modeName = AppLocalizations.of(context)!.signIn;
|
||||
if (widget.mode != Mode.signin) {
|
||||
modeName = "Sign Up";
|
||||
modeName = AppLocalizations.of(context)!.signUp;
|
||||
}
|
||||
String modeDescription = "Log into account";
|
||||
String modeDescription = AppLocalizations.of(context)!.logIntoAccount;
|
||||
if (widget.mode != Mode.signin) {
|
||||
modeDescription = "Create new account";
|
||||
modeDescription = AppLocalizations.of(context)!.createNewAccount;
|
||||
}
|
||||
|
||||
final textTheme = Theme.of(context)
|
||||
|
@ -52,7 +53,7 @@ class _AuthPageState extends State<AuthPage> {
|
|||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const CircularProgressIndicator(),
|
||||
Text('Loading', style: textTheme.titleLarge),
|
||||
Text(AppLocalizations.of(context)!.loading, style: textTheme.titleLarge),
|
||||
])))
|
||||
: Scaffold(
|
||||
appBar: AppBar(
|
||||
|
@ -70,13 +71,12 @@ class _AuthPageState extends State<AuthPage> {
|
|||
child: TextField(
|
||||
controller: _ctrServer,
|
||||
keyboardType: TextInputType.url,
|
||||
decoration: const InputDecoration(
|
||||
prefixIcon: Icon(Icons.dns),
|
||||
labelText: 'Server',
|
||||
hintText: 'Your homeserver url',
|
||||
helperText:
|
||||
'Your data will be stored on your homeserver',
|
||||
border: OutlineInputBorder(),
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.dns),
|
||||
labelText: AppLocalizations.of(context)!.inputServerLabel,
|
||||
hintText: AppLocalizations.of(context)!.inputServerHint,
|
||||
helperText:AppLocalizations.of(context)!.inputServerHelp,
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -85,13 +85,12 @@ class _AuthPageState extends State<AuthPage> {
|
|||
child: TextField(
|
||||
controller: _ctrUsername,
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
decoration: const InputDecoration(
|
||||
prefixIcon: Icon(Icons.person),
|
||||
labelText: 'Username',
|
||||
hintText: 'Your username',
|
||||
helperText:
|
||||
'your username and server tag allow others to identify you',
|
||||
border: OutlineInputBorder(),
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.person),
|
||||
labelText: AppLocalizations.of(context)!.inputUsernameLabel,
|
||||
hintText: AppLocalizations.of(context)!.inputUsernameHint,
|
||||
helperText:AppLocalizations.of(context)!.inputUsernameHelp,
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -101,13 +100,12 @@ class _AuthPageState extends State<AuthPage> {
|
|||
controller: _ctrPassword,
|
||||
keyboardType: TextInputType.visiblePassword,
|
||||
obscureText: true,
|
||||
decoration: const InputDecoration(
|
||||
prefixIcon: Icon(Icons.lock),
|
||||
labelText: 'Password',
|
||||
hintText: 'Your password',
|
||||
helperText:
|
||||
'Password have to be at least six characters long',
|
||||
border: OutlineInputBorder(),
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.lock),
|
||||
labelText: AppLocalizations.of(context)!.inputPasswordLabel,
|
||||
hintText: AppLocalizations.of(context)!.inputPasswordHint,
|
||||
helperText:AppLocalizations.of(context)!.inputPasswordHelp,
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -120,13 +118,12 @@ class _AuthPageState extends State<AuthPage> {
|
|||
controller: _ctrPasswordRpt,
|
||||
keyboardType: TextInputType.visiblePassword,
|
||||
obscureText: true,
|
||||
decoration: const InputDecoration(
|
||||
prefixIcon: Icon(Icons.lock),
|
||||
labelText: 'Repeat Password',
|
||||
hintText: 'Type your password again',
|
||||
helperText:
|
||||
'Make sure to type the correct password',
|
||||
border: OutlineInputBorder(),
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.lock),
|
||||
labelText: AppLocalizations.of(context)!.inputPasswordRepeatLabel,
|
||||
hintText: AppLocalizations.of(context)!.inputPasswordRepeatHint,
|
||||
helperText:AppLocalizations.of(context)!.inputPasswordRepeatHelp,
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
@ -140,13 +137,12 @@ class _AuthPageState extends State<AuthPage> {
|
|||
child: TextField(
|
||||
controller: _ctrOTA,
|
||||
keyboardType: TextInputType.visiblePassword,
|
||||
decoration: const InputDecoration(
|
||||
prefixIcon: Icon(Icons.key),
|
||||
labelText: 'OTA',
|
||||
hintText: 'One-Time-Authorization token',
|
||||
helperText:
|
||||
'This token might be required if the server is rate limited',
|
||||
border: OutlineInputBorder(),
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.key),
|
||||
labelText: AppLocalizations.of(context)!.inputOTALabel,
|
||||
hintText: AppLocalizations.of(context)!.inputOTAHint,
|
||||
helperText:AppLocalizations.of(context)!.inputOTAHelp,
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
@ -170,7 +166,8 @@ class _AuthPageState extends State<AuthPage> {
|
|||
});
|
||||
|
||||
showSimpleSnackbar(scaffMgr,
|
||||
text: 'Passwords do not match', action: 'Dismiss');
|
||||
text: AppLocalizations.of(context)!.errorPasswordsDoNotMatch,
|
||||
action: AppLocalizations.of(context)!.dismiss);
|
||||
|
||||
_ctrPasswordRpt.clear();
|
||||
return;
|
||||
|
@ -184,8 +181,8 @@ class _AuthPageState extends State<AuthPage> {
|
|||
});
|
||||
|
||||
showSimpleSnackbar(scaffMgr,
|
||||
text: 'Password has to be at least 6 characters longs',
|
||||
action: 'Dismiss');
|
||||
text: AppLocalizations.of(context)!.errorPasswordLength,
|
||||
action: AppLocalizations.of(context)!.dismiss);
|
||||
|
||||
_ctrPasswordRpt.clear();
|
||||
return;
|
||||
|
@ -204,9 +201,8 @@ class _AuthPageState extends State<AuthPage> {
|
|||
});
|
||||
|
||||
showSimpleSnackbar(scaffMgr,
|
||||
text:
|
||||
'Unable to find valid outbag server on ${_ctrServer.text}',
|
||||
action: 'Dismiss');
|
||||
text: AppLocalizations.of(context)!.errorInvalidServer(_ctrServer.text),
|
||||
action: AppLocalizations.of(context)!.dismiss);
|
||||
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import 'package:outbag_app/backend/user.dart';
|
|||
import 'package:outbag_app/tools/fetch_wrapper.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import '../backend/room.dart';
|
||||
|
||||
class HomePage extends StatefulWidget {
|
||||
|
@ -69,7 +70,7 @@ class _HomePageState extends State<HomePage> {
|
|||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.search),
|
||||
tooltip: "Search",
|
||||
tooltip: AppLocalizations.of(context)!.search,
|
||||
onPressed: () {
|
||||
// show searchbar
|
||||
// NOTE: location currently unknown
|
||||
|
@ -91,7 +92,7 @@ class _HomePageState extends State<HomePage> {
|
|||
menuChildren: [
|
||||
MenuItemButton(
|
||||
leadingIcon: const Icon(Icons.settings),
|
||||
child: const Text('Settings'),
|
||||
child: Text(AppLocalizations.of(context)!.settings),
|
||||
onPressed: () {
|
||||
// show settings screen
|
||||
context.goNamed('settings');
|
||||
|
@ -103,7 +104,7 @@ class _HomePageState extends State<HomePage> {
|
|||
? [
|
||||
MenuItemButton(
|
||||
leadingIcon: const Icon(Icons.dns),
|
||||
child: const Text('Server Dashboard'),
|
||||
child: Text(AppLocalizations.of(context)!.serverDashboard),
|
||||
onPressed: () {
|
||||
// show settings screen
|
||||
context.goNamed('dash');
|
||||
|
@ -112,7 +113,7 @@ class _HomePageState extends State<HomePage> {
|
|||
: [],
|
||||
MenuItemButton(
|
||||
leadingIcon: const Icon(Icons.info_rounded),
|
||||
child: const Text('About'),
|
||||
child: Text(AppLocalizations.of(context)!.about),
|
||||
onPressed: () {
|
||||
// show about screen
|
||||
context.goNamed('about');
|
||||
|
@ -154,13 +155,13 @@ class _HomePageState extends State<HomePage> {
|
|||
},
|
||||
),
|
||||
floatingActionButton: FloatingActionButton.extended(
|
||||
label: const Text('Add Room'),
|
||||
label: Text(AppLocalizations.of(context)!.addRoom),
|
||||
icon: const Icon(Icons.add),
|
||||
onPressed: () {
|
||||
// create new room
|
||||
context.goNamed('add-room');
|
||||
},
|
||||
tooltip: 'Add new Room',
|
||||
tooltip: AppLocalizations.of(context)!.addRoomHint,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:outbag_app/backend/errors.dart';
|
||||
import 'package:outbag_app/backend/request.dart';
|
||||
import 'package:outbag_app/backend/room.dart';
|
||||
import 'package:outbag_app/backend/user.dart';
|
||||
import 'package:outbag_app/tools/fetch_wrapper.dart';
|
||||
import 'package:outbag_app/tools/snackbar.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'dart:math';
|
||||
|
||||
class EditRoomPage extends StatefulWidget {
|
||||
|
@ -69,7 +70,7 @@ class _EditRoomPageState extends State<EditRoomPage> {
|
|||
} catch (_) {
|
||||
// no room data available
|
||||
// close screen
|
||||
router.pushReplacementNamed('homoe');
|
||||
router.pushReplacementNamed('home');
|
||||
}
|
||||
})();
|
||||
return true;
|
||||
|
@ -77,7 +78,7 @@ class _EditRoomPageState extends State<EditRoomPage> {
|
|||
onServerErr: (json) {
|
||||
// user not allowed to be here
|
||||
// close screen
|
||||
router.pushReplacementNamed('homoe');
|
||||
router.pushReplacementNamed('home');
|
||||
return true;
|
||||
},
|
||||
);
|
||||
|
@ -127,11 +128,11 @@ class _EditRoomPageState extends State<EditRoomPage> {
|
|||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const CircularProgressIndicator(),
|
||||
Text('Loading', style: textTheme.titleLarge),
|
||||
Text(AppLocalizations.of(context)!.loading, style: textTheme.titleLarge),
|
||||
])))
|
||||
: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Edit Room'),
|
||||
title: Text(AppLocalizations.of(context)!.editRoomMetadataShort),
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: Center(
|
||||
|
@ -149,13 +150,13 @@ class _EditRoomPageState extends State<EditRoomPage> {
|
|||
width: smallest * 0.3,
|
||||
height: smallest * 0.3,
|
||||
),
|
||||
tooltip: 'Change room icon',
|
||||
tooltip: AppLocalizations.of(context)!.changeRoomIcon,
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (ctx) => AlertDialog(
|
||||
title: const Text(
|
||||
'Choose a room Icon'),
|
||||
title: Text(
|
||||
AppLocalizations.of(context)!.chooseRoomIcon),
|
||||
actions: const [],
|
||||
content: SizedBox(
|
||||
width: smallest * 0.3 * 3,
|
||||
|
@ -195,13 +196,15 @@ class _EditRoomPageState extends State<EditRoomPage> {
|
|||
enabled: false,
|
||||
controller: _ctrID,
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
decoration: const InputDecoration(
|
||||
prefixIcon: Icon(Icons.fact_check),
|
||||
labelText: 'Room ID',
|
||||
hintText: 'Unique room id',
|
||||
helperText:
|
||||
'the room id and server tag allow the room to be identified',
|
||||
border: OutlineInputBorder(),
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.fact_check),
|
||||
labelText: AppLocalizations.of(context)!
|
||||
.inputRoomIdLabel,
|
||||
hintText: AppLocalizations.of(context)!
|
||||
.inputRoomIdHint,
|
||||
helperText: AppLocalizations.of(context)!
|
||||
.inputRoomIdHelp,
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -210,13 +213,15 @@ class _EditRoomPageState extends State<EditRoomPage> {
|
|||
child: TextField(
|
||||
controller: _ctrName,
|
||||
keyboardType: TextInputType.name,
|
||||
decoration: const InputDecoration(
|
||||
prefixIcon: Icon(Icons.badge),
|
||||
labelText: 'Room Name',
|
||||
hintText: 'Give your room a name',
|
||||
helperText:
|
||||
'Easily identify a room with a human readable name',
|
||||
border: OutlineInputBorder(),
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.badge),
|
||||
labelText: AppLocalizations.of(context)!
|
||||
.inputRoomNameLabel,
|
||||
hintText: AppLocalizations.of(context)!
|
||||
.inputRoomNameHint,
|
||||
helperText: AppLocalizations.of(context)!
|
||||
.inputRoomNameHelp,
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -225,13 +230,15 @@ class _EditRoomPageState extends State<EditRoomPage> {
|
|||
child: TextField(
|
||||
controller: _ctrDescription,
|
||||
keyboardType: TextInputType.text,
|
||||
decoration: const InputDecoration(
|
||||
prefixIcon: Icon(Icons.dns),
|
||||
labelText: 'Room Description',
|
||||
hintText: 'Briefly describe your Room',
|
||||
helperText:
|
||||
'Make it easier for other to know what this room is used for',
|
||||
border: OutlineInputBorder(),
|
||||
decoration: InputDecoration(
|
||||
labelText: AppLocalizations.of(context)!
|
||||
.inputRoomDescriptionLabel,
|
||||
hintText: AppLocalizations.of(context)!
|
||||
.inputRoomDescriptionHint,
|
||||
helperText: AppLocalizations.of(context)!
|
||||
.inputRoomDescriptionHelp,
|
||||
prefixIcon: const Icon(Icons.dns),
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -241,22 +248,12 @@ class _EditRoomPageState extends State<EditRoomPage> {
|
|||
onPressed: () async {
|
||||
final scaffMgr = ScaffoldMessenger.of(context);
|
||||
final nav = Navigator.of(context);
|
||||
final trans = AppLocalizations.of(context);
|
||||
|
||||
// name may not be empty
|
||||
if (_ctrName.text.isEmpty) {
|
||||
final snackBar = SnackBar(
|
||||
behavior: SnackBarBehavior.floating,
|
||||
content: const Text('Please specify a room name'),
|
||||
action: SnackBarAction(
|
||||
label: 'Ok',
|
||||
onPressed: () {
|
||||
scaffMgr.hideCurrentSnackBar();
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
scaffMgr.hideCurrentSnackBar();
|
||||
scaffMgr.showSnackBar(snackBar);
|
||||
showSimpleSnackbar(scaffMgr,
|
||||
text: trans!.errorNoRoomName, action: trans.ok);
|
||||
|
||||
return;
|
||||
}
|
||||
|
@ -275,8 +272,8 @@ class _EditRoomPageState extends State<EditRoomPage> {
|
|||
clone.description = _ctrDescription.text;
|
||||
clone.icon = _ctrIcon;
|
||||
|
||||
try {
|
||||
final resp = await postWithCreadentials(
|
||||
doNetworkRequest(scaffMgr,
|
||||
req: ()=>postWithCreadentials(
|
||||
target: user.server,
|
||||
credentials: user,
|
||||
path: 'changeRoomMeta',
|
||||
|
@ -286,45 +283,16 @@ class _EditRoomPageState extends State<EditRoomPage> {
|
|||
'title': clone.name,
|
||||
'description': clone.description,
|
||||
'icon': clone.icon?.type,
|
||||
});
|
||||
if (resp.res == Result.ok) {
|
||||
}),
|
||||
onOK: (_) async {
|
||||
// room was created
|
||||
// save room
|
||||
await clone.toDisk();
|
||||
nav.pop();
|
||||
} else {
|
||||
// error
|
||||
final snackBar = SnackBar(
|
||||
behavior: SnackBarBehavior.floating,
|
||||
content: Text(errorAsString(resp.body)),
|
||||
action: SnackBarAction(
|
||||
label: 'Dismiss',
|
||||
onPressed: () {
|
||||
scaffMgr.hideCurrentSnackBar();
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
scaffMgr.hideCurrentSnackBar();
|
||||
scaffMgr.showSnackBar(snackBar);
|
||||
}
|
||||
} catch (_) {
|
||||
final snackBar = SnackBar(
|
||||
behavior: SnackBarBehavior.floating,
|
||||
content: const Text('Network error'),
|
||||
action: SnackBarAction(
|
||||
label: 'Dismiss',
|
||||
onPressed: () {
|
||||
scaffMgr.hideCurrentSnackBar();
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
scaffMgr.hideCurrentSnackBar();
|
||||
scaffMgr.showSnackBar(snackBar);
|
||||
}
|
||||
);
|
||||
},
|
||||
label: const Text('Update'),
|
||||
label: Text(AppLocalizations.of(context)!.update),
|
||||
icon: const Icon(Icons.edit)),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import 'package:outbag_app/backend/room.dart';
|
|||
import 'package:outbag_app/backend/user.dart';
|
||||
import 'package:outbag_app/tools/fetch_wrapper.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'dart:math';
|
||||
|
||||
class JoinRoomPage extends StatefulWidget {
|
||||
|
@ -102,7 +103,7 @@ class _JoinRoomPageState extends State {
|
|||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.search),
|
||||
tooltip: "Search",
|
||||
tooltip: AppLocalizations.of(context)!.search,
|
||||
onPressed: () {
|
||||
// show searchbar
|
||||
// NOTE: location currently unknown
|
||||
|
@ -110,7 +111,7 @@ class _JoinRoomPageState extends State {
|
|||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.refresh),
|
||||
tooltip: "Refresh",
|
||||
tooltip: AppLocalizations.of(context)!.refresh,
|
||||
onPressed: () {
|
||||
// fetch public rooms again
|
||||
didChangeDependencies();
|
||||
|
@ -132,7 +133,7 @@ class _JoinRoomPageState extends State {
|
|||
menuChildren: [
|
||||
MenuItemButton(
|
||||
leadingIcon: const Icon(Icons.drafts),
|
||||
child: const Text('Join invite-only room'),
|
||||
child: Text(AppLocalizations.of(context)!.joinRoomInvite),
|
||||
onPressed: () {
|
||||
// show settings screen
|
||||
context.goNamed('join-room-ota');
|
||||
|
@ -146,7 +147,7 @@ class _JoinRoomPageState extends State {
|
|||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Text('No new Rooms found', style: textTheme.titleLarge),
|
||||
Text(AppLocalizations.of(context)!.noNewRoomsFound, style: textTheme.titleLarge),
|
||||
],
|
||||
))
|
||||
: ListView.builder(
|
||||
|
@ -159,7 +160,7 @@ class _JoinRoomPageState extends State {
|
|||
semanticContainer: true,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
// TODO: show modalBottomSheet
|
||||
// show modalBottomSheet
|
||||
// with room information
|
||||
// and join button
|
||||
showModalBottomSheet(
|
||||
|
@ -202,7 +203,7 @@ class _JoinRoomPageState extends State {
|
|||
children: [
|
||||
Icon(room.visibility?.icon),
|
||||
Text((room
|
||||
.visibility?.text)!),
|
||||
.visibility?.text(context))!),
|
||||
]),
|
||||
])),
|
||||
// action buttons
|
||||
|
@ -217,7 +218,7 @@ class _JoinRoomPageState extends State {
|
|||
child: ElevatedButton.icon(
|
||||
icon:
|
||||
const Icon(Icons.close),
|
||||
label: const Text('Cancel'),
|
||||
label: Text(AppLocalizations.of(context)!.cancel),
|
||||
onPressed: () {
|
||||
// close sheet
|
||||
Navigator.pop(context);
|
||||
|
@ -230,7 +231,7 @@ class _JoinRoomPageState extends State {
|
|||
child: FilledButton.icon(
|
||||
icon:
|
||||
const Icon(Icons.check),
|
||||
label: const Text('Join'),
|
||||
label: Text(AppLocalizations.of(context)!.joinRoom),
|
||||
onPressed: () async {
|
||||
final scaffMgr =
|
||||
ScaffoldMessenger.of(
|
||||
|
@ -294,13 +295,13 @@ class _JoinRoomPageState extends State {
|
|||
},
|
||||
),
|
||||
floatingActionButton: FloatingActionButton.extended(
|
||||
label: const Text('New'),
|
||||
label: Text(AppLocalizations.of(context)!.newRoom),
|
||||
icon: const Icon(Icons.add),
|
||||
onPressed: () {
|
||||
// create new room
|
||||
context.goNamed('new-room');
|
||||
},
|
||||
tooltip: 'Create Room',
|
||||
tooltip: AppLocalizations.of(context)!.createRoomShort,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import 'package:outbag_app/screens/room/pages/products.dart';
|
|||
import 'package:outbag_app/screens/room/pages/list.dart';
|
||||
import 'package:outbag_app/tools/fetch_wrapper.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
class RoomPage extends StatefulWidget {
|
||||
final String server;
|
||||
|
@ -121,23 +122,27 @@ class _RoomPageState extends State<RoomPage> {
|
|||
duration: const Duration(milliseconds: 300));
|
||||
},
|
||||
selectedIndex: page,
|
||||
destinations: const [
|
||||
destinations: [
|
||||
NavigationDestination(
|
||||
icon: Icon(Icons.list),
|
||||
label: "List",
|
||||
tooltip: 'View shopping list'),
|
||||
icon: const Icon(Icons.list),
|
||||
label: AppLocalizations.of(context)!.roomListTitle,
|
||||
tooltip: AppLocalizations.of(context)!.roomListSubtitle
|
||||
),
|
||||
NavigationDestination(
|
||||
icon: Icon(Icons.inventory_2),
|
||||
label: "Products",
|
||||
tooltip: 'View saved items'),
|
||||
icon: const Icon(Icons.inventory_2),
|
||||
label: AppLocalizations.of(context)!.roomProductsTitle,
|
||||
tooltip: AppLocalizations.of(context)!.roomProductsSubtitle
|
||||
),
|
||||
NavigationDestination(
|
||||
icon: Icon(Icons.category),
|
||||
label: "Categories",
|
||||
tooltip: 'View categories'),
|
||||
icon: const Icon(Icons.category),
|
||||
label: AppLocalizations.of(context)!.roomCategoriesTitle,
|
||||
tooltip: AppLocalizations.of(context)!.roomCategoriesSubtitle
|
||||
),
|
||||
NavigationDestination(
|
||||
icon: Icon(Icons.info_rounded),
|
||||
label: "About",
|
||||
tooltip: 'View room info'),
|
||||
icon: const Icon(Icons.info_rounded),
|
||||
label: AppLocalizations.of(context)!.roomAboutTitle,
|
||||
tooltip: AppLocalizations.of(context)!.roomAboutSubtitle
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
|
|
@ -6,6 +6,7 @@ import 'package:outbag_app/backend/room.dart';
|
|||
import 'package:outbag_app/backend/user.dart';
|
||||
import 'package:outbag_app/tools/fetch_wrapper.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
class ManageRoomMembersPage extends StatefulWidget {
|
||||
final String server;
|
||||
|
@ -95,15 +96,8 @@ class _ManageRoomMembersPageState extends State<ManageRoomMembersPage> {
|
|||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Room Members (${list.length})'),
|
||||
leading: IconButton(
|
||||
onPressed: () {
|
||||
// go back
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
tooltip: "Go back",
|
||||
),
|
||||
title: Text(
|
||||
AppLocalizations.of(context)!.roomMembersTitle(list.length)),
|
||||
//actions: [
|
||||
// // NOTE: Maybe add a search icon
|
||||
// // and general search functionality here
|
||||
|
@ -113,13 +107,13 @@ class _ManageRoomMembersPageState extends State<ManageRoomMembersPage> {
|
|||
itemBuilder: (BuildContext context, int index) {
|
||||
final item = list[index];
|
||||
|
||||
String role = "Member";
|
||||
String role = AppLocalizations.of(context)!.roleMember;
|
||||
if (info != null &&
|
||||
(info?.owner)! == item.id &&
|
||||
widget.server == item.serverTag) {
|
||||
role = "Owner";
|
||||
role = AppLocalizations.of(context)!.roleOwner;
|
||||
} else if (item.isAdmin) {
|
||||
role = "Admin";
|
||||
role = AppLocalizations.of(context)!.roleAdmin;
|
||||
}
|
||||
|
||||
bool enable = true;
|
||||
|
@ -172,11 +166,11 @@ class _ManageRoomMembersPageState extends State<ManageRoomMembersPage> {
|
|||
leading: const Icon(
|
||||
Icons.supervisor_account),
|
||||
title: Text(item.isAdmin
|
||||
? 'Remove admin privileges'
|
||||
: 'Make Admin'),
|
||||
? AppLocalizations.of(context)!.removeAdminTitle
|
||||
: AppLocalizations.of(context)!.makeAdminTitle),
|
||||
subtitle: Text(item.isAdmin
|
||||
? 'Revokes admin privileges from the user'
|
||||
: 'Grants the user the permission to do everything'),
|
||||
? AppLocalizations.of(context)!.removeAdminSubtitle
|
||||
: AppLocalizations.of(context)!.makeAdminSubtitle),
|
||||
onTap: () {
|
||||
// make user admin
|
||||
showDialog(
|
||||
|
@ -189,12 +183,12 @@ class _ManageRoomMembersPageState extends State<ManageRoomMembersPage> {
|
|||
.supervisor_account),
|
||||
title: Text(item
|
||||
.isAdmin
|
||||
? 'Remove admin privileges'
|
||||
: 'Make Admin'),
|
||||
? AppLocalizations.of(context)!.removeAdminTitle
|
||||
: AppLocalizations.of(context)!.makeAdminTitle),
|
||||
content: Text(item
|
||||
.isAdmin
|
||||
? "Do you really want to remove ${item.humanReadableName}'s admin privileges"
|
||||
: 'Do you really want to make ${item.humanReadableName} admin?'),
|
||||
? AppLocalizations.of(context)!.removeAdminConfirm(item.humanReadableName)
|
||||
: AppLocalizations.of(context)!.makeAdminConfirm(item.humanReadableName)),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed:
|
||||
|
@ -205,8 +199,7 @@ class _ManageRoomMembersPageState extends State<ManageRoomMembersPage> {
|
|||
Navigator.of(context)
|
||||
.pop();
|
||||
},
|
||||
child: const Text(
|
||||
'Cancel'),
|
||||
child: Text(AppLocalizations.of(context)!.cancel),
|
||||
),
|
||||
FilledButton(
|
||||
onPressed:
|
||||
|
@ -238,8 +231,7 @@ class _ManageRoomMembersPageState extends State<ManageRoomMembersPage> {
|
|||
nav.pop();
|
||||
});
|
||||
},
|
||||
child: const Text(
|
||||
'OK'),
|
||||
child: Text(AppLocalizations.of(context)!.ok),
|
||||
)
|
||||
],
|
||||
));
|
||||
|
@ -257,10 +249,8 @@ class _ManageRoomMembersPageState extends State<ManageRoomMembersPage> {
|
|||
ListTile(
|
||||
leading: const Icon(
|
||||
Icons.person_remove),
|
||||
title:
|
||||
const Text('Kick User'),
|
||||
subtitle: const Text(
|
||||
"Temporarrily remove user from server (they'll be able to join the room again)"),
|
||||
title: Text(AppLocalizations.of(context)!.kickUserTitle),
|
||||
subtitle: Text(AppLocalizations.of(context)!.kickUserSubtitle),
|
||||
onTap: () {
|
||||
// remove user from room
|
||||
showDialog(
|
||||
|
@ -271,10 +261,10 @@ class _ManageRoomMembersPageState extends State<ManageRoomMembersPage> {
|
|||
icon: const Icon(
|
||||
Icons
|
||||
.person_remove),
|
||||
title: const Text(
|
||||
'Kick User'),
|
||||
title: Text(
|
||||
AppLocalizations.of(context)!.kickUserTitle),
|
||||
content: Text(
|
||||
'Do you really want to kick ${item.humanReadableName}?'),
|
||||
AppLocalizations.of(context)!.kichUserConfirm(item.humanReadableName)),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed:
|
||||
|
@ -286,8 +276,8 @@ class _ManageRoomMembersPageState extends State<ManageRoomMembersPage> {
|
|||
Navigator.of(ctx)
|
||||
.pop();
|
||||
},
|
||||
child: const Text(
|
||||
'Cancel'),
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.cancel),
|
||||
),
|
||||
FilledButton(
|
||||
onPressed:
|
||||
|
@ -318,8 +308,8 @@ class _ManageRoomMembersPageState extends State<ManageRoomMembersPage> {
|
|||
nav.pop();
|
||||
});
|
||||
},
|
||||
child: const Text(
|
||||
'Kick User'),
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.ok),
|
||||
)
|
||||
],
|
||||
));
|
||||
|
@ -331,7 +321,7 @@ class _ManageRoomMembersPageState extends State<ManageRoomMembersPage> {
|
|||
),
|
||||
),
|
||||
FilledButton(
|
||||
child: const Text('Close'),
|
||||
child: Text(AppLocalizations.of(context)!.close),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
|
|
|
@ -7,6 +7,7 @@ import 'package:outbag_app/backend/user.dart';
|
|||
import 'package:outbag_app/tools/fetch_wrapper.dart';
|
||||
import 'package:outbag_app/tools/snackbar.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'dart:math';
|
||||
|
||||
class NewRoomPage extends StatefulWidget {
|
||||
|
@ -28,208 +29,226 @@ class _NewRoomPageState extends State {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final textTheme = Theme.of(context)
|
||||
.textTheme
|
||||
.apply(displayColor: Theme.of(context).colorScheme.onSurface);
|
||||
.textTheme
|
||||
.apply(displayColor: Theme.of(context).colorScheme.onSurface);
|
||||
|
||||
double width = MediaQuery.of(context).size.width;
|
||||
double height = MediaQuery.of(context).size.height;
|
||||
double smallest = min(min(width, height), 400);
|
||||
|
||||
return showSpinner
|
||||
? Scaffold(
|
||||
body: Center(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const CircularProgressIndicator(),
|
||||
Text('Loading', style: textTheme.titleLarge),
|
||||
])))
|
||||
: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('New Room'),
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(14),
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 400),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
IconButton(
|
||||
icon: SvgPicture.asset(
|
||||
_ctrIcon.img,
|
||||
width: smallest * 0.3,
|
||||
height: smallest * 0.3,
|
||||
),
|
||||
tooltip: 'Change room icon',
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (ctx) => AlertDialog(
|
||||
title: const Text(
|
||||
'Choose a room Icon'),
|
||||
actions: const [],
|
||||
content: SizedBox(
|
||||
width: smallest * 0.3 * 3,
|
||||
height: smallest * 0.3 * 3,
|
||||
child: GridView.count(
|
||||
crossAxisCount: 3,
|
||||
children: RoomIcon.list()
|
||||
.map((icon) {
|
||||
return GridTile(
|
||||
child: IconButton(
|
||||
icon: SvgPicture
|
||||
.asset(
|
||||
icon.img,
|
||||
width:
|
||||
smallest *
|
||||
0.3,
|
||||
height:
|
||||
smallest *
|
||||
0.3,
|
||||
),
|
||||
tooltip:
|
||||
icon.text,
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_ctrIcon =
|
||||
icon;
|
||||
});
|
||||
Navigator.of(
|
||||
context)
|
||||
.pop();
|
||||
}));
|
||||
}).toList())),
|
||||
));
|
||||
},
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: TextField(
|
||||
controller: _ctrID,
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
decoration: const InputDecoration(
|
||||
prefixIcon: Icon(Icons.fact_check),
|
||||
labelText: 'Room ID',
|
||||
hintText: 'Unique room id',
|
||||
helperText:
|
||||
'the room id and server tag allow the room to be identified',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: TextField(
|
||||
controller: _ctrName,
|
||||
keyboardType: TextInputType.name,
|
||||
decoration: const InputDecoration(
|
||||
prefixIcon: Icon(Icons.badge),
|
||||
labelText: 'Room Name',
|
||||
hintText: 'Give your room a name',
|
||||
helperText:
|
||||
'Easily identify a room with a human readable name',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: TextField(
|
||||
controller: _ctrDescription,
|
||||
keyboardType: TextInputType.text,
|
||||
decoration: const InputDecoration(
|
||||
prefixIcon: Icon(Icons.dns),
|
||||
labelText: 'Room Description',
|
||||
hintText: 'Briefly describe your Room',
|
||||
helperText:
|
||||
'Make it easier for other to know what this room is used for',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
),
|
||||
Text('Visibility', style: textTheme.labelLarge),
|
||||
Text('Specify who has access to your room',
|
||||
style: textTheme.bodySmall),
|
||||
SegmentedButton<RoomVisibility>(
|
||||
showSelectedIcon: true,
|
||||
multiSelectionEnabled: false,
|
||||
emptySelectionAllowed: false,
|
||||
segments: RoomVisibility.list().map((vis) {
|
||||
return ButtonSegment<RoomVisibility>(
|
||||
value: vis,
|
||||
label: Text(vis.text),
|
||||
icon: Icon(vis.icon));
|
||||
}).toList(),
|
||||
onSelectionChanged: ((vset) {
|
||||
setState(() {
|
||||
_ctrVis = vset.single;
|
||||
});
|
||||
}),
|
||||
selected: {_ctrVis},
|
||||
selectedIcon: Icon(_ctrVis.icon),
|
||||
),
|
||||
],
|
||||
))))),
|
||||
floatingActionButton: FloatingActionButton.extended(
|
||||
onPressed: () async {
|
||||
final scaffMgr = ScaffoldMessenger.of(context);
|
||||
final router = GoRouter.of(context);
|
||||
? Scaffold(
|
||||
body: Center(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const CircularProgressIndicator(),
|
||||
Text(AppLocalizations.of(context)!.loading,
|
||||
style: textTheme.titleLarge),
|
||||
])))
|
||||
: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(AppLocalizations.of(context)!.newRoom),
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(14),
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 400),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
IconButton(
|
||||
icon: SvgPicture.asset(
|
||||
_ctrIcon.img,
|
||||
width: smallest * 0.3,
|
||||
height: smallest * 0.3,
|
||||
),
|
||||
tooltip: AppLocalizations.of(context)!
|
||||
.changeRoomIcon,
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (ctx) => AlertDialog(
|
||||
title: Text(
|
||||
AppLocalizations.of(context)!
|
||||
.chooseRoomIcon),
|
||||
actions: const [],
|
||||
content: SizedBox(
|
||||
width: smallest * 0.3 * 3,
|
||||
height: smallest * 0.3 * 3,
|
||||
child: GridView.count(
|
||||
crossAxisCount: 3,
|
||||
children: RoomIcon.list()
|
||||
.map((icon) {
|
||||
return GridTile(
|
||||
child: IconButton(
|
||||
icon: SvgPicture
|
||||
.asset(
|
||||
icon.img,
|
||||
width:
|
||||
smallest *
|
||||
0.3,
|
||||
height:
|
||||
smallest *
|
||||
0.3,
|
||||
),
|
||||
// do not display tooltip for now
|
||||
// as it is hard to translate
|
||||
// and the tooltip prevented the click event,
|
||||
// when clicked on the tooltip bar
|
||||
// tooltip:icon.text,
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_ctrIcon =
|
||||
icon;
|
||||
});
|
||||
Navigator.of(
|
||||
context)
|
||||
.pop();
|
||||
}));
|
||||
}).toList())),
|
||||
));
|
||||
},
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: TextField(
|
||||
controller: _ctrID,
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.fact_check),
|
||||
labelText: AppLocalizations.of(context)!
|
||||
.inputRoomIdLabel,
|
||||
hintText: AppLocalizations.of(context)!
|
||||
.inputRoomIdHint,
|
||||
helperText: AppLocalizations.of(context)!
|
||||
.inputRoomIdHelp,
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: TextField(
|
||||
controller: _ctrName,
|
||||
keyboardType: TextInputType.name,
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.badge),
|
||||
labelText: AppLocalizations.of(context)!
|
||||
.inputRoomNameLabel,
|
||||
hintText: AppLocalizations.of(context)!
|
||||
.inputRoomNameHint,
|
||||
helperText: AppLocalizations.of(context)!
|
||||
.inputRoomNameHelp,
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: TextField(
|
||||
controller: _ctrDescription,
|
||||
keyboardType: TextInputType.text,
|
||||
decoration: InputDecoration(
|
||||
labelText: AppLocalizations.of(context)!
|
||||
.inputRoomDescriptionLabel,
|
||||
hintText: AppLocalizations.of(context)!
|
||||
.inputRoomDescriptionHint,
|
||||
helperText: AppLocalizations.of(context)!
|
||||
.inputRoomDescriptionHelp,
|
||||
prefixIcon: const Icon(Icons.dns),
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
AppLocalizations.of(context)!
|
||||
.roomVisibilityTitle,
|
||||
style: textTheme.labelLarge),
|
||||
Text(
|
||||
AppLocalizations.of(context)!
|
||||
.roomVisibilitySubtitle,
|
||||
style: textTheme.bodySmall),
|
||||
SegmentedButton<RoomVisibility>(
|
||||
showSelectedIcon: true,
|
||||
multiSelectionEnabled: false,
|
||||
emptySelectionAllowed: false,
|
||||
segments: RoomVisibility.list().map((vis) {
|
||||
return ButtonSegment<RoomVisibility>(
|
||||
value: vis,
|
||||
label: Text(vis.text(context)),
|
||||
icon: Icon(vis.icon));
|
||||
}).toList(),
|
||||
onSelectionChanged: ((vset) {
|
||||
setState(() {
|
||||
_ctrVis = vset.single;
|
||||
});
|
||||
}),
|
||||
selected: {_ctrVis},
|
||||
selectedIcon: Icon(_ctrVis.icon),
|
||||
),
|
||||
],
|
||||
))))),
|
||||
floatingActionButton: FloatingActionButton.extended(
|
||||
onPressed: () async {
|
||||
final scaffMgr = ScaffoldMessenger.of(context);
|
||||
final router = GoRouter.of(context);
|
||||
final trans = AppLocalizations.of(context);
|
||||
|
||||
// ID should be at least three characters long
|
||||
if (_ctrID.text.length < 3) {
|
||||
showSimpleSnackbar(scaffMgr,
|
||||
text: _ctrID.text.isEmpty
|
||||
? 'Please specify a Room ID'
|
||||
: 'Room ID has to be at least three characters long',
|
||||
action: 'OK');
|
||||
// ID should be at least three characters long
|
||||
if (_ctrID.text.length < 3) {
|
||||
showSimpleSnackbar(scaffMgr,
|
||||
text: _ctrID.text.isEmpty
|
||||
? trans!.errorNoRoomId
|
||||
: trans!.errorRoomIdLength,
|
||||
action: trans.ok);
|
||||
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// name may not be empty
|
||||
if (_ctrName.text.isEmpty) {
|
||||
showSimpleSnackbar(scaffMgr,
|
||||
text: 'Please specify a room name', action: 'OK');
|
||||
// name may not be empty
|
||||
if (_ctrName.text.isEmpty) {
|
||||
showSimpleSnackbar(scaffMgr,
|
||||
text: trans!.errorNoRoomName, action: trans.ok);
|
||||
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
final user = context.read<User>();
|
||||
final room = Room(
|
||||
id: _ctrID.text,
|
||||
serverTag: user.server.tag,
|
||||
name: _ctrName.text,
|
||||
description: _ctrDescription.text,
|
||||
icon: _ctrIcon,
|
||||
visibility: _ctrVis);
|
||||
final user = context.read<User>();
|
||||
final room = Room(
|
||||
id: _ctrID.text,
|
||||
serverTag: user.server.tag,
|
||||
name: _ctrName.text,
|
||||
description: _ctrDescription.text,
|
||||
icon: _ctrIcon,
|
||||
visibility: _ctrVis);
|
||||
|
||||
doNetworkRequest(scaffMgr,
|
||||
req: () => postWithCreadentials(
|
||||
target: user.server,
|
||||
credentials: user,
|
||||
path: 'createRoom',
|
||||
body: {
|
||||
'room': room.id,
|
||||
'title': room.name,
|
||||
'description': room.description,
|
||||
'icon': room.icon?.type,
|
||||
'visibility': room.visibility?.type
|
||||
}),
|
||||
onOK: (_) async {
|
||||
// room was created
|
||||
// save room
|
||||
await room.toDisk();
|
||||
// move to home page
|
||||
router.pushReplacementNamed('home');
|
||||
});
|
||||
},
|
||||
label: const Text('Create'),
|
||||
icon: const Icon(Icons.add)),
|
||||
);
|
||||
doNetworkRequest(scaffMgr,
|
||||
req: () => postWithCreadentials(
|
||||
target: user.server,
|
||||
credentials: user,
|
||||
path: 'createRoom',
|
||||
body: {
|
||||
'room': room.id,
|
||||
'title': room.name,
|
||||
'description': room.description,
|
||||
'icon': room.icon?.type,
|
||||
'visibility': room.visibility?.type
|
||||
}),
|
||||
onOK: (_) async {
|
||||
// room was created
|
||||
// save room
|
||||
await room.toDisk();
|
||||
// move to home page
|
||||
router.pushReplacementNamed('home');
|
||||
});
|
||||
},
|
||||
label: Text(AppLocalizations.of(context)!.createRoomShort),
|
||||
icon: const Icon(Icons.add)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import 'package:outbag_app/backend/user.dart';
|
|||
import 'dart:math';
|
||||
import 'package:outbag_app/tools/fetch_wrapper.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
class AboutRoomPage extends StatefulWidget {
|
||||
final RoomInfo? info;
|
||||
|
@ -67,7 +68,7 @@ class _AboutRoomPageState extends State<AboutRoomPage> {
|
|||
segments: RoomVisibility.list().map((vis) {
|
||||
return ButtonSegment<int>(
|
||||
value: vis.type,
|
||||
label: Text(vis.text),
|
||||
label: Text(vis.text(context)),
|
||||
icon: Icon(vis.icon));
|
||||
}).toList(),
|
||||
onSelectionChanged: ((vset) {
|
||||
|
@ -90,16 +91,14 @@ class _AboutRoomPageState extends State<AboutRoomPage> {
|
|||
showDialog(
|
||||
context: context,
|
||||
builder: (ctx) => AlertDialog(
|
||||
title:
|
||||
const Text('Change room visibility'),
|
||||
content: Text(
|
||||
'Do you really want to change the room visibility to: ${vis.text}'),
|
||||
title: Text(AppLocalizations.of(context)!.changeRoomVisibilityTitle),
|
||||
content: Text(AppLocalizations.of(context)!.changeRoomVisibilitySubtitle(vis.text(context))),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
context.pop();
|
||||
},
|
||||
child: const Text('Cancel'),
|
||||
child: Text(AppLocalizations.of(context)!.cancel),
|
||||
),
|
||||
FilledButton(
|
||||
onPressed: () async {
|
||||
|
@ -128,7 +127,7 @@ class _AboutRoomPageState extends State<AboutRoomPage> {
|
|||
nav.pop();
|
||||
});
|
||||
},
|
||||
child: const Text('Ok'),
|
||||
child: Text(AppLocalizations.of(context)!.ok),
|
||||
)
|
||||
],
|
||||
));
|
||||
|
@ -156,9 +155,8 @@ class _AboutRoomPageState extends State<AboutRoomPage> {
|
|||
? [
|
||||
ListTile(
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
title: const Text('Edit Metadata'),
|
||||
subtitle: const Text(
|
||||
'Change the rooms name, description and icon'),
|
||||
title: Text(AppLocalizations.of(context)!.editRoomMetadata),
|
||||
subtitle: Text(AppLocalizations.of(context)!.editRoomMetadataSubtitle),
|
||||
onTap: () {
|
||||
// show edit room screen
|
||||
context.goNamed('edit-room', params: {
|
||||
|
@ -172,8 +170,8 @@ class _AboutRoomPageState extends State<AboutRoomPage> {
|
|||
// open members view
|
||||
ListTile(
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
title: const Text('Members'),
|
||||
subtitle: const Text('Show Member list'),
|
||||
title: Text(AppLocalizations.of(context)!.showRoomMembers),
|
||||
subtitle: Text(AppLocalizations.of(context)!.showRoomMembersSubtitle),
|
||||
onTap: () {
|
||||
// open member view screen
|
||||
context.goNamed('room-members', params: {
|
||||
|
@ -192,9 +190,8 @@ class _AboutRoomPageState extends State<AboutRoomPage> {
|
|||
? [
|
||||
ListTile(
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
title: const Text('Edit Permissions'),
|
||||
subtitle: const Text(
|
||||
'Change the default permission-set for all members'),
|
||||
title: Text(AppLocalizations.of(context)!.editRoomPermissions),
|
||||
subtitle: Text(AppLocalizations.of(context)!.editRoomPermissionsSubtitle),
|
||||
onTap: () {
|
||||
// show checkbox screen
|
||||
context.goNamed('room-permissions', params: {
|
||||
|
@ -213,8 +210,8 @@ class _AboutRoomPageState extends State<AboutRoomPage> {
|
|||
? [
|
||||
ListTile(
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
title: const Text('OTA'),
|
||||
subtitle: const Text('Add and delete OTAs'),
|
||||
title: Text(AppLocalizations.of(context)!.manageRoomOTA),
|
||||
subtitle: Text(AppLocalizations.of(context)!.manageRoomOTASubtitle),
|
||||
onTap: () {
|
||||
// show manage ota screen
|
||||
context.goNamed('room-ota', params: {
|
||||
|
@ -225,8 +222,8 @@ class _AboutRoomPageState extends State<AboutRoomPage> {
|
|||
),
|
||||
ListTile(
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
title: const Text('Invites'),
|
||||
subtitle: const Text('Invite people to this room'),
|
||||
title: Text(AppLocalizations.of(context)!.manageRoomInvites),
|
||||
subtitle: Text(AppLocalizations.of(context)!.manageRoomInvitesSubtitle),
|
||||
onTap: () {
|
||||
// show manage ota screen
|
||||
context.goNamed('room-invite', params: {
|
||||
|
@ -246,25 +243,26 @@ class _AboutRoomPageState extends State<AboutRoomPage> {
|
|||
padding: const EdgeInsets.all(8),
|
||||
child: FilledButton.tonal(
|
||||
child: Text(((widget.info?.isOwner)!)
|
||||
? 'Delete Room'
|
||||
: 'Leave Room'),
|
||||
? AppLocalizations.of(context)!.deleteRoom
|
||||
: AppLocalizations.of(context)!.leaveRoom),
|
||||
onPressed: () {
|
||||
// show confirm dialog
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (ctx) => AlertDialog(
|
||||
title: Text(((widget.info?.isOwner)!)
|
||||
? 'Delete Room'
|
||||
: 'Leave Room'),
|
||||
content: Text(
|
||||
'Do you really want to ${((widget.info?.isOwner)!) ? "delete" : "leave"} the room?'),
|
||||
? AppLocalizations.of(context)!.deleteRoom
|
||||
: AppLocalizations.of(context)!.leaveRoom),
|
||||
content: Text(((widget.info?.isOwner)!)
|
||||
? AppLocalizations.of(context)!.deleteRoomConfirm
|
||||
: AppLocalizations.of(context)!.leaveRoomConfirm),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
// close popup
|
||||
Navigator.of(ctx).pop();
|
||||
},
|
||||
child: const Text('Cancel'),
|
||||
child: Text(AppLocalizations.of(context)!.cancel),
|
||||
),
|
||||
FilledButton(
|
||||
onPressed: () async {
|
||||
|
@ -302,8 +300,8 @@ class _AboutRoomPageState extends State<AboutRoomPage> {
|
|||
});
|
||||
},
|
||||
child: Text(((widget.info?.isOwner)!)
|
||||
? 'Delete'
|
||||
: 'Leave'),
|
||||
? AppLocalizations.of(context)!.deleteRoomShort
|
||||
: AppLocalizations.of(context)!.leaveRoomShort),
|
||||
)
|
||||
],
|
||||
));
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:outbag_app/backend/permissions.dart';
|
||||
|
@ -31,10 +31,10 @@ class _EditRoomPermissionSetPageState extends State<EditRoomPermissionSetPage> {
|
|||
doNetworkRequest(
|
||||
sm,
|
||||
req: () => postWithCreadentials(
|
||||
path: 'getRoomInfo',
|
||||
credentials: user,
|
||||
target: user.server,
|
||||
body: {'room': widget.tag, 'server': widget.server}),
|
||||
path: 'getRoomInfo',
|
||||
credentials: user,
|
||||
target: user.server,
|
||||
body: {'room': widget.tag, 'server': widget.server}),
|
||||
onAnyErr: () {
|
||||
// user should not be here
|
||||
// close screen
|
||||
|
@ -44,7 +44,7 @@ class _EditRoomPermissionSetPageState extends State<EditRoomPermissionSetPage> {
|
|||
onOK: (body) async {
|
||||
final info = RoomInfo.fromJSON(body['data']);
|
||||
setState(() {
|
||||
permissions = info.permissions;
|
||||
permissions = info.permissions;
|
||||
});
|
||||
return true;
|
||||
},
|
||||
|
@ -62,15 +62,7 @@ class _EditRoomPermissionSetPageState extends State<EditRoomPermissionSetPage> {
|
|||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Default permissions'),
|
||||
leading: IconButton(
|
||||
onPressed: () {
|
||||
// go back
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
tooltip: "Go back",
|
||||
),
|
||||
title: Text(AppLocalizations.of(context)!.roomDefaultPermissions),
|
||||
),
|
||||
body: ListView.builder(
|
||||
itemCount: items.length,
|
||||
|
@ -80,20 +72,20 @@ class _EditRoomPermissionSetPageState extends State<EditRoomPermissionSetPage> {
|
|||
final int col = pow(2, index + 1) as int;
|
||||
|
||||
return SwitchListTile(
|
||||
title: Text(RoomPermission.name(item)),
|
||||
subtitle: Text(RoomPermission.describe(item)),
|
||||
onChanged: (state) {
|
||||
setState(() {
|
||||
title: Text(RoomPermission.name(item, context)),
|
||||
subtitle: Text(RoomPermission.describe(item, context)),
|
||||
onChanged: (state) {
|
||||
setState(() {
|
||||
permissions += (state ? 1 : -1) * col;
|
||||
});
|
||||
},
|
||||
value: permissions & col != 0);
|
||||
});
|
||||
},
|
||||
value: permissions & col != 0);
|
||||
},
|
||||
),
|
||||
floatingActionButton: FloatingActionButton.extended(
|
||||
icon: const Icon(Icons.edit),
|
||||
tooltip: "Update default permission set",
|
||||
label: const Text('Edit'),
|
||||
tooltip: AppLocalizations.of(context)!.updateRoomPermissionsHint,
|
||||
label: Text(AppLocalizations.of(context)!.updateRoomPermissions),
|
||||
onPressed: () {
|
||||
final router = GoRouter.of(context);
|
||||
final sm = ScaffoldMessenger.of(context);
|
||||
|
@ -101,18 +93,18 @@ class _EditRoomPermissionSetPageState extends State<EditRoomPermissionSetPage> {
|
|||
|
||||
// update permissions
|
||||
doNetworkRequest(sm,
|
||||
req: () => postWithCreadentials(
|
||||
path: 'setRoomRight',
|
||||
credentials: user,
|
||||
target: user.server,
|
||||
body: {
|
||||
'room': widget.tag,
|
||||
'server': widget.server,
|
||||
'rights': permissions
|
||||
}),
|
||||
onOK: (_) {
|
||||
router.pop();
|
||||
});
|
||||
req: () => postWithCreadentials(
|
||||
path: 'setRoomRight',
|
||||
credentials: user,
|
||||
target: user.server,
|
||||
body: {
|
||||
'room': widget.tag,
|
||||
'server': widget.server,
|
||||
'rights': permissions
|
||||
}),
|
||||
onOK: (_) {
|
||||
router.pop();
|
||||
});
|
||||
},
|
||||
),
|
||||
);
|
||||
|
|
|
@ -4,6 +4,7 @@ import 'package:outbag_app/backend/request.dart';
|
|||
import 'package:outbag_app/backend/user.dart';
|
||||
import 'package:outbag_app/tools/fetch_wrapper.dart';
|
||||
import 'package:outbag_app/tools/snackbar.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class ChangePasswordDialog extends StatefulWidget {
|
||||
|
@ -21,59 +22,57 @@ class _ChangePasswordDialogState extends State<ChangePasswordDialog> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: const Text('Change Password'),
|
||||
title: Text(AppLocalizations.of(context)!.changeThemeTitle),
|
||||
icon: const Icon(Icons.password),
|
||||
content: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: TextField(
|
||||
controller: _ctrOldPassword,
|
||||
keyboardType: TextInputType.visiblePassword,
|
||||
obscureText: true,
|
||||
decoration: const InputDecoration(
|
||||
prefixIcon: Icon(Icons.lock),
|
||||
labelText: 'Old Password',
|
||||
hintText: 'Your current password',
|
||||
helperText:
|
||||
'For safety, you have to type your current passwort',
|
||||
border: OutlineInputBorder(),
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: TextField(
|
||||
controller: _ctrOldPassword,
|
||||
keyboardType: TextInputType.visiblePassword,
|
||||
obscureText: true,
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.lock),
|
||||
labelText: AppLocalizations.of(context)!.inputOldPasswordLabel,
|
||||
hintText: AppLocalizations.of(context)!.inputOldPasswordHint,
|
||||
helperText:AppLocalizations.of(context)!.inputOldPasswordHelp,
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: TextField(
|
||||
controller: _ctrNewPassword,
|
||||
keyboardType: TextInputType.visiblePassword,
|
||||
obscureText: true,
|
||||
decoration: const InputDecoration(
|
||||
prefixIcon: Icon(Icons.lock),
|
||||
labelText: 'New Password',
|
||||
hintText: 'Your new password',
|
||||
helperText: 'Password have to be at least six characters long',
|
||||
border: OutlineInputBorder(),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: TextField(
|
||||
controller: _ctrNewPassword,
|
||||
keyboardType: TextInputType.visiblePassword,
|
||||
obscureText: true,
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.lock),
|
||||
labelText: AppLocalizations.of(context)!.inputNewPasswordLabel,
|
||||
hintText: AppLocalizations.of(context)!.inputNewPasswordHint,
|
||||
helperText:AppLocalizations.of(context)!.inputNewPasswordHelp,
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: TextField(
|
||||
controller: _ctrNewPasswordRepeat,
|
||||
keyboardType: TextInputType.visiblePassword,
|
||||
obscureText: true,
|
||||
decoration: const InputDecoration(
|
||||
prefixIcon: Icon(Icons.lock),
|
||||
labelText: 'Repeat new Password',
|
||||
hintText: 'Type your new password again',
|
||||
helperText:
|
||||
'Type your new password again, to make sure you know it',
|
||||
border: OutlineInputBorder(),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: TextField(
|
||||
controller: _ctrNewPasswordRepeat,
|
||||
keyboardType: TextInputType.visiblePassword,
|
||||
obscureText: true,
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.lock),
|
||||
labelText: AppLocalizations.of(context)!.inputNewPasswordRepeatLabel,
|
||||
hintText: AppLocalizations.of(context)!.inputNewPasswordRepeatHint,
|
||||
helperText:AppLocalizations.of(context)!.inputNewPasswordRepeatHelp,
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
)),
|
||||
actions: [
|
||||
TextButton(
|
||||
|
@ -81,20 +80,21 @@ class _ChangePasswordDialogState extends State<ChangePasswordDialog> {
|
|||
// close popup
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: const Text('Cancel'),
|
||||
child: Text(AppLocalizations.of(context)!.cancel),
|
||||
),
|
||||
FilledButton(
|
||||
onPressed: () async {
|
||||
final scaffMgr = ScaffoldMessenger.of(context);
|
||||
final nav = Navigator.of(context);
|
||||
final user = context.read<User>();
|
||||
final trans = AppLocalizations.of(context);
|
||||
|
||||
// validate password
|
||||
if (_ctrNewPassword.text.length < 6) {
|
||||
// password has to be at least 6 characters long
|
||||
showSimpleSnackbar(scaffMgr,
|
||||
text: 'Password has to be at least 6 characters longs',
|
||||
action: 'Dismiss');
|
||||
text: trans!.errorPasswordLength,
|
||||
action: trans.dismiss);
|
||||
|
||||
_ctrNewPasswordRepeat.clear();
|
||||
return;
|
||||
|
@ -102,7 +102,7 @@ class _ChangePasswordDialogState extends State<ChangePasswordDialog> {
|
|||
if (_ctrNewPassword.text != _ctrNewPasswordRepeat.text) {
|
||||
// new passwords do not match
|
||||
showSimpleSnackbar(scaffMgr,
|
||||
text: 'New passwords do not match', action: 'Dismiss');
|
||||
text: trans!.errorPasswordsDoNotMatch, action: trans.dismiss);
|
||||
|
||||
_ctrNewPasswordRepeat.clear();
|
||||
return;
|
||||
|
@ -110,7 +110,7 @@ class _ChangePasswordDialogState extends State<ChangePasswordDialog> {
|
|||
if (hashPassword(_ctrOldPassword.text) != user.password) {
|
||||
// current password wrong
|
||||
showSimpleSnackbar(scaffMgr,
|
||||
text: 'Old password is wrong', action: 'Dismiss');
|
||||
text: trans!.errorOldPasswordWrong, action: trans.dismiss);
|
||||
|
||||
_ctrOldPassword.clear();
|
||||
return;
|
||||
|
@ -120,25 +120,25 @@ class _ChangePasswordDialogState extends State<ChangePasswordDialog> {
|
|||
|
||||
// send request
|
||||
doNetworkRequest(scaffMgr,
|
||||
req: () => postWithCreadentials(
|
||||
path: 'changePassword',
|
||||
target: user.server,
|
||||
body: {'accountKey': password},
|
||||
credentials: user),
|
||||
onOK: (_) async {
|
||||
// update local user struct
|
||||
final updatedUser = User(
|
||||
username: user.username,
|
||||
password: password,
|
||||
server: user.server);
|
||||
await updatedUser.toDisk();
|
||||
},
|
||||
after: () {
|
||||
// close popup
|
||||
nav.pop();
|
||||
});
|
||||
req: () => postWithCreadentials(
|
||||
path: 'changePassword',
|
||||
target: user.server,
|
||||
body: {'accountKey': password},
|
||||
credentials: user),
|
||||
onOK: (_) async {
|
||||
// update local user struct
|
||||
final updatedUser = User(
|
||||
username: user.username,
|
||||
password: password,
|
||||
server: user.server);
|
||||
await updatedUser.toDisk();
|
||||
},
|
||||
after: () {
|
||||
// close popup
|
||||
nav.pop();
|
||||
});
|
||||
},
|
||||
child: const Text('Change password'),
|
||||
child: Text(AppLocalizations.of(context)!.changeThemeTitle),
|
||||
)
|
||||
],
|
||||
);
|
||||
|
|
|
@ -6,6 +6,7 @@ import 'package:outbag_app/backend/user.dart';
|
|||
import 'package:outbag_app/screens/settings/dialogs/password.dart';
|
||||
import 'package:outbag_app/tools/fetch_wrapper.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
class SettingsPage extends StatefulWidget {
|
||||
const SettingsPage({super.key});
|
||||
|
@ -26,7 +27,7 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Settings'),
|
||||
title: Text(AppLocalizations.of(context)!.settings),
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: Center(
|
||||
|
@ -44,51 +45,58 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||
child: Text(user.humanReadable,
|
||||
style: textTheme.titleLarge)),
|
||||
ListTile(
|
||||
title: const Text('Room count limit:'),
|
||||
subtitle: const Text(
|
||||
'How many rooms you are allowed to own'),
|
||||
title: Text(
|
||||
AppLocalizations.of(context)!.limitRoomCount),
|
||||
subtitle: Text(AppLocalizations.of(context)!
|
||||
.limitRoomCountSubtitle),
|
||||
trailing: Text('${meta?.maxRoomCount ?? ""}'),
|
||||
),
|
||||
ListTile(
|
||||
title: const Text('Room size limit:'),
|
||||
subtitle: const Text(
|
||||
'How many items/products/categories each room may contain'),
|
||||
title: Text(
|
||||
AppLocalizations.of(context)!.limitRoomSize),
|
||||
subtitle: Text(AppLocalizations.of(context)!
|
||||
.limitRoomSizeSubtitle),
|
||||
trailing: Text('${meta?.maxRoomSize ?? ""}'),
|
||||
),
|
||||
ListTile(
|
||||
title: const Text('Room member limit:'),
|
||||
subtitle: const Text(
|
||||
'How many members each of your rooms may have'),
|
||||
title: Text(AppLocalizations.of(context)!
|
||||
.limitRoomMemberCount),
|
||||
subtitle: Text(AppLocalizations.of(context)!
|
||||
.limitRoomMemberCountSubtitle),
|
||||
trailing:
|
||||
Text('${meta?.maxRoomMemberCount ?? ""}')),
|
||||
ListTile(
|
||||
title: const Text('Discoverable'),
|
||||
subtitle: const Text(
|
||||
'Determines if your account can be discovered by users from other servers'),
|
||||
title: Text(AppLocalizations.of(context)!
|
||||
.userDiscoverable),
|
||||
subtitle: Text(AppLocalizations.of(context)!
|
||||
.userDiscoverableSubtitle),
|
||||
trailing: Checkbox(
|
||||
tristate: true,
|
||||
value: meta?.discoverable,
|
||||
onChanged: (_) {},
|
||||
onChanged: (_) {
|
||||
// TODO: implement changeVisibility
|
||||
},
|
||||
))
|
||||
],
|
||||
)))),
|
||||
|
||||
// change theme button
|
||||
ListTile(
|
||||
title: const Text('Change Theme'),
|
||||
subtitle: const Text(
|
||||
'You can change between a light theme, a dark theme and automatic theme selection'),
|
||||
title: Text(AppLocalizations.of(context)!.changeThemeTitle),
|
||||
subtitle: Text(AppLocalizations.of(context)!.changeThemeSubtitle),
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
onTap: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('Change Theme'),
|
||||
title: Text(
|
||||
AppLocalizations.of(context)!.changeThemeTitle),
|
||||
content: SingleChildScrollView(
|
||||
child: Column(children: [
|
||||
const Padding(
|
||||
padding: EdgeInsets.all(8),
|
||||
child: Text('Choose your preferred theme'),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Text(AppLocalizations.of(context)!
|
||||
.changeThemeSubtitle),
|
||||
),
|
||||
SegmentedButton<AppTheme>(
|
||||
selected: {context.watch<AppTheme>()},
|
||||
|
@ -100,7 +108,7 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||
return ButtonSegment<AppTheme>(
|
||||
value: item,
|
||||
icon: Icon(item.icon),
|
||||
label: Text(item.name));
|
||||
label: Text(item.name(context)));
|
||||
}).toList(),
|
||||
onSelectionChanged: (item) async {
|
||||
try {
|
||||
|
@ -111,9 +119,9 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||
])),
|
||||
actions: [
|
||||
FilledButton(
|
||||
child: const Text('Close'),
|
||||
child: Text(AppLocalizations.of(context)!.close),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
context.pop();
|
||||
},
|
||||
)
|
||||
],
|
||||
|
@ -123,8 +131,9 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||
|
||||
// change password button
|
||||
ListTile(
|
||||
title: const Text('Change password'),
|
||||
subtitle: const Text('Choose a new password for your account'),
|
||||
title: Text(AppLocalizations.of(context)!.changePasswordTitle),
|
||||
subtitle:
|
||||
Text(AppLocalizations.of(context)!.changePasswordSubtitle),
|
||||
onTap: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
|
@ -135,8 +144,8 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||
|
||||
// export account to json
|
||||
ListTile(
|
||||
title: const Text('Export account'),
|
||||
subtitle: const Text('Export account data'),
|
||||
title: Text(AppLocalizations.of(context)!.exportAccountTitle),
|
||||
subtitle: Text(AppLocalizations.of(context)!.exportAccountSubtitle),
|
||||
onTap: () {
|
||||
// TODO: show confirm dialog
|
||||
// NOTE: json dump the localstore
|
||||
|
@ -148,8 +157,8 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||
|
||||
// delete account button
|
||||
ListTile(
|
||||
title: const Text('Delete account'),
|
||||
subtitle: const Text('Delete your account from your homeserver'),
|
||||
title: Text(AppLocalizations.of(context)!.deleteAccountTitle),
|
||||
subtitle: Text(AppLocalizations.of(context)!.deleteAccountSubtitle),
|
||||
onTap: () {
|
||||
// show confirm dialog
|
||||
// NOTE: same as logout
|
||||
|
@ -158,16 +167,17 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||
showDialog(
|
||||
context: context,
|
||||
builder: (ctx) => AlertDialog(
|
||||
title: const Text('Delete account'),
|
||||
content: const Text(
|
||||
'Do you really want to delete your account?'),
|
||||
title: Text(
|
||||
AppLocalizations.of(context)!.deleteAccountTitle),
|
||||
content: Text(
|
||||
AppLocalizations.of(context)!.deleteAccountConfirm),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
// close popup
|
||||
Navigator.of(ctx).pop();
|
||||
},
|
||||
child: const Text('Cancel'),
|
||||
child: Text(AppLocalizations.of(context)!.cancel),
|
||||
),
|
||||
FilledButton(
|
||||
onPressed: () async {
|
||||
|
@ -198,7 +208,7 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||
nav.pop();
|
||||
});
|
||||
},
|
||||
child: const Text('Delete Account'),
|
||||
child: Text(AppLocalizations.of(context)!.yes),
|
||||
)
|
||||
],
|
||||
));
|
||||
|
@ -210,22 +220,23 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: FilledButton.tonal(
|
||||
child: const Text('Log out'),
|
||||
child: Text(AppLocalizations.of(context)!.logOut),
|
||||
onPressed: () {
|
||||
// show confirm dialog
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (ctx) => AlertDialog(
|
||||
title: const Text('Log out'),
|
||||
content:
|
||||
const Text('Do you really want to log out?'),
|
||||
title: Text(AppLocalizations.of(context)!.logOut),
|
||||
content: Text(
|
||||
AppLocalizations.of(context)!.logOutConfirm),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
// close popup
|
||||
Navigator.of(ctx).pop();
|
||||
},
|
||||
child: const Text('Cancel'),
|
||||
child:
|
||||
Text(AppLocalizations.of(context)!.cancel),
|
||||
),
|
||||
FilledButton(
|
||||
onPressed: () async {
|
||||
|
@ -242,7 +253,7 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||
// go back home
|
||||
router.pushReplacementNamed('home');
|
||||
},
|
||||
child: const Text('Log out'),
|
||||
child: Text(AppLocalizations.of(context)!.yes),
|
||||
)
|
||||
],
|
||||
));
|
||||
|
|
|
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
|||
import 'package:go_router/go_router.dart';
|
||||
import 'package:outbag_app/tools/assets.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'dart:math';
|
||||
|
||||
class WelcomePage extends StatefulWidget {
|
||||
|
@ -33,17 +34,17 @@ class _WelcomePageState extends State<WelcomePage> {
|
|||
.textTheme
|
||||
.apply(displayColor: Theme.of(context).colorScheme.onSurface);
|
||||
|
||||
String fabText = "Next";
|
||||
String fabText = AppLocalizations.of(context)!.next;
|
||||
if (_currentPage == 0) {
|
||||
fabText = "Let's go";
|
||||
fabText = AppLocalizations.of(context)!.letsGo;
|
||||
} else if (_currentPage == 4 - 1) {
|
||||
fabText = "Sign up";
|
||||
fabText = AppLocalizations.of(context)!.signUp;
|
||||
}
|
||||
String fabTooltip = "Continue Tour";
|
||||
String fabTooltip = AppLocalizations.of(context)!.continueTour;
|
||||
if (_currentPage == 0) {
|
||||
fabTooltip = "Take Tour";
|
||||
fabTooltip = AppLocalizations.of(context)!.takeTour;
|
||||
} else if (_currentPage == 4 - 1) {
|
||||
fabTooltip = "Create an account";
|
||||
fabTooltip = AppLocalizations.of(context)!.createNewAccount;
|
||||
}
|
||||
|
||||
double width = MediaQuery.of(context).size.width;
|
||||
|
@ -66,10 +67,13 @@ class _WelcomePageState extends State<WelcomePage> {
|
|||
width: smallest * 0.5,
|
||||
height: smallest * 0.5),
|
||||
Text(
|
||||
'Welcome to Outbag',
|
||||
AppLocalizations.of(context)!.welcomeTitle,
|
||||
style: textTheme.displaySmall,
|
||||
),
|
||||
Text('Shopping lists made easy', style: textTheme.bodyMedium)
|
||||
Text(
|
||||
AppLocalizations.of(context)!.welcomeSubtitle,
|
||||
style: textTheme.bodyMedium
|
||||
)
|
||||
],
|
||||
),
|
||||
Column(
|
||||
|
@ -81,10 +85,11 @@ class _WelcomePageState extends State<WelcomePage> {
|
|||
width: smallest * 0.5,
|
||||
height: smallest * 0.5),
|
||||
Text(
|
||||
'Open. Decentralized',
|
||||
AppLocalizations.of(context)!.page2Title,
|
||||
style: textTheme.displaySmall,
|
||||
),
|
||||
Text('One account, multiple servers',
|
||||
Text(
|
||||
AppLocalizations.of(context)!.page2Subtitle,
|
||||
style: textTheme.bodyMedium)
|
||||
],
|
||||
),
|
||||
|
@ -97,10 +102,11 @@ class _WelcomePageState extends State<WelcomePage> {
|
|||
width: smallest * 0.5,
|
||||
height: smallest * 0.5),
|
||||
Text(
|
||||
'Made to share',
|
||||
AppLocalizations.of(context)!.page3Title,
|
||||
style: textTheme.displaySmall,
|
||||
),
|
||||
Text('Collaborate on your shopping list in real time',
|
||||
Text(
|
||||
AppLocalizations.of(context)!.page3Subtitle,
|
||||
style: textTheme.bodyMedium)
|
||||
],
|
||||
),
|
||||
|
@ -113,10 +119,11 @@ class _WelcomePageState extends State<WelcomePage> {
|
|||
width: smallest * 0.5,
|
||||
height: smallest * 0.5),
|
||||
Text(
|
||||
'Pocket-sized',
|
||||
AppLocalizations.of(context)!.page4Title,
|
||||
style: textTheme.displaySmall,
|
||||
),
|
||||
Text('Always have your shopping list with you',
|
||||
Text(
|
||||
AppLocalizations.of(context)!.page4Subtitle,
|
||||
style: textTheme.bodyMedium)
|
||||
],
|
||||
),
|
||||
|
@ -126,7 +133,8 @@ class _WelcomePageState extends State<WelcomePage> {
|
|||
onPressed: () {
|
||||
context.goNamed('signin');
|
||||
},
|
||||
child: const Text('I already have an account'),
|
||||
child: Text(AppLocalizations.of(context)!.userHasAnAccount
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
|
|
|
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
|||
import 'package:outbag_app/backend/errors.dart';
|
||||
import 'package:outbag_app/backend/request.dart';
|
||||
import 'package:outbag_app/tools/snackbar.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
void doNetworkRequest(ScaffoldMessengerState? sm,
|
||||
{required Future<Response> Function() req,
|
||||
|
@ -11,6 +12,8 @@ void doNetworkRequest(ScaffoldMessengerState? sm,
|
|||
Function()? onAnyErr,
|
||||
Function()? after,
|
||||
bool Function(Map<String, dynamic>)? onServerErr}) async {
|
||||
AppLocalizations? trans = (sm!=null)?AppLocalizations.of(sm.context):null;
|
||||
|
||||
Response res;
|
||||
try {
|
||||
res = await req();
|
||||
|
@ -23,7 +26,7 @@ void doNetworkRequest(ScaffoldMessengerState? sm,
|
|||
}
|
||||
|
||||
if (showBar && sm != null) {
|
||||
showSimpleSnackbar(sm, text: 'Network Error', action: 'Dismiss');
|
||||
showSimpleSnackbar(sm, text: trans!.errorNetwork, action: trans.dismiss);
|
||||
}
|
||||
if (onAnyErr != null) {
|
||||
onAnyErr();
|
||||
|
@ -47,7 +50,7 @@ void doNetworkRequest(ScaffoldMessengerState? sm,
|
|||
showBar = onServerErr(res.body);
|
||||
}
|
||||
if (showBar && sm != null) {
|
||||
showSimpleSnackbar(sm, text: errorAsString(res.body), action: 'OK');
|
||||
showSimpleSnackbar(sm, text: errorAsString(res.body, trans!), action: trans.ok);
|
||||
}
|
||||
if (onAnyErr != null) {
|
||||
onAnyErr();
|
||||
|
@ -61,7 +64,7 @@ void doNetworkRequest(ScaffoldMessengerState? sm,
|
|||
}
|
||||
|
||||
if (showBar && sm != null) {
|
||||
showSimpleSnackbar(sm, text: 'Unknown Error', action: 'OK');
|
||||
showSimpleSnackbar(sm, text: trans!.errorUnknown, action: trans.ok);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
13
pubspec.lock
13
pubspec.lock
|
@ -134,6 +134,11 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
flutter_localizations:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_svg:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -184,6 +189,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.15"
|
||||
intl:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: intl
|
||||
sha256: "910f85bce16fb5c6f614e117efa303e85a1731bb0081edf3604a2ae6e9a3cc91"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.17.0"
|
||||
js:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
@ -36,6 +36,9 @@ dependencies:
|
|||
crypto: ^3.0.2
|
||||
provider: ^6.0.5
|
||||
go_router: ^6.5.0
|
||||
flutter_localizations:
|
||||
sdk: flutter
|
||||
intl: any
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
@ -54,7 +57,8 @@ dev_dependencies:
|
|||
|
||||
# The following section is specific to Flutter packages.
|
||||
flutter:
|
||||
|
||||
# needed for l10n localizations
|
||||
generate: true
|
||||
# The following line ensures that the Material Icons font is
|
||||
# included with your application, so that you can use the icons in
|
||||
# the material Icons class.
|
||||
|
|
Loading…
Reference in a new issue