Formatted files using dart format

This commit is contained in:
Jakob Meier 2023-12-22 20:14:36 +01:00
parent 1975d66419
commit b320d51fa1
No known key found for this signature in database
GPG key ID: 66BDC7E6A01A6152
28 changed files with 2466 additions and 2422 deletions

View file

@ -107,19 +107,19 @@ class RoomPermission {
switch (permission) { switch (permission) {
case 1: case 1:
return trans!.roomPermissionAddItems; return trans!.roomPermissionAddItems;
case 2: case 2:
return trans!.roomPermissionRemoveItems; return trans!.roomPermissionRemoveItems;
case 4: case 4:
return trans!.roomPermissionEditContent; return trans!.roomPermissionEditContent;
case 8: case 8:
return trans!.roomPermissionChangeMeta; return trans!.roomPermissionChangeMeta;
case 16: case 16:
return trans!.roomPermissionManageOTA; return trans!.roomPermissionManageOTA;
case 32: case 32:
return trans!.roomPermissionManageAdmins; return trans!.roomPermissionManageAdmins;
case 64: case 64:
return trans!.roomPermissionManageMembers; return trans!.roomPermissionManageMembers;
} }
return trans!.roomPermissionUnknown; return trans!.roomPermissionUnknown;
@ -130,19 +130,19 @@ class RoomPermission {
switch (permission) { switch (permission) {
case 1: case 1:
return trans!.roomPermissionAddItemsSubtitle; return trans!.roomPermissionAddItemsSubtitle;
case 2: case 2:
return trans!.roomPermissionRemoveItemsSubtitle; return trans!.roomPermissionRemoveItemsSubtitle;
case 4: case 4:
return trans!.roomPermissionEditContentSubtitle; return trans!.roomPermissionEditContentSubtitle;
case 8: case 8:
return trans!.roomPermissionChangeMetaSubtitle; return trans!.roomPermissionChangeMetaSubtitle;
case 16: case 16:
return trans!.roomPermissionManageOTASubtitle; return trans!.roomPermissionManageOTASubtitle;
case 32: case 32:
return trans!.roomPermissionManageAdminsSubtitle; return trans!.roomPermissionManageAdminsSubtitle;
case 64: case 64:
return trans!.roomPermissionManageMembersSubtitle; return trans!.roomPermissionManageMembersSubtitle;
} }
return trans!.roomPermissionUnknownSubtitle; return trans!.roomPermissionUnknownSubtitle;

View file

@ -13,19 +13,19 @@ class Response {
} }
Future<Response> usePostApi( Future<Response> usePostApi(
{required OutbagServer target, {required OutbagServer target,
String path = '', String path = '',
required Map<String, String> headers, required Map<String, String> headers,
required Map<String, dynamic> body}) async { required Map<String, dynamic> body}) async {
final resp = await http.post(Uri.parse('${target.base}api/$path'), final resp = await http.post(Uri.parse('${target.base}api/$path'),
headers: headers, body: jsonEncode({'data': body})); headers: headers, body: jsonEncode({'data': body}));
final json = jsonDecode(resp.body); final json = jsonDecode(resp.body);
return Response( return Response(
body: json, res: resp.statusCode == 200 ? Result.ok : Result.err); body: json, res: resp.statusCode == 200 ? Result.ok : Result.err);
} }
Future<Response> postWithCreadentials( Future<Response> postWithCreadentials(
{required OutbagServer target, {required OutbagServer target,
String path = '', String path = '',
required Map<String, dynamic> body, required Map<String, dynamic> body,
required User credentials}) async { required User credentials}) async {
@ -33,14 +33,14 @@ Future<Response> postWithCreadentials(
"Content-Type": "application/json", "Content-Type": "application/json",
'Connection': 'keep-alive', 'Connection': 'keep-alive',
'Authorization': 'Authorization':
'Digest name=${credentials.username} server=${target.tag} accountKey=${credentials.password}' 'Digest name=${credentials.username} server=${target.tag} accountKey=${credentials.password}'
}; };
return await usePostApi( return await usePostApi(
target: target, path: path, headers: headers, body: body); target: target, path: path, headers: headers, body: body);
} }
Future<Response> postWithToken( Future<Response> postWithToken(
{required OutbagServer target, {required OutbagServer target,
String path = '', String path = '',
required Map<String, dynamic> body, required Map<String, dynamic> body,
required String token}) async { required String token}) async {
@ -49,16 +49,16 @@ Future<Response> postWithToken(
'Authorization': 'Bearer $token' 'Authorization': 'Bearer $token'
}; };
return await usePostApi( return await usePostApi(
target: target, path: path, headers: headers, body: body); target: target, path: path, headers: headers, body: body);
} }
Future<Response> postUnauthorized( Future<Response> postUnauthorized(
{required OutbagServer target, {required OutbagServer target,
String path = '', String path = '',
required Map<String, dynamic> body}) async { required Map<String, dynamic> body}) async {
Map<String, String> headers = { Map<String, String> headers = {
"Content-Type": "application/json", "Content-Type": "application/json",
}; };
return await usePostApi( return await usePostApi(
target: target, path: path, headers: headers, body: body); target: target, path: path, headers: headers, body: body);
} }

View file

@ -61,11 +61,10 @@ class OutbagServer {
final SharedPreferences prefs = await SharedPreferences.getInstance(); final SharedPreferences prefs = await SharedPreferences.getInstance();
return OutbagServer( return OutbagServer(
path: prefs.getString('server-path')!, path: prefs.getString('server-path')!,
port: prefs.getInt('server-port')!, port: prefs.getInt('server-port')!,
tag: prefs.getString('server-tag')!, tag: prefs.getString('server-tag')!,
host: prefs.getString('server-host')! host: prefs.getString('server-host')!);
);
} }
static Future<void> removeDisk() async { static Future<void> removeDisk() async {

View file

@ -322,11 +322,17 @@ class RoomMember {
final bool isInvitePending; final bool isInvitePending;
const RoomMember( const RoomMember(
{required this.id, required this.serverTag, required this.isAdmin, this.isInvitePending=false}); {required this.id,
required this.serverTag,
required this.isAdmin,
this.isInvitePending = false});
factory RoomMember.fromJSON(dynamic json) { factory RoomMember.fromJSON(dynamic json) {
return RoomMember( return RoomMember(
id: json['name'], serverTag: json['server'], isAdmin: json['admin'], isInvitePending: json['confirmed']); id: json['name'],
serverTag: json['server'],
isAdmin: json['admin'],
isInvitePending: json['confirmed']);
} }
String get humanReadableName { String get humanReadableName {

View file

@ -9,7 +9,8 @@ class CategoryChip extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ActionChip( return ActionChip(
avatar: Icon(Icons.square_rounded, color: category?.color ?? RoomCategory.other(context).color), avatar: Icon(Icons.square_rounded,
color: category?.color ?? RoomCategory.other(context).color),
label: Text(category?.name ?? RoomCategory.other(context).name), label: Text(category?.name ?? RoomCategory.other(context).name),
); );
} }

View file

@ -12,7 +12,7 @@ class CategoryPicker extends StatelessWidget {
final String? label; final String? label;
const CategoryPicker( const CategoryPicker(
{super.key, {super.key,
required this.categories, required this.categories,
this.selected, this.selected,
this.onSelect, this.onSelect,
@ -23,36 +23,34 @@ class CategoryPicker extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Padding( return Padding(
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
child: DropdownButtonFormField<int?>( child: DropdownButtonFormField<int?>(
hint: hint==null?null:Text(hint!), hint: hint == null ? null : Text(hint!),
decoration: InputDecoration( decoration: InputDecoration(
label: label==null?null:Text(label!), label: label == null ? null : Text(label!),
border: const OutlineInputBorder(), border: const OutlineInputBorder(),
prefixIcon: const Icon(Icons.category) prefixIcon: const Icon(Icons.category)),
), value: selected,
value: selected, items: [...categories, RoomCategory.other(context)]
items: [ .map((category) => DropdownMenuItem<int?>(
...categories, value: category.id,
RoomCategory.other(context) child: Row(
].map((category)=>DropdownMenuItem<int?>( mainAxisAlignment: MainAxisAlignment.spaceBetween,
value: category.id, crossAxisAlignment: CrossAxisAlignment.center,
child: Row( children: [
mainAxisAlignment: MainAxisAlignment.spaceBetween, Text(category.name),
crossAxisAlignment: CrossAxisAlignment.center, Icon(Icons.square_rounded,
children: [ color: category.color, size: 32)
Text(category.name), ]),
Icon(Icons.square_rounded, ))
color:category.color, .toList(),
size: 32) onChanged: enabled
] ? (cid) {
), if (onSelect != null) {
)).toList(), onSelect!(cid);
onChanged: enabled?(cid) { }
if (onSelect != null) { }
onSelect!(cid); : null,
} ));
}:null,
));
} }
} }

View file

@ -6,14 +6,10 @@ class LabeledDivider extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Row( return Row(children: [
children: [ const Expanded(child: Divider()),
const Expanded(child: Divider()), Padding(padding: const EdgeInsets.all(8), child: Text(label)),
Padding( const Expanded(child: Divider()),
padding: const EdgeInsets.all(8), ]);
child: Text(label)),
const Expanded(child: Divider()),
]
);
} }
} }

View file

@ -14,7 +14,7 @@ class ProductPicker extends StatelessWidget {
final String? help; final String? help;
const ProductPicker( const ProductPicker(
{super.key, {super.key,
required this.products, required this.products,
this.selected, this.selected,
this.onSelect, this.onSelect,
@ -26,32 +26,32 @@ class ProductPicker extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Padding( return Padding(
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
child: DropdownButtonFormField<int?>( child: DropdownButtonFormField<int?>(
hint: hint == null ? null : Text(hint!), hint: hint == null ? null : Text(hint!),
decoration: InputDecoration( decoration: InputDecoration(
label: label == null ? null : Text(label!), label: label == null ? null : Text(label!),
border: const OutlineInputBorder(), border: const OutlineInputBorder(),
prefixIcon: const Icon(Icons.inventory_2), prefixIcon: const Icon(Icons.inventory_2),
helperText: help), helperText: help),
value: selected, value: selected,
items: [ items: [
// "no product" entry // "no product" entry
DropdownMenuItem<int?>( DropdownMenuItem<int?>(
value: null, value: null,
child: Text(AppLocalizations.of(context)!.productNameNone), child: Text(AppLocalizations.of(context)!.productNameNone),
), ),
// other products // other products
...products.map((product) => DropdownMenuItem<int?>( ...products.map((product) => DropdownMenuItem<int?>(
value: product.id, child: Text(product.name))) value: product.id, child: Text(product.name)))
], ],
onChanged: enabled onChanged: enabled
? (pid) { ? (pid) {
if (onSelect != null) { if (onSelect != null) {
onSelect!(pid); onSelect!(pid);
} }
} }
: null, : null,
)); ));
} }
} }

View file

@ -39,12 +39,7 @@ class RoomIconPicker extends StatelessWidget {
if (onSelect != null) { if (onSelect != null) {
onSelect!(icon); onSelect!(icon);
} }
} }));
) }).toList())));
);
}).toList()
)
)
);
} }
} }

View file

@ -25,15 +25,16 @@ import 'package:outbag_app/screens/settings/main.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
void main() { void main() {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
runApp(const OutbagApp()); runApp(const OutbagApp());
} }
final GlobalKey<NavigatorState> _rootNavigatorKey = final GlobalKey<NavigatorState> _rootNavigatorKey =
GlobalKey<NavigatorState>(debugLabel: 'root'); GlobalKey<NavigatorState>(debugLabel: 'root');
final GlobalKey<NavigatorState> _userShellNavigatorKey = final GlobalKey<NavigatorState> _userShellNavigatorKey =
GlobalKey<NavigatorState>(debugLabel: 'user'); GlobalKey<NavigatorState>(debugLabel: 'user');
class OutbagApp extends StatefulWidget { class OutbagApp extends StatefulWidget {
const OutbagApp({super.key}); const OutbagApp({super.key});
@ -55,7 +56,7 @@ class _OutbagAppState extends State {
try { try {
final theme = await AppTheme.fromDisk(); final theme = await AppTheme.fromDisk();
setState(() { setState(() {
this.theme = theme; this.theme = theme;
}); });
} catch (_) {} } catch (_) {}
} }
@ -65,14 +66,14 @@ class _OutbagAppState extends State {
try { try {
final user = await User.fromDisk(); final user = await User.fromDisk();
setState(() { setState(() {
this.user = user; this.user = user;
}); });
} catch (_) { } catch (_) {
// user unavailable // user unavailable
// invalid credentials // invalid credentials
// log out // log out
setState(() { setState(() {
user = null; user = null;
}); });
} }
} }
@ -82,8 +83,8 @@ class _OutbagAppState extends State {
super.initState(); super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) async { WidgetsBinding.instance.addPostFrameCallback((_) async {
loadTheme(); loadTheme();
loadUser(); loadUser();
}); });
} }
@ -93,31 +94,31 @@ class _OutbagAppState extends State {
// with existing details // with existing details
// NOTE: also functions as a way to verify ther data // NOTE: also functions as a way to verify ther data
await doNetworkRequest(null, await doNetworkRequest(null,
req: () => postWithCreadentials( req: () => postWithCreadentials(
target: user.server, target: user.server,
path: 'getMyAccount', path: 'getMyAccount',
credentials: user, credentials: user,
body: {}), body: {}),
onOK: (body) async { onOK: (body) async {
final i = AccountMeta.fromJSON(body['data']); final i = AccountMeta.fromJSON(body['data']);
info = i; info = i;
}, },
onServerErr: (_) { onServerErr: (_) {
info = null; info = null;
setState(() { setState(() {
this.user = null; this.user = null;
});
return true;
},
onNetworkErr: () {
info = null;
// user is currently offline
// approve login,
// until user goes back offline
// NOTE TODO: check user data once online
return true;
}); });
return true;
},
onNetworkErr: () {
info = null;
// user is currently offline
// approve login,
// until user goes back offline
// NOTE TODO: check user data once online
return true;
});
return info; return info;
} }
@ -125,195 +126,213 @@ class _OutbagAppState extends State {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MultiProvider( return MultiProvider(
providers: [ providers: [
Provider<AppTheme>.value(value: theme), Provider<AppTheme>.value(value: theme),
],
child: MaterialApp.router(
title: "Outbag",
localizationsDelegates: const [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
AppLocalizations.delegate
], ],
supportedLocales: AppLocalizations.supportedLocales, child: MaterialApp.router(
themeMode: theme.mode, title: "Outbag",
theme: ThemeData(useMaterial3: true, brightness: Brightness.light), localizationsDelegates: const [
darkTheme: ThemeData(useMaterial3: true, brightness: Brightness.dark), GlobalMaterialLocalizations.delegate,
routerConfig: GoRouter( GlobalWidgetsLocalizations.delegate,
navigatorKey: _rootNavigatorKey, GlobalCupertinoLocalizations.delegate,
initialLocation: '/', AppLocalizations.delegate
redirect: (context, state) async { ],
if (user == null) { supportedLocales: AppLocalizations.supportedLocales,
// prelogin themeMode: theme.mode,
if (!state.subloc.startsWith('/welcome')) { theme: ThemeData(useMaterial3: true, brightness: Brightness.light),
// prevent unauthorized user from accessing home darkTheme: ThemeData(useMaterial3: true, brightness: Brightness.dark),
return '/welcome'; routerConfig: GoRouter(
} navigatorKey: _rootNavigatorKey,
} else { initialLocation: '/',
// post login redirect: (context, state) async {
if (state.subloc.startsWith('/welcome')) { if (user == null) {
// prevent authorized user from accessing /welcome // prelogin
return '/'; if (!state.subloc.startsWith('/welcome')) {
} // prevent unauthorized user from accessing home
} return '/welcome';
}
} else {
// post login
if (state.subloc.startsWith('/welcome')) {
// prevent authorized user from accessing /welcome
return '/';
}
}
return null; return null;
}, },
routes: <RouteBase>[
// unauthorized routes
GoRoute(
name: 'welcome',
path: '/welcome',
builder: (context, state) => const WelcomePage(),
routes: <RouteBase>[ routes: <RouteBase>[
// unauthorized routes
GoRoute( GoRoute(
name: 'signin', name: 'welcome',
path: 'signin', path: '/welcome',
builder: (context, state) => builder: (context, state) => const WelcomePage(),
AuthPage(mode: Mode.signin, refresh: loadUser), routes: <RouteBase>[
), GoRoute(
GoRoute( name: 'signin',
name: 'signup', path: 'signin',
path: 'signup', builder: (context, state) =>
builder: (context, state) => AuthPage(mode: Mode.signin, refresh: loadUser),
AuthPage(mode: Mode.signup, refresh: loadUser), ),
), GoRoute(
GoRoute( name: 'signup',
name: 'signup-ota', path: 'signup',
path: 'signup-ota', builder: (context, state) =>
builder: (context, state) => AuthPage(mode: Mode.signup, refresh: loadUser),
AuthPage(mode: Mode.signupOTA, refresh: loadUser), ),
), GoRoute(
]), name: 'signup-ota',
path: 'signup-ota',
// authorized routes builder: (context, state) =>
ShellRoute( AuthPage(mode: Mode.signupOTA, refresh: loadUser),
navigatorKey: _userShellNavigatorKey, ),
builder: (context, state, child) => Provider.value(
value: user!,
child: FutureProvider(
initialData: null,
child: child,
create: (context)=>fetchInfo(context.read<User>()),
)),
routes: <RouteBase>[
GoRoute(
path: '/',
name: 'home',
builder: (context, state) => const HomePage(),
routes: [
GoRoute(
name: 'settings',
path: 'settings',
builder: (context, state) =>
SettingsPage(
refreshTheme: loadTheme,
refreshUser: loadUser
)),
GoRoute(
path: 'join-room',
name: 'add-room',
builder: (context, state) =>
const JoinRoomPage(),
routes: <RouteBase>[
GoRoute(
path: 'new',
name: 'new-room',
builder: (context, state) => NewRoomPage()),
]), ]),
GoRoute(
name: 'room',
path: 'r/:server/:id',
builder: (context, state) => RoomPage(
state.params['server'] ?? '',
state.params['id'] ?? ''),
routes: <RouteBase>[
GoRoute(
name: 'edit-room',
path: 'edit',
builder: (context, state) => NewRoomPage(
server: state.params['server'] ?? '',
tag: state.params['id'] ?? '')),
GoRoute(
name: 'room-members',
path: 'members',
builder: (context, state) =>
ManageRoomMembersPage(
state.params['server'] ?? '',
state.params['id'] ?? '')),
GoRoute(
name: 'room-permissions',
path: 'roles',
builder: (context, state) =>
EditRoomPermissionSetPage(
state.params['server'] ?? '',
state.params['id'] ?? '')),
GoRoute( // authorized routes
name: 'new-category', ShellRoute(
path: 'new-category', navigatorKey: _userShellNavigatorKey,
builder: (context, state)=>EditCategoryPage( builder: (context, state, child) => Provider.value(
state.params['server'] ?? '', value: user!,
state.params['id'] ?? '')), child: FutureProvider(
GoRoute( initialData: null,
name: 'edit-category', child: child,
path: 'edit-category/:category', create: (context) => fetchInfo(context.read<User>()),
builder: (context, state)=>EditCategoryPage( )),
state.params['server'] ?? '', routes: <RouteBase>[
state.params['id'] ?? '', GoRoute(
id: int.tryParse(state.params['category'] ?? ''))), path: '/',
name: 'home',
GoRoute( builder: (context, state) => const HomePage(),
name: 'new-product',
path: 'new-product',
builder: (context, state)=>EditProductPage(
server: state.params['server'] ?? '',
room: state.params['id'] ?? '',)),
GoRoute(
name: 'view-product',
path: 'p/:product',
builder: (context, state)=>ViewProductPage(
server: state.params['server'] ?? '',
room: state.params['id'] ?? '',
product: int.tryParse(state.params['product'] ?? '') ?? 0),
routes: [ routes: [
GoRoute( GoRoute(
name: 'edit-product', name: 'settings',
path: 'edit', path: 'settings',
builder: (context, state)=>EditProductPage( builder: (context, state) => SettingsPage(
server: state.params['server'] ?? '', refreshTheme: loadTheme,
room: state.params['id'] ?? '', refreshUser: loadUser)),
product: int.tryParse(state.params['product'] ?? ''))), GoRoute(
] path: 'join-room',
), name: 'add-room',
builder: (context, state) =>
const JoinRoomPage(),
routes: <RouteBase>[
GoRoute(
path: 'new',
name: 'new-room',
builder: (context, state) =>
NewRoomPage()),
]),
GoRoute(
name: 'room',
path: 'r/:server/:id',
builder: (context, state) => RoomPage(
state.params['server'] ?? '',
state.params['id'] ?? ''),
routes: <RouteBase>[
GoRoute(
name: 'edit-room',
path: 'edit',
builder: (context, state) => NewRoomPage(
server: state.params['server'] ?? '',
tag: state.params['id'] ?? '')),
GoRoute(
name: 'room-members',
path: 'members',
builder: (context, state) =>
ManageRoomMembersPage(
state.params['server'] ?? '',
state.params['id'] ?? '')),
GoRoute(
name: 'room-permissions',
path: 'roles',
builder: (context, state) =>
EditRoomPermissionSetPage(
state.params['server'] ?? '',
state.params['id'] ?? '')),
GoRoute(
name: 'new-category',
path: 'new-category',
builder: (context, state) =>
EditCategoryPage(
state.params['server'] ?? '',
state.params['id'] ?? '')),
GoRoute(
name: 'edit-category',
path: 'edit-category/:category',
builder: (context, state) =>
EditCategoryPage(
state.params['server'] ?? '',
state.params['id'] ?? '',
id: int.tryParse(
state.params['category'] ??
''))),
GoRoute(
name: 'new-product',
path: 'new-product',
builder: (context, state) =>
EditProductPage(
server:
state.params['server'] ?? '',
room: state.params['id'] ?? '',
)),
GoRoute(
name: 'view-product',
path: 'p/:product',
builder: (context, state) =>
ViewProductPage(
server:
state.params['server'] ?? '',
room: state.params['id'] ?? '',
product: int.tryParse(
state.params['product'] ??
'') ??
0),
routes: [
GoRoute(
name: 'edit-product',
path: 'edit',
builder: (context, state) =>
EditProductPage(
server: state
.params['server'] ??
'',
room: state.params['id'] ??
'',
product: int.tryParse(
state.params[
'product'] ??
''))),
]),
GoRoute(
name: 'new-item',
path: 'new-item',
builder: (context, state) => EditItemPage(
server:
state.params['server'] ?? '',
room: state.params['id'] ?? '',
)),
GoRoute(
name: 'edit-item',
path: 'i/:item',
builder: (context, state) => EditItemPage(
server: state.params['server'] ?? '',
room: state.params['id'] ?? '',
item: int.tryParse(
state.params['item'] ?? '') ??
0),
)
])
]),
]),
GoRoute( // routes that can be accessed
name: 'new-item', // with and without an account
path: 'new-item', // i.e the about screen
builder: (context, state)=>EditItemPage( GoRoute(
server: state.params['server'] ?? '', path: '/about',
room: state.params['id'] ?? '',)), name: 'about',
GoRoute( builder: (context, state) => const Text('About'))
name: 'edit-item', ]),
path: 'i/:item', ));
builder: (context, state)=>EditItemPage(
server: state.params['server'] ?? '',
room: state.params['id'] ?? '',
item: int.tryParse(state.params['item'] ?? '') ?? 0),
)
])
]),
]),
// routes that can be accessed
// with and without an account
// i.e the about screen
GoRoute(
path: '/about',
name: 'about',
builder: (context, state) => const Text('About'))
]),
));
} }
} }

View file

@ -24,12 +24,12 @@ class _HomePageState extends State<HomePage> {
// wait for background room changes // wait for background room changes
Room.listen((_) async { Room.listen((_) async {
try { try {
final newRooms = await Room.listRooms(); final newRooms = await Room.listRooms();
setState(() { setState(() {
rooms = newRooms; rooms = newRooms;
}); });
} catch (_) {} } catch (_) {}
}); });
WidgetsBinding.instance.addPostFrameCallback((_) => fetchList()); WidgetsBinding.instance.addPostFrameCallback((_) => fetchList());
@ -43,17 +43,17 @@ class _HomePageState extends State<HomePage> {
try { try {
final newRooms = await Room.listRooms(); final newRooms = await Room.listRooms();
setState(() { setState(() {
rooms = newRooms; rooms = newRooms;
}); });
} catch (_) {} } catch (_) {}
doNetworkRequest( doNetworkRequest(
sm, sm,
req: () => postWithCreadentials( req: () => postWithCreadentials(
path: 'listRooms', credentials: user, target: user.server, body: {}), path: 'listRooms', credentials: user, target: user.server, body: {}),
onOK: (body) async { onOK: (body) async {
final List<Room> list = body['data'].map<Room>((json) { final List<Room> list = body['data'].map<Room>((json) {
return Room.fromJSON(json); return Room.fromJSON(json);
}).toList(); }).toList();
for (Room r in list) { for (Room r in list) {
await r.toDisk(); await r.toDisk();
@ -91,33 +91,34 @@ class _HomePageState extends State<HomePage> {
}, },
menuChildren: [ menuChildren: [
MenuItemButton( MenuItemButton(
leadingIcon: const Icon(Icons.settings), leadingIcon: const Icon(Icons.settings),
child: Text(AppLocalizations.of(context)!.settings), child: Text(AppLocalizations.of(context)!.settings),
onPressed: () {
// show settings screen
context.goNamed('settings');
}),
...(context.watch<AccountMeta?>() != null &&
(context.watch<AccountMeta?>()?.permissions)! &
ServerPermission.allManagement !=
0)
? [
MenuItemButton(
leadingIcon: const Icon(Icons.dns),
child: Text(AppLocalizations.of(context)!.serverDashboard),
onPressed: () { onPressed: () {
// show settings screen // show settings screen
context.goNamed('dash'); context.goNamed('settings');
}), }),
] ...(context.watch<AccountMeta?>() != null &&
: [], (context.watch<AccountMeta?>()?.permissions)! &
ServerPermission.allManagement !=
0)
? [
MenuItemButton(
leadingIcon: const Icon(Icons.dns),
child: Text(
AppLocalizations.of(context)!.serverDashboard),
onPressed: () {
// show settings screen
context.goNamed('dash');
}),
]
: [],
MenuItemButton( MenuItemButton(
leadingIcon: const Icon(Icons.info_rounded), leadingIcon: const Icon(Icons.info_rounded),
child: Text(AppLocalizations.of(context)!.about), child: Text(AppLocalizations.of(context)!.about),
onPressed: () { onPressed: () {
// show about screen // show about screen
context.goNamed('about'); context.goNamed('about');
}), }),
], ],
) )
], ],
@ -127,31 +128,31 @@ class _HomePageState extends State<HomePage> {
itemBuilder: (ctx, i) { itemBuilder: (ctx, i) {
final room = rooms[i]; final room = rooms[i];
return Card( return Card(
margin: const EdgeInsets.all(8.0), margin: const EdgeInsets.all(8.0),
clipBehavior: Clip.antiAliasWithSaveLayer, clipBehavior: Clip.antiAliasWithSaveLayer,
semanticContainer: true, semanticContainer: true,
child: InkWell( child: InkWell(
onTap: () { onTap: () {
// open room // open room
context.goNamed('room', context.goNamed('room',
params: {'server': room.serverTag, 'id': room.id}); params: {'server': room.serverTag, 'id': room.id});
}, },
onLongPress: () { onLongPress: () {
// open bottom sheet // open bottom sheet
// NOTE: feature yet to be confirmed // NOTE: feature yet to be confirmed
}, },
child: Container( child: Container(
padding: const EdgeInsets.fromLTRB(10, 5, 5, 10), padding: const EdgeInsets.fromLTRB(10, 5, 5, 10),
child: ListTile( child: ListTile(
title: Text(room.name), title: Text(room.name),
visualDensity: const VisualDensity(vertical: 3), visualDensity: const VisualDensity(vertical: 3),
subtitle: Text(room.description), subtitle: Text(room.description),
leading: AspectRatio( leading: AspectRatio(
aspectRatio: 1 / 1, aspectRatio: 1 / 1,
child: SvgPicture.asset("${room.icon?.img}"), child: SvgPicture.asset("${room.icon?.img}"),
), ),
hoverColor: Colors.transparent, hoverColor: Colors.transparent,
)))); ))));
}, },
), ),
floatingActionButton: FloatingActionButton.extended( floatingActionButton: FloatingActionButton.extended(

View file

@ -0,0 +1 @@

View file

@ -30,10 +30,10 @@ class _ManageRoomMembersPageState extends State<ManageRoomMembersPage> {
doNetworkRequest( doNetworkRequest(
sm, sm,
req: () => postWithCreadentials( req: () => postWithCreadentials(
path: 'getRoomInfo', path: 'getRoomInfo',
credentials: user, credentials: user,
target: user.server, target: user.server,
body: {'room': widget.tag, 'server': widget.server}), body: {'room': widget.tag, 'server': widget.server}),
onAnyErr: () { onAnyErr: () {
// user should not be here // user should not be here
// close screen // close screen
@ -43,7 +43,7 @@ class _ManageRoomMembersPageState extends State<ManageRoomMembersPage> {
onOK: (body) async { onOK: (body) async {
final info = RoomInfo.fromJSON(body['data']); final info = RoomInfo.fromJSON(body['data']);
setState(() { setState(() {
this.info = info; this.info = info;
}); });
return true; return true;
}, },
@ -56,26 +56,26 @@ class _ManageRoomMembersPageState extends State<ManageRoomMembersPage> {
final user = context.read<User>(); final user = context.read<User>();
doNetworkRequest(sm, doNetworkRequest(sm,
req: () => postWithCreadentials( req: () => postWithCreadentials(
credentials: user, credentials: user,
target: user.server, target: user.server,
path: 'getRoomMembers', path: 'getRoomMembers',
body: {'room': widget.tag, 'server': widget.server}), body: {'room': widget.tag, 'server': widget.server}),
onAnyErr: () { onAnyErr: () {
// user should not be here // user should not be here
// close screen // close screen
router.pushReplacementNamed('home'); router.pushReplacementNamed('home');
return false; return false;
}, },
onOK: (body) { onOK: (body) {
final List<RoomMember> list = body['data'].map<RoomMember>((json) { final List<RoomMember> list = body['data'].map<RoomMember>((json) {
return RoomMember.fromJSON(json); return RoomMember.fromJSON(json);
}).toList(); }).toList();
setState(() { setState(() {
this.list = list; this.list = list;
});
}); });
});
} }
@override @override
@ -83,21 +83,21 @@ class _ManageRoomMembersPageState extends State<ManageRoomMembersPage> {
super.initState(); super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
fetchUserInfo(); fetchUserInfo();
fetchMembers(); fetchMembers();
}); });
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final textTheme = Theme.of(context) final textTheme = Theme.of(context)
.textTheme .textTheme
.apply(displayColor: Theme.of(context).colorScheme.onSurface); .apply(displayColor: Theme.of(context).colorScheme.onSurface);
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: title:
Text(AppLocalizations.of(context)!.roomMembersTitle(list.length)), Text(AppLocalizations.of(context)!.roomMembersTitle(list.length)),
//actions: [ //actions: [
// // NOTE: Maybe add a search icon // // NOTE: Maybe add a search icon
// // and general search functionality here // // and general search functionality here
@ -109,8 +109,8 @@ class _ManageRoomMembersPageState extends State<ManageRoomMembersPage> {
String role = AppLocalizations.of(context)!.roleMember; String role = AppLocalizations.of(context)!.roleMember;
if (info != null && if (info != null &&
(info?.owner)! == item.id && (info?.owner)! == item.id &&
widget.server == item.serverTag) { widget.server == item.serverTag) {
role = AppLocalizations.of(context)!.roleOwner; role = AppLocalizations.of(context)!.roleOwner;
} else if (item.isAdmin) { } else if (item.isAdmin) {
role = AppLocalizations.of(context)!.roleAdmin; role = AppLocalizations.of(context)!.roleAdmin;
@ -119,17 +119,17 @@ class _ManageRoomMembersPageState extends State<ManageRoomMembersPage> {
bool enable = true; bool enable = true;
// perform permission check // perform permission check
if (info == null || if (info == null ||
!((info?.isAdmin)! || !((info?.isAdmin)! ||
(info?.isOwner)! || (info?.isOwner)! ||
((info?.permissions)! & oB("1100000") != 0))) { ((info?.permissions)! & oB("1100000") != 0))) {
// NOTE: do not show error message // NOTE: do not show error message
// user should assume, // user should assume,
// that it wasn't even possible // that it wasn't even possible
// to click on ListTile // to click on ListTile
enable = false; enable = false;
} else if (info != null && } else if (info != null &&
item.id == info?.owner && item.id == info?.owner &&
widget.server == item.serverTag) { widget.server == item.serverTag) {
// cannot kick admin // cannot kick admin
enable = false; enable = false;
} }
@ -139,236 +139,236 @@ class _ManageRoomMembersPageState extends State<ManageRoomMembersPage> {
subtitle: Text(role), subtitle: Text(role),
leading: const Icon(Icons.person), leading: const Icon(Icons.person),
onTap: !enable onTap: !enable
? null ? null
: () { : () {
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
builder: (context) => BottomSheet( builder: (context) => BottomSheet(
onClosing: () {}, onClosing: () {},
builder: (context) => Column( builder: (context) => Column(
children: [ children: [
Padding( Padding(
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
child: Text(item.humanReadableName, child: Text(item.humanReadableName,
style: textTheme.displaySmall)), style: textTheme.displaySmall)),
Padding( Padding(
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
child: Column( child: Column(
children: [ children: [
...((info?.isAdmin)! || ...((info?.isAdmin)! ||
(info?.isOwner)! || (info?.isOwner)! ||
((info?.permissions)! & ((info?.permissions)! &
RoomPermission RoomPermission
.changeAdmin != .changeAdmin !=
0)) 0))
? [ ? [
ListTile( ListTile(
leading: const Icon( leading: const Icon(
Icons.supervisor_account), Icons.supervisor_account),
title: Text(item.isAdmin title: Text(item.isAdmin
? AppLocalizations.of( ? AppLocalizations.of(
context)! context)!
.removeAdminTitle .removeAdminTitle
: AppLocalizations.of( : AppLocalizations.of(
context)! context)!
.makeAdminTitle), .makeAdminTitle),
subtitle: Text(item.isAdmin subtitle: Text(item.isAdmin
? AppLocalizations.of( ? AppLocalizations.of(
context)! context)!
.removeAdminSubtitle .removeAdminSubtitle
: AppLocalizations.of( : AppLocalizations.of(
context)! context)!
.makeAdminSubtitle), .makeAdminSubtitle),
onTap: () { onTap: () {
// make user admin // make user admin
showDialog( showDialog(
context: context, context: context,
builder: builder:
(ctx) => (ctx) =>
AlertDialog( AlertDialog(
icon: const Icon( icon: const Icon(
Icons Icons
.supervisor_account), .supervisor_account),
title: Text(item.isAdmin title: Text(item.isAdmin
? AppLocalizations.of( ? AppLocalizations.of(
context)! context)!
.removeAdminTitle .removeAdminTitle
: AppLocalizations.of( : AppLocalizations.of(
context)! context)!
.makeAdminTitle), .makeAdminTitle),
content: Text(item content: Text(item
.isAdmin .isAdmin
? AppLocalizations.of( ? AppLocalizations.of(
context)! context)!
.removeAdminConfirm(item .removeAdminConfirm(item
.humanReadableName) .humanReadableName)
: AppLocalizations.of( : AppLocalizations.of(
context)! context)!
.makeAdminConfirm( .makeAdminConfirm(
item.humanReadableName)), item.humanReadableName)),
actions: [ actions: [
TextButton( TextButton(
onPressed: onPressed:
() { () {
// close popup // close popup
// NOTE: cancel only closes the dialog // NOTE: cancel only closes the dialog
// whilst OK closes both // whilst OK closes both
Navigator.of(ctx) Navigator.of(ctx)
.pop(); .pop();
}, },
child: Text( child: Text(
AppLocalizations.of(context)! AppLocalizations.of(context)!
.cancel), .cancel),
), ),
FilledButton( FilledButton(
onPressed: onPressed:
() async { () async {
// send request // send request
final scaffMgr = final scaffMgr =
ScaffoldMessenger.of(context); ScaffoldMessenger.of(context);
final nav = final nav =
Navigator.of(ctx); Navigator.of(ctx);
final nav2 = final nav2 =
Navigator.of(context); Navigator.of(context);
final user = final user =
context.read<User>(); context.read<User>();
doNetworkRequest( doNetworkRequest(
scaffMgr, scaffMgr,
req: () => req: () =>
postWithCreadentials(path: 'setAdminStatus', credentials: user, target: user.server, body: { postWithCreadentials(path: 'setAdminStatus', credentials: user, target: user.server, body: {
'room': widget.tag, 'room': widget.tag,
'roomServer': widget.server, 'roomServer': widget.server,
'server': item.serverTag, 'server': item.serverTag,
'name': item.id, 'name': item.id,
'admin': !item.isAdmin 'admin': !item.isAdmin
}), }),
onOK: (_) { onOK: (_) {
fetchMembers(); fetchMembers();
}, },
after: () { after: () {
// close popup // close popup
nav.pop(); nav.pop();
// close bottom sheet // close bottom sheet
nav2.pop(); nav2.pop();
}); });
}, },
child: Text( child: Text(
AppLocalizations.of(context)! AppLocalizations.of(context)!
.ok), .ok),
) )
],
));
},
)
]
: [],
...((info?.isAdmin)! ||
(info?.isOwner)! ||
((info?.permissions)! &
RoomPermission
.manageMembers !=
0))
? [
ListTile(
leading: const Icon(
Icons.person_remove),
title: Text(
AppLocalizations.of(
context)!
.kickUserTitle),
subtitle: Text(
AppLocalizations.of(
context)!
.kickUserSubtitle),
onTap: () {
// remove user from room
showDialog(
context: context,
builder:
(ctx) =>
AlertDialog(
icon: const Icon(
Icons
.person_remove),
title: Text(AppLocalizations.of(
context)!
.kickUserTitle),
content: Text(AppLocalizations.of(
context)!
.kichUserConfirm(
item.humanReadableName)),
actions: [
TextButton(
onPressed:
() {
// close popup
// NOTE: cancel only closes the dialog
// whilst OK closes both
Navigator.of(ctx)
.pop();
},
child: Text(
AppLocalizations.of(context)!
.cancel),
),
FilledButton(
onPressed:
() async {
// send request
final scaffMgr =
ScaffoldMessenger.of(ctx);
final nav =
Navigator.of(ctx);
final nav2 =
Navigator.of(context);
final user =
context.read<User>();
doNetworkRequest(
scaffMgr,
req: () =>
postWithCreadentials(path: 'kickMember', credentials: user, target: user.server, body: {
'room': widget.tag,
'roomServer': widget.server,
'name': item.id,
'server': item.serverTag
}),
onOK: (_) {
fetchMembers();
},
after: () {
// close popup
nav.pop();
// close bottom sheet
nav2.pop();
});
},
child: Text(
AppLocalizations.of(context)!
.ok),
)
],
));
},
)
]
: [],
], ],
)); ),
}, ),
) FilledButton(
] child: Text(
: [], AppLocalizations.of(context)!.close),
...((info?.isAdmin)! || onPressed: () {
(info?.isOwner)! || Navigator.of(context).pop();
((info?.permissions)! & },
RoomPermission )
.manageMembers != ],
0)) ),
? [ ));
ListTile( },
leading: const Icon(
Icons.person_remove),
title: Text(
AppLocalizations.of(
context)!
.kickUserTitle),
subtitle: Text(
AppLocalizations.of(
context)!
.kickUserSubtitle),
onTap: () {
// remove user from room
showDialog(
context: context,
builder:
(ctx) =>
AlertDialog(
icon: const Icon(
Icons
.person_remove),
title: Text(AppLocalizations.of(
context)!
.kickUserTitle),
content: Text(AppLocalizations.of(
context)!
.kichUserConfirm(
item.humanReadableName)),
actions: [
TextButton(
onPressed:
() {
// close popup
// NOTE: cancel only closes the dialog
// whilst OK closes both
Navigator.of(ctx)
.pop();
},
child: Text(
AppLocalizations.of(context)!
.cancel),
),
FilledButton(
onPressed:
() async {
// send request
final scaffMgr =
ScaffoldMessenger.of(ctx);
final nav =
Navigator.of(ctx);
final nav2 =
Navigator.of(context);
final user =
context.read<User>();
doNetworkRequest(
scaffMgr,
req: () =>
postWithCreadentials(path: 'kickMember', credentials: user, target: user.server, body: {
'room': widget.tag,
'roomServer': widget.server,
'name': item.id,
'server': item.serverTag
}),
onOK: (_) {
fetchMembers();
},
after: () {
// close popup
nav.pop();
// close bottom sheet
nav2.pop();
});
},
child: Text(
AppLocalizations.of(context)!
.ok),
)
],
));
},
)
]
: [],
],
),
),
FilledButton(
child: Text(
AppLocalizations.of(context)!.close),
onPressed: () {
Navigator.of(context).pop();
},
)
],
),
));
},
); );
}, },
itemCount: list.length, itemCount: list.length,

View file

@ -0,0 +1 @@

View file

@ -31,10 +31,10 @@ class _EditRoomPermissionSetPageState extends State<EditRoomPermissionSetPage> {
doNetworkRequest( doNetworkRequest(
sm, sm,
req: () => postWithCreadentials( req: () => postWithCreadentials(
path: 'getRoomInfo', path: 'getRoomInfo',
credentials: user, credentials: user,
target: user.server, target: user.server,
body: {'room': widget.tag, 'server': widget.server}), body: {'room': widget.tag, 'server': widget.server}),
onAnyErr: () { onAnyErr: () {
// user should not be here // user should not be here
// close screen // close screen
@ -44,7 +44,7 @@ class _EditRoomPermissionSetPageState extends State<EditRoomPermissionSetPage> {
onOK: (body) async { onOK: (body) async {
final info = RoomInfo.fromJSON(body['data']); final info = RoomInfo.fromJSON(body['data']);
setState(() { setState(() {
permissions = info.permissions; permissions = info.permissions;
}); });
return true; return true;
}, },
@ -72,18 +72,18 @@ class _EditRoomPermissionSetPageState extends State<EditRoomPermissionSetPage> {
final int col = pow(2, index) as int; final int col = pow(2, index) as int;
return SwitchListTile( return SwitchListTile(
title: Text(RoomPermission.name(item, context)), title: Text(RoomPermission.name(item, context)),
subtitle: Text(RoomPermission.describe(item, context)), subtitle: Text(RoomPermission.describe(item, context)),
onChanged: (state) { onChanged: (state) {
setState(() { setState(() {
if (state) { if (state) {
permissions |= col; permissions |= col;
} else { } else {
permissions &= ~col; permissions &= ~col;
} }
}); });
}, },
value: permissions & col != 0); value: permissions & col != 0);
}, },
), ),
floatingActionButton: FloatingActionButton.extended( floatingActionButton: FloatingActionButton.extended(
@ -97,18 +97,18 @@ class _EditRoomPermissionSetPageState extends State<EditRoomPermissionSetPage> {
// update permissions // update permissions
doNetworkRequest(sm, doNetworkRequest(sm,
req: () => postWithCreadentials( req: () => postWithCreadentials(
path: 'setRoomRight', path: 'setRoomRight',
credentials: user, credentials: user,
target: user.server, target: user.server,
body: { body: {
'room': widget.tag, 'room': widget.tag,
'server': widget.server, 'server': widget.server,
'rights': permissions 'rights': permissions
}), }),
onOK: (_) { onOK: (_) {
router.pop(); router.pop();
}); });
}, },
), ),
); );

View file

@ -31,11 +31,11 @@ class _EditCategoryPageState extends State<EditCategoryPage> {
super.initState(); super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
if (widget.id == null) { if (widget.id == null) {
// trying to create a new category // trying to create a new category
return; return;
} }
fetchCategory(); fetchCategory();
}); });
} }
@ -45,210 +45,210 @@ class _EditCategoryPageState extends State<EditCategoryPage> {
// TODO: load cached rooms // TODO: load cached rooms
doNetworkRequest(ScaffoldMessenger.of(context), doNetworkRequest(ScaffoldMessenger.of(context),
req: () => postWithCreadentials( req: () => postWithCreadentials(
credentials: user, credentials: user,
target: user.server, target: user.server,
path: 'getCategory', path: 'getCategory',
body: { body: {
'room': widget.tag, 'room': widget.tag,
'server': widget.server, 'server': widget.server,
'listCatID': widget.id 'listCatID': widget.id
}), }),
onOK: (json) { onOK: (json) {
setState(() { setState(() {
_ctrName.text = json['data']['title']; _ctrName.text = json['data']['title'];
_ctrColor = colorFromString(json['data']['color']); _ctrColor = colorFromString(json['data']['color']);
});
}); });
});
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final textTheme = Theme.of(context) final textTheme = Theme.of(context)
.textTheme .textTheme
.apply(displayColor: Theme.of(context).colorScheme.onSurface); .apply(displayColor: Theme.of(context).colorScheme.onSurface);
double width = MediaQuery.of(context).size.width; double width = MediaQuery.of(context).size.width;
double height = MediaQuery.of(context).size.height; double height = MediaQuery.of(context).size.height;
double smallest = min(min(width, height), 400); double smallest = min(min(width, height), 400);
return showSpinner return showSpinner
? Scaffold( ? Scaffold(
body: Center( body: Center(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
const CircularProgressIndicator(), const CircularProgressIndicator(),
Text(AppLocalizations.of(context)!.loading, Text(AppLocalizations.of(context)!.loading,
style: textTheme.titleLarge), style: textTheme.titleLarge),
]))) ])))
: Scaffold( : Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text((widget.id == null) title: Text((widget.id == null)
? AppLocalizations.of(context)!.newCategory ? AppLocalizations.of(context)!.newCategory
: AppLocalizations.of(context)!.editCategory), : AppLocalizations.of(context)!.editCategory),
), ),
body: SingleChildScrollView( body: SingleChildScrollView(
child: Center( child: Center(
child: Padding( child: Padding(
padding: const EdgeInsets.all(14), padding: const EdgeInsets.all(14),
child: ConstrainedBox( child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 400), constraints: const BoxConstraints(maxWidth: 400),
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
IconButton( IconButton(
icon: Icon(Icons.square_rounded, icon: Icon(Icons.square_rounded,
size: 48.0, color: _ctrColor), size: 48.0, color: _ctrColor),
tooltip: AppLocalizations.of(context)! tooltip: AppLocalizations.of(context)!
.changeCategoryColor, .changeCategoryColor,
onPressed: () { onPressed: () {
showDialog( showDialog(
context: context, context: context,
builder: (ctx) => AlertDialog( builder: (ctx) => AlertDialog(
title: Text( title: Text(
AppLocalizations.of(context)! AppLocalizations.of(context)!
.chooseCategoryColor), .chooseCategoryColor),
actions: const [], actions: const [],
content: SizedBox( content: SizedBox(
width: smallest * 0.3 * 3, width: smallest * 0.3 * 3,
height: smallest * 0.3 * 3, height: smallest * 0.3 * 3,
child: GridView.count( child: GridView.count(
crossAxisCount: 3, crossAxisCount: 3,
children: RoomCategory children: RoomCategory
.listColors() .listColors()
.map((color) { .map((color) {
return GridTile( return GridTile(
child: IconButton( child: IconButton(
icon: Icon( icon: Icon(
Icons Icons
.square_rounded, .square_rounded,
color: color:
color, color,
size: 48.0), size: 48.0),
// do not display tooltip for now // do not display tooltip for now
// as it is hard to translate // as it is hard to translate
// and the tooltip prevented the click event, // and the tooltip prevented the click event,
// when clicked on the tooltip bar // when clicked on the tooltip bar
// tooltip:icon.text, // tooltip:icon.text,
onPressed: () { onPressed: () {
setState(() { setState(() {
_ctrColor = _ctrColor =
color; color;
}); });
Navigator.of( Navigator.of(
ctx) ctx)
.pop(); .pop();
})); }));
}).toList())), }).toList())),
)); ));
}, },
), ),
Padding( Padding(
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
child: TextField( child: TextField(
controller: _ctrName, controller: _ctrName,
keyboardType: TextInputType.name, keyboardType: TextInputType.name,
decoration: InputDecoration( decoration: InputDecoration(
prefixIcon: const Icon(Icons.badge), prefixIcon: const Icon(Icons.badge),
labelText: AppLocalizations.of(context)! labelText: AppLocalizations.of(context)!
.inputCategoryNameLabel, .inputCategoryNameLabel,
hintText: AppLocalizations.of(context)! hintText: AppLocalizations.of(context)!
.inputCategoryNameHint, .inputCategoryNameHint,
helperText: AppLocalizations.of(context)! helperText: AppLocalizations.of(context)!
.inputCategoryNameHelp, .inputCategoryNameHelp,
border: const OutlineInputBorder(), border: const OutlineInputBorder(),
), ),
), ),
), ),
], ],
))))), ))))),
floatingActionButton: FloatingActionButton.extended( floatingActionButton: FloatingActionButton.extended(
onPressed: () async { onPressed: () async {
final scaffMgr = ScaffoldMessenger.of(context); final scaffMgr = ScaffoldMessenger.of(context);
final router = GoRouter.of(context); final router = GoRouter.of(context);
final trans = AppLocalizations.of(context); final trans = AppLocalizations.of(context);
// name may not be empty // name may not be empty
if (_ctrName.text.isEmpty) { if (_ctrName.text.isEmpty) {
showSimpleSnackbar(scaffMgr, showSimpleSnackbar(scaffMgr,
text: trans!.errorNoRoomName, action: trans.ok); text: trans!.errorNoRoomName, action: trans.ok);
return; return;
} }
setState(() { setState(() {
showSpinner = true; showSpinner = true;
}); });
final user = context.read<User>(); final user = context.read<User>();
final color = colorIdFromColor(_ctrColor); final color = colorIdFromColor(_ctrColor);
if (widget.id == null) { if (widget.id == null) {
await doNetworkRequest(scaffMgr, await doNetworkRequest(scaffMgr,
req: () => postWithCreadentials( req: () => postWithCreadentials(
target: user.server, target: user.server,
credentials: user, credentials: user,
path: 'addCategory', path: 'addCategory',
body: { body: {
'room': widget.tag, 'room': widget.tag,
'server': widget.server, 'server': widget.server,
'title': _ctrName.text, 'title': _ctrName.text,
'color': color 'color': color
}), }),
onOK: (body) async { onOK: (body) async {
final id = body['data']['catID']; final id = body['data']['catID'];
final cat = RoomCategory( final cat = RoomCategory(
id: id, name: _ctrName.text, color: _ctrColor); id: id, name: _ctrName.text, color: _ctrColor);
// TODO: cache category // TODO: cache category
// go back // go back
router.pop(); router.pop();
return; return;
}, },
after: () { after: () {
setState(() { setState(() {
showSpinner = false; showSpinner = false;
}); });
}); });
} else { } else {
await doNetworkRequest(scaffMgr, await doNetworkRequest(scaffMgr,
req: () => postWithCreadentials( req: () => postWithCreadentials(
target: user.server, target: user.server,
credentials: user, credentials: user,
path: 'changeCategory', path: 'changeCategory',
body: { body: {
'room': widget.tag, 'room': widget.tag,
'server': widget.server, 'server': widget.server,
'title': _ctrName.text, 'title': _ctrName.text,
'listCatID': widget.id, 'listCatID': widget.id,
'color': color 'color': color
}), }),
onOK: (body) async { onOK: (body) async {
final cat = RoomCategory( final cat = RoomCategory(
id: widget.id!, id: widget.id!,
name: _ctrName.text, name: _ctrName.text,
color: _ctrColor); color: _ctrColor);
// TODO: cache category // TODO: cache category
// go back // go back
router.pop(); router.pop();
return; return;
}, },
after: () { after: () {
setState(() { setState(() {
showSpinner = false; showSpinner = false;
}); });
}); });
} }
}, },
label: Text((widget.id == null) label: Text((widget.id == null)
? AppLocalizations.of(context)!.newCategoryShort ? AppLocalizations.of(context)!.newCategoryShort
: AppLocalizations.of(context)!.editCategoryShort), : AppLocalizations.of(context)!.editCategoryShort),
icon: Icon((widget.id == null) ? Icons.add : Icons.edit)), icon: Icon((widget.id == null) ? Icons.add : Icons.edit)),
); );
} }
} }

View file

@ -17,7 +17,7 @@ class EditItemPage extends StatefulWidget {
final int? item; final int? item;
const EditItemPage( const EditItemPage(
{super.key, required this.room, required this.server, this.item}); {super.key, required this.room, required this.server, this.item});
@override @override
State<StatefulWidget> createState() => _EditItemPageState(); State<StatefulWidget> createState() => _EditItemPageState();
@ -43,20 +43,20 @@ class _EditItemPageState extends State<EditItemPage> {
// TODO: load cached categories first // TODO: load cached categories first
doNetworkRequest(ScaffoldMessenger.of(context), doNetworkRequest(ScaffoldMessenger.of(context),
req: () => postWithCreadentials( req: () => postWithCreadentials(
credentials: user, credentials: user,
target: user.server, target: user.server,
path: 'getCategories', path: 'getCategories',
body: {'room': widget.room, 'server': widget.server}), body: {'room': widget.room, 'server': widget.server}),
onOK: (body) async { onOK: (body) async {
final resp = body['data'] final resp = body['data']
.map<RoomCategory>((raw) => RoomCategory.fromJSON(raw)) .map<RoomCategory>((raw) => RoomCategory.fromJSON(raw))
.toList(); .toList();
setState(() { setState(() {
categories = resp; categories = resp;
});
}); });
});
} }
void fetchProducts() { void fetchProducts() {
@ -65,20 +65,20 @@ class _EditItemPageState extends State<EditItemPage> {
// TODO: load cached products first // TODO: load cached products first
doNetworkRequest(ScaffoldMessenger.of(context), doNetworkRequest(ScaffoldMessenger.of(context),
req: () => postWithCreadentials( req: () => postWithCreadentials(
credentials: user, credentials: user,
target: user.server, target: user.server,
path: 'getProducts', path: 'getProducts',
body: {'room': widget.room, 'server': widget.server}), body: {'room': widget.room, 'server': widget.server}),
onOK: (body) async { onOK: (body) async {
final resp = body['data'] final resp = body['data']
.map<RoomProduct>((raw) => RoomProduct.fromJSON(raw)) .map<RoomProduct>((raw) => RoomProduct.fromJSON(raw))
.toList(); .toList();
setState(() { setState(() {
products = resp; products = resp;
});
}); });
});
} }
void fetchItem() { void fetchItem() {
@ -87,21 +87,21 @@ class _EditItemPageState extends State<EditItemPage> {
// TODO: load cached item first // TODO: load cached item first
doNetworkRequest(ScaffoldMessenger.of(context), doNetworkRequest(ScaffoldMessenger.of(context),
req: () => postWithCreadentials( req: () => postWithCreadentials(
credentials: user, credentials: user,
target: user.server, target: user.server,
path: 'getItem', path: 'getItem',
body: { body: {
'room': widget.room, 'room': widget.room,
'server': widget.server, 'server': widget.server,
'listItemID': widget.item 'listItemID': widget.item
}), }),
onOK: (body) async { onOK: (body) async {
final resp = RoomItem.fromJSON(body['data']); final resp = RoomItem.fromJSON(body['data']);
setState(() { setState(() {
item = resp; item = resp;
});
}); });
});
} }
@override @override
@ -109,12 +109,12 @@ class _EditItemPageState extends State<EditItemPage> {
super.initState(); super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
fetchCategories(); fetchCategories();
fetchProducts(); fetchProducts();
if (widget.item != null) { if (widget.item != null) {
fetchItem(); fetchItem();
} }
}); });
} }
@ -123,156 +123,156 @@ class _EditItemPageState extends State<EditItemPage> {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text((widget.item == null) title: Text((widget.item == null)
? AppLocalizations.of(context)!.createItem ? AppLocalizations.of(context)!.createItem
: AppLocalizations.of(context)!.editItem), : AppLocalizations.of(context)!.editItem),
), ),
body: SingleChildScrollView( body: SingleChildScrollView(
child: Center( child: Center(
child: Padding( child: Padding(
padding: const EdgeInsets.all(14), padding: const EdgeInsets.all(14),
child: ConstrainedBox( child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 400), constraints: const BoxConstraints(maxWidth: 400),
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
Padding( Padding(
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
child: TextField( child: TextField(
controller: _ctrName, controller: _ctrName,
keyboardType: TextInputType.name, keyboardType: TextInputType.name,
decoration: InputDecoration( decoration: InputDecoration(
prefixIcon: const Icon(Icons.badge), prefixIcon: const Icon(Icons.badge),
labelText: AppLocalizations.of(context)! labelText: AppLocalizations.of(context)!
.inputItemNameLabel, .inputItemNameLabel,
hintText: AppLocalizations.of(context)! hintText: AppLocalizations.of(context)!
.inputItemNameHint, .inputItemNameHint,
helperText: AppLocalizations.of(context)! helperText: AppLocalizations.of(context)!
.inputItemNameHelp, .inputItemNameHelp,
border: const OutlineInputBorder(), border: const OutlineInputBorder(),
), ),
), ),
), ),
ProductPicker( ProductPicker(
label: AppLocalizations.of(context)! label: AppLocalizations.of(context)!
.selectLinkedProductLabel, .selectLinkedProductLabel,
hint: AppLocalizations.of(context)! hint: AppLocalizations.of(context)!
.selectLinkedProductHint, .selectLinkedProductHint,
products: products, products: products,
selected: _ctrLink, selected: _ctrLink,
onSelect: (pid) { onSelect: (pid) {
setState(() { setState(() {
_ctrLink = pid; _ctrLink = pid;
}); });
}, },
), ),
Padding( Padding(
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
child: TextField( child: TextField(
controller: _ctrDescription, controller: _ctrDescription,
keyboardType: TextInputType.text, keyboardType: TextInputType.text,
decoration: InputDecoration( decoration: InputDecoration(
labelText: AppLocalizations.of(context)! labelText: AppLocalizations.of(context)!
.inputItemDescriptionLabel, .inputItemDescriptionLabel,
hintText: AppLocalizations.of(context)! hintText: AppLocalizations.of(context)!
.inputItemDescriptionHint, .inputItemDescriptionHint,
helperText: AppLocalizations.of(context)! helperText: AppLocalizations.of(context)!
.inputItemDescriptionHelp, .inputItemDescriptionHelp,
prefixIcon: const Icon(Icons.dns), prefixIcon: const Icon(Icons.dns),
border: const OutlineInputBorder(), border: const OutlineInputBorder(),
), ),
), ),
), ),
DynamicValueUnitInput( DynamicValueUnitInput(
initialUnit: _ctrUnit, initialUnit: _ctrUnit,
initialValue: _ctrValue, initialValue: _ctrValue,
onUnitChange: (unit) { onUnitChange: (unit) {
setState(() { setState(() {
_ctrUnit = unit; _ctrUnit = unit;
}); });
}, },
onValueChange: (value) { onValueChange: (value) {
setState(() { setState(() {
_ctrValue = value; _ctrValue = value;
}); });
}, },
), ),
CategoryPicker( CategoryPicker(
label: AppLocalizations.of(context)! label: AppLocalizations.of(context)!
.selectCategoryLabel, .selectCategoryLabel,
hint: AppLocalizations.of(context)! hint: AppLocalizations.of(context)!
.selectCategoryHint, .selectCategoryHint,
categories: categories, categories: categories,
selected: _ctrCategory, selected: _ctrCategory,
onSelect: (cid) { onSelect: (cid) {
setState(() { setState(() {
_ctrCategory = cid; _ctrCategory = cid;
}); });
}, },
), ),
], ],
))))), ))))),
floatingActionButton: FloatingActionButton.extended( floatingActionButton: FloatingActionButton.extended(
onPressed: () async { onPressed: () async {
final scaffMgr = ScaffoldMessenger.of(context); final scaffMgr = ScaffoldMessenger.of(context);
final trans = AppLocalizations.of(context); final trans = AppLocalizations.of(context);
final nav = Navigator.of(context); final nav = Navigator.of(context);
if (_ctrName.text.isEmpty) { if (_ctrName.text.isEmpty) {
showSimpleSnackbar(scaffMgr, showSimpleSnackbar(scaffMgr,
text: trans!.errorProductNameShouldNotBeEmpty, text: trans!.errorProductNameShouldNotBeEmpty,
action: trans.ok); action: trans.ok);
return; return;
} }
final user = context.read<User>(); final user = context.read<User>();
if (widget.item == null) { if (widget.item == null) {
doNetworkRequest(scaffMgr, doNetworkRequest(scaffMgr,
req: () => postWithCreadentials( req: () => postWithCreadentials(
credentials: user, credentials: user,
target: user.server, target: user.server,
path: 'addItem', path: 'addItem',
body: { body: {
'room': widget.room, 'room': widget.room,
'server': widget.server, 'server': widget.server,
'state': 0, 'state': 0,
'title': _ctrName.text, 'title': _ctrName.text,
'description': _ctrDescription.text, 'description': _ctrDescription.text,
'listCatID': _ctrCategory, 'listCatID': _ctrCategory,
'unit': _ctrUnit, 'unit': _ctrUnit,
'value': _ctrValue, 'value': _ctrValue,
'listProdID': _ctrLink 'listProdID': _ctrLink
}), }),
onOK: (_) async { onOK: (_) async {
nav.pop(); nav.pop();
}); });
} else { } else {
doNetworkRequest(scaffMgr, doNetworkRequest(scaffMgr,
req: () => postWithCreadentials( req: () => postWithCreadentials(
credentials: user, credentials: user,
target: user.server, target: user.server,
path: 'changeItem', path: 'changeItem',
body: { body: {
'listItemID': widget.item, 'listItemID': widget.item,
'room': widget.room, 'room': widget.room,
'server': widget.server, 'server': widget.server,
'title': _ctrName.text, 'title': _ctrName.text,
'description': _ctrDescription.text, 'description': _ctrDescription.text,
'listCatID': _ctrCategory, 'listCatID': _ctrCategory,
'defUnit': _ctrUnit, 'defUnit': _ctrUnit,
'defValue': _ctrValue, 'defValue': _ctrValue,
'listProdID': _ctrLink 'listProdID': _ctrLink
}), }),
onOK: (_) async { onOK: (_) async {
nav.pop(); nav.pop();
}); });
} }
}, },
label: Text(widget.item != null label: Text(widget.item != null
? AppLocalizations.of(context)!.editItemShort ? AppLocalizations.of(context)!.editItemShort
: AppLocalizations.of(context)!.createItemShort), : AppLocalizations.of(context)!.createItemShort),
icon: Icon(widget.item != null ? Icons.edit : Icons.add)), icon: Icon(widget.item != null ? Icons.edit : Icons.add)),
); );
} }
} }

View file

@ -24,60 +24,60 @@ class _JoinRoomPageState extends State {
final user = context.read<User>(); final user = context.read<User>();
doNetworkRequest(null, doNetworkRequest(null,
req: () => postWithCreadentials( req: () => postWithCreadentials(
path: 'listPublicRooms', path: 'listPublicRooms',
credentials: user,
target: user.server,
body: {}),
onOK: (body) async {
// parse rooms
final list = body['data'];
// try to fetch a list of rooms the user is a member of
// use an empty blacklist when request is not successful
final List<Room> blacklist = [];
await doNetworkRequest(sm,
req: () => postWithCreadentials(
path: 'listRooms',
credentials: user, credentials: user,
target: user.server, target: user.server,
body: {}), body: {}),
onOK: (body) { onOK: (body) async {
final List<Room> list = body['data'].map<Room>((json) { // parse rooms
return Room.fromJSON(json); final list = body['data'];
}).toList();
for (Room r in list) {
blacklist.add(r);
}
});
// process the list of public rooms // try to fetch a list of rooms the user is a member of
final List<Room> builder = []; // use an empty blacklist when request is not successful
processor: final List<Room> blacklist = [];
for (dynamic raw in list) { await doNetworkRequest(sm,
try { req: () => postWithCreadentials(
final room = Room.fromJSON(raw); path: 'listRooms',
credentials: user,
target: user.server,
body: {}),
onOK: (body) {
final List<Room> list = body['data'].map<Room>((json) {
return Room.fromJSON(json);
}).toList();
for (Room r in list) {
blacklist.add(r);
}
});
// figure out if room is on blacklist // process the list of public rooms
// only add room to list, final List<Room> builder = [];
// if not on blacklist processor:
for (Room r in blacklist) { for (dynamic raw in list) {
if (r == room) { try {
// server on white list final room = Room.fromJSON(raw);
// move to next iteration on outer for loop
continue processor; // figure out if room is on blacklist
// only add room to list,
// if not on blacklist
for (Room r in blacklist) {
if (r == room) {
// server on white list
// move to next iteration on outer for loop
continue processor;
}
} }
}
builder.add(room); builder.add(room);
} catch (_) { } catch (_) {
// ignore room // ignore room
}
} }
} setState(() {
setState(() {
rooms = builder; rooms = builder;
});
}); });
});
} }
@override @override
@ -90,8 +90,8 @@ class _JoinRoomPageState extends State {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final textTheme = Theme.of(context) final textTheme = Theme.of(context)
.textTheme .textTheme
.apply(displayColor: Theme.of(context).colorScheme.onSurface); .apply(displayColor: Theme.of(context).colorScheme.onSurface);
double width = MediaQuery.of(context).size.width; double width = MediaQuery.of(context).size.width;
double height = MediaQuery.of(context).size.height; double height = MediaQuery.of(context).size.height;
@ -118,182 +118,189 @@ class _JoinRoomPageState extends State {
}, },
), ),
MenuAnchor( MenuAnchor(
builder: (ctx, controller, child) { builder: (ctx, controller, child) {
return IconButton( return IconButton(
onPressed: () { onPressed: () {
if (controller.isOpen) { if (controller.isOpen) {
controller.close(); controller.close();
} else { } else {
controller.open(); controller.open();
} }
}, },
icon: const Icon(Icons.more_vert), icon: const Icon(Icons.more_vert),
); );
}, },
menuChildren: [ menuChildren: [
MenuItemButton( MenuItemButton(
leadingIcon: const Icon(Icons.drafts), leadingIcon: const Icon(Icons.drafts),
child: Text(AppLocalizations.of(context)!.joinRoomInvite), child: Text(AppLocalizations.of(context)!.joinRoomInvite),
onPressed: () { onPressed: () {
// show settings screen // show settings screen
context.goNamed('join-room-ota'); context.goNamed('join-room-ota');
}), }),
]) ])
], ],
), ),
body: rooms.isEmpty body: rooms.isEmpty
? Center( ? Center(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
Text(AppLocalizations.of(context)!.noNewRoomsFound, style: textTheme.titleLarge), Text(AppLocalizations.of(context)!.noNewRoomsFound,
], style: textTheme.titleLarge),
)) ],
: ListView.builder( ))
itemCount: rooms.length, : ListView.builder(
itemBuilder: (ctx, i) { itemCount: rooms.length,
final room = rooms[i]; itemBuilder: (ctx, i) {
return Card( final room = rooms[i];
margin: const EdgeInsets.all(8.0), return Card(
clipBehavior: Clip.antiAliasWithSaveLayer, margin: const EdgeInsets.all(8.0),
semanticContainer: true, clipBehavior: Clip.antiAliasWithSaveLayer,
child: InkWell( semanticContainer: true,
onTap: () { child: InkWell(
// show modalBottomSheet onTap: () {
// with room information // show modalBottomSheet
// and join button // with room information
showModalBottomSheet( // and join button
context: ctx, showModalBottomSheet(
builder: (ctx) { context: ctx,
return BottomSheet( builder: (ctx) {
onClosing: () {}, return BottomSheet(
builder: (ctx) { onClosing: () {},
return Column( builder: (ctx) {
crossAxisAlignment: return Column(
CrossAxisAlignment.center, crossAxisAlignment:
mainAxisAlignment: CrossAxisAlignment.center,
MainAxisAlignment.center, mainAxisAlignment:
children: [ MainAxisAlignment.center,
Padding( children: [
padding: const EdgeInsets.all(14), Padding(
child: Column(children: [ padding: const EdgeInsets.all(14),
// room icon child: Column(children: [
SvgPicture.asset( // room icon
(room.icon?.img)!, SvgPicture.asset(
width: smallest * 0.2, (room.icon?.img)!,
height: smallest * 0.2, width: smallest * 0.2,
), height: smallest * 0.2,
// room name ),
Text( // room name
room.name, Text(
style: textTheme.displayMedium, room.name,
), style: textTheme.displayMedium,
Text( ),
'${room.id}@${room.serverTag}', Text(
style: textTheme.labelSmall, '${room.id}@${room.serverTag}',
), style: textTheme.labelSmall,
// description ),
Text(room.description, // description
style: textTheme.bodyLarge), Text(room.description,
// visibility style: textTheme.bodyLarge),
Row( // visibility
mainAxisAlignment: Row(
MainAxisAlignment.center, mainAxisAlignment:
children: [ MainAxisAlignment.center,
Icon(room.visibility?.icon), children: [
Text((room Icon(room.visibility?.icon),
.visibility?.text(context))!), Text((room.visibility
]), ?.text(context))!),
])), ]),
// action buttons ])),
Row( // action buttons
mainAxisAlignment: Row(
MainAxisAlignment.center, mainAxisAlignment:
children: [ MainAxisAlignment.center,
// cancel button children: [
Padding( // cancel button
padding: Padding(
const EdgeInsets.all(14), padding:
child: ElevatedButton.icon( const EdgeInsets.all(14),
icon: child: ElevatedButton.icon(
const Icon(Icons.close), icon:
label: Text(AppLocalizations.of(context)!.cancel), const Icon(Icons.close),
onPressed: () { label: Text(
// close sheet AppLocalizations.of(
Navigator.pop(context); context)!
}, .cancel),
)), onPressed: () {
// join room button // close sheet
Padding( Navigator.pop(context);
padding: },
const EdgeInsets.all(14), )),
child: FilledButton.icon( // join room button
icon: Padding(
const Icon(Icons.check), padding:
label: Text(AppLocalizations.of(context)!.joinRoom), const EdgeInsets.all(14),
onPressed: () async { child: FilledButton.icon(
final scaffMgr = icon:
ScaffoldMessenger.of( const Icon(Icons.check),
context); label: Text(
AppLocalizations.of(
context)!
.joinRoom),
onPressed: () async {
final scaffMgr =
ScaffoldMessenger.of(
context);
final nav = final nav =
Navigator.of(context); Navigator.of(context);
final user = final user =
context.read<User>(); context.read<User>();
final router = final router =
GoRouter.of(context); GoRouter.of(context);
doNetworkRequest(scaffMgr, doNetworkRequest(scaffMgr,
req: () => req: () =>
postWithCreadentials( postWithCreadentials(
credentials: credentials:
user, user,
target: user target: user
.server, .server,
path: path:
'joinPublicRoom', 'joinPublicRoom',
body: { body: {
'room': 'room':
room.id, room.id,
'server': room 'server': room
.serverTag .serverTag
}), }),
onOK: (body) async { onOK: (body) async {
await room.toDisk(); await room.toDisk();
nav.pop(); nav.pop();
router router
.pushReplacementNamed( .pushReplacementNamed(
'room', 'room',
params: { params: {
'server': room 'server': room
.serverTag, .serverTag,
'id': room.id 'id': room.id
}); });
}); });
}, },
)) ))
]) ])
], ],
); );
}, },
); );
}); });
},
child: Container(
padding: const EdgeInsets.fromLTRB(10, 5, 5, 10),
child: ListTile(
title: Text(room.name),
visualDensity: const VisualDensity(vertical: 3),
subtitle: Text(room.description),
leading: AspectRatio(
aspectRatio: 1 / 1,
child: SvgPicture.asset("${room.icon?.img}"),
),
hoverColor: Colors.transparent,
))));
}, },
child: Container( ),
padding: const EdgeInsets.fromLTRB(10, 5, 5, 10),
child: ListTile(
title: Text(room.name),
visualDensity: const VisualDensity(vertical: 3),
subtitle: Text(room.description),
leading: AspectRatio(
aspectRatio: 1 / 1,
child: SvgPicture.asset("${room.icon?.img}"),
),
hoverColor: Colors.transparent,
))));
},
),
floatingActionButton: FloatingActionButton.extended( floatingActionButton: FloatingActionButton.extended(
label: Text(AppLocalizations.of(context)!.newRoom), label: Text(AppLocalizations.of(context)!.newRoom),
icon: const Icon(Icons.add), icon: const Icon(Icons.add),

View file

@ -34,72 +34,69 @@ class _RoomPageState extends State<RoomPage> {
try { try {
final diskRoom = final diskRoom =
await Room.fromDisk(serverTag: widget.server, id: widget.tag); await Room.fromDisk(serverTag: widget.server, id: widget.tag);
setState(() { setState(() {
room = diskRoom; room = diskRoom;
}); });
} catch (_) {} } catch (_) {}
doNetworkRequest(sm, doNetworkRequest(sm,
req: () => postWithCreadentials( req: () => postWithCreadentials(
path: 'getRoomInfo', path: 'getRoomInfo',
credentials: user, credentials: user,
target: user.server, target: user.server,
body: {'room': widget.tag, 'server': widget.server}), body: {'room': widget.tag, 'server': widget.server}),
onOK: (body) async { onOK: (body) async {
final info = RoomInfo.fromJSON(body['data']); final info = RoomInfo.fromJSON(body['data']);
final room = Room.fromJSON(body['data']); final room = Room.fromJSON(body['data']);
room.toDisk(); room.toDisk();
setState(() { setState(() {
this.info = info; this.info = info;
});
return true;
},
onNetworkErr: () {
// user offline
if (room == null) {
// no room data available
// NOTE: close room?
}
return true;
},
onServerErr: (json) {
// user no longer in room
// TODO: close room
return true;
}); });
return true;
},
onNetworkErr: () {
// user offline
if (room == null) {
// no room data available
// NOTE: close room?
}
return true;
},
onServerErr: (json) {
// user no longer in room
// TODO: close room
return true;
});
} }
@override @override
void initState() { void initState() {
super.initState(); super.initState();
room = Room( room = Room(id: widget.tag, serverTag: widget.server);
id: widget.tag,
serverTag: widget.server
);
_ctr.addListener(() { _ctr.addListener(() {
setState(() { setState(() {
page = _ctr.page?.toInt() ?? _ctr.initialPage; page = _ctr.page?.toInt() ?? _ctr.initialPage;
}); });
}); });
Room.listen((_) async { Room.listen((_) async {
// rooms changed on disk // rooms changed on disk
// probably this one, // probably this one,
// because it is currently open // because it is currently open
// NOTE: might be a different room // NOTE: might be a different room
// (if a background listener is implemented at some point, // (if a background listener is implemented at some point,
// checking if this room changed might improve performance) // checking if this room changed might improve performance)
try { try {
final r = await Room.fromDisk(serverTag: widget.server, id: widget.tag); final r = await Room.fromDisk(serverTag: widget.server, id: widget.tag);
setState(() { setState(() {
room = r; room = r;
}); });
} catch (_) {} } catch (_) {}
}); });
WidgetsBinding.instance.addPostFrameCallback((_) => fetchInfo()); WidgetsBinding.instance.addPostFrameCallback((_) => fetchInfo());
@ -123,27 +120,27 @@ class _RoomPageState extends State<RoomPage> {
bottomNavigationBar: NavigationBar( bottomNavigationBar: NavigationBar(
onDestinationSelected: (int index) { onDestinationSelected: (int index) {
_ctr.animateToPage(index, _ctr.animateToPage(index,
curve: Curves.easeInOut, curve: Curves.easeInOut,
duration: const Duration(milliseconds: 300)); duration: const Duration(milliseconds: 300));
}, },
selectedIndex: page, selectedIndex: page,
destinations: [ destinations: [
NavigationDestination( NavigationDestination(
icon: const Icon(Icons.list), icon: const Icon(Icons.list),
label: AppLocalizations.of(context)!.roomListTitle, label: AppLocalizations.of(context)!.roomListTitle,
tooltip: AppLocalizations.of(context)!.roomListSubtitle), tooltip: AppLocalizations.of(context)!.roomListSubtitle),
NavigationDestination( NavigationDestination(
icon: const Icon(Icons.inventory_2), icon: const Icon(Icons.inventory_2),
label: AppLocalizations.of(context)!.roomProductsTitle, label: AppLocalizations.of(context)!.roomProductsTitle,
tooltip: AppLocalizations.of(context)!.roomProductsSubtitle), tooltip: AppLocalizations.of(context)!.roomProductsSubtitle),
NavigationDestination( NavigationDestination(
icon: const Icon(Icons.category), icon: const Icon(Icons.category),
label: AppLocalizations.of(context)!.roomCategoriesTitle, label: AppLocalizations.of(context)!.roomCategoriesTitle,
tooltip: AppLocalizations.of(context)!.roomCategoriesSubtitle), tooltip: AppLocalizations.of(context)!.roomCategoriesSubtitle),
NavigationDestination( NavigationDestination(
icon: const Icon(Icons.info_rounded), icon: const Icon(Icons.info_rounded),
label: AppLocalizations.of(context)!.roomAboutTitle, label: AppLocalizations.of(context)!.roomAboutTitle,
tooltip: AppLocalizations.of(context)!.roomAboutSubtitle), tooltip: AppLocalizations.of(context)!.roomAboutSubtitle),
], ],
), ),
); );

View file

@ -38,7 +38,7 @@ class _NewRoomPageState extends State<NewRoomPage> {
_ctrIcon = room.icon!; _ctrIcon = room.icon!;
setState(() { setState(() {
this.room = room; this.room = room;
}); });
} }
@ -50,17 +50,17 @@ class _NewRoomPageState extends State<NewRoomPage> {
try { try {
final diskRoom = final diskRoom =
await Room.fromDisk(serverTag: widget.server!, id: widget.tag!); await Room.fromDisk(serverTag: widget.server!, id: widget.tag!);
initFromRoom(diskRoom); initFromRoom(diskRoom);
} catch (_) {} } catch (_) {}
doNetworkRequest( doNetworkRequest(
sm, sm,
req: () => postWithCreadentials( req: () => postWithCreadentials(
path: 'getRoomInfo', path: 'getRoomInfo',
credentials: user, credentials: user,
target: user.server, target: user.server,
body: {'room': widget.tag, 'server': widget.server}), body: {'room': widget.tag, 'server': widget.server}),
onOK: (body) async { onOK: (body) async {
final room = Room.fromJSON(body['data']); final room = Room.fromJSON(body['data']);
room.toDisk(); room.toDisk();
@ -70,9 +70,9 @@ class _NewRoomPageState extends State<NewRoomPage> {
// no room data available // no room data available
// use data from disk // use data from disk
(() async { (() async {
// no room data available // no room data available
// close screen // close screen
router.pushReplacementNamed('home'); router.pushReplacementNamed('home');
})(); })();
return true; return true;
}, },
@ -89,9 +89,9 @@ class _NewRoomPageState extends State<NewRoomPage> {
void initState() { void initState() {
super.initState(); super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
if (isEditPage()) { if (isEditPage()) {
fetchInfo(); fetchInfo();
} }
}); });
} }
@ -102,235 +102,233 @@ class _NewRoomPageState extends State<NewRoomPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final textTheme = Theme.of(context) final textTheme = Theme.of(context)
.textTheme .textTheme
.apply(displayColor: Theme.of(context).colorScheme.onSurface); .apply(displayColor: Theme.of(context).colorScheme.onSurface);
double width = MediaQuery.of(context).size.width; double width = MediaQuery.of(context).size.width;
double height = MediaQuery.of(context).size.height; double height = MediaQuery.of(context).size.height;
double smallest = min(min(width, height), 400); double smallest = min(min(width, height), 400);
return showSpinner return showSpinner
? Scaffold( ? Scaffold(
body: Center( body: Center(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
const CircularProgressIndicator(), const CircularProgressIndicator(),
Text(AppLocalizations.of(context)!.loading, Text(AppLocalizations.of(context)!.loading,
style: textTheme.titleLarge), style: textTheme.titleLarge),
]))) ])))
: Scaffold( : Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text(isEditPage() title: Text(isEditPage()
? AppLocalizations.of(context)!.editRoomMetadata ? AppLocalizations.of(context)!.editRoomMetadata
: AppLocalizations.of(context)!.newRoom), : AppLocalizations.of(context)!.newRoom),
), ),
body: SingleChildScrollView( body: SingleChildScrollView(
child: Center( child: Center(
child: Padding( child: Padding(
padding: const EdgeInsets.all(14), padding: const EdgeInsets.all(14),
child: ConstrainedBox( child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 400), constraints: const BoxConstraints(maxWidth: 400),
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
IconButton( IconButton(
icon: SvgPicture.asset( icon: SvgPicture.asset(
_ctrIcon.img, _ctrIcon.img,
width: smallest * 0.3, width: smallest * 0.3,
height: smallest * 0.3, height: smallest * 0.3,
), ),
tooltip: AppLocalizations.of(context)! tooltip: AppLocalizations.of(context)!
.changeRoomIcon, .changeRoomIcon,
onPressed: () { onPressed: () {
showDialog( showDialog(
context: context, context: context,
builder: (ctx) => builder: (ctx) =>
RoomIconPicker(onSelect: (icon) { RoomIconPicker(onSelect: (icon) {
setState(() { setState(() {
_ctrIcon = icon; _ctrIcon = icon;
}); });
context.pop(); context.pop();
})); }));
}), }),
Padding( Padding(
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
child: TextField( child: TextField(
enabled: !isEditPage(), enabled: !isEditPage(),
controller: _ctrID, controller: _ctrID,
keyboardType: TextInputType.emailAddress, keyboardType: TextInputType.emailAddress,
decoration: InputDecoration( decoration: InputDecoration(
prefixIcon: const Icon(Icons.fact_check), prefixIcon: const Icon(Icons.fact_check),
labelText: AppLocalizations.of(context)! labelText: AppLocalizations.of(context)!
.inputRoomIdLabel, .inputRoomIdLabel,
hintText: AppLocalizations.of(context)! hintText: AppLocalizations.of(context)!
.inputRoomIdHint, .inputRoomIdHint,
helperText: AppLocalizations.of(context)! helperText: AppLocalizations.of(context)!
.inputRoomIdHelp, .inputRoomIdHelp,
border: const OutlineInputBorder(), border: const OutlineInputBorder(),
), ),
), ),
), ),
Padding( Padding(
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
child: TextField( child: TextField(
controller: _ctrName, controller: _ctrName,
keyboardType: TextInputType.name, keyboardType: TextInputType.name,
decoration: InputDecoration( decoration: InputDecoration(
prefixIcon: const Icon(Icons.badge), prefixIcon: const Icon(Icons.badge),
labelText: AppLocalizations.of(context)! labelText: AppLocalizations.of(context)!
.inputRoomNameLabel, .inputRoomNameLabel,
hintText: AppLocalizations.of(context)! hintText: AppLocalizations.of(context)!
.inputRoomNameHint, .inputRoomNameHint,
helperText: AppLocalizations.of(context)! helperText: AppLocalizations.of(context)!
.inputRoomNameHelp, .inputRoomNameHelp,
border: const OutlineInputBorder(), border: const OutlineInputBorder(),
), ),
), ),
), ),
Padding( Padding(
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
child: TextField( child: TextField(
controller: _ctrDescription, controller: _ctrDescription,
keyboardType: TextInputType.text, keyboardType: TextInputType.text,
decoration: InputDecoration( decoration: InputDecoration(
labelText: AppLocalizations.of(context)! labelText: AppLocalizations.of(context)!
.inputRoomDescriptionLabel, .inputRoomDescriptionLabel,
hintText: AppLocalizations.of(context)! hintText: AppLocalizations.of(context)!
.inputRoomDescriptionHint, .inputRoomDescriptionHint,
helperText: AppLocalizations.of(context)! helperText: AppLocalizations.of(context)!
.inputRoomDescriptionHelp, .inputRoomDescriptionHelp,
prefixIcon: const Icon(Icons.dns), prefixIcon: const Icon(Icons.dns),
border: const OutlineInputBorder(), border: const OutlineInputBorder(),
), ),
), ),
), ),
...(!isEditPage()) ...(!isEditPage())
? [ ? [
Text( Text(
AppLocalizations.of(context)! AppLocalizations.of(context)!
.roomVisibilityTitle, .roomVisibilityTitle,
style: textTheme.labelLarge), style: textTheme.labelLarge),
Text( Text(
AppLocalizations.of(context)! AppLocalizations.of(context)!
.roomVisibilitySubtitle, .roomVisibilitySubtitle,
style: textTheme.bodySmall), style: textTheme.bodySmall),
SegmentedButton<RoomVisibility>( SegmentedButton<RoomVisibility>(
showSelectedIcon: true, showSelectedIcon: true,
multiSelectionEnabled: false, multiSelectionEnabled: false,
emptySelectionAllowed: false, emptySelectionAllowed: false,
segments: segments:
RoomVisibility.list().map((vis) { RoomVisibility.list().map((vis) {
return ButtonSegment< return ButtonSegment<
RoomVisibility>( RoomVisibility>(
value: vis, value: vis,
label: Text(vis.text(context)), label: Text(vis.text(context)),
icon: Icon(vis.icon)); icon: Icon(vis.icon));
}).toList(), }).toList(),
onSelectionChanged: ((vset) { onSelectionChanged: ((vset) {
setState(() { setState(() {
_ctrVis = vset.single; _ctrVis = vset.single;
}); });
}), }),
selected: {_ctrVis}, selected: {_ctrVis},
selectedIcon: Icon(_ctrVis.icon), selectedIcon: Icon(_ctrVis.icon),
), ),
] ]
: [] : []
], ],
))))), ))))),
floatingActionButton: FloatingActionButton.extended( floatingActionButton: FloatingActionButton.extended(
onPressed: () async { onPressed: () async {
final scaffMgr = ScaffoldMessenger.of(context); final scaffMgr = ScaffoldMessenger.of(context);
final router = GoRouter.of(context); final router = GoRouter.of(context);
final trans = AppLocalizations.of(context); final trans = AppLocalizations.of(context);
// name may not be empty // name may not be empty
if (_ctrName.text.isEmpty) { if (_ctrName.text.isEmpty) {
showSimpleSnackbar(scaffMgr, showSimpleSnackbar(scaffMgr,
text: trans!.errorNoRoomName, action: trans.ok); text: trans!.errorNoRoomName, action: trans.ok);
return; return;
} }
final user = context.read<User>(); final user = context.read<User>();
if (isEditPage()) { if (isEditPage()) {
final nav = Navigator.of(context); final nav = Navigator.of(context);
Room clone = room!; Room clone = room!;
clone.name = _ctrName.text; clone.name = _ctrName.text;
clone.description = _ctrDescription.text; clone.description = _ctrDescription.text;
clone.icon = _ctrIcon; clone.icon = _ctrIcon;
doNetworkRequest(scaffMgr, doNetworkRequest(scaffMgr,
req: () => postWithCreadentials( req: () => postWithCreadentials(
target: user.server, target: user.server,
credentials: user, credentials: user,
path: 'changeRoomMeta', path: 'changeRoomMeta',
body: { body: {
'room': clone.id, 'room': clone.id,
'server': clone.serverTag, 'server': clone.serverTag,
'title': clone.name, 'title': clone.name,
'description': clone.description, 'description': clone.description,
'icon': clone.icon?.type, 'icon': clone.icon?.type,
}), }),
onOK: (_) async { onOK: (_) async {
// room was created // room was created
// save room // save room
await clone.toDisk(); await clone.toDisk();
nav.pop(); nav.pop();
}); });
} else {
// new room specific tests & request
} else { // ID should be at least three characters long
// new room specific tests & request if (_ctrID.text.length < 3) {
showSimpleSnackbar(scaffMgr,
text: _ctrID.text.isEmpty
? trans!.errorNoRoomId
: trans!.errorRoomIdLength,
action: trans.ok);
// ID should be at least three characters long return;
if (_ctrID.text.length < 3) { }
showSimpleSnackbar(scaffMgr,
text: _ctrID.text.isEmpty
? trans!.errorNoRoomId
: trans!.errorRoomIdLength,
action: trans.ok);
return; final room = Room(
} id: _ctrID.text,
serverTag: user.server.tag,
name: _ctrName.text,
description: _ctrDescription.text,
icon: _ctrIcon,
visibility: _ctrVis);
final room = Room( doNetworkRequest(scaffMgr,
id: _ctrID.text, req: () => postWithCreadentials(
serverTag: user.server.tag, target: user.server,
name: _ctrName.text, credentials: user,
description: _ctrDescription.text, path: 'createRoom',
icon: _ctrIcon, body: {
visibility: _ctrVis); 'room': room.id,
'title': room.name,
doNetworkRequest(scaffMgr, 'description': room.description,
req: () => postWithCreadentials( 'icon': room.icon?.type,
target: user.server, 'visibility': room.visibility?.type
credentials: user, }),
path: 'createRoom', onOK: (_) async {
body: { // room was created
'room': room.id, // save room
'title': room.name, await room.toDisk();
'description': room.description, // move to home page
'icon': room.icon?.type, router.pushReplacementNamed('home');
'visibility': room.visibility?.type });
}), }
onOK: (_) async { },
// room was created label: Text(isEditPage()
// save room ? AppLocalizations.of(context)!.editRoomMetadataShort
await room.toDisk(); : AppLocalizations.of(context)!.createRoomShort),
// move to home page icon: Icon(isEditPage() ? Icons.edit : Icons.add)),
router.pushReplacementNamed('home'); );
});
}
},
label: Text(isEditPage()
? AppLocalizations.of(context)!.editRoomMetadataShort
: AppLocalizations.of(context)!.createRoomShort),
icon: Icon(isEditPage() ? Icons.edit : Icons.add)),
);
} }
} }

View file

@ -24,19 +24,19 @@ class _AboutRoomPageState extends State<AboutRoomPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final textTheme = Theme.of(context) final textTheme = Theme.of(context)
.textTheme .textTheme
.apply(displayColor: Theme.of(context).colorScheme.onSurface); .apply(displayColor: Theme.of(context).colorScheme.onSurface);
double width = MediaQuery.of(context).size.width; double width = MediaQuery.of(context).size.width;
double height = MediaQuery.of(context).size.height; double height = MediaQuery.of(context).size.height;
double smallest = min(width, height); double smallest = min(width, height);
return SingleChildScrollView( return SingleChildScrollView(
child: Center( child: Center(
child: Column(children: [ child: Column(children: [
// room meta display // room meta display
...(widget.room != null) ...(widget.room != null)
? [ ? [
Padding( Padding(
padding: const EdgeInsets.all(14), padding: const EdgeInsets.all(14),
child: Column( child: Column(
@ -60,27 +60,27 @@ class _AboutRoomPageState extends State<AboutRoomPage> {
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
Padding( Padding(
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
child: SegmentedButton<int>( child: SegmentedButton<int>(
showSelectedIcon: true, showSelectedIcon: true,
multiSelectionEnabled: false, multiSelectionEnabled: false,
emptySelectionAllowed: false, emptySelectionAllowed: false,
segments: RoomVisibility.list().map((vis) { segments: RoomVisibility.list().map((vis) {
return ButtonSegment<int>( return ButtonSegment<int>(
value: vis.type, value: vis.type,
label: Text(vis.text(context)), label: Text(vis.text(context)),
icon: Icon(vis.icon)); icon: Icon(vis.icon));
}).toList(), }).toList(),
onSelectionChanged: ((vset) { onSelectionChanged: ((vset) {
// check permission // check permission
// only show confirm dialog when user // only show confirm dialog when user
// is admin, owner or has CHANGE_ADMIN permission // is admin, owner or has CHANGE_ADMIN permission
if (widget.info == null || if (widget.info == null ||
(!(widget.info?.isAdmin ?? false) && (!(widget.info?.isAdmin ?? false) &&
!(widget.info?.isOwner ?? false) && !(widget.info?.isOwner ?? false) &&
((widget.info?.permissions)! & ((widget.info?.permissions)! &
RoomPermission.ota == RoomPermission.ota ==
0))) { 0))) {
// action not permitted // action not permitted
// NOTE: no error dialog should be shown // NOTE: no error dialog should be shown
// because the action is supposed to be hidden // because the action is supposed to be hidden
@ -89,226 +89,247 @@ class _AboutRoomPageState extends State<AboutRoomPage> {
final vis = RoomVisibility(vset.first); final vis = RoomVisibility(vset.first);
showDialog( showDialog(
context: context, context: context,
builder: (ctx) => AlertDialog( builder: (ctx) => AlertDialog(
title: Text(AppLocalizations.of(context)!.changeRoomVisibilityTitle), title: Text(AppLocalizations.of(context)!
content: Text(AppLocalizations.of(context)!.changeRoomVisibilitySubtitle(vis.text(context))), .changeRoomVisibilityTitle),
actions: [ content: Text(
TextButton( AppLocalizations.of(context)!
onPressed: () { .changeRoomVisibilitySubtitle(
context.pop(); vis.text(context))),
}, actions: [
child: Text(AppLocalizations.of(context)!.cancel), TextButton(
), onPressed: () {
FilledButton( context.pop();
onPressed: () async {
final scaffMgr =
ScaffoldMessenger.of(context);
final nav = Navigator.of(context);
final user = context.read<User>();
doNetworkRequest(scaffMgr,
req: () => postWithCreadentials(
path: 'setVisibility',
target: user.server,
body: {
'room': widget.room?.id,
'server': (widget
.room?.serverTag)!,
'visibility': vset.first
}, },
credentials: user), child: Text(
onOK: (_) { AppLocalizations.of(context)!
Room r = widget.room!; .cancel),
r.visibility = vis; ),
r.toDisk(); FilledButton(
}, onPressed: () async {
after: () { final scaffMgr =
nav.pop(); ScaffoldMessenger.of(context);
}); final nav = Navigator.of(context);
}, final user = context.read<User>();
child: Text(AppLocalizations.of(context)!.ok),
) doNetworkRequest(scaffMgr,
], req: () => postWithCreadentials(
)); path: 'setVisibility',
}), target: user.server,
selected: {(widget.room?.visibility?.type)!}, body: {
selectedIcon: Icon((widget.room?.visibility?.icon)!), 'room': widget.room?.id,
)), 'server': (widget
.room?.serverTag)!,
'visibility': vset.first
},
credentials: user),
onOK: (_) {
Room r = widget.room!;
r.visibility = vis;
r.toDisk();
},
after: () {
nav.pop();
});
},
child: Text(
AppLocalizations.of(context)!.ok),
)
],
));
}),
selected: {(widget.room?.visibility?.type)!},
selectedIcon: Icon((widget.room?.visibility?.icon)!),
)),
], ],
), ),
) )
] ]
: [], : [],
Padding( Padding(
padding: const EdgeInsets.all(14), padding: const EdgeInsets.all(14),
child: Column( child: Column(
children: [ children: [
// edit room meta button // edit room meta button
...(widget.info != null && ...(widget.info != null &&
((widget.info?.isAdmin ?? false) || ((widget.info?.isAdmin ?? false) ||
(widget.info?.isOwner ?? false) || (widget.info?.isOwner ?? false) ||
((widget.info?.permissions)! & ((widget.info?.permissions)! &
RoomPermission.changeMeta != RoomPermission.changeMeta !=
0))) 0)))
? [ ? [
ListTile( ListTile(
trailing: const Icon(Icons.chevron_right), trailing: const Icon(Icons.chevron_right),
title: Text(AppLocalizations.of(context)!.editRoomMetadata), title: Text(
subtitle: Text(AppLocalizations.of(context)!.editRoomMetadataSubtitle), AppLocalizations.of(context)!.editRoomMetadata),
onTap: () { subtitle: Text(AppLocalizations.of(context)!
// show edit room screen .editRoomMetadataSubtitle),
context.goNamed('edit-room', params: { onTap: () {
// show edit room screen
context.goNamed('edit-room', params: {
'server': (widget.room?.serverTag)!, 'server': (widget.room?.serverTag)!,
'id': (widget.room?.id)! 'id': (widget.room?.id)!
}); });
}, },
), ),
] ]
: [], : [],
// open members view // open members view
ListTile( ListTile(
trailing: const Icon(Icons.chevron_right), trailing: const Icon(Icons.chevron_right),
title: Text(AppLocalizations.of(context)!.showRoomMembers), title: Text(AppLocalizations.of(context)!.showRoomMembers),
subtitle: Text(AppLocalizations.of(context)!.showRoomMembersSubtitle), subtitle:
onTap: () { Text(AppLocalizations.of(context)!.showRoomMembersSubtitle),
// open member view screen onTap: () {
context.goNamed('room-members', params: { // open member view screen
'server': (widget.room?.serverTag)!, context.goNamed('room-members', params: {
'id': (widget.room?.id)! 'server': (widget.room?.serverTag)!,
}); 'id': (widget.room?.id)!
}, });
), },
// edit default member permission ),
...(widget.info != null && // edit default member permission
((widget.info?.isAdmin ?? false) || ...(widget.info != null &&
(widget.info?.isOwner ?? false) || ((widget.info?.isAdmin ?? false) ||
((widget.info?.permissions)! & (widget.info?.isOwner ?? false) ||
RoomPermission.changeAdmin != ((widget.info?.permissions)! &
0))) RoomPermission.changeAdmin !=
0)))
? [ ? [
ListTile( ListTile(
trailing: const Icon(Icons.chevron_right), trailing: const Icon(Icons.chevron_right),
title: Text(AppLocalizations.of(context)!.editRoomPermissions), title: Text(
subtitle: Text(AppLocalizations.of(context)!.editRoomPermissionsSubtitle), AppLocalizations.of(context)!.editRoomPermissions),
onTap: () { subtitle: Text(AppLocalizations.of(context)!
// show checkbox screen .editRoomPermissionsSubtitle),
context.goNamed('room-permissions', params: { onTap: () {
// show checkbox screen
context.goNamed('room-permissions', params: {
'server': (widget.room?.serverTag)!, 'server': (widget.room?.serverTag)!,
'id': (widget.room?.id)! 'id': (widget.room?.id)!
}); });
}, },
), ),
] ]
: [], : [],
...(widget.info != null && ...(widget.info != null &&
((widget.info?.isAdmin ?? false) || ((widget.info?.isAdmin ?? false) ||
(widget.info?.isOwner ?? false) || (widget.info?.isOwner ?? false) ||
((widget.info?.permissions)! & RoomPermission.ota != ((widget.info?.permissions)! & RoomPermission.ota !=
0))) 0)))
? [ ? [
ListTile( ListTile(
trailing: const Icon(Icons.chevron_right), trailing: const Icon(Icons.chevron_right),
title: Text(AppLocalizations.of(context)!.manageRoomOTA), title:
subtitle: Text(AppLocalizations.of(context)!.manageRoomOTASubtitle), Text(AppLocalizations.of(context)!.manageRoomOTA),
onTap: () { subtitle: Text(AppLocalizations.of(context)!
// show manage ota screen .manageRoomOTASubtitle),
context.goNamed('room-ota', params: { onTap: () {
// show manage ota screen
context.goNamed('room-ota', params: {
'server': (widget.room?.serverTag)!, 'server': (widget.room?.serverTag)!,
'id': (widget.room?.id)! 'id': (widget.room?.id)!
}); });
}, },
), ),
ListTile( ListTile(
trailing: const Icon(Icons.chevron_right), trailing: const Icon(Icons.chevron_right),
title: Text(AppLocalizations.of(context)!.manageRoomInvites), title: Text(
subtitle: Text(AppLocalizations.of(context)!.manageRoomInvitesSubtitle), AppLocalizations.of(context)!.manageRoomInvites),
onTap: () { subtitle: Text(AppLocalizations.of(context)!
// show manage ota screen .manageRoomInvitesSubtitle),
context.goNamed('room-invite', params: { onTap: () {
// show manage ota screen
context.goNamed('room-invite', params: {
'server': (widget.room?.serverTag)!, 'server': (widget.room?.serverTag)!,
'id': (widget.room?.id)! 'id': (widget.room?.id)!
}); });
}, },
), ),
] ]
: [], : [],
], ],
)), )),
...(widget.info != null) ...(widget.info != null)
? [ ? [
Padding( Padding(
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
child: FilledButton.tonal( child: FilledButton.tonal(
child: Text(((widget.info?.isOwner)!) child: Text(((widget.info?.isOwner)!)
? AppLocalizations.of(context)!.deleteRoom ? AppLocalizations.of(context)!.deleteRoom
: AppLocalizations.of(context)!.leaveRoom), : AppLocalizations.of(context)!.leaveRoom),
onPressed: () { onPressed: () {
// show confirm dialog // show confirm dialog
showDialog( showDialog(
context: context, context: context,
builder: (ctx) => AlertDialog( builder: (ctx) => AlertDialog(
title: Text(((widget.info?.isOwner)!) title: Text(((widget.info?.isOwner)!)
? AppLocalizations.of(context)!.deleteRoom ? AppLocalizations.of(context)!.deleteRoom
: AppLocalizations.of(context)!.leaveRoom), : AppLocalizations.of(context)!.leaveRoom),
content: Text(((widget.info?.isOwner)!) content: Text(((widget.info?.isOwner)!)
? AppLocalizations.of(context)!.deleteRoomConfirm ? AppLocalizations.of(context)!
: AppLocalizations.of(context)!.leaveRoomConfirm), .deleteRoomConfirm
actions: [ : AppLocalizations.of(context)!
TextButton( .leaveRoomConfirm),
onPressed: () { actions: [
// close popup TextButton(
Navigator.of(ctx).pop(); onPressed: () {
}, // close popup
child: Text(AppLocalizations.of(context)!.cancel), Navigator.of(ctx).pop();
), },
FilledButton( child: Text(
onPressed: () async { AppLocalizations.of(context)!.cancel),
// send request ),
final scaffMgr = FilledButton(
ScaffoldMessenger.of(ctx); onPressed: () async {
final nav = Navigator.of(ctx); // send request
final router = GoRouter.of(context); final scaffMgr =
final user = context.read<User>(); ScaffoldMessenger.of(ctx);
final nav = Navigator.of(ctx);
final router = GoRouter.of(context);
final user = context.read<User>();
doNetworkRequest(scaffMgr, doNetworkRequest(scaffMgr,
req: () => postWithCreadentials( req: () => postWithCreadentials(
path: ((widget.info?.isOwner)!) path: ((widget.info?.isOwner)!)
? 'deleteRoom' ? 'deleteRoom'
: 'leaveRoom', : 'leaveRoom',
target: user.server, target: user.server,
body: { body: {
'room': widget.room?.id, 'room': widget.room?.id,
'server': 'server':
(widget.room?.serverTag)!, (widget.room?.serverTag)!,
}, },
credentials: user), credentials: user),
onOK: (_) async { onOK: (_) async {
// try delete room from disk // try delete room from disk
try { try {
await widget.room?.removeDisk(); await widget.room?.removeDisk();
} catch (_) {} } catch (_) {}
// go back home // go back home
router.pushReplacementNamed('home'); router.pushReplacementNamed('home');
}, },
after: () { after: () {
// close popup // close popup
nav.pop(); nav.pop();
}); });
}, },
child: Text(((widget.info?.isOwner)!) child: Text(((widget.info?.isOwner)!)
? AppLocalizations.of(context)!.deleteRoomShort ? AppLocalizations.of(context)!
: AppLocalizations.of(context)!.leaveRoomShort), .deleteRoomShort
) : AppLocalizations.of(context)!
], .leaveRoomShort),
)); )
}, ],
)) ));
},
))
] ]
: [], : [],
]))); ])));
} }
} }

View file

@ -26,7 +26,7 @@ class _RoomCategoriesPageState extends State<RoomCategoriesPage> {
super.initState(); super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
fetchCategories(); fetchCategories();
}); });
} }
@ -36,29 +36,29 @@ class _RoomCategoriesPageState extends State<RoomCategoriesPage> {
// TODO: load cached rooms // TODO: load cached rooms
doNetworkRequest(ScaffoldMessenger.of(context), doNetworkRequest(ScaffoldMessenger.of(context),
req: () => postWithCreadentials( req: () => postWithCreadentials(
credentials: user, credentials: user,
target: user.server, target: user.server,
path: 'getCategories', path: 'getCategories',
body: {'room': widget.room?.id, 'server': widget.room?.serverTag}), body: {'room': widget.room?.id, 'server': widget.room?.serverTag}),
onOK: (json) { onOK: (json) {
final resp = json['data'] final resp = json['data']
.map<RoomCategory>((raw) => RoomCategory.fromJSON(raw)) .map<RoomCategory>((raw) => RoomCategory.fromJSON(raw))
.toList(); .toList();
if (mounted) { if (mounted) {
setState(() { setState(() {
list = resp; list = resp;
}); });
} }
}); });
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final textTheme = Theme.of(context) final textTheme = Theme.of(context)
.textTheme .textTheme
.apply(displayColor: Theme.of(context).colorScheme.onSurface); .apply(displayColor: Theme.of(context).colorScheme.onSurface);
return Scaffold( return Scaffold(
body: ReorderableListView.builder( body: ReorderableListView.builder(
@ -70,13 +70,13 @@ class _RoomCategoriesPageState extends State<RoomCategoriesPage> {
key: Key('cat-${item.id}'), key: Key('cat-${item.id}'),
leading: Icon(Icons.square_rounded, color: item.color), leading: Icon(Icons.square_rounded, color: item.color),
trailing: ((widget.info?.isAdmin ?? false) || trailing: ((widget.info?.isAdmin ?? false) ||
(widget.info?.isOwner ?? false) || (widget.info?.isOwner ?? false) ||
((widget.info?.permissions)! & ((widget.info?.permissions)! &
RoomPermission.editRoomContent != RoomPermission.editRoomContent !=
0)) 0))
? ReorderableDragStartListener( ? ReorderableDragStartListener(
index: index, child: const Icon(Icons.drag_handle)) index: index, child: const Icon(Icons.drag_handle))
: null, : null,
title: Text(item.name), title: Text(item.name),
onTap: () { onTap: () {
// TODO show edit category popup // TODO show edit category popup
@ -86,8 +86,8 @@ class _RoomCategoriesPageState extends State<RoomCategoriesPage> {
if (!((widget.info?.isAdmin ?? false) || if (!((widget.info?.isAdmin ?? false) ||
(widget.info?.isOwner ?? false) || (widget.info?.isOwner ?? false) ||
((widget.info?.permissions)! & ((widget.info?.permissions)! &
RoomPermission.editRoomContent != RoomPermission.editRoomContent !=
0))) { 0))) {
// user is not allowed to edit or delete categories // user is not allowed to edit or delete categories
return; return;
} }
@ -96,104 +96,104 @@ class _RoomCategoriesPageState extends State<RoomCategoriesPage> {
context: context, context: context,
builder: (context) => BottomSheet( builder: (context) => BottomSheet(
builder: (context) => Column(children: [ builder: (context) => Column(children: [
Padding( Padding(
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
Icon(Icons.square_rounded, Icon(Icons.square_rounded,
size: 48.0, color: item.color), size: 48.0, color: item.color),
Text(item.name, style: textTheme.titleLarge) Text(item.name, style: textTheme.titleLarge)
], ],
)), )),
// edit category // edit category
ListTile( ListTile(
leading: const Icon(Icons.edit), leading: const Icon(Icons.edit),
title: Text(AppLocalizations.of(context)!.editCategory), title: Text(AppLocalizations.of(context)!.editCategory),
subtitle: subtitle:
Text(AppLocalizations.of(context)!.editCategoryLong), Text(AppLocalizations.of(context)!.editCategoryLong),
trailing: const Icon(Icons.chevron_right), trailing: const Icon(Icons.chevron_right),
onTap: () { onTap: () {
// close the modal bottom sheet // close the modal bottom sheet
// so the user returns to the list, // so the user returns to the list,
// when leaving the category editor // when leaving the category editor
Navigator.of(context).pop(); Navigator.of(context).pop();
// launch category editor // launch category editor
context.pushNamed('edit-category', params: { context.pushNamed('edit-category', params: {
'server': widget.room!.serverTag, 'server': widget.room!.serverTag,
'id': widget.room!.id, 'id': widget.room!.id,
'category': item.id.toString() 'category': item.id.toString()
}); });
}, },
), ),
// delete category // delete category
ListTile( ListTile(
leading: const Icon(Icons.delete), leading: const Icon(Icons.delete),
title: Text(AppLocalizations.of(context)!.deleteCategory), title: Text(AppLocalizations.of(context)!.deleteCategory),
subtitle: Text( subtitle: Text(
AppLocalizations.of(context)!.deleteCategoryLong), AppLocalizations.of(context)!.deleteCategoryLong),
trailing: const Icon(Icons.chevron_right), trailing: const Icon(Icons.chevron_right),
onTap: () { onTap: () {
// show popup // show popup
showDialog( showDialog(
context: context, context: context,
builder: (ctx) => AlertDialog( builder: (ctx) => AlertDialog(
icon: const Icon(Icons.delete), icon: const Icon(Icons.delete),
title: Text(AppLocalizations.of(context)! title: Text(AppLocalizations.of(context)!
.deleteCategory), .deleteCategory),
content: Text(AppLocalizations.of(context)! content: Text(AppLocalizations.of(context)!
.deleteCategoryConfirm(item.name)), .deleteCategoryConfirm(item.name)),
actions: [ actions: [
TextButton( TextButton(
onPressed: () { onPressed: () {
// close popup
Navigator.of(ctx).pop();
},
child: Text(
AppLocalizations.of(context)!.cancel),
),
FilledButton(
onPressed: () async {
// send request
final scaffMgr =
ScaffoldMessenger.of(ctx);
// popup context
final navInner = Navigator.of(ctx);
// bottomsheet context
final nav = Navigator.of(context);
final user = context.read<User>();
doNetworkRequest(scaffMgr,
req: () => postWithCreadentials(
path: 'deleteCategory',
target: user.server,
body: {
'room': widget.room?.id,
'server':
widget.room?.serverTag,
'listCatID': item.id
},
credentials: user),
onOK: (_) async {
// TODO: remove cached category
fetchCategories();
},
after: () {
// close popup // close popup
navInner.pop(); Navigator.of(ctx).pop();
// close modal bottom sheet },
nav.pop(); child: Text(
}); AppLocalizations.of(context)!.cancel),
}, ),
child: Text(AppLocalizations.of(context)! FilledButton(
.deleteCategory), onPressed: () async {
) // send request
], final scaffMgr =
)); ScaffoldMessenger.of(ctx);
}, // popup context
), final navInner = Navigator.of(ctx);
// bottomsheet context
final nav = Navigator.of(context);
final user = context.read<User>();
doNetworkRequest(scaffMgr,
req: () => postWithCreadentials(
path: 'deleteCategory',
target: user.server,
body: {
'room': widget.room?.id,
'server':
widget.room?.serverTag,
'listCatID': item.id
},
credentials: user),
onOK: (_) async {
// TODO: remove cached category
fetchCategories();
},
after: () {
// close popup
navInner.pop();
// close modal bottom sheet
nav.pop();
});
},
child: Text(AppLocalizations.of(context)!
.deleteCategory),
)
],
));
},
),
]), ]),
onClosing: () {}, onClosing: () {},
), ),
@ -206,50 +206,52 @@ class _RoomCategoriesPageState extends State<RoomCategoriesPage> {
if (!((widget.info?.isAdmin ?? false) || if (!((widget.info?.isAdmin ?? false) ||
(widget.info?.isOwner ?? false) || (widget.info?.isOwner ?? false) ||
((widget.info?.permissions)! & RoomPermission.editRoomContent != ((widget.info?.permissions)! & RoomPermission.editRoomContent !=
0))) { 0))) {
// user is not allowed to edit or delete categories // user is not allowed to edit or delete categories
return; return;
} }
setState(() { setState(() {
if (oldIndex < newIndex) { if (oldIndex < newIndex) {
newIndex -= 1; newIndex -= 1;
} }
final item = list.removeAt(oldIndex); final item = list.removeAt(oldIndex);
list.insert(newIndex, item); list.insert(newIndex, item);
// network request // network request
final user = context.read<User>(); final user = context.read<User>();
doNetworkRequest(ScaffoldMessenger.of(context), doNetworkRequest(ScaffoldMessenger.of(context),
req: () => postWithCreadentials( req: () => postWithCreadentials(
credentials: user, credentials: user,
target: user.server, target: user.server,
path: 'changeCategoriesOrder', path: 'changeCategoriesOrder',
body: { body: {
'room': widget.room?.id, 'room': widget.room?.id,
'server': widget.room?.serverTag, 'server': widget.room?.serverTag,
'listCatIDs': list.map((item) => item.id).toList() 'listCatIDs': list.map((item) => item.id).toList()
})); }));
}); });
}, },
), ),
floatingActionButton: (widget.info != null && ((widget.info?.isAdmin ?? false) || floatingActionButton: (widget.info != null &&
(widget.info?.isOwner ?? false) || ((widget.info?.isAdmin ?? false) ||
((widget.info?.permissions)! & RoomPermission.editRoomContent != (widget.info?.isOwner ?? false) ||
0))) ((widget.info?.permissions)! &
? FloatingActionButton.extended( RoomPermission.editRoomContent !=
icon: const Icon(Icons.add), 0)))
label: Text(AppLocalizations.of(context)!.newCategoryShort), ? FloatingActionButton.extended(
tooltip: AppLocalizations.of(context)!.newCategoryLong, icon: const Icon(Icons.add),
onPressed: () { label: Text(AppLocalizations.of(context)!.newCategoryShort),
// show new category popup tooltip: AppLocalizations.of(context)!.newCategoryLong,
context.pushNamed('new-category', params: { onPressed: () {
'server': widget.room!.serverTag, // show new category popup
'id': widget.room!.id, context.pushNamed('new-category', params: {
}); 'server': widget.room!.serverTag,
}, 'id': widget.room!.id,
) });
: null, },
)
: null,
); );
} }
} }

View file

@ -36,74 +36,74 @@ class _ShoppingListPageState extends State<ShoppingListPage> {
// TODO: load cached items first // TODO: load cached items first
doNetworkRequest(ScaffoldMessenger.of(context), doNetworkRequest(ScaffoldMessenger.of(context),
req: () => postWithCreadentials( req: () => postWithCreadentials(
credentials: user, credentials: user,
target: user.server, target: user.server,
path: 'getItems', path: 'getItems',
body: {'room': widget.room?.id, 'server': widget.room?.serverTag}), body: {'room': widget.room?.id, 'server': widget.room?.serverTag}),
onOK: (body) async { onOK: (body) async {
final resp = body['data'] final resp = body['data']
.map<RoomItem>((raw) => RoomItem.fromJSON(raw)) .map<RoomItem>((raw) => RoomItem.fromJSON(raw))
.toList(); .toList();
final List<RoomItem> l = []; final List<RoomItem> l = [];
final List<RoomItem> c = []; final List<RoomItem> c = [];
for (RoomItem item in resp) { for (RoomItem item in resp) {
if (item.state == 0) { if (item.state == 0) {
l.add(item); l.add(item);
} else { } else {
c.add(item); c.add(item);
}
} }
}
// TODO: cache items // TODO: cache items
if (mounted) { if (mounted) {
setState(() { setState(() {
list = l; list = l;
cart = c; cart = c;
sortAll(); sortAll();
}); });
} }
}); });
} }
void sortAll() { void sortAll() {
for (List<RoomItem> input in [list, cart]) { for (List<RoomItem> input in [list, cart]) {
setState(() { setState(() {
input.sort((a, b) { input.sort((a, b) {
if (a.category == b.category) { if (a.category == b.category) {
return 0; return 0;
} }
if (a.category == null) { if (a.category == null) {
// b should be below // b should be below
return -1; return -1;
} }
if (b.category == null) { if (b.category == null) {
// a should be below // a should be below
return 1; return 1;
} }
final weightA = weights[a.category]; final weightA = weights[a.category];
final weightB = weights[b.category]; final weightB = weights[b.category];
// both could be null now, // both could be null now,
// so we have to check agein // so we have to check agein
if (weightA == weightB) { if (weightA == weightB) {
return 0; return 0;
} }
if (weightA == null) { if (weightA == null) {
// b should be below // b should be below
return -1; return -1;
} }
if (weightB == null) { if (weightB == null) {
// a should be below // a should be below
return 1; return 1;
} }
return weightA.compareTo(weightB); return weightA.compareTo(weightB);
}); });
}); });
} }
} }
@ -114,31 +114,31 @@ class _ShoppingListPageState extends State<ShoppingListPage> {
// TODO: load cached categories first // TODO: load cached categories first
doNetworkRequest(ScaffoldMessenger.of(context), doNetworkRequest(ScaffoldMessenger.of(context),
req: () => postWithCreadentials( req: () => postWithCreadentials(
credentials: user, credentials: user,
target: user.server, target: user.server,
path: 'getCategories', path: 'getCategories',
body: {'room': widget.room?.id, 'server': widget.room?.serverTag}), body: {'room': widget.room?.id, 'server': widget.room?.serverTag}),
onOK: (body) async { onOK: (body) async {
final resp = body['data'] final resp = body['data']
.map<RoomCategory>((raw) => RoomCategory.fromJSON(raw)) .map<RoomCategory>((raw) => RoomCategory.fromJSON(raw))
.toList(); .toList();
Map<int, int> map = {}; Map<int, int> map = {};
Map<int?, RoomCategory> cat = {}; Map<int?, RoomCategory> cat = {};
for (int i = 0; i < resp.length; i++) { for (int i = 0; i < resp.length; i++) {
map[resp[i].id] = i; map[resp[i].id] = i;
cat[resp[i].id] = resp[i]; cat[resp[i].id] = resp[i];
} }
if (mounted) { if (mounted) {
setState(() { setState(() {
weights = map; weights = map;
categories = cat; categories = cat;
sortAll(); sortAll();
}); });
} }
}); });
} }
void fetchProducts() { void fetchProducts() {
@ -147,22 +147,22 @@ class _ShoppingListPageState extends State<ShoppingListPage> {
// TODO: load cached products first // TODO: load cached products first
doNetworkRequest(ScaffoldMessenger.of(context), doNetworkRequest(ScaffoldMessenger.of(context),
req: () => postWithCreadentials( req: () => postWithCreadentials(
credentials: user, credentials: user,
target: user.server, target: user.server,
path: 'getProducts', path: 'getProducts',
body: {'room': widget.room?.id, 'server': widget.room?.serverTag}), body: {'room': widget.room?.id, 'server': widget.room?.serverTag}),
onOK: (body) async { onOK: (body) async {
final resp = body['data'] final resp = body['data']
.map<RoomProduct>((raw) => RoomProduct.fromJSON(raw)) .map<RoomProduct>((raw) => RoomProduct.fromJSON(raw))
.toList(); .toList();
if (mounted) { if (mounted) {
setState(() { setState(() {
products = resp; products = resp;
}); });
} }
}); });
} }
@override @override
@ -170,41 +170,41 @@ class _ShoppingListPageState extends State<ShoppingListPage> {
super.initState(); super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
fetchItems(); fetchItems();
fetchCategories(); fetchCategories();
fetchProducts(); fetchProducts();
}); });
} }
void changeItemState(RoomItem item) { void changeItemState(RoomItem item) {
final user = context.read<User>(); final user = context.read<User>();
doNetworkRequest(ScaffoldMessenger.of(context), doNetworkRequest(ScaffoldMessenger.of(context),
req: () => postWithCreadentials( req: () => postWithCreadentials(
credentials: user, credentials: user,
target: user.server, target: user.server,
path: 'changeItemState', path: 'changeItemState',
body: { body: {
'room': widget.room?.id, 'room': widget.room?.id,
'server': widget.room?.serverTag, 'server': widget.room?.serverTag,
'listItemID': item.id, 'listItemID': item.id,
'state': item.state 'state': item.state
})); }));
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
body: ListView(children: [ body: ListView(children: [
LabeledDivider(AppLocalizations.of(context)!.shoppingList), LabeledDivider(AppLocalizations.of(context)!.shoppingList),
ListView.builder( ListView.builder(
shrinkWrap: true, shrinkWrap: true,
physics: const ClampingScrollPhysics(), physics: const ClampingScrollPhysics(),
itemCount: list.length, itemCount: list.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final item = list[index]; final item = list[index];
final cat = final cat =
categories[item.category] ?? RoomCategory.other(context); categories[item.category] ?? RoomCategory.other(context);
return ShoppingListItem( return ShoppingListItem(
name: item.name, name: item.name,
description: item.description, description: item.description,
category: cat, category: cat,
@ -215,9 +215,9 @@ class _ShoppingListPageState extends State<ShoppingListPage> {
item.state = 1; item.state = 1;
setState(() { setState(() {
list.removeAt(index); list.removeAt(index);
cart.add(item); cart.add(item);
sortAll(); sortAll();
}); });
// network request // network request
@ -235,28 +235,28 @@ class _ShoppingListPageState extends State<ShoppingListPage> {
// - delete item (if allowed) // - delete item (if allowed)
// - move to/from shopping cart? // - move to/from shopping cart?
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
builder: (context) => ShoppingListItemInfo( builder: (context) => ShoppingListItemInfo(
products: products, products: products,
category: cat, category: cat,
info: widget.info, info: widget.info,
item: item, item: item,
room: widget.room!.id, room: widget.room!.id,
server: widget.room!.serverTag)); server: widget.room!.serverTag));
}); });
}, },
), ),
LabeledDivider(AppLocalizations.of(context)!.shoppingCart), LabeledDivider(AppLocalizations.of(context)!.shoppingCart),
ListView.builder( ListView.builder(
itemCount: cart.length, itemCount: cart.length,
shrinkWrap: true, shrinkWrap: true,
physics: const ClampingScrollPhysics(), physics: const ClampingScrollPhysics(),
itemBuilder: (context, index) { itemBuilder: (context, index) {
final item = cart[index]; final item = cart[index];
final cat = final cat =
categories[item.category] ?? RoomCategory.other(context); categories[item.category] ?? RoomCategory.other(context);
return ShoppingListItem( return ShoppingListItem(
name: item.name, name: item.name,
description: item.description, description: item.description,
category: cat, category: cat,
@ -266,9 +266,9 @@ class _ShoppingListPageState extends State<ShoppingListPage> {
// move back to list // move back to list
item.state = 0; item.state = 0;
setState(() { setState(() {
cart.removeAt(index); cart.removeAt(index);
list.add(item); list.add(item);
sortAll(); sortAll();
}); });
// network request // network request
@ -286,37 +286,37 @@ class _ShoppingListPageState extends State<ShoppingListPage> {
// - delete item (if allowed) // - delete item (if allowed)
// - move to/from shopping cart? // - move to/from shopping cart?
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
builder: (context) => ShoppingListItemInfo( builder: (context) => ShoppingListItemInfo(
products: products, products: products,
category: cat, category: cat,
item: item, item: item,
info: widget.info, info: widget.info,
room: widget.room!.id, room: widget.room!.id,
server: widget.room!.serverTag)); server: widget.room!.serverTag));
}); });
}, },
) )
]), ]),
floatingActionButton: (widget.info != null && floatingActionButton: (widget.info != null &&
((widget.info?.isAdmin ?? false) || ((widget.info?.isAdmin ?? false) ||
(widget.info?.isOwner ?? false) || (widget.info?.isOwner ?? false) ||
((widget.info?.permissions)! & ((widget.info?.permissions)! &
RoomPermission.addShoppingListItems != RoomPermission.addShoppingListItems !=
0))) 0)))
? FloatingActionButton.extended( ? FloatingActionButton.extended(
icon: const Icon(Icons.add), icon: const Icon(Icons.add),
label: Text(AppLocalizations.of(context)!.newItemShort), label: Text(AppLocalizations.of(context)!.newItemShort),
tooltip: AppLocalizations.of(context)!.newItemLong, tooltip: AppLocalizations.of(context)!.newItemLong,
onPressed: () { onPressed: () {
// show new category popup // show new category popup
context.pushNamed('new-item', params: { context.pushNamed('new-item', params: {
'server': widget.room!.serverTag, 'server': widget.room!.serverTag,
'id': widget.room!.id, 'id': widget.room!.id,
}); });
}, },
) )
: null, : null,
); );
} }
} }
@ -331,14 +331,14 @@ class ShoppingListItem extends StatelessWidget {
final Function()? onTap; final Function()? onTap;
const ShoppingListItem( const ShoppingListItem(
{required this.name, {required this.name,
required this.category, required this.category,
required this.inCart, required this.inCart,
required this.description, required this.description,
required key, required key,
this.onDismiss, this.onDismiss,
this.onTap}) this.onTap})
: _key = key; : _key = key;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -360,21 +360,21 @@ class ShoppingListItem extends StatelessWidget {
return true; return true;
}, },
background: background:
Icon(!inCart ? Icons.shopping_cart : Icons.remove_shopping_cart), Icon(!inCart ? Icons.shopping_cart : Icons.remove_shopping_cart),
child: Opacity( child: Opacity(
opacity: inCart ? 0.5 : 1.0, opacity: inCart ? 0.5 : 1.0,
child: ListTile( child: ListTile(
title: Text(name), title: Text(name),
subtitle: Text(description), subtitle: Text(description),
trailing: CategoryChip( trailing: CategoryChip(
category: category, category: category,
), ),
onTap: () { onTap: () {
if (onTap != null) { if (onTap != null) {
onTap!(); onTap!();
} }
}, },
)), )),
); );
} }
} }
@ -388,7 +388,7 @@ class ShoppingListItemInfo extends StatelessWidget {
final List<RoomProduct> products; final List<RoomProduct> products;
const ShoppingListItemInfo( const ShoppingListItemInfo(
{super.key, {super.key,
this.info, this.info,
required this.item, required this.item,
required this.server, required this.server,
@ -404,88 +404,88 @@ class ShoppingListItemInfo extends StatelessWidget {
builder: (context) => Column( builder: (context) => Column(
children: [ children: [
Padding( Padding(
padding: const EdgeInsets.all(14), padding: const EdgeInsets.all(14),
child: Center( child: Center(
child: Column(children: [ child: Column(children: [
Text(item.name, style: textTheme.headlineLarge), Text(item.name, style: textTheme.headlineLarge),
Text(item.description, style: textTheme.titleMedium), Text(item.description, style: textTheme.titleMedium),
CategoryChip( CategoryChip(
category: category, category: category,
), ),
Text(Unit.fromId(item.unit).display(context, item.value)) Text(Unit.fromId(item.unit).display(context, item.value))
]))), ]))),
...(item.link != null) ...(item.link != null)
? [ ? [
ListTile( ListTile(
title: Text(AppLocalizations.of(context)! title: Text(AppLocalizations.of(context)!
.itemShowLinkedProductTitle), .itemShowLinkedProductTitle),
subtitle: Text(AppLocalizations.of(context)! subtitle: Text(AppLocalizations.of(context)!
.itemShowLinkedProductSubtitle), .itemShowLinkedProductSubtitle),
trailing: const Icon(Icons.chevron_right), trailing: const Icon(Icons.chevron_right),
onTap: () { onTap: () {
// launch "view-product" page for specific product // launch "view-product" page for specific product
context.pushNamed('view-product', params: { context.pushNamed('view-product', params: {
'server': server, 'server': server,
'id': room, 'id': room,
'product': item.link.toString() 'product': item.link.toString()
}); });
}, },
) )
] ]
: [], : [],
...(info != null && ...(info != null &&
((info?.isAdmin ?? false) || ((info?.isAdmin ?? false) ||
(info?.isOwner ?? false) || (info?.isOwner ?? false) ||
((info?.permissions)! & ((info?.permissions)! &
RoomPermission.addShoppingListItems != RoomPermission.addShoppingListItems !=
0))) 0)))
? [ ? [
ListTile( ListTile(
title: Text(AppLocalizations.of(context)!.editItem), title: Text(AppLocalizations.of(context)!.editItem),
subtitle: Text(AppLocalizations.of(context)!.editItemLong), subtitle: Text(AppLocalizations.of(context)!.editItemLong),
trailing: const Icon(Icons.chevron_right), trailing: const Icon(Icons.chevron_right),
onTap: () { onTap: () {
context.pushNamed('edit-product', params: { context.pushNamed('edit-product', params: {
'server': server, 'server': server,
'id': room, 'id': room,
'item': item.id.toString() 'item': item.id.toString()
}); });
}, },
), ),
ListTile( ListTile(
title: title:
Text(AppLocalizations.of(context)!.deleteItemTitle), Text(AppLocalizations.of(context)!.deleteItemTitle),
subtitle: Text( subtitle: Text(
AppLocalizations.of(context)!.deleteItemSubtitle), AppLocalizations.of(context)!.deleteItemSubtitle),
trailing: const Icon(Icons.chevron_right), trailing: const Icon(Icons.chevron_right),
onTap: () { onTap: () {
// TODO: show confirm dialog // TODO: show confirm dialog
}), }),
] ]
: [], : [],
ListTile( ListTile(
title: Text(item.state == 0 title: Text(item.state == 0
? AppLocalizations.of(context)!.moveItemToCartTitle ? AppLocalizations.of(context)!.moveItemToCartTitle
: AppLocalizations.of(context)!.moveItemToCartSubtitle), : AppLocalizations.of(context)!.moveItemToCartSubtitle),
subtitle: Text(item.state == 0 subtitle: Text(item.state == 0
? AppLocalizations.of(context)!.removeItemFromCartTitle ? AppLocalizations.of(context)!.removeItemFromCartTitle
: AppLocalizations.of(context)!.removeItemFromCartSubtitle), : AppLocalizations.of(context)!.removeItemFromCartSubtitle),
onTap: () { onTap: () {
// flip state // flip state
item.state = (item.state - 1).abs(); item.state = (item.state - 1).abs();
final user = context.read<User>(); final user = context.read<User>();
doNetworkRequest(ScaffoldMessenger.of(context), doNetworkRequest(ScaffoldMessenger.of(context),
req: () => postWithCreadentials( req: () => postWithCreadentials(
credentials: user, credentials: user,
target: user.server, target: user.server,
path: 'changeItemState', path: 'changeItemState',
body: { body: {
'room': room, 'room': room,
'server': server, 'server': server,
'listItemID': item.id, 'listItemID': item.id,
'state': item.state 'state': item.state
})); }));
}) })
], ],
), ),
); );

View file

@ -25,24 +25,24 @@ class _RoomProductsPageState extends State<RoomProductsPage> {
final user = context.read<User>(); final user = context.read<User>();
doNetworkRequest(ScaffoldMessenger.of(context), doNetworkRequest(ScaffoldMessenger.of(context),
req: () => postWithCreadentials( req: () => postWithCreadentials(
credentials: user, credentials: user,
target: user.server, target: user.server,
path: 'getProducts', path: 'getProducts',
body: {'room': widget.room?.id, 'server': widget.room?.serverTag}), body: {'room': widget.room?.id, 'server': widget.room?.serverTag}),
onOK: (body) async { onOK: (body) async {
final resp = body['data'] final resp = body['data']
.map<RoomProduct>((raw) => RoomProduct.fromJSON(raw)) .map<RoomProduct>((raw) => RoomProduct.fromJSON(raw))
.toList(); .toList();
// TODO: cache products // TODO: cache products
if (mounted) { if (mounted) {
setState(() { setState(() {
products = resp; products = resp;
}); });
} }
}); });
} }
@override @override
@ -73,31 +73,33 @@ class _RoomProductsPageState extends State<RoomProductsPage> {
// where as reading the shopping item description, // where as reading the shopping item description,
// might be a good idea // might be a good idea
context.pushNamed('view-product', params: { context.pushNamed('view-product', params: {
'server': widget.room!.serverTag, 'server': widget.room!.serverTag,
'id': widget.room!.id, 'id': widget.room!.id,
'product': item.id.toString() 'product': item.id.toString()
}); });
}, },
); );
}, },
), ),
floatingActionButton: (widget.info != null && ((widget.info?.isAdmin ?? false) || floatingActionButton: (widget.info != null &&
(widget.info?.isOwner ?? false) || ((widget.info?.isAdmin ?? false) ||
((widget.info?.permissions)! & RoomPermission.editRoomContent != (widget.info?.isOwner ?? false) ||
0))) ((widget.info?.permissions)! &
? FloatingActionButton.extended( RoomPermission.editRoomContent !=
icon: const Icon(Icons.add), 0)))
label: Text(AppLocalizations.of(context)!.newProductShort), ? FloatingActionButton.extended(
tooltip: AppLocalizations.of(context)!.newProductLong, icon: const Icon(Icons.add),
onPressed: () { label: Text(AppLocalizations.of(context)!.newProductShort),
// show new category popup tooltip: AppLocalizations.of(context)!.newProductLong,
context.pushNamed('new-product', params: { onPressed: () {
'server': widget.room!.serverTag, // show new category popup
'id': widget.room!.id, context.pushNamed('new-product', params: {
}); 'server': widget.room!.serverTag,
}, 'id': widget.room!.id,
) });
: null, },
)
: null,
); );
} }
} }

View file

@ -15,7 +15,7 @@ class ViewProductPage extends StatefulWidget {
final String server; final String server;
final String room; final String room;
const ViewProductPage( const ViewProductPage(
{required this.server, {required this.server,
required this.room, required this.room,
required this.product, required this.product,
super.key}); super.key});
@ -39,14 +39,14 @@ class _ViewProductPageState extends State<ViewProductPage> {
doNetworkRequest( doNetworkRequest(
sm, sm,
req: () => postWithCreadentials( req: () => postWithCreadentials(
path: 'getRoomInfo', path: 'getRoomInfo',
credentials: user, credentials: user,
target: user.server, target: user.server,
body: {'room': widget.room, 'server': widget.server}), body: {'room': widget.room, 'server': widget.server}),
onOK: (body) async { onOK: (body) async {
final info = RoomInfo.fromJSON(body['data']); final info = RoomInfo.fromJSON(body['data']);
setState(() { setState(() {
this.info = info; this.info = info;
}); });
return true; return true;
}, },
@ -59,25 +59,25 @@ class _ViewProductPageState extends State<ViewProductPage> {
// TODO: load cached categories first // TODO: load cached categories first
doNetworkRequest(ScaffoldMessenger.of(context), doNetworkRequest(ScaffoldMessenger.of(context),
req: () => postWithCreadentials( req: () => postWithCreadentials(
credentials: user, credentials: user,
target: user.server, target: user.server,
path: 'getCategories', path: 'getCategories',
body: {'room': widget.room, 'server': widget.server}), body: {'room': widget.room, 'server': widget.server}),
onOK: (body) async { onOK: (body) async {
final resp = body['data'] final resp = body['data']
.map<RoomCategory>((raw) => RoomCategory.fromJSON(raw)) .map<RoomCategory>((raw) => RoomCategory.fromJSON(raw))
.toList(); .toList();
Map<int?, RoomCategory> map = {}; Map<int?, RoomCategory> map = {};
for (RoomCategory cat in resp) { for (RoomCategory cat in resp) {
map[cat.id] = cat; map[cat.id] = cat;
} }
setState(() { setState(() {
categories = map; categories = map;
});
}); });
});
} }
void fetchProducts() { void fetchProducts() {
@ -86,29 +86,29 @@ class _ViewProductPageState extends State<ViewProductPage> {
// TODO: load cached products first // TODO: load cached products first
doNetworkRequest(ScaffoldMessenger.of(context), doNetworkRequest(ScaffoldMessenger.of(context),
req: () => postWithCreadentials( req: () => postWithCreadentials(
credentials: user, credentials: user,
target: user.server, target: user.server,
path: 'getProducts', path: 'getProducts',
body: {'room': widget.room, 'server': widget.server}), body: {'room': widget.room, 'server': widget.server}),
onOK: (body) async { onOK: (body) async {
final resp = body['data'] final resp = body['data']
.map<RoomProduct>((raw) => RoomProduct.fromJSON(raw)) .map<RoomProduct>((raw) => RoomProduct.fromJSON(raw))
.toList(); .toList();
for (RoomProduct prod in resp) { for (RoomProduct prod in resp) {
// load product info // load product info
// for current product // for current product
if (prod.id == widget.product) { if (prod.id == widget.product) {
setState(() { setState(() {
product = prod; product = prod;
}); });
}
} }
} setState(() {
setState(() {
products = resp; products = resp;
});
}); });
});
} }
@override @override
@ -116,9 +116,9 @@ class _ViewProductPageState extends State<ViewProductPage> {
super.initState(); super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
fetchCategories(); fetchCategories();
fetchProducts(); fetchProducts();
fetchInfo(); fetchInfo();
}); });
} }
@ -131,10 +131,10 @@ class _ViewProductPageState extends State<ViewProductPage> {
title: Text(product?.name ?? ''), title: Text(product?.name ?? ''),
), ),
body: SingleChildScrollView( body: SingleChildScrollView(
child: Column(children: [ child: Column(children: [
// display product into // display product into
Center( Center(
child: Padding( child: Padding(
padding: const EdgeInsets.all(14), padding: const EdgeInsets.all(14),
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
@ -142,68 +142,71 @@ class _ViewProductPageState extends State<ViewProductPage> {
children: [ children: [
Text(product?.name ?? '', style: textTheme.headlineLarge), Text(product?.name ?? '', style: textTheme.headlineLarge),
Text(product?.description ?? '', Text(product?.description ?? '',
style: textTheme.titleMedium), style: textTheme.titleMedium),
Text(product?.ean ?? ''), Text(product?.ean ?? ''),
CategoryChip(category: categories[product?.category]), CategoryChip(category: categories[product?.category]),
Text(product!=null?Unit.fromId(product!.defaultUnit).display(context, product!.defaultValue):'') Text(product != null
? Unit.fromId(product!.defaultUnit)
.display(context, product!.defaultValue)
: '')
], ],
))), ))),
// show actions (if allowed / available // show actions (if allowed / available
// edit product button // edit product button
...(info != null && ...(info != null &&
(info!.isAdmin || (info!.isAdmin ||
info!.isOwner || info!.isOwner ||
(info!.permissions & RoomPermission.editRoomContent != 0))) (info!.permissions & RoomPermission.editRoomContent != 0)))
? [ ? [
ListTile( ListTile(
title: Text(AppLocalizations.of(context)!.editProductTitle), title: Text(AppLocalizations.of(context)!.editProductTitle),
subtitle: subtitle:
Text(AppLocalizations.of(context)!.editProductSubtitle), Text(AppLocalizations.of(context)!.editProductSubtitle),
onTap: () { onTap: () {
context.pushNamed('edit-product', params: { context.pushNamed('edit-product', params: {
'server': widget.server, 'server': widget.server,
'id': widget.room, 'id': widget.room,
'product': widget.product.toString() 'product': widget.product.toString()
}); });
}, },
trailing: const Icon(Icons.chevron_right), trailing: const Icon(Icons.chevron_right),
), ),
] ]
: [], : [],
// show parent? // show parent?
...(product?.parent != null) ...(product?.parent != null)
? [ ? [
ListTile( ListTile(
title: Text( title: Text(
AppLocalizations.of(context)!.viewParentProductTitle), AppLocalizations.of(context)!.viewParentProductTitle),
subtitle: Text( subtitle: Text(
AppLocalizations.of(context)!.viewParentProductSubtitle), AppLocalizations.of(context)!.viewParentProductSubtitle),
onTap: () { onTap: () {
context.pushNamed('view-product', params: { context.pushNamed('view-product', params: {
'server': widget.server, 'server': widget.server,
'id': widget.room, 'id': widget.room,
'product': product!.parent.toString() 'product': product!.parent.toString()
}); });
}, },
trailing: const Icon(Icons.chevron_right), trailing: const Icon(Icons.chevron_right),
), ),
] ]
: [], : [],
// show/manage children // show/manage children
ListTile( ListTile(
title: Text(AppLocalizations.of(context)!.viewProductChildrenTitle), title: Text(AppLocalizations.of(context)!.viewProductChildrenTitle),
subtitle: subtitle:
Text(AppLocalizations.of(context)!.viewProductChildrenSubtitle), Text(AppLocalizations.of(context)!.viewProductChildrenSubtitle),
onTap: () { onTap: () {
context.pushNamed('view-product-children', params: { context.pushNamed('view-product-children', params: {
'server': widget.server, 'server': widget.server,
'id': widget.room, 'id': widget.room,
'product': widget.product.toString() 'product': widget.product.toString()
}); });
}, },
trailing: const Icon(Icons.chevron_right), trailing: const Icon(Icons.chevron_right),
), ),
])), ])),
); );
} }

View file

@ -0,0 +1 @@

View file

@ -25,54 +25,57 @@ class _ChangePasswordDialogState extends State<ChangePasswordDialog> {
title: Text(AppLocalizations.of(context)!.changeThemeTitle), title: Text(AppLocalizations.of(context)!.changeThemeTitle),
icon: const Icon(Icons.password), icon: const Icon(Icons.password),
content: SingleChildScrollView( content: SingleChildScrollView(
child: Column( child: Column(
children: [ children: [
Padding( Padding(
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
child: TextField( child: TextField(
controller: _ctrOldPassword, controller: _ctrOldPassword,
keyboardType: TextInputType.visiblePassword, keyboardType: TextInputType.visiblePassword,
obscureText: true, obscureText: true,
decoration: InputDecoration( decoration: InputDecoration(
prefixIcon: const Icon(Icons.lock), prefixIcon: const Icon(Icons.lock),
labelText: AppLocalizations.of(context)!.inputOldPasswordLabel, labelText: AppLocalizations.of(context)!.inputOldPasswordLabel,
hintText: AppLocalizations.of(context)!.inputOldPasswordHint, hintText: AppLocalizations.of(context)!.inputOldPasswordHint,
helperText:AppLocalizations.of(context)!.inputOldPasswordHelp, helperText: AppLocalizations.of(context)!.inputOldPasswordHelp,
border: const OutlineInputBorder(), border: const OutlineInputBorder(),
),
), ),
), ),
Padding( ),
padding: const EdgeInsets.all(8), Padding(
child: TextField( padding: const EdgeInsets.all(8),
controller: _ctrNewPassword, child: TextField(
keyboardType: TextInputType.visiblePassword, controller: _ctrNewPassword,
obscureText: true, keyboardType: TextInputType.visiblePassword,
decoration: InputDecoration( obscureText: true,
prefixIcon: const Icon(Icons.lock), decoration: InputDecoration(
labelText: AppLocalizations.of(context)!.inputNewPasswordLabel, prefixIcon: const Icon(Icons.lock),
hintText: AppLocalizations.of(context)!.inputNewPasswordHint, labelText: AppLocalizations.of(context)!.inputNewPasswordLabel,
helperText:AppLocalizations.of(context)!.inputNewPasswordHelp, hintText: AppLocalizations.of(context)!.inputNewPasswordHint,
border: const OutlineInputBorder(), helperText: AppLocalizations.of(context)!.inputNewPasswordHelp,
), border: const OutlineInputBorder(),
), ),
), ),
Padding( ),
padding: const EdgeInsets.all(8), Padding(
child: TextField( padding: const EdgeInsets.all(8),
controller: _ctrNewPasswordRepeat, child: TextField(
keyboardType: TextInputType.visiblePassword, controller: _ctrNewPasswordRepeat,
obscureText: true, keyboardType: TextInputType.visiblePassword,
decoration: InputDecoration( obscureText: true,
prefixIcon: const Icon(Icons.lock), decoration: InputDecoration(
labelText: AppLocalizations.of(context)!.inputNewPasswordRepeatLabel, prefixIcon: const Icon(Icons.lock),
hintText: AppLocalizations.of(context)!.inputNewPasswordRepeatHint, labelText:
helperText:AppLocalizations.of(context)!.inputNewPasswordRepeatHelp, AppLocalizations.of(context)!.inputNewPasswordRepeatLabel,
border: const OutlineInputBorder(), hintText:
), AppLocalizations.of(context)!.inputNewPasswordRepeatHint,
helperText:
AppLocalizations.of(context)!.inputNewPasswordRepeatHelp,
border: const OutlineInputBorder(),
), ),
), ),
], ),
],
)), )),
actions: [ actions: [
TextButton( TextButton(
@ -93,8 +96,7 @@ class _ChangePasswordDialogState extends State<ChangePasswordDialog> {
if (_ctrNewPassword.text.length < 6) { if (_ctrNewPassword.text.length < 6) {
// password has to be at least 6 characters long // password has to be at least 6 characters long
showSimpleSnackbar(scaffMgr, showSimpleSnackbar(scaffMgr,
text: trans!.errorPasswordLength, text: trans!.errorPasswordLength, action: trans.dismiss);
action: trans.dismiss);
_ctrNewPasswordRepeat.clear(); _ctrNewPasswordRepeat.clear();
return; return;
@ -102,7 +104,7 @@ class _ChangePasswordDialogState extends State<ChangePasswordDialog> {
if (_ctrNewPassword.text != _ctrNewPasswordRepeat.text) { if (_ctrNewPassword.text != _ctrNewPasswordRepeat.text) {
// new passwords do not match // new passwords do not match
showSimpleSnackbar(scaffMgr, showSimpleSnackbar(scaffMgr,
text: trans!.errorPasswordsDoNotMatch, action: trans.dismiss); text: trans!.errorPasswordsDoNotMatch, action: trans.dismiss);
_ctrNewPasswordRepeat.clear(); _ctrNewPasswordRepeat.clear();
return; return;
@ -110,7 +112,7 @@ class _ChangePasswordDialogState extends State<ChangePasswordDialog> {
if (hashPassword(_ctrOldPassword.text) != user.password) { if (hashPassword(_ctrOldPassword.text) != user.password) {
// current password wrong // current password wrong
showSimpleSnackbar(scaffMgr, showSimpleSnackbar(scaffMgr,
text: trans!.errorOldPasswordWrong, action: trans.dismiss); text: trans!.errorOldPasswordWrong, action: trans.dismiss);
_ctrOldPassword.clear(); _ctrOldPassword.clear();
return; return;
@ -120,23 +122,23 @@ class _ChangePasswordDialogState extends State<ChangePasswordDialog> {
// send request // send request
doNetworkRequest(scaffMgr, doNetworkRequest(scaffMgr,
req: () => postWithCreadentials( req: () => postWithCreadentials(
path: 'changePassword', path: 'changePassword',
target: user.server, target: user.server,
body: {'accountKey': password}, body: {'accountKey': password},
credentials: user), credentials: user),
onOK: (_) async { onOK: (_) async {
// update local user struct // update local user struct
final updatedUser = User( final updatedUser = User(
username: user.username, username: user.username,
password: password, password: password,
server: user.server); server: user.server);
await updatedUser.toDisk(); await updatedUser.toDisk();
}, },
after: () { after: () {
// close popup // close popup
nav.pop(); nav.pop();
}); });
}, },
child: Text(AppLocalizations.of(context)!.changeThemeTitle), child: Text(AppLocalizations.of(context)!.changeThemeTitle),
) )

View file

@ -22,17 +22,17 @@ class _WelcomePageState extends State<WelcomePage> {
_currentPage = controller.initialPage; _currentPage = controller.initialPage;
controller.addListener(() { controller.addListener(() {
setState(() { setState(() {
_currentPage = controller.page?.toInt() ?? controller.initialPage; _currentPage = controller.page?.toInt() ?? controller.initialPage;
}); });
}); });
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final textTheme = Theme.of(context) final textTheme = Theme.of(context)
.textTheme .textTheme
.apply(displayColor: Theme.of(context).colorScheme.onSurface); .apply(displayColor: Theme.of(context).colorScheme.onSurface);
String fabText = AppLocalizations.of(context)!.next; String fabText = AppLocalizations.of(context)!.next;
if (_currentPage == 0) { if (_currentPage == 0) {
@ -55,86 +55,80 @@ class _WelcomePageState extends State<WelcomePage> {
body: Column( body: Column(
children: [ children: [
Expanded( Expanded(
child: PageView( child: PageView(
controller: controller, controller: controller,
children: <Widget>[ children: <Widget>[
Column( Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[ children: <Widget>[
SvgPicture.asset(asset("undraw/undraw_shopping_app.svg"), SvgPicture.asset(asset("undraw/undraw_shopping_app.svg"),
fit: BoxFit.contain, fit: BoxFit.contain,
width: smallest * 0.5, width: smallest * 0.5,
height: smallest * 0.5), height: smallest * 0.5),
Text( Text(
AppLocalizations.of(context)!.welcomeTitle, AppLocalizations.of(context)!.welcomeTitle,
style: textTheme.displaySmall, style: textTheme.displaySmall,
), ),
Text( Text(AppLocalizations.of(context)!.welcomeSubtitle,
AppLocalizations.of(context)!.welcomeSubtitle,
style: textTheme.bodyMedium
)
],
),
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
SvgPicture.asset(asset("undraw/undraw_mobile_login.svg"),
fit: BoxFit.contain,
width: smallest * 0.5,
height: smallest * 0.5),
Text(
AppLocalizations.of(context)!.page2Title,
style: textTheme.displaySmall,
),
Text(
AppLocalizations.of(context)!.page2Subtitle,
style: textTheme.bodyMedium) style: textTheme.bodyMedium)
], ],
), ),
Column( Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[ children: <Widget>[
SvgPicture.asset(asset("undraw/undraw_online_connection.svg"), SvgPicture.asset(asset("undraw/undraw_mobile_login.svg"),
fit: BoxFit.contain, fit: BoxFit.contain,
width: smallest * 0.5, width: smallest * 0.5,
height: smallest * 0.5), height: smallest * 0.5),
Text( Text(
AppLocalizations.of(context)!.page3Title, AppLocalizations.of(context)!.page2Title,
style: textTheme.displaySmall, style: textTheme.displaySmall,
), ),
Text( Text(AppLocalizations.of(context)!.page2Subtitle,
AppLocalizations.of(context)!.page3Subtitle,
style: textTheme.bodyMedium) style: textTheme.bodyMedium)
], ],
), ),
Column( Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[ children: <Widget>[
SvgPicture.asset(asset("undraw/undraw_online_groceries.svg"), SvgPicture.asset(asset("undraw/undraw_online_connection.svg"),
fit: BoxFit.contain, fit: BoxFit.contain,
width: smallest * 0.5, width: smallest * 0.5,
height: smallest * 0.5), height: smallest * 0.5),
Text( Text(
AppLocalizations.of(context)!.page4Title, AppLocalizations.of(context)!.page3Title,
style: textTheme.displaySmall, style: textTheme.displaySmall,
), ),
Text( Text(AppLocalizations.of(context)!.page3Subtitle,
AppLocalizations.of(context)!.page4Subtitle,
style: textTheme.bodyMedium) style: textTheme.bodyMedium)
], ],
), ),
], Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
SvgPicture.asset(asset("undraw/undraw_online_groceries.svg"),
fit: BoxFit.contain,
width: smallest * 0.5,
height: smallest * 0.5),
Text(
AppLocalizations.of(context)!.page4Title,
style: textTheme.displaySmall,
),
Text(AppLocalizations.of(context)!.page4Subtitle,
style: textTheme.bodyMedium)
],
),
],
)), )),
TextButton( TextButton(
onPressed: () { onPressed: () {
context.goNamed('signin'); context.goNamed('signin');
}, },
child: Text(AppLocalizations.of(context)!.userHasAnAccount child: Text(AppLocalizations.of(context)!.userHasAnAccount),
),
) )
], ],
), ),
@ -149,8 +143,8 @@ class _WelcomePageState extends State<WelcomePage> {
} else { } else {
// move to next page // move to next page
controller.nextPage( controller.nextPage(
curve: Curves.easeInOut, curve: Curves.easeInOut,
duration: const Duration(milliseconds: 300)); duration: const Duration(milliseconds: 300));
} }
}, },
), ),