Formatted files using dart format
This commit is contained in:
parent
1975d66419
commit
b320d51fa1
28 changed files with 2466 additions and 2422 deletions
|
@ -107,19 +107,19 @@ class RoomPermission {
|
|||
|
||||
switch (permission) {
|
||||
case 1:
|
||||
return trans!.roomPermissionAddItems;
|
||||
return trans!.roomPermissionAddItems;
|
||||
case 2:
|
||||
return trans!.roomPermissionRemoveItems;
|
||||
return trans!.roomPermissionRemoveItems;
|
||||
case 4:
|
||||
return trans!.roomPermissionEditContent;
|
||||
return trans!.roomPermissionEditContent;
|
||||
case 8:
|
||||
return trans!.roomPermissionChangeMeta;
|
||||
return trans!.roomPermissionChangeMeta;
|
||||
case 16:
|
||||
return trans!.roomPermissionManageOTA;
|
||||
return trans!.roomPermissionManageOTA;
|
||||
case 32:
|
||||
return trans!.roomPermissionManageAdmins;
|
||||
return trans!.roomPermissionManageAdmins;
|
||||
case 64:
|
||||
return trans!.roomPermissionManageMembers;
|
||||
return trans!.roomPermissionManageMembers;
|
||||
}
|
||||
|
||||
return trans!.roomPermissionUnknown;
|
||||
|
@ -130,19 +130,19 @@ class RoomPermission {
|
|||
|
||||
switch (permission) {
|
||||
case 1:
|
||||
return trans!.roomPermissionAddItemsSubtitle;
|
||||
return trans!.roomPermissionAddItemsSubtitle;
|
||||
case 2:
|
||||
return trans!.roomPermissionRemoveItemsSubtitle;
|
||||
return trans!.roomPermissionRemoveItemsSubtitle;
|
||||
case 4:
|
||||
return trans!.roomPermissionEditContentSubtitle;
|
||||
return trans!.roomPermissionEditContentSubtitle;
|
||||
case 8:
|
||||
return trans!.roomPermissionChangeMetaSubtitle;
|
||||
return trans!.roomPermissionChangeMetaSubtitle;
|
||||
case 16:
|
||||
return trans!.roomPermissionManageOTASubtitle;
|
||||
return trans!.roomPermissionManageOTASubtitle;
|
||||
case 32:
|
||||
return trans!.roomPermissionManageAdminsSubtitle;
|
||||
return trans!.roomPermissionManageAdminsSubtitle;
|
||||
case 64:
|
||||
return trans!.roomPermissionManageMembersSubtitle;
|
||||
return trans!.roomPermissionManageMembersSubtitle;
|
||||
}
|
||||
|
||||
return trans!.roomPermissionUnknownSubtitle;
|
||||
|
|
|
@ -13,19 +13,19 @@ class Response {
|
|||
}
|
||||
|
||||
Future<Response> usePostApi(
|
||||
{required OutbagServer target,
|
||||
{required OutbagServer target,
|
||||
String path = '',
|
||||
required Map<String, String> headers,
|
||||
required Map<String, dynamic> body}) async {
|
||||
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);
|
||||
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(
|
||||
{required OutbagServer target,
|
||||
{required OutbagServer target,
|
||||
String path = '',
|
||||
required Map<String, dynamic> body,
|
||||
required User credentials}) async {
|
||||
|
@ -33,14 +33,14 @@ Future<Response> postWithCreadentials(
|
|||
"Content-Type": "application/json",
|
||||
'Connection': 'keep-alive',
|
||||
'Authorization':
|
||||
'Digest name=${credentials.username} server=${target.tag} accountKey=${credentials.password}'
|
||||
'Digest name=${credentials.username} server=${target.tag} accountKey=${credentials.password}'
|
||||
};
|
||||
return await usePostApi(
|
||||
target: target, path: path, headers: headers, body: body);
|
||||
target: target, path: path, headers: headers, body: body);
|
||||
}
|
||||
|
||||
Future<Response> postWithToken(
|
||||
{required OutbagServer target,
|
||||
{required OutbagServer target,
|
||||
String path = '',
|
||||
required Map<String, dynamic> body,
|
||||
required String token}) async {
|
||||
|
@ -49,16 +49,16 @@ Future<Response> postWithToken(
|
|||
'Authorization': 'Bearer $token'
|
||||
};
|
||||
return await usePostApi(
|
||||
target: target, path: path, headers: headers, body: body);
|
||||
target: target, path: path, headers: headers, body: body);
|
||||
}
|
||||
|
||||
Future<Response> postUnauthorized(
|
||||
{required OutbagServer target,
|
||||
{required OutbagServer target,
|
||||
String path = '',
|
||||
required Map<String, dynamic> body}) async {
|
||||
Map<String, String> headers = {
|
||||
"Content-Type": "application/json",
|
||||
};
|
||||
return await usePostApi(
|
||||
target: target, path: path, headers: headers, body: body);
|
||||
target: target, path: path, headers: headers, body: body);
|
||||
}
|
||||
|
|
|
@ -61,11 +61,10 @@ class OutbagServer {
|
|||
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
|
||||
return OutbagServer(
|
||||
path: prefs.getString('server-path')!,
|
||||
port: prefs.getInt('server-port')!,
|
||||
tag: prefs.getString('server-tag')!,
|
||||
host: prefs.getString('server-host')!
|
||||
);
|
||||
path: prefs.getString('server-path')!,
|
||||
port: prefs.getInt('server-port')!,
|
||||
tag: prefs.getString('server-tag')!,
|
||||
host: prefs.getString('server-host')!);
|
||||
}
|
||||
|
||||
static Future<void> removeDisk() async {
|
||||
|
|
|
@ -322,11 +322,17 @@ class RoomMember {
|
|||
final bool isInvitePending;
|
||||
|
||||
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) {
|
||||
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 {
|
||||
|
|
|
@ -9,7 +9,8 @@ class CategoryChip extends StatelessWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
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),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ class CategoryPicker extends StatelessWidget {
|
|||
final String? label;
|
||||
|
||||
const CategoryPicker(
|
||||
{super.key,
|
||||
{super.key,
|
||||
required this.categories,
|
||||
this.selected,
|
||||
this.onSelect,
|
||||
|
@ -23,36 +23,34 @@ class CategoryPicker extends StatelessWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: DropdownButtonFormField<int?>(
|
||||
hint: hint==null?null:Text(hint!),
|
||||
decoration: InputDecoration(
|
||||
label: label==null?null:Text(label!),
|
||||
border: const OutlineInputBorder(),
|
||||
prefixIcon: const Icon(Icons.category)
|
||||
),
|
||||
value: selected,
|
||||
items: [
|
||||
...categories,
|
||||
RoomCategory.other(context)
|
||||
].map((category)=>DropdownMenuItem<int?>(
|
||||
value: category.id,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Text(category.name),
|
||||
Icon(Icons.square_rounded,
|
||||
color:category.color,
|
||||
size: 32)
|
||||
]
|
||||
),
|
||||
)).toList(),
|
||||
onChanged: enabled?(cid) {
|
||||
if (onSelect != null) {
|
||||
onSelect!(cid);
|
||||
}
|
||||
}:null,
|
||||
));
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: DropdownButtonFormField<int?>(
|
||||
hint: hint == null ? null : Text(hint!),
|
||||
decoration: InputDecoration(
|
||||
label: label == null ? null : Text(label!),
|
||||
border: const OutlineInputBorder(),
|
||||
prefixIcon: const Icon(Icons.category)),
|
||||
value: selected,
|
||||
items: [...categories, RoomCategory.other(context)]
|
||||
.map((category) => DropdownMenuItem<int?>(
|
||||
value: category.id,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Text(category.name),
|
||||
Icon(Icons.square_rounded,
|
||||
color: category.color, size: 32)
|
||||
]),
|
||||
))
|
||||
.toList(),
|
||||
onChanged: enabled
|
||||
? (cid) {
|
||||
if (onSelect != null) {
|
||||
onSelect!(cid);
|
||||
}
|
||||
}
|
||||
: null,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,14 +6,10 @@ class LabeledDivider extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
const Expanded(child: Divider()),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Text(label)),
|
||||
const Expanded(child: Divider()),
|
||||
]
|
||||
);
|
||||
return Row(children: [
|
||||
const Expanded(child: Divider()),
|
||||
Padding(padding: const EdgeInsets.all(8), child: Text(label)),
|
||||
const Expanded(child: Divider()),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ class ProductPicker extends StatelessWidget {
|
|||
final String? help;
|
||||
|
||||
const ProductPicker(
|
||||
{super.key,
|
||||
{super.key,
|
||||
required this.products,
|
||||
this.selected,
|
||||
this.onSelect,
|
||||
|
@ -26,32 +26,32 @@ class ProductPicker extends StatelessWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: DropdownButtonFormField<int?>(
|
||||
hint: hint == null ? null : Text(hint!),
|
||||
decoration: InputDecoration(
|
||||
label: label == null ? null : Text(label!),
|
||||
border: const OutlineInputBorder(),
|
||||
prefixIcon: const Icon(Icons.inventory_2),
|
||||
helperText: help),
|
||||
value: selected,
|
||||
items: [
|
||||
// "no product" entry
|
||||
DropdownMenuItem<int?>(
|
||||
value: null,
|
||||
child: Text(AppLocalizations.of(context)!.productNameNone),
|
||||
),
|
||||
// other products
|
||||
...products.map((product) => DropdownMenuItem<int?>(
|
||||
value: product.id, child: Text(product.name)))
|
||||
],
|
||||
onChanged: enabled
|
||||
? (pid) {
|
||||
if (onSelect != null) {
|
||||
onSelect!(pid);
|
||||
}
|
||||
}
|
||||
: null,
|
||||
));
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: DropdownButtonFormField<int?>(
|
||||
hint: hint == null ? null : Text(hint!),
|
||||
decoration: InputDecoration(
|
||||
label: label == null ? null : Text(label!),
|
||||
border: const OutlineInputBorder(),
|
||||
prefixIcon: const Icon(Icons.inventory_2),
|
||||
helperText: help),
|
||||
value: selected,
|
||||
items: [
|
||||
// "no product" entry
|
||||
DropdownMenuItem<int?>(
|
||||
value: null,
|
||||
child: Text(AppLocalizations.of(context)!.productNameNone),
|
||||
),
|
||||
// other products
|
||||
...products.map((product) => DropdownMenuItem<int?>(
|
||||
value: product.id, child: Text(product.name)))
|
||||
],
|
||||
onChanged: enabled
|
||||
? (pid) {
|
||||
if (onSelect != null) {
|
||||
onSelect!(pid);
|
||||
}
|
||||
}
|
||||
: null,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,12 +39,7 @@ class RoomIconPicker extends StatelessWidget {
|
|||
if (onSelect != null) {
|
||||
onSelect!(icon);
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
}).toList()
|
||||
)
|
||||
)
|
||||
);
|
||||
}));
|
||||
}).toList())));
|
||||
}
|
||||
}
|
||||
|
|
439
lib/main.dart
439
lib/main.dart
|
@ -25,15 +25,16 @@ import 'package:outbag_app/screens/settings/main.dart';
|
|||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
void main() {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
runApp(const OutbagApp());
|
||||
}
|
||||
|
||||
final GlobalKey<NavigatorState> _rootNavigatorKey =
|
||||
GlobalKey<NavigatorState>(debugLabel: 'root');
|
||||
GlobalKey<NavigatorState>(debugLabel: 'root');
|
||||
final GlobalKey<NavigatorState> _userShellNavigatorKey =
|
||||
GlobalKey<NavigatorState>(debugLabel: 'user');
|
||||
GlobalKey<NavigatorState>(debugLabel: 'user');
|
||||
|
||||
class OutbagApp extends StatefulWidget {
|
||||
const OutbagApp({super.key});
|
||||
|
@ -55,7 +56,7 @@ class _OutbagAppState extends State {
|
|||
try {
|
||||
final theme = await AppTheme.fromDisk();
|
||||
setState(() {
|
||||
this.theme = theme;
|
||||
this.theme = theme;
|
||||
});
|
||||
} catch (_) {}
|
||||
}
|
||||
|
@ -65,14 +66,14 @@ class _OutbagAppState extends State {
|
|||
try {
|
||||
final user = await User.fromDisk();
|
||||
setState(() {
|
||||
this.user = user;
|
||||
this.user = user;
|
||||
});
|
||||
} catch (_) {
|
||||
// user unavailable
|
||||
// invalid credentials
|
||||
// log out
|
||||
setState(() {
|
||||
user = null;
|
||||
user = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -82,8 +83,8 @@ class _OutbagAppState extends State {
|
|||
super.initState();
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
loadTheme();
|
||||
loadUser();
|
||||
loadTheme();
|
||||
loadUser();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -93,31 +94,31 @@ class _OutbagAppState extends State {
|
|||
// with existing details
|
||||
// NOTE: also functions as a way to verify ther data
|
||||
await doNetworkRequest(null,
|
||||
req: () => postWithCreadentials(
|
||||
target: user.server,
|
||||
path: 'getMyAccount',
|
||||
credentials: user,
|
||||
body: {}),
|
||||
onOK: (body) async {
|
||||
final i = AccountMeta.fromJSON(body['data']);
|
||||
info = i;
|
||||
},
|
||||
onServerErr: (_) {
|
||||
info = null;
|
||||
req: () => postWithCreadentials(
|
||||
target: user.server,
|
||||
path: 'getMyAccount',
|
||||
credentials: user,
|
||||
body: {}),
|
||||
onOK: (body) async {
|
||||
final i = AccountMeta.fromJSON(body['data']);
|
||||
info = i;
|
||||
},
|
||||
onServerErr: (_) {
|
||||
info = null;
|
||||
|
||||
setState(() {
|
||||
setState(() {
|
||||
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;
|
||||
}
|
||||
|
@ -125,195 +126,213 @@ class _OutbagAppState extends State {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
Provider<AppTheme>.value(value: theme),
|
||||
],
|
||||
child: MaterialApp.router(
|
||||
title: "Outbag",
|
||||
localizationsDelegates: const [
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
AppLocalizations.delegate
|
||||
providers: [
|
||||
Provider<AppTheme>.value(value: theme),
|
||||
],
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
themeMode: theme.mode,
|
||||
theme: ThemeData(useMaterial3: true, brightness: Brightness.light),
|
||||
darkTheme: ThemeData(useMaterial3: true, brightness: Brightness.dark),
|
||||
routerConfig: GoRouter(
|
||||
navigatorKey: _rootNavigatorKey,
|
||||
initialLocation: '/',
|
||||
redirect: (context, state) async {
|
||||
if (user == null) {
|
||||
// prelogin
|
||||
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 '/';
|
||||
}
|
||||
}
|
||||
child: MaterialApp.router(
|
||||
title: "Outbag",
|
||||
localizationsDelegates: const [
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
AppLocalizations.delegate
|
||||
],
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
themeMode: theme.mode,
|
||||
theme: ThemeData(useMaterial3: true, brightness: Brightness.light),
|
||||
darkTheme: ThemeData(useMaterial3: true, brightness: Brightness.dark),
|
||||
routerConfig: GoRouter(
|
||||
navigatorKey: _rootNavigatorKey,
|
||||
initialLocation: '/',
|
||||
redirect: (context, state) async {
|
||||
if (user == null) {
|
||||
// prelogin
|
||||
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;
|
||||
},
|
||||
routes: <RouteBase>[
|
||||
// unauthorized routes
|
||||
GoRoute(
|
||||
name: 'welcome',
|
||||
path: '/welcome',
|
||||
builder: (context, state) => const WelcomePage(),
|
||||
return null;
|
||||
},
|
||||
routes: <RouteBase>[
|
||||
// unauthorized routes
|
||||
GoRoute(
|
||||
name: 'signin',
|
||||
path: 'signin',
|
||||
builder: (context, state) =>
|
||||
AuthPage(mode: Mode.signin, refresh: loadUser),
|
||||
),
|
||||
GoRoute(
|
||||
name: 'signup',
|
||||
path: 'signup',
|
||||
builder: (context, state) =>
|
||||
AuthPage(mode: Mode.signup, refresh: loadUser),
|
||||
),
|
||||
GoRoute(
|
||||
name: 'signup-ota',
|
||||
path: 'signup-ota',
|
||||
builder: (context, state) =>
|
||||
AuthPage(mode: Mode.signupOTA, refresh: loadUser),
|
||||
),
|
||||
]),
|
||||
|
||||
// authorized routes
|
||||
ShellRoute(
|
||||
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()),
|
||||
name: 'welcome',
|
||||
path: '/welcome',
|
||||
builder: (context, state) => const WelcomePage(),
|
||||
routes: <RouteBase>[
|
||||
GoRoute(
|
||||
name: 'signin',
|
||||
path: 'signin',
|
||||
builder: (context, state) =>
|
||||
AuthPage(mode: Mode.signin, refresh: loadUser),
|
||||
),
|
||||
GoRoute(
|
||||
name: 'signup',
|
||||
path: 'signup',
|
||||
builder: (context, state) =>
|
||||
AuthPage(mode: Mode.signup, refresh: loadUser),
|
||||
),
|
||||
GoRoute(
|
||||
name: 'signup-ota',
|
||||
path: 'signup-ota',
|
||||
builder: (context, state) =>
|
||||
AuthPage(mode: Mode.signupOTA, refresh: loadUser),
|
||||
),
|
||||
]),
|
||||
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),
|
||||
// authorized routes
|
||||
ShellRoute(
|
||||
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: 'edit-product',
|
||||
path: 'edit',
|
||||
builder: (context, state)=>EditProductPage(
|
||||
server: state.params['server'] ?? '',
|
||||
room: state.params['id'] ?? '',
|
||||
product: int.tryParse(state.params['product'] ?? ''))),
|
||||
]
|
||||
),
|
||||
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(
|
||||
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(
|
||||
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),
|
||||
)
|
||||
])
|
||||
]),
|
||||
]),
|
||||
|
||||
// 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'))
|
||||
]),
|
||||
));
|
||||
// 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'))
|
||||
]),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,12 +24,12 @@ class _HomePageState extends State<HomePage> {
|
|||
|
||||
// wait for background room changes
|
||||
Room.listen((_) async {
|
||||
try {
|
||||
final newRooms = await Room.listRooms();
|
||||
setState(() {
|
||||
rooms = newRooms;
|
||||
});
|
||||
} catch (_) {}
|
||||
try {
|
||||
final newRooms = await Room.listRooms();
|
||||
setState(() {
|
||||
rooms = newRooms;
|
||||
});
|
||||
} catch (_) {}
|
||||
});
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => fetchList());
|
||||
|
@ -43,17 +43,17 @@ class _HomePageState extends State<HomePage> {
|
|||
try {
|
||||
final newRooms = await Room.listRooms();
|
||||
setState(() {
|
||||
rooms = newRooms;
|
||||
rooms = newRooms;
|
||||
});
|
||||
} catch (_) {}
|
||||
|
||||
doNetworkRequest(
|
||||
sm,
|
||||
req: () => postWithCreadentials(
|
||||
path: 'listRooms', credentials: user, target: user.server, body: {}),
|
||||
path: 'listRooms', credentials: user, target: user.server, body: {}),
|
||||
onOK: (body) async {
|
||||
final List<Room> list = body['data'].map<Room>((json) {
|
||||
return Room.fromJSON(json);
|
||||
return Room.fromJSON(json);
|
||||
}).toList();
|
||||
for (Room r in list) {
|
||||
await r.toDisk();
|
||||
|
@ -91,33 +91,34 @@ class _HomePageState extends State<HomePage> {
|
|||
},
|
||||
menuChildren: [
|
||||
MenuItemButton(
|
||||
leadingIcon: const Icon(Icons.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),
|
||||
leadingIcon: const Icon(Icons.settings),
|
||||
child: Text(AppLocalizations.of(context)!.settings),
|
||||
onPressed: () {
|
||||
// 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(
|
||||
leadingIcon: const Icon(Icons.info_rounded),
|
||||
child: Text(AppLocalizations.of(context)!.about),
|
||||
onPressed: () {
|
||||
// show about screen
|
||||
context.goNamed('about');
|
||||
}),
|
||||
leadingIcon: const Icon(Icons.info_rounded),
|
||||
child: Text(AppLocalizations.of(context)!.about),
|
||||
onPressed: () {
|
||||
// show about screen
|
||||
context.goNamed('about');
|
||||
}),
|
||||
],
|
||||
)
|
||||
],
|
||||
|
@ -127,31 +128,31 @@ class _HomePageState extends State<HomePage> {
|
|||
itemBuilder: (ctx, i) {
|
||||
final room = rooms[i];
|
||||
return Card(
|
||||
margin: const EdgeInsets.all(8.0),
|
||||
clipBehavior: Clip.antiAliasWithSaveLayer,
|
||||
semanticContainer: true,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
// open room
|
||||
context.goNamed('room',
|
||||
params: {'server': room.serverTag, 'id': room.id});
|
||||
},
|
||||
onLongPress: () {
|
||||
// open bottom sheet
|
||||
// NOTE: feature yet to be confirmed
|
||||
},
|
||||
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,
|
||||
))));
|
||||
margin: const EdgeInsets.all(8.0),
|
||||
clipBehavior: Clip.antiAliasWithSaveLayer,
|
||||
semanticContainer: true,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
// open room
|
||||
context.goNamed('room',
|
||||
params: {'server': room.serverTag, 'id': room.id});
|
||||
},
|
||||
onLongPress: () {
|
||||
// open bottom sheet
|
||||
// NOTE: feature yet to be confirmed
|
||||
},
|
||||
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(
|
||||
|
|
1
lib/screens/room/about/invite.dart
Normal file
1
lib/screens/room/about/invite.dart
Normal file
|
@ -0,0 +1 @@
|
|||
|
|
@ -30,10 +30,10 @@ class _ManageRoomMembersPageState extends State<ManageRoomMembersPage> {
|
|||
doNetworkRequest(
|
||||
sm,
|
||||
req: () => postWithCreadentials(
|
||||
path: 'getRoomInfo',
|
||||
credentials: user,
|
||||
target: user.server,
|
||||
body: {'room': widget.tag, 'server': widget.server}),
|
||||
path: 'getRoomInfo',
|
||||
credentials: user,
|
||||
target: user.server,
|
||||
body: {'room': widget.tag, 'server': widget.server}),
|
||||
onAnyErr: () {
|
||||
// user should not be here
|
||||
// close screen
|
||||
|
@ -43,7 +43,7 @@ class _ManageRoomMembersPageState extends State<ManageRoomMembersPage> {
|
|||
onOK: (body) async {
|
||||
final info = RoomInfo.fromJSON(body['data']);
|
||||
setState(() {
|
||||
this.info = info;
|
||||
this.info = info;
|
||||
});
|
||||
return true;
|
||||
},
|
||||
|
@ -56,26 +56,26 @@ class _ManageRoomMembersPageState extends State<ManageRoomMembersPage> {
|
|||
final user = context.read<User>();
|
||||
|
||||
doNetworkRequest(sm,
|
||||
req: () => postWithCreadentials(
|
||||
credentials: user,
|
||||
target: user.server,
|
||||
path: 'getRoomMembers',
|
||||
body: {'room': widget.tag, 'server': widget.server}),
|
||||
onAnyErr: () {
|
||||
// user should not be here
|
||||
// close screen
|
||||
router.pushReplacementNamed('home');
|
||||
return false;
|
||||
},
|
||||
onOK: (body) {
|
||||
final List<RoomMember> list = body['data'].map<RoomMember>((json) {
|
||||
req: () => postWithCreadentials(
|
||||
credentials: user,
|
||||
target: user.server,
|
||||
path: 'getRoomMembers',
|
||||
body: {'room': widget.tag, 'server': widget.server}),
|
||||
onAnyErr: () {
|
||||
// user should not be here
|
||||
// close screen
|
||||
router.pushReplacementNamed('home');
|
||||
return false;
|
||||
},
|
||||
onOK: (body) {
|
||||
final List<RoomMember> list = body['data'].map<RoomMember>((json) {
|
||||
return RoomMember.fromJSON(json);
|
||||
}).toList();
|
||||
}).toList();
|
||||
|
||||
setState(() {
|
||||
setState(() {
|
||||
this.list = list;
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -83,21 +83,21 @@ class _ManageRoomMembersPageState extends State<ManageRoomMembersPage> {
|
|||
super.initState();
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
fetchUserInfo();
|
||||
fetchMembers();
|
||||
fetchUserInfo();
|
||||
fetchMembers();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final textTheme = Theme.of(context)
|
||||
.textTheme
|
||||
.apply(displayColor: Theme.of(context).colorScheme.onSurface);
|
||||
.textTheme
|
||||
.apply(displayColor: Theme.of(context).colorScheme.onSurface);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title:
|
||||
Text(AppLocalizations.of(context)!.roomMembersTitle(list.length)),
|
||||
Text(AppLocalizations.of(context)!.roomMembersTitle(list.length)),
|
||||
//actions: [
|
||||
// // NOTE: Maybe add a search icon
|
||||
// // and general search functionality here
|
||||
|
@ -109,8 +109,8 @@ class _ManageRoomMembersPageState extends State<ManageRoomMembersPage> {
|
|||
|
||||
String role = AppLocalizations.of(context)!.roleMember;
|
||||
if (info != null &&
|
||||
(info?.owner)! == item.id &&
|
||||
widget.server == item.serverTag) {
|
||||
(info?.owner)! == item.id &&
|
||||
widget.server == item.serverTag) {
|
||||
role = AppLocalizations.of(context)!.roleOwner;
|
||||
} else if (item.isAdmin) {
|
||||
role = AppLocalizations.of(context)!.roleAdmin;
|
||||
|
@ -119,17 +119,17 @@ class _ManageRoomMembersPageState extends State<ManageRoomMembersPage> {
|
|||
bool enable = true;
|
||||
// perform permission check
|
||||
if (info == null ||
|
||||
!((info?.isAdmin)! ||
|
||||
(info?.isOwner)! ||
|
||||
((info?.permissions)! & oB("1100000") != 0))) {
|
||||
!((info?.isAdmin)! ||
|
||||
(info?.isOwner)! ||
|
||||
((info?.permissions)! & oB("1100000") != 0))) {
|
||||
// NOTE: do not show error message
|
||||
// user should assume,
|
||||
// that it wasn't even possible
|
||||
// to click on ListTile
|
||||
enable = false;
|
||||
} else if (info != null &&
|
||||
item.id == info?.owner &&
|
||||
widget.server == item.serverTag) {
|
||||
item.id == info?.owner &&
|
||||
widget.server == item.serverTag) {
|
||||
// cannot kick admin
|
||||
enable = false;
|
||||
}
|
||||
|
@ -139,236 +139,236 @@ class _ManageRoomMembersPageState extends State<ManageRoomMembersPage> {
|
|||
subtitle: Text(role),
|
||||
leading: const Icon(Icons.person),
|
||||
onTap: !enable
|
||||
? null
|
||||
: () {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (context) => BottomSheet(
|
||||
onClosing: () {},
|
||||
builder: (context) => Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Text(item.humanReadableName,
|
||||
style: textTheme.displaySmall)),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Column(
|
||||
children: [
|
||||
...((info?.isAdmin)! ||
|
||||
(info?.isOwner)! ||
|
||||
((info?.permissions)! &
|
||||
RoomPermission
|
||||
.changeAdmin !=
|
||||
0))
|
||||
? [
|
||||
ListTile(
|
||||
leading: const Icon(
|
||||
Icons.supervisor_account),
|
||||
title: Text(item.isAdmin
|
||||
? AppLocalizations.of(
|
||||
context)!
|
||||
.removeAdminTitle
|
||||
: AppLocalizations.of(
|
||||
context)!
|
||||
.makeAdminTitle),
|
||||
subtitle: Text(item.isAdmin
|
||||
? AppLocalizations.of(
|
||||
context)!
|
||||
.removeAdminSubtitle
|
||||
: AppLocalizations.of(
|
||||
context)!
|
||||
.makeAdminSubtitle),
|
||||
onTap: () {
|
||||
// make user admin
|
||||
showDialog(
|
||||
context: context,
|
||||
builder:
|
||||
(ctx) =>
|
||||
AlertDialog(
|
||||
icon: const Icon(
|
||||
Icons
|
||||
.supervisor_account),
|
||||
title: Text(item.isAdmin
|
||||
? AppLocalizations.of(
|
||||
context)!
|
||||
.removeAdminTitle
|
||||
: AppLocalizations.of(
|
||||
context)!
|
||||
.makeAdminTitle),
|
||||
content: Text(item
|
||||
.isAdmin
|
||||
? AppLocalizations.of(
|
||||
context)!
|
||||
.removeAdminConfirm(item
|
||||
.humanReadableName)
|
||||
: AppLocalizations.of(
|
||||
context)!
|
||||
.makeAdminConfirm(
|
||||
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(context);
|
||||
final nav =
|
||||
Navigator.of(ctx);
|
||||
final nav2 =
|
||||
Navigator.of(context);
|
||||
final user =
|
||||
context.read<User>();
|
||||
? null
|
||||
: () {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (context) => BottomSheet(
|
||||
onClosing: () {},
|
||||
builder: (context) => Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Text(item.humanReadableName,
|
||||
style: textTheme.displaySmall)),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Column(
|
||||
children: [
|
||||
...((info?.isAdmin)! ||
|
||||
(info?.isOwner)! ||
|
||||
((info?.permissions)! &
|
||||
RoomPermission
|
||||
.changeAdmin !=
|
||||
0))
|
||||
? [
|
||||
ListTile(
|
||||
leading: const Icon(
|
||||
Icons.supervisor_account),
|
||||
title: Text(item.isAdmin
|
||||
? AppLocalizations.of(
|
||||
context)!
|
||||
.removeAdminTitle
|
||||
: AppLocalizations.of(
|
||||
context)!
|
||||
.makeAdminTitle),
|
||||
subtitle: Text(item.isAdmin
|
||||
? AppLocalizations.of(
|
||||
context)!
|
||||
.removeAdminSubtitle
|
||||
: AppLocalizations.of(
|
||||
context)!
|
||||
.makeAdminSubtitle),
|
||||
onTap: () {
|
||||
// make user admin
|
||||
showDialog(
|
||||
context: context,
|
||||
builder:
|
||||
(ctx) =>
|
||||
AlertDialog(
|
||||
icon: const Icon(
|
||||
Icons
|
||||
.supervisor_account),
|
||||
title: Text(item.isAdmin
|
||||
? AppLocalizations.of(
|
||||
context)!
|
||||
.removeAdminTitle
|
||||
: AppLocalizations.of(
|
||||
context)!
|
||||
.makeAdminTitle),
|
||||
content: Text(item
|
||||
.isAdmin
|
||||
? AppLocalizations.of(
|
||||
context)!
|
||||
.removeAdminConfirm(item
|
||||
.humanReadableName)
|
||||
: AppLocalizations.of(
|
||||
context)!
|
||||
.makeAdminConfirm(
|
||||
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(context);
|
||||
final nav =
|
||||
Navigator.of(ctx);
|
||||
final nav2 =
|
||||
Navigator.of(context);
|
||||
final user =
|
||||
context.read<User>();
|
||||
|
||||
doNetworkRequest(
|
||||
scaffMgr,
|
||||
req: () =>
|
||||
postWithCreadentials(path: 'setAdminStatus', credentials: user, target: user.server, body: {
|
||||
'room': widget.tag,
|
||||
'roomServer': widget.server,
|
||||
'server': item.serverTag,
|
||||
'name': item.id,
|
||||
'admin': !item.isAdmin
|
||||
}),
|
||||
onOK: (_) {
|
||||
fetchMembers();
|
||||
},
|
||||
after: () {
|
||||
// close popup
|
||||
nav.pop();
|
||||
// close bottom sheet
|
||||
nav2.pop();
|
||||
});
|
||||
},
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!
|
||||
.ok),
|
||||
)
|
||||
doNetworkRequest(
|
||||
scaffMgr,
|
||||
req: () =>
|
||||
postWithCreadentials(path: 'setAdminStatus', credentials: user, target: user.server, body: {
|
||||
'room': widget.tag,
|
||||
'roomServer': widget.server,
|
||||
'server': item.serverTag,
|
||||
'name': item.id,
|
||||
'admin': !item.isAdmin
|
||||
}),
|
||||
onOK: (_) {
|
||||
fetchMembers();
|
||||
},
|
||||
after: () {
|
||||
// close popup
|
||||
nav.pop();
|
||||
// close bottom sheet
|
||||
nav2.pop();
|
||||
});
|
||||
},
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!
|
||||
.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),
|
||||
)
|
||||
],
|
||||
));
|
||||
},
|
||||
)
|
||||
]
|
||||
: [],
|
||||
],
|
||||
));
|
||||
},
|
||||
)
|
||||
]
|
||||
: [],
|
||||
...((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),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
));
|
||||
},
|
||||
),
|
||||
),
|
||||
FilledButton(
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.close),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
));
|
||||
},
|
||||
);
|
||||
},
|
||||
itemCount: list.length,
|
||||
|
|
1
lib/screens/room/about/ota.dart
Normal file
1
lib/screens/room/about/ota.dart
Normal file
|
@ -0,0 +1 @@
|
|||
|
|
@ -31,10 +31,10 @@ class _EditRoomPermissionSetPageState extends State<EditRoomPermissionSetPage> {
|
|||
doNetworkRequest(
|
||||
sm,
|
||||
req: () => postWithCreadentials(
|
||||
path: 'getRoomInfo',
|
||||
credentials: user,
|
||||
target: user.server,
|
||||
body: {'room': widget.tag, 'server': widget.server}),
|
||||
path: 'getRoomInfo',
|
||||
credentials: user,
|
||||
target: user.server,
|
||||
body: {'room': widget.tag, 'server': widget.server}),
|
||||
onAnyErr: () {
|
||||
// user should not be here
|
||||
// close screen
|
||||
|
@ -44,7 +44,7 @@ class _EditRoomPermissionSetPageState extends State<EditRoomPermissionSetPage> {
|
|||
onOK: (body) async {
|
||||
final info = RoomInfo.fromJSON(body['data']);
|
||||
setState(() {
|
||||
permissions = info.permissions;
|
||||
permissions = info.permissions;
|
||||
});
|
||||
return true;
|
||||
},
|
||||
|
@ -72,18 +72,18 @@ class _EditRoomPermissionSetPageState extends State<EditRoomPermissionSetPage> {
|
|||
final int col = pow(2, index) as int;
|
||||
|
||||
return SwitchListTile(
|
||||
title: Text(RoomPermission.name(item, context)),
|
||||
subtitle: Text(RoomPermission.describe(item, context)),
|
||||
onChanged: (state) {
|
||||
setState(() {
|
||||
title: Text(RoomPermission.name(item, context)),
|
||||
subtitle: Text(RoomPermission.describe(item, context)),
|
||||
onChanged: (state) {
|
||||
setState(() {
|
||||
if (state) {
|
||||
permissions |= col;
|
||||
} else {
|
||||
permissions &= ~col;
|
||||
}
|
||||
});
|
||||
},
|
||||
value: permissions & col != 0);
|
||||
});
|
||||
},
|
||||
value: permissions & col != 0);
|
||||
},
|
||||
),
|
||||
floatingActionButton: FloatingActionButton.extended(
|
||||
|
@ -97,18 +97,18 @@ class _EditRoomPermissionSetPageState extends State<EditRoomPermissionSetPage> {
|
|||
|
||||
// update permissions
|
||||
doNetworkRequest(sm,
|
||||
req: () => postWithCreadentials(
|
||||
path: 'setRoomRight',
|
||||
credentials: user,
|
||||
target: user.server,
|
||||
body: {
|
||||
'room': widget.tag,
|
||||
'server': widget.server,
|
||||
'rights': permissions
|
||||
}),
|
||||
onOK: (_) {
|
||||
router.pop();
|
||||
});
|
||||
req: () => postWithCreadentials(
|
||||
path: 'setRoomRight',
|
||||
credentials: user,
|
||||
target: user.server,
|
||||
body: {
|
||||
'room': widget.tag,
|
||||
'server': widget.server,
|
||||
'rights': permissions
|
||||
}),
|
||||
onOK: (_) {
|
||||
router.pop();
|
||||
});
|
||||
},
|
||||
),
|
||||
);
|
||||
|
|
|
@ -31,11 +31,11 @@ class _EditCategoryPageState extends State<EditCategoryPage> {
|
|||
super.initState();
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (widget.id == null) {
|
||||
// trying to create a new category
|
||||
return;
|
||||
}
|
||||
fetchCategory();
|
||||
if (widget.id == null) {
|
||||
// trying to create a new category
|
||||
return;
|
||||
}
|
||||
fetchCategory();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -45,210 +45,210 @@ class _EditCategoryPageState extends State<EditCategoryPage> {
|
|||
// TODO: load cached rooms
|
||||
|
||||
doNetworkRequest(ScaffoldMessenger.of(context),
|
||||
req: () => postWithCreadentials(
|
||||
credentials: user,
|
||||
target: user.server,
|
||||
path: 'getCategory',
|
||||
body: {
|
||||
'room': widget.tag,
|
||||
'server': widget.server,
|
||||
'listCatID': widget.id
|
||||
}),
|
||||
onOK: (json) {
|
||||
setState(() {
|
||||
req: () => postWithCreadentials(
|
||||
credentials: user,
|
||||
target: user.server,
|
||||
path: 'getCategory',
|
||||
body: {
|
||||
'room': widget.tag,
|
||||
'server': widget.server,
|
||||
'listCatID': widget.id
|
||||
}),
|
||||
onOK: (json) {
|
||||
setState(() {
|
||||
_ctrName.text = json['data']['title'];
|
||||
_ctrColor = colorFromString(json['data']['color']);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final textTheme = Theme.of(context)
|
||||
.textTheme
|
||||
.apply(displayColor: Theme.of(context).colorScheme.onSurface);
|
||||
.textTheme
|
||||
.apply(displayColor: Theme.of(context).colorScheme.onSurface);
|
||||
|
||||
double width = MediaQuery.of(context).size.width;
|
||||
double height = MediaQuery.of(context).size.height;
|
||||
double smallest = min(min(width, height), 400);
|
||||
|
||||
return showSpinner
|
||||
? Scaffold(
|
||||
body: Center(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const CircularProgressIndicator(),
|
||||
Text(AppLocalizations.of(context)!.loading,
|
||||
style: textTheme.titleLarge),
|
||||
])))
|
||||
: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text((widget.id == null)
|
||||
? AppLocalizations.of(context)!.newCategory
|
||||
: AppLocalizations.of(context)!.editCategory),
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(14),
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 400),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
IconButton(
|
||||
icon: Icon(Icons.square_rounded,
|
||||
size: 48.0, color: _ctrColor),
|
||||
tooltip: AppLocalizations.of(context)!
|
||||
.changeCategoryColor,
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (ctx) => AlertDialog(
|
||||
title: Text(
|
||||
AppLocalizations.of(context)!
|
||||
.chooseCategoryColor),
|
||||
actions: const [],
|
||||
content: SizedBox(
|
||||
width: smallest * 0.3 * 3,
|
||||
height: smallest * 0.3 * 3,
|
||||
child: GridView.count(
|
||||
crossAxisCount: 3,
|
||||
children: RoomCategory
|
||||
.listColors()
|
||||
.map((color) {
|
||||
return GridTile(
|
||||
child: IconButton(
|
||||
icon: Icon(
|
||||
Icons
|
||||
.square_rounded,
|
||||
color:
|
||||
color,
|
||||
size: 48.0),
|
||||
// do not display tooltip for now
|
||||
// as it is hard to translate
|
||||
// and the tooltip prevented the click event,
|
||||
// when clicked on the tooltip bar
|
||||
// tooltip:icon.text,
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_ctrColor =
|
||||
color;
|
||||
});
|
||||
Navigator.of(
|
||||
ctx)
|
||||
.pop();
|
||||
}));
|
||||
}).toList())),
|
||||
));
|
||||
},
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: TextField(
|
||||
controller: _ctrName,
|
||||
keyboardType: TextInputType.name,
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.badge),
|
||||
labelText: AppLocalizations.of(context)!
|
||||
.inputCategoryNameLabel,
|
||||
hintText: AppLocalizations.of(context)!
|
||||
.inputCategoryNameHint,
|
||||
helperText: AppLocalizations.of(context)!
|
||||
.inputCategoryNameHelp,
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
))))),
|
||||
floatingActionButton: FloatingActionButton.extended(
|
||||
onPressed: () async {
|
||||
final scaffMgr = ScaffoldMessenger.of(context);
|
||||
final router = GoRouter.of(context);
|
||||
final trans = AppLocalizations.of(context);
|
||||
? Scaffold(
|
||||
body: Center(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const CircularProgressIndicator(),
|
||||
Text(AppLocalizations.of(context)!.loading,
|
||||
style: textTheme.titleLarge),
|
||||
])))
|
||||
: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text((widget.id == null)
|
||||
? AppLocalizations.of(context)!.newCategory
|
||||
: AppLocalizations.of(context)!.editCategory),
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(14),
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 400),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
IconButton(
|
||||
icon: Icon(Icons.square_rounded,
|
||||
size: 48.0, color: _ctrColor),
|
||||
tooltip: AppLocalizations.of(context)!
|
||||
.changeCategoryColor,
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (ctx) => AlertDialog(
|
||||
title: Text(
|
||||
AppLocalizations.of(context)!
|
||||
.chooseCategoryColor),
|
||||
actions: const [],
|
||||
content: SizedBox(
|
||||
width: smallest * 0.3 * 3,
|
||||
height: smallest * 0.3 * 3,
|
||||
child: GridView.count(
|
||||
crossAxisCount: 3,
|
||||
children: RoomCategory
|
||||
.listColors()
|
||||
.map((color) {
|
||||
return GridTile(
|
||||
child: IconButton(
|
||||
icon: Icon(
|
||||
Icons
|
||||
.square_rounded,
|
||||
color:
|
||||
color,
|
||||
size: 48.0),
|
||||
// do not display tooltip for now
|
||||
// as it is hard to translate
|
||||
// and the tooltip prevented the click event,
|
||||
// when clicked on the tooltip bar
|
||||
// tooltip:icon.text,
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_ctrColor =
|
||||
color;
|
||||
});
|
||||
Navigator.of(
|
||||
ctx)
|
||||
.pop();
|
||||
}));
|
||||
}).toList())),
|
||||
));
|
||||
},
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: TextField(
|
||||
controller: _ctrName,
|
||||
keyboardType: TextInputType.name,
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.badge),
|
||||
labelText: AppLocalizations.of(context)!
|
||||
.inputCategoryNameLabel,
|
||||
hintText: AppLocalizations.of(context)!
|
||||
.inputCategoryNameHint,
|
||||
helperText: AppLocalizations.of(context)!
|
||||
.inputCategoryNameHelp,
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
))))),
|
||||
floatingActionButton: FloatingActionButton.extended(
|
||||
onPressed: () async {
|
||||
final scaffMgr = ScaffoldMessenger.of(context);
|
||||
final router = GoRouter.of(context);
|
||||
final trans = AppLocalizations.of(context);
|
||||
|
||||
// name may not be empty
|
||||
if (_ctrName.text.isEmpty) {
|
||||
showSimpleSnackbar(scaffMgr,
|
||||
text: trans!.errorNoRoomName, action: trans.ok);
|
||||
// name may not be empty
|
||||
if (_ctrName.text.isEmpty) {
|
||||
showSimpleSnackbar(scaffMgr,
|
||||
text: trans!.errorNoRoomName, action: trans.ok);
|
||||
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
showSpinner = true;
|
||||
});
|
||||
setState(() {
|
||||
showSpinner = true;
|
||||
});
|
||||
|
||||
final user = context.read<User>();
|
||||
final color = colorIdFromColor(_ctrColor);
|
||||
final user = context.read<User>();
|
||||
final color = colorIdFromColor(_ctrColor);
|
||||
|
||||
if (widget.id == null) {
|
||||
await doNetworkRequest(scaffMgr,
|
||||
req: () => postWithCreadentials(
|
||||
target: user.server,
|
||||
credentials: user,
|
||||
path: 'addCategory',
|
||||
body: {
|
||||
'room': widget.tag,
|
||||
'server': widget.server,
|
||||
'title': _ctrName.text,
|
||||
'color': color
|
||||
}),
|
||||
onOK: (body) async {
|
||||
final id = body['data']['catID'];
|
||||
if (widget.id == null) {
|
||||
await doNetworkRequest(scaffMgr,
|
||||
req: () => postWithCreadentials(
|
||||
target: user.server,
|
||||
credentials: user,
|
||||
path: 'addCategory',
|
||||
body: {
|
||||
'room': widget.tag,
|
||||
'server': widget.server,
|
||||
'title': _ctrName.text,
|
||||
'color': color
|
||||
}),
|
||||
onOK: (body) async {
|
||||
final id = body['data']['catID'];
|
||||
|
||||
final cat = RoomCategory(
|
||||
id: id, name: _ctrName.text, color: _ctrColor);
|
||||
// TODO: cache category
|
||||
final cat = RoomCategory(
|
||||
id: id, name: _ctrName.text, color: _ctrColor);
|
||||
// TODO: cache category
|
||||
|
||||
// go back
|
||||
router.pop();
|
||||
return;
|
||||
},
|
||||
after: () {
|
||||
setState(() {
|
||||
showSpinner = false;
|
||||
});
|
||||
});
|
||||
} else {
|
||||
await doNetworkRequest(scaffMgr,
|
||||
req: () => postWithCreadentials(
|
||||
target: user.server,
|
||||
credentials: user,
|
||||
path: 'changeCategory',
|
||||
body: {
|
||||
'room': widget.tag,
|
||||
'server': widget.server,
|
||||
'title': _ctrName.text,
|
||||
'listCatID': widget.id,
|
||||
'color': color
|
||||
}),
|
||||
onOK: (body) async {
|
||||
final cat = RoomCategory(
|
||||
id: widget.id!,
|
||||
name: _ctrName.text,
|
||||
color: _ctrColor);
|
||||
// TODO: cache category
|
||||
// go back
|
||||
router.pop();
|
||||
return;
|
||||
},
|
||||
after: () {
|
||||
setState(() {
|
||||
showSpinner = false;
|
||||
});
|
||||
});
|
||||
} else {
|
||||
await doNetworkRequest(scaffMgr,
|
||||
req: () => postWithCreadentials(
|
||||
target: user.server,
|
||||
credentials: user,
|
||||
path: 'changeCategory',
|
||||
body: {
|
||||
'room': widget.tag,
|
||||
'server': widget.server,
|
||||
'title': _ctrName.text,
|
||||
'listCatID': widget.id,
|
||||
'color': color
|
||||
}),
|
||||
onOK: (body) async {
|
||||
final cat = RoomCategory(
|
||||
id: widget.id!,
|
||||
name: _ctrName.text,
|
||||
color: _ctrColor);
|
||||
// TODO: cache category
|
||||
|
||||
// go back
|
||||
router.pop();
|
||||
return;
|
||||
},
|
||||
after: () {
|
||||
setState(() {
|
||||
showSpinner = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
label: Text((widget.id == null)
|
||||
? AppLocalizations.of(context)!.newCategoryShort
|
||||
: AppLocalizations.of(context)!.editCategoryShort),
|
||||
icon: Icon((widget.id == null) ? Icons.add : Icons.edit)),
|
||||
);
|
||||
// go back
|
||||
router.pop();
|
||||
return;
|
||||
},
|
||||
after: () {
|
||||
setState(() {
|
||||
showSpinner = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
label: Text((widget.id == null)
|
||||
? AppLocalizations.of(context)!.newCategoryShort
|
||||
: AppLocalizations.of(context)!.editCategoryShort),
|
||||
icon: Icon((widget.id == null) ? Icons.add : Icons.edit)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ class EditItemPage extends StatefulWidget {
|
|||
final int? item;
|
||||
|
||||
const EditItemPage(
|
||||
{super.key, required this.room, required this.server, this.item});
|
||||
{super.key, required this.room, required this.server, this.item});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _EditItemPageState();
|
||||
|
@ -43,20 +43,20 @@ class _EditItemPageState extends State<EditItemPage> {
|
|||
// TODO: load cached categories first
|
||||
|
||||
doNetworkRequest(ScaffoldMessenger.of(context),
|
||||
req: () => postWithCreadentials(
|
||||
credentials: user,
|
||||
target: user.server,
|
||||
path: 'getCategories',
|
||||
body: {'room': widget.room, 'server': widget.server}),
|
||||
onOK: (body) async {
|
||||
final resp = body['data']
|
||||
.map<RoomCategory>((raw) => RoomCategory.fromJSON(raw))
|
||||
.toList();
|
||||
req: () => postWithCreadentials(
|
||||
credentials: user,
|
||||
target: user.server,
|
||||
path: 'getCategories',
|
||||
body: {'room': widget.room, 'server': widget.server}),
|
||||
onOK: (body) async {
|
||||
final resp = body['data']
|
||||
.map<RoomCategory>((raw) => RoomCategory.fromJSON(raw))
|
||||
.toList();
|
||||
|
||||
setState(() {
|
||||
setState(() {
|
||||
categories = resp;
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void fetchProducts() {
|
||||
|
@ -65,20 +65,20 @@ class _EditItemPageState extends State<EditItemPage> {
|
|||
// TODO: load cached products first
|
||||
|
||||
doNetworkRequest(ScaffoldMessenger.of(context),
|
||||
req: () => postWithCreadentials(
|
||||
credentials: user,
|
||||
target: user.server,
|
||||
path: 'getProducts',
|
||||
body: {'room': widget.room, 'server': widget.server}),
|
||||
onOK: (body) async {
|
||||
final resp = body['data']
|
||||
.map<RoomProduct>((raw) => RoomProduct.fromJSON(raw))
|
||||
.toList();
|
||||
req: () => postWithCreadentials(
|
||||
credentials: user,
|
||||
target: user.server,
|
||||
path: 'getProducts',
|
||||
body: {'room': widget.room, 'server': widget.server}),
|
||||
onOK: (body) async {
|
||||
final resp = body['data']
|
||||
.map<RoomProduct>((raw) => RoomProduct.fromJSON(raw))
|
||||
.toList();
|
||||
|
||||
setState(() {
|
||||
setState(() {
|
||||
products = resp;
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void fetchItem() {
|
||||
|
@ -87,21 +87,21 @@ class _EditItemPageState extends State<EditItemPage> {
|
|||
// TODO: load cached item first
|
||||
|
||||
doNetworkRequest(ScaffoldMessenger.of(context),
|
||||
req: () => postWithCreadentials(
|
||||
credentials: user,
|
||||
target: user.server,
|
||||
path: 'getItem',
|
||||
body: {
|
||||
'room': widget.room,
|
||||
'server': widget.server,
|
||||
'listItemID': widget.item
|
||||
}),
|
||||
onOK: (body) async {
|
||||
final resp = RoomItem.fromJSON(body['data']);
|
||||
setState(() {
|
||||
req: () => postWithCreadentials(
|
||||
credentials: user,
|
||||
target: user.server,
|
||||
path: 'getItem',
|
||||
body: {
|
||||
'room': widget.room,
|
||||
'server': widget.server,
|
||||
'listItemID': widget.item
|
||||
}),
|
||||
onOK: (body) async {
|
||||
final resp = RoomItem.fromJSON(body['data']);
|
||||
setState(() {
|
||||
item = resp;
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -109,12 +109,12 @@ class _EditItemPageState extends State<EditItemPage> {
|
|||
super.initState();
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
fetchCategories();
|
||||
fetchProducts();
|
||||
fetchCategories();
|
||||
fetchProducts();
|
||||
|
||||
if (widget.item != null) {
|
||||
fetchItem();
|
||||
}
|
||||
if (widget.item != null) {
|
||||
fetchItem();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -123,156 +123,156 @@ class _EditItemPageState extends State<EditItemPage> {
|
|||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text((widget.item == null)
|
||||
? AppLocalizations.of(context)!.createItem
|
||||
: AppLocalizations.of(context)!.editItem),
|
||||
? AppLocalizations.of(context)!.createItem
|
||||
: AppLocalizations.of(context)!.editItem),
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(14),
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 400),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: TextField(
|
||||
controller: _ctrName,
|
||||
keyboardType: TextInputType.name,
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.badge),
|
||||
labelText: AppLocalizations.of(context)!
|
||||
.inputItemNameLabel,
|
||||
hintText: AppLocalizations.of(context)!
|
||||
.inputItemNameHint,
|
||||
helperText: AppLocalizations.of(context)!
|
||||
.inputItemNameHelp,
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
),
|
||||
ProductPicker(
|
||||
label: AppLocalizations.of(context)!
|
||||
.selectLinkedProductLabel,
|
||||
hint: AppLocalizations.of(context)!
|
||||
.selectLinkedProductHint,
|
||||
products: products,
|
||||
selected: _ctrLink,
|
||||
onSelect: (pid) {
|
||||
setState(() {
|
||||
_ctrLink = pid;
|
||||
});
|
||||
},
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: TextField(
|
||||
controller: _ctrDescription,
|
||||
keyboardType: TextInputType.text,
|
||||
decoration: InputDecoration(
|
||||
labelText: AppLocalizations.of(context)!
|
||||
.inputItemDescriptionLabel,
|
||||
hintText: AppLocalizations.of(context)!
|
||||
.inputItemDescriptionHint,
|
||||
helperText: AppLocalizations.of(context)!
|
||||
.inputItemDescriptionHelp,
|
||||
prefixIcon: const Icon(Icons.dns),
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
),
|
||||
DynamicValueUnitInput(
|
||||
initialUnit: _ctrUnit,
|
||||
initialValue: _ctrValue,
|
||||
onUnitChange: (unit) {
|
||||
setState(() {
|
||||
_ctrUnit = unit;
|
||||
});
|
||||
},
|
||||
onValueChange: (value) {
|
||||
setState(() {
|
||||
_ctrValue = value;
|
||||
});
|
||||
},
|
||||
),
|
||||
CategoryPicker(
|
||||
label: AppLocalizations.of(context)!
|
||||
.selectCategoryLabel,
|
||||
hint: AppLocalizations.of(context)!
|
||||
.selectCategoryHint,
|
||||
categories: categories,
|
||||
selected: _ctrCategory,
|
||||
onSelect: (cid) {
|
||||
setState(() {
|
||||
_ctrCategory = cid;
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
))))),
|
||||
child: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(14),
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 400),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: TextField(
|
||||
controller: _ctrName,
|
||||
keyboardType: TextInputType.name,
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.badge),
|
||||
labelText: AppLocalizations.of(context)!
|
||||
.inputItemNameLabel,
|
||||
hintText: AppLocalizations.of(context)!
|
||||
.inputItemNameHint,
|
||||
helperText: AppLocalizations.of(context)!
|
||||
.inputItemNameHelp,
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
),
|
||||
ProductPicker(
|
||||
label: AppLocalizations.of(context)!
|
||||
.selectLinkedProductLabel,
|
||||
hint: AppLocalizations.of(context)!
|
||||
.selectLinkedProductHint,
|
||||
products: products,
|
||||
selected: _ctrLink,
|
||||
onSelect: (pid) {
|
||||
setState(() {
|
||||
_ctrLink = pid;
|
||||
});
|
||||
},
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: TextField(
|
||||
controller: _ctrDescription,
|
||||
keyboardType: TextInputType.text,
|
||||
decoration: InputDecoration(
|
||||
labelText: AppLocalizations.of(context)!
|
||||
.inputItemDescriptionLabel,
|
||||
hintText: AppLocalizations.of(context)!
|
||||
.inputItemDescriptionHint,
|
||||
helperText: AppLocalizations.of(context)!
|
||||
.inputItemDescriptionHelp,
|
||||
prefixIcon: const Icon(Icons.dns),
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
),
|
||||
DynamicValueUnitInput(
|
||||
initialUnit: _ctrUnit,
|
||||
initialValue: _ctrValue,
|
||||
onUnitChange: (unit) {
|
||||
setState(() {
|
||||
_ctrUnit = unit;
|
||||
});
|
||||
},
|
||||
onValueChange: (value) {
|
||||
setState(() {
|
||||
_ctrValue = value;
|
||||
});
|
||||
},
|
||||
),
|
||||
CategoryPicker(
|
||||
label: AppLocalizations.of(context)!
|
||||
.selectCategoryLabel,
|
||||
hint: AppLocalizations.of(context)!
|
||||
.selectCategoryHint,
|
||||
categories: categories,
|
||||
selected: _ctrCategory,
|
||||
onSelect: (cid) {
|
||||
setState(() {
|
||||
_ctrCategory = cid;
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
))))),
|
||||
floatingActionButton: FloatingActionButton.extended(
|
||||
onPressed: () async {
|
||||
final scaffMgr = ScaffoldMessenger.of(context);
|
||||
final trans = AppLocalizations.of(context);
|
||||
final nav = Navigator.of(context);
|
||||
onPressed: () async {
|
||||
final scaffMgr = ScaffoldMessenger.of(context);
|
||||
final trans = AppLocalizations.of(context);
|
||||
final nav = Navigator.of(context);
|
||||
|
||||
if (_ctrName.text.isEmpty) {
|
||||
showSimpleSnackbar(scaffMgr,
|
||||
text: trans!.errorProductNameShouldNotBeEmpty,
|
||||
action: trans.ok);
|
||||
return;
|
||||
}
|
||||
if (_ctrName.text.isEmpty) {
|
||||
showSimpleSnackbar(scaffMgr,
|
||||
text: trans!.errorProductNameShouldNotBeEmpty,
|
||||
action: trans.ok);
|
||||
return;
|
||||
}
|
||||
|
||||
final user = context.read<User>();
|
||||
final user = context.read<User>();
|
||||
|
||||
if (widget.item == null) {
|
||||
doNetworkRequest(scaffMgr,
|
||||
req: () => postWithCreadentials(
|
||||
credentials: user,
|
||||
target: user.server,
|
||||
path: 'addItem',
|
||||
body: {
|
||||
'room': widget.room,
|
||||
'server': widget.server,
|
||||
'state': 0,
|
||||
'title': _ctrName.text,
|
||||
'description': _ctrDescription.text,
|
||||
'listCatID': _ctrCategory,
|
||||
'unit': _ctrUnit,
|
||||
'value': _ctrValue,
|
||||
'listProdID': _ctrLink
|
||||
}),
|
||||
onOK: (_) async {
|
||||
nav.pop();
|
||||
});
|
||||
} else {
|
||||
doNetworkRequest(scaffMgr,
|
||||
req: () => postWithCreadentials(
|
||||
credentials: user,
|
||||
target: user.server,
|
||||
path: 'changeItem',
|
||||
body: {
|
||||
'listItemID': widget.item,
|
||||
'room': widget.room,
|
||||
'server': widget.server,
|
||||
'title': _ctrName.text,
|
||||
'description': _ctrDescription.text,
|
||||
'listCatID': _ctrCategory,
|
||||
'defUnit': _ctrUnit,
|
||||
'defValue': _ctrValue,
|
||||
'listProdID': _ctrLink
|
||||
}),
|
||||
onOK: (_) async {
|
||||
nav.pop();
|
||||
});
|
||||
}
|
||||
},
|
||||
label: Text(widget.item != null
|
||||
? AppLocalizations.of(context)!.editItemShort
|
||||
: AppLocalizations.of(context)!.createItemShort),
|
||||
icon: Icon(widget.item != null ? Icons.edit : Icons.add)),
|
||||
if (widget.item == null) {
|
||||
doNetworkRequest(scaffMgr,
|
||||
req: () => postWithCreadentials(
|
||||
credentials: user,
|
||||
target: user.server,
|
||||
path: 'addItem',
|
||||
body: {
|
||||
'room': widget.room,
|
||||
'server': widget.server,
|
||||
'state': 0,
|
||||
'title': _ctrName.text,
|
||||
'description': _ctrDescription.text,
|
||||
'listCatID': _ctrCategory,
|
||||
'unit': _ctrUnit,
|
||||
'value': _ctrValue,
|
||||
'listProdID': _ctrLink
|
||||
}),
|
||||
onOK: (_) async {
|
||||
nav.pop();
|
||||
});
|
||||
} else {
|
||||
doNetworkRequest(scaffMgr,
|
||||
req: () => postWithCreadentials(
|
||||
credentials: user,
|
||||
target: user.server,
|
||||
path: 'changeItem',
|
||||
body: {
|
||||
'listItemID': widget.item,
|
||||
'room': widget.room,
|
||||
'server': widget.server,
|
||||
'title': _ctrName.text,
|
||||
'description': _ctrDescription.text,
|
||||
'listCatID': _ctrCategory,
|
||||
'defUnit': _ctrUnit,
|
||||
'defValue': _ctrValue,
|
||||
'listProdID': _ctrLink
|
||||
}),
|
||||
onOK: (_) async {
|
||||
nav.pop();
|
||||
});
|
||||
}
|
||||
},
|
||||
label: Text(widget.item != null
|
||||
? AppLocalizations.of(context)!.editItemShort
|
||||
: AppLocalizations.of(context)!.createItemShort),
|
||||
icon: Icon(widget.item != null ? Icons.edit : Icons.add)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,60 +24,60 @@ class _JoinRoomPageState extends State {
|
|||
final user = context.read<User>();
|
||||
|
||||
doNetworkRequest(null,
|
||||
req: () => postWithCreadentials(
|
||||
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',
|
||||
req: () => postWithCreadentials(
|
||||
path: 'listPublicRooms',
|
||||
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);
|
||||
}
|
||||
});
|
||||
onOK: (body) async {
|
||||
// parse rooms
|
||||
final list = body['data'];
|
||||
|
||||
// process the list of public rooms
|
||||
final List<Room> builder = [];
|
||||
processor:
|
||||
for (dynamic raw in list) {
|
||||
try {
|
||||
final room = Room.fromJSON(raw);
|
||||
// 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,
|
||||
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
|
||||
// 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;
|
||||
// process the list of public rooms
|
||||
final List<Room> builder = [];
|
||||
processor:
|
||||
for (dynamic raw in list) {
|
||||
try {
|
||||
final room = Room.fromJSON(raw);
|
||||
|
||||
// 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);
|
||||
} catch (_) {
|
||||
// ignore room
|
||||
builder.add(room);
|
||||
} catch (_) {
|
||||
// ignore room
|
||||
}
|
||||
}
|
||||
}
|
||||
setState(() {
|
||||
setState(() {
|
||||
rooms = builder;
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -90,8 +90,8 @@ class _JoinRoomPageState extends State {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final textTheme = Theme.of(context)
|
||||
.textTheme
|
||||
.apply(displayColor: Theme.of(context).colorScheme.onSurface);
|
||||
.textTheme
|
||||
.apply(displayColor: Theme.of(context).colorScheme.onSurface);
|
||||
|
||||
double width = MediaQuery.of(context).size.width;
|
||||
double height = MediaQuery.of(context).size.height;
|
||||
|
@ -118,182 +118,189 @@ class _JoinRoomPageState extends State {
|
|||
},
|
||||
),
|
||||
MenuAnchor(
|
||||
builder: (ctx, controller, child) {
|
||||
return IconButton(
|
||||
onPressed: () {
|
||||
if (controller.isOpen) {
|
||||
controller.close();
|
||||
} else {
|
||||
controller.open();
|
||||
}
|
||||
},
|
||||
icon: const Icon(Icons.more_vert),
|
||||
);
|
||||
},
|
||||
menuChildren: [
|
||||
MenuItemButton(
|
||||
leadingIcon: const Icon(Icons.drafts),
|
||||
child: Text(AppLocalizations.of(context)!.joinRoomInvite),
|
||||
onPressed: () {
|
||||
// show settings screen
|
||||
context.goNamed('join-room-ota');
|
||||
}),
|
||||
])
|
||||
builder: (ctx, controller, child) {
|
||||
return IconButton(
|
||||
onPressed: () {
|
||||
if (controller.isOpen) {
|
||||
controller.close();
|
||||
} else {
|
||||
controller.open();
|
||||
}
|
||||
},
|
||||
icon: const Icon(Icons.more_vert),
|
||||
);
|
||||
},
|
||||
menuChildren: [
|
||||
MenuItemButton(
|
||||
leadingIcon: const Icon(Icons.drafts),
|
||||
child: Text(AppLocalizations.of(context)!.joinRoomInvite),
|
||||
onPressed: () {
|
||||
// show settings screen
|
||||
context.goNamed('join-room-ota');
|
||||
}),
|
||||
])
|
||||
],
|
||||
),
|
||||
body: rooms.isEmpty
|
||||
? Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Text(AppLocalizations.of(context)!.noNewRoomsFound, style: textTheme.titleLarge),
|
||||
],
|
||||
))
|
||||
: ListView.builder(
|
||||
itemCount: rooms.length,
|
||||
itemBuilder: (ctx, i) {
|
||||
final room = rooms[i];
|
||||
return Card(
|
||||
margin: const EdgeInsets.all(8.0),
|
||||
clipBehavior: Clip.antiAliasWithSaveLayer,
|
||||
semanticContainer: true,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
// show modalBottomSheet
|
||||
// with room information
|
||||
// and join button
|
||||
showModalBottomSheet(
|
||||
context: ctx,
|
||||
builder: (ctx) {
|
||||
return BottomSheet(
|
||||
onClosing: () {},
|
||||
builder: (ctx) {
|
||||
return Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.center,
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.center,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(14),
|
||||
child: Column(children: [
|
||||
// room icon
|
||||
SvgPicture.asset(
|
||||
(room.icon?.img)!,
|
||||
width: smallest * 0.2,
|
||||
height: smallest * 0.2,
|
||||
),
|
||||
// room name
|
||||
Text(
|
||||
room.name,
|
||||
style: textTheme.displayMedium,
|
||||
),
|
||||
Text(
|
||||
'${room.id}@${room.serverTag}',
|
||||
style: textTheme.labelSmall,
|
||||
),
|
||||
// description
|
||||
Text(room.description,
|
||||
style: textTheme.bodyLarge),
|
||||
// visibility
|
||||
Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(room.visibility?.icon),
|
||||
Text((room
|
||||
.visibility?.text(context))!),
|
||||
]),
|
||||
])),
|
||||
// action buttons
|
||||
Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.center,
|
||||
children: [
|
||||
// cancel button
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.all(14),
|
||||
child: ElevatedButton.icon(
|
||||
icon:
|
||||
const Icon(Icons.close),
|
||||
label: Text(AppLocalizations.of(context)!.cancel),
|
||||
onPressed: () {
|
||||
// close sheet
|
||||
Navigator.pop(context);
|
||||
},
|
||||
)),
|
||||
// join room button
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.all(14),
|
||||
child: FilledButton.icon(
|
||||
icon:
|
||||
const Icon(Icons.check),
|
||||
label: Text(AppLocalizations.of(context)!.joinRoom),
|
||||
onPressed: () async {
|
||||
final scaffMgr =
|
||||
ScaffoldMessenger.of(
|
||||
context);
|
||||
? Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Text(AppLocalizations.of(context)!.noNewRoomsFound,
|
||||
style: textTheme.titleLarge),
|
||||
],
|
||||
))
|
||||
: ListView.builder(
|
||||
itemCount: rooms.length,
|
||||
itemBuilder: (ctx, i) {
|
||||
final room = rooms[i];
|
||||
return Card(
|
||||
margin: const EdgeInsets.all(8.0),
|
||||
clipBehavior: Clip.antiAliasWithSaveLayer,
|
||||
semanticContainer: true,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
// show modalBottomSheet
|
||||
// with room information
|
||||
// and join button
|
||||
showModalBottomSheet(
|
||||
context: ctx,
|
||||
builder: (ctx) {
|
||||
return BottomSheet(
|
||||
onClosing: () {},
|
||||
builder: (ctx) {
|
||||
return Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.center,
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.center,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(14),
|
||||
child: Column(children: [
|
||||
// room icon
|
||||
SvgPicture.asset(
|
||||
(room.icon?.img)!,
|
||||
width: smallest * 0.2,
|
||||
height: smallest * 0.2,
|
||||
),
|
||||
// room name
|
||||
Text(
|
||||
room.name,
|
||||
style: textTheme.displayMedium,
|
||||
),
|
||||
Text(
|
||||
'${room.id}@${room.serverTag}',
|
||||
style: textTheme.labelSmall,
|
||||
),
|
||||
// description
|
||||
Text(room.description,
|
||||
style: textTheme.bodyLarge),
|
||||
// visibility
|
||||
Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(room.visibility?.icon),
|
||||
Text((room.visibility
|
||||
?.text(context))!),
|
||||
]),
|
||||
])),
|
||||
// action buttons
|
||||
Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.center,
|
||||
children: [
|
||||
// cancel button
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.all(14),
|
||||
child: ElevatedButton.icon(
|
||||
icon:
|
||||
const Icon(Icons.close),
|
||||
label: Text(
|
||||
AppLocalizations.of(
|
||||
context)!
|
||||
.cancel),
|
||||
onPressed: () {
|
||||
// close sheet
|
||||
Navigator.pop(context);
|
||||
},
|
||||
)),
|
||||
// join room button
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.all(14),
|
||||
child: FilledButton.icon(
|
||||
icon:
|
||||
const Icon(Icons.check),
|
||||
label: Text(
|
||||
AppLocalizations.of(
|
||||
context)!
|
||||
.joinRoom),
|
||||
onPressed: () async {
|
||||
final scaffMgr =
|
||||
ScaffoldMessenger.of(
|
||||
context);
|
||||
|
||||
final nav =
|
||||
Navigator.of(context);
|
||||
final user =
|
||||
context.read<User>();
|
||||
final router =
|
||||
GoRouter.of(context);
|
||||
final nav =
|
||||
Navigator.of(context);
|
||||
final user =
|
||||
context.read<User>();
|
||||
final router =
|
||||
GoRouter.of(context);
|
||||
|
||||
doNetworkRequest(scaffMgr,
|
||||
req: () =>
|
||||
postWithCreadentials(
|
||||
credentials:
|
||||
user,
|
||||
target: user
|
||||
.server,
|
||||
path:
|
||||
'joinPublicRoom',
|
||||
body: {
|
||||
'room':
|
||||
room.id,
|
||||
'server': room
|
||||
.serverTag
|
||||
}),
|
||||
onOK: (body) async {
|
||||
await room.toDisk();
|
||||
nav.pop();
|
||||
router
|
||||
.pushReplacementNamed(
|
||||
'room',
|
||||
params: {
|
||||
'server': room
|
||||
.serverTag,
|
||||
'id': room.id
|
||||
});
|
||||
});
|
||||
},
|
||||
))
|
||||
])
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
doNetworkRequest(scaffMgr,
|
||||
req: () =>
|
||||
postWithCreadentials(
|
||||
credentials:
|
||||
user,
|
||||
target: user
|
||||
.server,
|
||||
path:
|
||||
'joinPublicRoom',
|
||||
body: {
|
||||
'room':
|
||||
room.id,
|
||||
'server': room
|
||||
.serverTag
|
||||
}),
|
||||
onOK: (body) async {
|
||||
await room.toDisk();
|
||||
nav.pop();
|
||||
router
|
||||
.pushReplacementNamed(
|
||||
'room',
|
||||
params: {
|
||||
'server': room
|
||||
.serverTag,
|
||||
'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(
|
||||
label: Text(AppLocalizations.of(context)!.newRoom),
|
||||
icon: const Icon(Icons.add),
|
||||
|
|
|
@ -34,72 +34,69 @@ class _RoomPageState extends State<RoomPage> {
|
|||
|
||||
try {
|
||||
final diskRoom =
|
||||
await Room.fromDisk(serverTag: widget.server, id: widget.tag);
|
||||
await Room.fromDisk(serverTag: widget.server, id: widget.tag);
|
||||
setState(() {
|
||||
room = diskRoom;
|
||||
room = diskRoom;
|
||||
});
|
||||
} catch (_) {}
|
||||
|
||||
doNetworkRequest(sm,
|
||||
req: () => postWithCreadentials(
|
||||
path: 'getRoomInfo',
|
||||
credentials: user,
|
||||
target: user.server,
|
||||
body: {'room': widget.tag, 'server': widget.server}),
|
||||
onOK: (body) async {
|
||||
final info = RoomInfo.fromJSON(body['data']);
|
||||
final room = Room.fromJSON(body['data']);
|
||||
req: () => postWithCreadentials(
|
||||
path: 'getRoomInfo',
|
||||
credentials: user,
|
||||
target: user.server,
|
||||
body: {'room': widget.tag, 'server': widget.server}),
|
||||
onOK: (body) async {
|
||||
final info = RoomInfo.fromJSON(body['data']);
|
||||
final room = Room.fromJSON(body['data']);
|
||||
|
||||
room.toDisk();
|
||||
room.toDisk();
|
||||
|
||||
setState(() {
|
||||
setState(() {
|
||||
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
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
room = Room(
|
||||
id: widget.tag,
|
||||
serverTag: widget.server
|
||||
);
|
||||
room = Room(id: widget.tag, serverTag: widget.server);
|
||||
|
||||
_ctr.addListener(() {
|
||||
setState(() {
|
||||
page = _ctr.page?.toInt() ?? _ctr.initialPage;
|
||||
});
|
||||
setState(() {
|
||||
page = _ctr.page?.toInt() ?? _ctr.initialPage;
|
||||
});
|
||||
});
|
||||
|
||||
Room.listen((_) async {
|
||||
// rooms changed on disk
|
||||
// probably this one,
|
||||
// because it is currently open
|
||||
// NOTE: might be a different room
|
||||
// (if a background listener is implemented at some point,
|
||||
// checking if this room changed might improve performance)
|
||||
try {
|
||||
final r = await Room.fromDisk(serverTag: widget.server, id: widget.tag);
|
||||
setState(() {
|
||||
room = r;
|
||||
});
|
||||
} catch (_) {}
|
||||
// rooms changed on disk
|
||||
// probably this one,
|
||||
// because it is currently open
|
||||
// NOTE: might be a different room
|
||||
// (if a background listener is implemented at some point,
|
||||
// checking if this room changed might improve performance)
|
||||
try {
|
||||
final r = await Room.fromDisk(serverTag: widget.server, id: widget.tag);
|
||||
setState(() {
|
||||
room = r;
|
||||
});
|
||||
} catch (_) {}
|
||||
});
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => fetchInfo());
|
||||
|
@ -123,27 +120,27 @@ class _RoomPageState extends State<RoomPage> {
|
|||
bottomNavigationBar: NavigationBar(
|
||||
onDestinationSelected: (int index) {
|
||||
_ctr.animateToPage(index,
|
||||
curve: Curves.easeInOut,
|
||||
duration: const Duration(milliseconds: 300));
|
||||
curve: Curves.easeInOut,
|
||||
duration: const Duration(milliseconds: 300));
|
||||
},
|
||||
selectedIndex: page,
|
||||
destinations: [
|
||||
NavigationDestination(
|
||||
icon: const Icon(Icons.list),
|
||||
label: AppLocalizations.of(context)!.roomListTitle,
|
||||
tooltip: AppLocalizations.of(context)!.roomListSubtitle),
|
||||
icon: const Icon(Icons.list),
|
||||
label: AppLocalizations.of(context)!.roomListTitle,
|
||||
tooltip: AppLocalizations.of(context)!.roomListSubtitle),
|
||||
NavigationDestination(
|
||||
icon: const Icon(Icons.inventory_2),
|
||||
label: AppLocalizations.of(context)!.roomProductsTitle,
|
||||
tooltip: AppLocalizations.of(context)!.roomProductsSubtitle),
|
||||
icon: const Icon(Icons.inventory_2),
|
||||
label: AppLocalizations.of(context)!.roomProductsTitle,
|
||||
tooltip: AppLocalizations.of(context)!.roomProductsSubtitle),
|
||||
NavigationDestination(
|
||||
icon: const Icon(Icons.category),
|
||||
label: AppLocalizations.of(context)!.roomCategoriesTitle,
|
||||
tooltip: AppLocalizations.of(context)!.roomCategoriesSubtitle),
|
||||
icon: const Icon(Icons.category),
|
||||
label: AppLocalizations.of(context)!.roomCategoriesTitle,
|
||||
tooltip: AppLocalizations.of(context)!.roomCategoriesSubtitle),
|
||||
NavigationDestination(
|
||||
icon: const Icon(Icons.info_rounded),
|
||||
label: AppLocalizations.of(context)!.roomAboutTitle,
|
||||
tooltip: AppLocalizations.of(context)!.roomAboutSubtitle),
|
||||
icon: const Icon(Icons.info_rounded),
|
||||
label: AppLocalizations.of(context)!.roomAboutTitle,
|
||||
tooltip: AppLocalizations.of(context)!.roomAboutSubtitle),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
|
|
@ -38,7 +38,7 @@ class _NewRoomPageState extends State<NewRoomPage> {
|
|||
_ctrIcon = room.icon!;
|
||||
|
||||
setState(() {
|
||||
this.room = room;
|
||||
this.room = room;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -50,17 +50,17 @@ class _NewRoomPageState extends State<NewRoomPage> {
|
|||
|
||||
try {
|
||||
final diskRoom =
|
||||
await Room.fromDisk(serverTag: widget.server!, id: widget.tag!);
|
||||
await Room.fromDisk(serverTag: widget.server!, id: widget.tag!);
|
||||
initFromRoom(diskRoom);
|
||||
} catch (_) {}
|
||||
|
||||
doNetworkRequest(
|
||||
sm,
|
||||
req: () => postWithCreadentials(
|
||||
path: 'getRoomInfo',
|
||||
credentials: user,
|
||||
target: user.server,
|
||||
body: {'room': widget.tag, 'server': widget.server}),
|
||||
path: 'getRoomInfo',
|
||||
credentials: user,
|
||||
target: user.server,
|
||||
body: {'room': widget.tag, 'server': widget.server}),
|
||||
onOK: (body) async {
|
||||
final room = Room.fromJSON(body['data']);
|
||||
room.toDisk();
|
||||
|
@ -70,9 +70,9 @@ class _NewRoomPageState extends State<NewRoomPage> {
|
|||
// no room data available
|
||||
// use data from disk
|
||||
(() async {
|
||||
// no room data available
|
||||
// close screen
|
||||
router.pushReplacementNamed('home');
|
||||
// no room data available
|
||||
// close screen
|
||||
router.pushReplacementNamed('home');
|
||||
})();
|
||||
return true;
|
||||
},
|
||||
|
@ -89,9 +89,9 @@ class _NewRoomPageState extends State<NewRoomPage> {
|
|||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (isEditPage()) {
|
||||
fetchInfo();
|
||||
}
|
||||
if (isEditPage()) {
|
||||
fetchInfo();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -102,235 +102,233 @@ class _NewRoomPageState extends State<NewRoomPage> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final textTheme = Theme.of(context)
|
||||
.textTheme
|
||||
.apply(displayColor: Theme.of(context).colorScheme.onSurface);
|
||||
.textTheme
|
||||
.apply(displayColor: Theme.of(context).colorScheme.onSurface);
|
||||
|
||||
double width = MediaQuery.of(context).size.width;
|
||||
double height = MediaQuery.of(context).size.height;
|
||||
double smallest = min(min(width, height), 400);
|
||||
|
||||
return showSpinner
|
||||
? Scaffold(
|
||||
body: Center(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const CircularProgressIndicator(),
|
||||
Text(AppLocalizations.of(context)!.loading,
|
||||
style: textTheme.titleLarge),
|
||||
])))
|
||||
: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(isEditPage()
|
||||
? AppLocalizations.of(context)!.editRoomMetadata
|
||||
: AppLocalizations.of(context)!.newRoom),
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(14),
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 400),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
IconButton(
|
||||
icon: SvgPicture.asset(
|
||||
_ctrIcon.img,
|
||||
width: smallest * 0.3,
|
||||
height: smallest * 0.3,
|
||||
),
|
||||
tooltip: AppLocalizations.of(context)!
|
||||
.changeRoomIcon,
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (ctx) =>
|
||||
RoomIconPicker(onSelect: (icon) {
|
||||
setState(() {
|
||||
_ctrIcon = icon;
|
||||
});
|
||||
context.pop();
|
||||
}));
|
||||
}),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: TextField(
|
||||
enabled: !isEditPage(),
|
||||
controller: _ctrID,
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.fact_check),
|
||||
labelText: AppLocalizations.of(context)!
|
||||
.inputRoomIdLabel,
|
||||
hintText: AppLocalizations.of(context)!
|
||||
.inputRoomIdHint,
|
||||
helperText: AppLocalizations.of(context)!
|
||||
.inputRoomIdHelp,
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: TextField(
|
||||
controller: _ctrName,
|
||||
keyboardType: TextInputType.name,
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.badge),
|
||||
labelText: AppLocalizations.of(context)!
|
||||
.inputRoomNameLabel,
|
||||
hintText: AppLocalizations.of(context)!
|
||||
.inputRoomNameHint,
|
||||
helperText: AppLocalizations.of(context)!
|
||||
.inputRoomNameHelp,
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: TextField(
|
||||
controller: _ctrDescription,
|
||||
keyboardType: TextInputType.text,
|
||||
decoration: InputDecoration(
|
||||
labelText: AppLocalizations.of(context)!
|
||||
.inputRoomDescriptionLabel,
|
||||
hintText: AppLocalizations.of(context)!
|
||||
.inputRoomDescriptionHint,
|
||||
helperText: AppLocalizations.of(context)!
|
||||
.inputRoomDescriptionHelp,
|
||||
prefixIcon: const Icon(Icons.dns),
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
),
|
||||
...(!isEditPage())
|
||||
? [
|
||||
Text(
|
||||
AppLocalizations.of(context)!
|
||||
.roomVisibilityTitle,
|
||||
style: textTheme.labelLarge),
|
||||
Text(
|
||||
AppLocalizations.of(context)!
|
||||
.roomVisibilitySubtitle,
|
||||
style: textTheme.bodySmall),
|
||||
SegmentedButton<RoomVisibility>(
|
||||
showSelectedIcon: true,
|
||||
multiSelectionEnabled: false,
|
||||
emptySelectionAllowed: false,
|
||||
segments:
|
||||
RoomVisibility.list().map((vis) {
|
||||
return ButtonSegment<
|
||||
RoomVisibility>(
|
||||
value: vis,
|
||||
label: Text(vis.text(context)),
|
||||
icon: Icon(vis.icon));
|
||||
}).toList(),
|
||||
onSelectionChanged: ((vset) {
|
||||
setState(() {
|
||||
_ctrVis = vset.single;
|
||||
});
|
||||
}),
|
||||
selected: {_ctrVis},
|
||||
selectedIcon: Icon(_ctrVis.icon),
|
||||
),
|
||||
]
|
||||
: []
|
||||
],
|
||||
))))),
|
||||
floatingActionButton: FloatingActionButton.extended(
|
||||
onPressed: () async {
|
||||
final scaffMgr = ScaffoldMessenger.of(context);
|
||||
final router = GoRouter.of(context);
|
||||
final trans = AppLocalizations.of(context);
|
||||
? Scaffold(
|
||||
body: Center(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const CircularProgressIndicator(),
|
||||
Text(AppLocalizations.of(context)!.loading,
|
||||
style: textTheme.titleLarge),
|
||||
])))
|
||||
: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(isEditPage()
|
||||
? AppLocalizations.of(context)!.editRoomMetadata
|
||||
: AppLocalizations.of(context)!.newRoom),
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(14),
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 400),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
IconButton(
|
||||
icon: SvgPicture.asset(
|
||||
_ctrIcon.img,
|
||||
width: smallest * 0.3,
|
||||
height: smallest * 0.3,
|
||||
),
|
||||
tooltip: AppLocalizations.of(context)!
|
||||
.changeRoomIcon,
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (ctx) =>
|
||||
RoomIconPicker(onSelect: (icon) {
|
||||
setState(() {
|
||||
_ctrIcon = icon;
|
||||
});
|
||||
context.pop();
|
||||
}));
|
||||
}),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: TextField(
|
||||
enabled: !isEditPage(),
|
||||
controller: _ctrID,
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.fact_check),
|
||||
labelText: AppLocalizations.of(context)!
|
||||
.inputRoomIdLabel,
|
||||
hintText: AppLocalizations.of(context)!
|
||||
.inputRoomIdHint,
|
||||
helperText: AppLocalizations.of(context)!
|
||||
.inputRoomIdHelp,
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: TextField(
|
||||
controller: _ctrName,
|
||||
keyboardType: TextInputType.name,
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.badge),
|
||||
labelText: AppLocalizations.of(context)!
|
||||
.inputRoomNameLabel,
|
||||
hintText: AppLocalizations.of(context)!
|
||||
.inputRoomNameHint,
|
||||
helperText: AppLocalizations.of(context)!
|
||||
.inputRoomNameHelp,
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: TextField(
|
||||
controller: _ctrDescription,
|
||||
keyboardType: TextInputType.text,
|
||||
decoration: InputDecoration(
|
||||
labelText: AppLocalizations.of(context)!
|
||||
.inputRoomDescriptionLabel,
|
||||
hintText: AppLocalizations.of(context)!
|
||||
.inputRoomDescriptionHint,
|
||||
helperText: AppLocalizations.of(context)!
|
||||
.inputRoomDescriptionHelp,
|
||||
prefixIcon: const Icon(Icons.dns),
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
),
|
||||
...(!isEditPage())
|
||||
? [
|
||||
Text(
|
||||
AppLocalizations.of(context)!
|
||||
.roomVisibilityTitle,
|
||||
style: textTheme.labelLarge),
|
||||
Text(
|
||||
AppLocalizations.of(context)!
|
||||
.roomVisibilitySubtitle,
|
||||
style: textTheme.bodySmall),
|
||||
SegmentedButton<RoomVisibility>(
|
||||
showSelectedIcon: true,
|
||||
multiSelectionEnabled: false,
|
||||
emptySelectionAllowed: false,
|
||||
segments:
|
||||
RoomVisibility.list().map((vis) {
|
||||
return ButtonSegment<
|
||||
RoomVisibility>(
|
||||
value: vis,
|
||||
label: Text(vis.text(context)),
|
||||
icon: Icon(vis.icon));
|
||||
}).toList(),
|
||||
onSelectionChanged: ((vset) {
|
||||
setState(() {
|
||||
_ctrVis = vset.single;
|
||||
});
|
||||
}),
|
||||
selected: {_ctrVis},
|
||||
selectedIcon: Icon(_ctrVis.icon),
|
||||
),
|
||||
]
|
||||
: []
|
||||
],
|
||||
))))),
|
||||
floatingActionButton: FloatingActionButton.extended(
|
||||
onPressed: () async {
|
||||
final scaffMgr = ScaffoldMessenger.of(context);
|
||||
final router = GoRouter.of(context);
|
||||
final trans = AppLocalizations.of(context);
|
||||
|
||||
// name may not be empty
|
||||
if (_ctrName.text.isEmpty) {
|
||||
showSimpleSnackbar(scaffMgr,
|
||||
text: trans!.errorNoRoomName, action: trans.ok);
|
||||
// name may not be empty
|
||||
if (_ctrName.text.isEmpty) {
|
||||
showSimpleSnackbar(scaffMgr,
|
||||
text: trans!.errorNoRoomName, action: trans.ok);
|
||||
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
final user = context.read<User>();
|
||||
final user = context.read<User>();
|
||||
|
||||
if (isEditPage()) {
|
||||
final nav = Navigator.of(context);
|
||||
Room clone = room!;
|
||||
clone.name = _ctrName.text;
|
||||
clone.description = _ctrDescription.text;
|
||||
clone.icon = _ctrIcon;
|
||||
if (isEditPage()) {
|
||||
final nav = Navigator.of(context);
|
||||
Room clone = room!;
|
||||
clone.name = _ctrName.text;
|
||||
clone.description = _ctrDescription.text;
|
||||
clone.icon = _ctrIcon;
|
||||
|
||||
doNetworkRequest(scaffMgr,
|
||||
req: () => postWithCreadentials(
|
||||
target: user.server,
|
||||
credentials: user,
|
||||
path: 'changeRoomMeta',
|
||||
body: {
|
||||
'room': clone.id,
|
||||
'server': clone.serverTag,
|
||||
'title': clone.name,
|
||||
'description': clone.description,
|
||||
'icon': clone.icon?.type,
|
||||
}),
|
||||
onOK: (_) async {
|
||||
// room was created
|
||||
// save room
|
||||
await clone.toDisk();
|
||||
nav.pop();
|
||||
});
|
||||
doNetworkRequest(scaffMgr,
|
||||
req: () => postWithCreadentials(
|
||||
target: user.server,
|
||||
credentials: user,
|
||||
path: 'changeRoomMeta',
|
||||
body: {
|
||||
'room': clone.id,
|
||||
'server': clone.serverTag,
|
||||
'title': clone.name,
|
||||
'description': clone.description,
|
||||
'icon': clone.icon?.type,
|
||||
}),
|
||||
onOK: (_) async {
|
||||
// room was created
|
||||
// save room
|
||||
await clone.toDisk();
|
||||
nav.pop();
|
||||
});
|
||||
} else {
|
||||
// new room specific tests & request
|
||||
|
||||
} else {
|
||||
// new room specific tests & request
|
||||
// ID should be at least three characters long
|
||||
if (_ctrID.text.length < 3) {
|
||||
showSimpleSnackbar(scaffMgr,
|
||||
text: _ctrID.text.isEmpty
|
||||
? trans!.errorNoRoomId
|
||||
: trans!.errorRoomIdLength,
|
||||
action: trans.ok);
|
||||
|
||||
// ID should be at least three characters long
|
||||
if (_ctrID.text.length < 3) {
|
||||
showSimpleSnackbar(scaffMgr,
|
||||
text: _ctrID.text.isEmpty
|
||||
? trans!.errorNoRoomId
|
||||
: trans!.errorRoomIdLength,
|
||||
action: trans.ok);
|
||||
return;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
final room = Room(
|
||||
id: _ctrID.text,
|
||||
serverTag: user.server.tag,
|
||||
name: _ctrName.text,
|
||||
description: _ctrDescription.text,
|
||||
icon: _ctrIcon,
|
||||
visibility: _ctrVis);
|
||||
|
||||
final room = Room(
|
||||
id: _ctrID.text,
|
||||
serverTag: user.server.tag,
|
||||
name: _ctrName.text,
|
||||
description: _ctrDescription.text,
|
||||
icon: _ctrIcon,
|
||||
visibility: _ctrVis);
|
||||
|
||||
doNetworkRequest(scaffMgr,
|
||||
req: () => postWithCreadentials(
|
||||
target: user.server,
|
||||
credentials: user,
|
||||
path: 'createRoom',
|
||||
body: {
|
||||
'room': room.id,
|
||||
'title': room.name,
|
||||
'description': room.description,
|
||||
'icon': room.icon?.type,
|
||||
'visibility': room.visibility?.type
|
||||
}),
|
||||
onOK: (_) async {
|
||||
// room was created
|
||||
// save room
|
||||
await room.toDisk();
|
||||
// move to home page
|
||||
router.pushReplacementNamed('home');
|
||||
});
|
||||
}
|
||||
|
||||
},
|
||||
label: Text(isEditPage()
|
||||
? AppLocalizations.of(context)!.editRoomMetadataShort
|
||||
: AppLocalizations.of(context)!.createRoomShort),
|
||||
icon: Icon(isEditPage() ? Icons.edit : Icons.add)),
|
||||
);
|
||||
doNetworkRequest(scaffMgr,
|
||||
req: () => postWithCreadentials(
|
||||
target: user.server,
|
||||
credentials: user,
|
||||
path: 'createRoom',
|
||||
body: {
|
||||
'room': room.id,
|
||||
'title': room.name,
|
||||
'description': room.description,
|
||||
'icon': room.icon?.type,
|
||||
'visibility': room.visibility?.type
|
||||
}),
|
||||
onOK: (_) async {
|
||||
// room was created
|
||||
// save room
|
||||
await room.toDisk();
|
||||
// move to home page
|
||||
router.pushReplacementNamed('home');
|
||||
});
|
||||
}
|
||||
},
|
||||
label: Text(isEditPage()
|
||||
? AppLocalizations.of(context)!.editRoomMetadataShort
|
||||
: AppLocalizations.of(context)!.createRoomShort),
|
||||
icon: Icon(isEditPage() ? Icons.edit : Icons.add)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,19 +24,19 @@ class _AboutRoomPageState extends State<AboutRoomPage> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final textTheme = Theme.of(context)
|
||||
.textTheme
|
||||
.apply(displayColor: Theme.of(context).colorScheme.onSurface);
|
||||
.textTheme
|
||||
.apply(displayColor: Theme.of(context).colorScheme.onSurface);
|
||||
|
||||
double width = MediaQuery.of(context).size.width;
|
||||
double height = MediaQuery.of(context).size.height;
|
||||
double smallest = min(width, height);
|
||||
|
||||
return SingleChildScrollView(
|
||||
child: Center(
|
||||
child: Column(children: [
|
||||
// room meta display
|
||||
...(widget.room != null)
|
||||
? [
|
||||
child: Center(
|
||||
child: Column(children: [
|
||||
// room meta display
|
||||
...(widget.room != null)
|
||||
? [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(14),
|
||||
child: Column(
|
||||
|
@ -60,27 +60,27 @@ class _AboutRoomPageState extends State<AboutRoomPage> {
|
|||
textAlign: TextAlign.center,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: SegmentedButton<int>(
|
||||
showSelectedIcon: true,
|
||||
multiSelectionEnabled: false,
|
||||
emptySelectionAllowed: false,
|
||||
segments: RoomVisibility.list().map((vis) {
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: SegmentedButton<int>(
|
||||
showSelectedIcon: true,
|
||||
multiSelectionEnabled: false,
|
||||
emptySelectionAllowed: false,
|
||||
segments: RoomVisibility.list().map((vis) {
|
||||
return ButtonSegment<int>(
|
||||
value: vis.type,
|
||||
label: Text(vis.text(context)),
|
||||
icon: Icon(vis.icon));
|
||||
}).toList(),
|
||||
onSelectionChanged: ((vset) {
|
||||
value: vis.type,
|
||||
label: Text(vis.text(context)),
|
||||
icon: Icon(vis.icon));
|
||||
}).toList(),
|
||||
onSelectionChanged: ((vset) {
|
||||
// check permission
|
||||
// only show confirm dialog when user
|
||||
// is admin, owner or has CHANGE_ADMIN permission
|
||||
if (widget.info == null ||
|
||||
(!(widget.info?.isAdmin ?? false) &&
|
||||
!(widget.info?.isOwner ?? false) &&
|
||||
((widget.info?.permissions)! &
|
||||
RoomPermission.ota ==
|
||||
0))) {
|
||||
(!(widget.info?.isAdmin ?? false) &&
|
||||
!(widget.info?.isOwner ?? false) &&
|
||||
((widget.info?.permissions)! &
|
||||
RoomPermission.ota ==
|
||||
0))) {
|
||||
// action not permitted
|
||||
// NOTE: no error dialog should be shown
|
||||
// because the action is supposed to be hidden
|
||||
|
@ -89,226 +89,247 @@ class _AboutRoomPageState extends State<AboutRoomPage> {
|
|||
|
||||
final vis = RoomVisibility(vset.first);
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (ctx) => AlertDialog(
|
||||
title: Text(AppLocalizations.of(context)!.changeRoomVisibilityTitle),
|
||||
content: Text(AppLocalizations.of(context)!.changeRoomVisibilitySubtitle(vis.text(context))),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
context.pop();
|
||||
},
|
||||
child: Text(AppLocalizations.of(context)!.cancel),
|
||||
),
|
||||
FilledButton(
|
||||
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
|
||||
context: context,
|
||||
builder: (ctx) => AlertDialog(
|
||||
title: Text(AppLocalizations.of(context)!
|
||||
.changeRoomVisibilityTitle),
|
||||
content: Text(
|
||||
AppLocalizations.of(context)!
|
||||
.changeRoomVisibilitySubtitle(
|
||||
vis.text(context))),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
context.pop();
|
||||
},
|
||||
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)!),
|
||||
)),
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!
|
||||
.cancel),
|
||||
),
|
||||
FilledButton(
|
||||
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),
|
||||
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: const EdgeInsets.all(14),
|
||||
child: Column(
|
||||
children: [
|
||||
// edit room meta button
|
||||
...(widget.info != null &&
|
||||
((widget.info?.isAdmin ?? false) ||
|
||||
(widget.info?.isOwner ?? false) ||
|
||||
((widget.info?.permissions)! &
|
||||
RoomPermission.changeMeta !=
|
||||
0)))
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(14),
|
||||
child: Column(
|
||||
children: [
|
||||
// edit room meta button
|
||||
...(widget.info != null &&
|
||||
((widget.info?.isAdmin ?? false) ||
|
||||
(widget.info?.isOwner ?? false) ||
|
||||
((widget.info?.permissions)! &
|
||||
RoomPermission.changeMeta !=
|
||||
0)))
|
||||
? [
|
||||
ListTile(
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
title: Text(AppLocalizations.of(context)!.editRoomMetadata),
|
||||
subtitle: Text(AppLocalizations.of(context)!.editRoomMetadataSubtitle),
|
||||
onTap: () {
|
||||
// show edit room screen
|
||||
context.goNamed('edit-room', params: {
|
||||
ListTile(
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
title: Text(
|
||||
AppLocalizations.of(context)!.editRoomMetadata),
|
||||
subtitle: Text(AppLocalizations.of(context)!
|
||||
.editRoomMetadataSubtitle),
|
||||
onTap: () {
|
||||
// show edit room screen
|
||||
context.goNamed('edit-room', params: {
|
||||
'server': (widget.room?.serverTag)!,
|
||||
'id': (widget.room?.id)!
|
||||
});
|
||||
},
|
||||
),
|
||||
]
|
||||
});
|
||||
},
|
||||
),
|
||||
]
|
||||
: [],
|
||||
// open members view
|
||||
ListTile(
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
title: Text(AppLocalizations.of(context)!.showRoomMembers),
|
||||
subtitle: Text(AppLocalizations.of(context)!.showRoomMembersSubtitle),
|
||||
onTap: () {
|
||||
// open member view screen
|
||||
context.goNamed('room-members', params: {
|
||||
'server': (widget.room?.serverTag)!,
|
||||
'id': (widget.room?.id)!
|
||||
});
|
||||
},
|
||||
),
|
||||
// edit default member permission
|
||||
...(widget.info != null &&
|
||||
((widget.info?.isAdmin ?? false) ||
|
||||
(widget.info?.isOwner ?? false) ||
|
||||
((widget.info?.permissions)! &
|
||||
RoomPermission.changeAdmin !=
|
||||
0)))
|
||||
// open members view
|
||||
ListTile(
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
title: Text(AppLocalizations.of(context)!.showRoomMembers),
|
||||
subtitle:
|
||||
Text(AppLocalizations.of(context)!.showRoomMembersSubtitle),
|
||||
onTap: () {
|
||||
// open member view screen
|
||||
context.goNamed('room-members', params: {
|
||||
'server': (widget.room?.serverTag)!,
|
||||
'id': (widget.room?.id)!
|
||||
});
|
||||
},
|
||||
),
|
||||
// edit default member permission
|
||||
...(widget.info != null &&
|
||||
((widget.info?.isAdmin ?? false) ||
|
||||
(widget.info?.isOwner ?? false) ||
|
||||
((widget.info?.permissions)! &
|
||||
RoomPermission.changeAdmin !=
|
||||
0)))
|
||||
? [
|
||||
ListTile(
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
title: Text(AppLocalizations.of(context)!.editRoomPermissions),
|
||||
subtitle: Text(AppLocalizations.of(context)!.editRoomPermissionsSubtitle),
|
||||
onTap: () {
|
||||
// show checkbox screen
|
||||
context.goNamed('room-permissions', params: {
|
||||
ListTile(
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
title: Text(
|
||||
AppLocalizations.of(context)!.editRoomPermissions),
|
||||
subtitle: Text(AppLocalizations.of(context)!
|
||||
.editRoomPermissionsSubtitle),
|
||||
onTap: () {
|
||||
// show checkbox screen
|
||||
context.goNamed('room-permissions', params: {
|
||||
'server': (widget.room?.serverTag)!,
|
||||
'id': (widget.room?.id)!
|
||||
});
|
||||
},
|
||||
),
|
||||
]
|
||||
});
|
||||
},
|
||||
),
|
||||
]
|
||||
: [],
|
||||
...(widget.info != null &&
|
||||
((widget.info?.isAdmin ?? false) ||
|
||||
(widget.info?.isOwner ?? false) ||
|
||||
((widget.info?.permissions)! & RoomPermission.ota !=
|
||||
0)))
|
||||
...(widget.info != null &&
|
||||
((widget.info?.isAdmin ?? false) ||
|
||||
(widget.info?.isOwner ?? false) ||
|
||||
((widget.info?.permissions)! & RoomPermission.ota !=
|
||||
0)))
|
||||
? [
|
||||
ListTile(
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
title: Text(AppLocalizations.of(context)!.manageRoomOTA),
|
||||
subtitle: Text(AppLocalizations.of(context)!.manageRoomOTASubtitle),
|
||||
onTap: () {
|
||||
// show manage ota screen
|
||||
context.goNamed('room-ota', params: {
|
||||
ListTile(
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
title:
|
||||
Text(AppLocalizations.of(context)!.manageRoomOTA),
|
||||
subtitle: Text(AppLocalizations.of(context)!
|
||||
.manageRoomOTASubtitle),
|
||||
onTap: () {
|
||||
// show manage ota screen
|
||||
context.goNamed('room-ota', params: {
|
||||
'server': (widget.room?.serverTag)!,
|
||||
'id': (widget.room?.id)!
|
||||
});
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
title: Text(AppLocalizations.of(context)!.manageRoomInvites),
|
||||
subtitle: Text(AppLocalizations.of(context)!.manageRoomInvitesSubtitle),
|
||||
onTap: () {
|
||||
// show manage ota screen
|
||||
context.goNamed('room-invite', params: {
|
||||
});
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
title: Text(
|
||||
AppLocalizations.of(context)!.manageRoomInvites),
|
||||
subtitle: Text(AppLocalizations.of(context)!
|
||||
.manageRoomInvitesSubtitle),
|
||||
onTap: () {
|
||||
// show manage ota screen
|
||||
context.goNamed('room-invite', params: {
|
||||
'server': (widget.room?.serverTag)!,
|
||||
'id': (widget.room?.id)!
|
||||
});
|
||||
},
|
||||
),
|
||||
]
|
||||
});
|
||||
},
|
||||
),
|
||||
]
|
||||
: [],
|
||||
],
|
||||
)),
|
||||
],
|
||||
)),
|
||||
|
||||
...(widget.info != null)
|
||||
? [
|
||||
...(widget.info != null)
|
||||
? [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: FilledButton.tonal(
|
||||
child: Text(((widget.info?.isOwner)!)
|
||||
? AppLocalizations.of(context)!.deleteRoom
|
||||
: AppLocalizations.of(context)!.leaveRoom),
|
||||
onPressed: () {
|
||||
// show confirm dialog
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (ctx) => AlertDialog(
|
||||
title: Text(((widget.info?.isOwner)!)
|
||||
? AppLocalizations.of(context)!.deleteRoom
|
||||
: AppLocalizations.of(context)!.leaveRoom),
|
||||
content: Text(((widget.info?.isOwner)!)
|
||||
? AppLocalizations.of(context)!.deleteRoomConfirm
|
||||
: AppLocalizations.of(context)!.leaveRoomConfirm),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
// close popup
|
||||
Navigator.of(ctx).pop();
|
||||
},
|
||||
child: Text(AppLocalizations.of(context)!.cancel),
|
||||
),
|
||||
FilledButton(
|
||||
onPressed: () async {
|
||||
// send request
|
||||
final scaffMgr =
|
||||
ScaffoldMessenger.of(ctx);
|
||||
final nav = Navigator.of(ctx);
|
||||
final router = GoRouter.of(context);
|
||||
final user = context.read<User>();
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: FilledButton.tonal(
|
||||
child: Text(((widget.info?.isOwner)!)
|
||||
? AppLocalizations.of(context)!.deleteRoom
|
||||
: AppLocalizations.of(context)!.leaveRoom),
|
||||
onPressed: () {
|
||||
// show confirm dialog
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (ctx) => AlertDialog(
|
||||
title: Text(((widget.info?.isOwner)!)
|
||||
? AppLocalizations.of(context)!.deleteRoom
|
||||
: AppLocalizations.of(context)!.leaveRoom),
|
||||
content: Text(((widget.info?.isOwner)!)
|
||||
? AppLocalizations.of(context)!
|
||||
.deleteRoomConfirm
|
||||
: AppLocalizations.of(context)!
|
||||
.leaveRoomConfirm),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
// close popup
|
||||
Navigator.of(ctx).pop();
|
||||
},
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.cancel),
|
||||
),
|
||||
FilledButton(
|
||||
onPressed: () async {
|
||||
// send request
|
||||
final scaffMgr =
|
||||
ScaffoldMessenger.of(ctx);
|
||||
final nav = Navigator.of(ctx);
|
||||
final router = GoRouter.of(context);
|
||||
final user = context.read<User>();
|
||||
|
||||
doNetworkRequest(scaffMgr,
|
||||
req: () => postWithCreadentials(
|
||||
path: ((widget.info?.isOwner)!)
|
||||
? 'deleteRoom'
|
||||
: 'leaveRoom',
|
||||
target: user.server,
|
||||
body: {
|
||||
'room': widget.room?.id,
|
||||
'server':
|
||||
(widget.room?.serverTag)!,
|
||||
},
|
||||
credentials: user),
|
||||
onOK: (_) async {
|
||||
// try delete room from disk
|
||||
try {
|
||||
await widget.room?.removeDisk();
|
||||
} catch (_) {}
|
||||
doNetworkRequest(scaffMgr,
|
||||
req: () => postWithCreadentials(
|
||||
path: ((widget.info?.isOwner)!)
|
||||
? 'deleteRoom'
|
||||
: 'leaveRoom',
|
||||
target: user.server,
|
||||
body: {
|
||||
'room': widget.room?.id,
|
||||
'server':
|
||||
(widget.room?.serverTag)!,
|
||||
},
|
||||
credentials: user),
|
||||
onOK: (_) async {
|
||||
// try delete room from disk
|
||||
try {
|
||||
await widget.room?.removeDisk();
|
||||
} catch (_) {}
|
||||
|
||||
// go back home
|
||||
router.pushReplacementNamed('home');
|
||||
},
|
||||
after: () {
|
||||
// close popup
|
||||
nav.pop();
|
||||
});
|
||||
},
|
||||
child: Text(((widget.info?.isOwner)!)
|
||||
? AppLocalizations.of(context)!.deleteRoomShort
|
||||
: AppLocalizations.of(context)!.leaveRoomShort),
|
||||
)
|
||||
],
|
||||
));
|
||||
},
|
||||
))
|
||||
// go back home
|
||||
router.pushReplacementNamed('home');
|
||||
},
|
||||
after: () {
|
||||
// close popup
|
||||
nav.pop();
|
||||
});
|
||||
},
|
||||
child: Text(((widget.info?.isOwner)!)
|
||||
? AppLocalizations.of(context)!
|
||||
.deleteRoomShort
|
||||
: AppLocalizations.of(context)!
|
||||
.leaveRoomShort),
|
||||
)
|
||||
],
|
||||
));
|
||||
},
|
||||
))
|
||||
]
|
||||
: [],
|
||||
: [],
|
||||
])));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ class _RoomCategoriesPageState extends State<RoomCategoriesPage> {
|
|||
super.initState();
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
fetchCategories();
|
||||
fetchCategories();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -36,29 +36,29 @@ class _RoomCategoriesPageState extends State<RoomCategoriesPage> {
|
|||
// TODO: load cached rooms
|
||||
|
||||
doNetworkRequest(ScaffoldMessenger.of(context),
|
||||
req: () => postWithCreadentials(
|
||||
credentials: user,
|
||||
target: user.server,
|
||||
path: 'getCategories',
|
||||
body: {'room': widget.room?.id, 'server': widget.room?.serverTag}),
|
||||
onOK: (json) {
|
||||
final resp = json['data']
|
||||
.map<RoomCategory>((raw) => RoomCategory.fromJSON(raw))
|
||||
.toList();
|
||||
req: () => postWithCreadentials(
|
||||
credentials: user,
|
||||
target: user.server,
|
||||
path: 'getCategories',
|
||||
body: {'room': widget.room?.id, 'server': widget.room?.serverTag}),
|
||||
onOK: (json) {
|
||||
final resp = json['data']
|
||||
.map<RoomCategory>((raw) => RoomCategory.fromJSON(raw))
|
||||
.toList();
|
||||
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
list = resp;
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final textTheme = Theme.of(context)
|
||||
.textTheme
|
||||
.apply(displayColor: Theme.of(context).colorScheme.onSurface);
|
||||
.textTheme
|
||||
.apply(displayColor: Theme.of(context).colorScheme.onSurface);
|
||||
|
||||
return Scaffold(
|
||||
body: ReorderableListView.builder(
|
||||
|
@ -70,13 +70,13 @@ class _RoomCategoriesPageState extends State<RoomCategoriesPage> {
|
|||
key: Key('cat-${item.id}'),
|
||||
leading: Icon(Icons.square_rounded, color: item.color),
|
||||
trailing: ((widget.info?.isAdmin ?? false) ||
|
||||
(widget.info?.isOwner ?? false) ||
|
||||
((widget.info?.permissions)! &
|
||||
RoomPermission.editRoomContent !=
|
||||
0))
|
||||
? ReorderableDragStartListener(
|
||||
index: index, child: const Icon(Icons.drag_handle))
|
||||
: null,
|
||||
(widget.info?.isOwner ?? false) ||
|
||||
((widget.info?.permissions)! &
|
||||
RoomPermission.editRoomContent !=
|
||||
0))
|
||||
? ReorderableDragStartListener(
|
||||
index: index, child: const Icon(Icons.drag_handle))
|
||||
: null,
|
||||
title: Text(item.name),
|
||||
onTap: () {
|
||||
// TODO show edit category popup
|
||||
|
@ -86,8 +86,8 @@ class _RoomCategoriesPageState extends State<RoomCategoriesPage> {
|
|||
if (!((widget.info?.isAdmin ?? false) ||
|
||||
(widget.info?.isOwner ?? false) ||
|
||||
((widget.info?.permissions)! &
|
||||
RoomPermission.editRoomContent !=
|
||||
0))) {
|
||||
RoomPermission.editRoomContent !=
|
||||
0))) {
|
||||
// user is not allowed to edit or delete categories
|
||||
return;
|
||||
}
|
||||
|
@ -96,104 +96,104 @@ class _RoomCategoriesPageState extends State<RoomCategoriesPage> {
|
|||
context: context,
|
||||
builder: (context) => BottomSheet(
|
||||
builder: (context) => Column(children: [
|
||||
Padding(
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Icon(Icons.square_rounded,
|
||||
size: 48.0, color: item.color),
|
||||
size: 48.0, color: item.color),
|
||||
Text(item.name, style: textTheme.titleLarge)
|
||||
],
|
||||
)),
|
||||
// edit category
|
||||
ListTile(
|
||||
leading: const Icon(Icons.edit),
|
||||
title: Text(AppLocalizations.of(context)!.editCategory),
|
||||
subtitle:
|
||||
Text(AppLocalizations.of(context)!.editCategoryLong),
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
onTap: () {
|
||||
// close the modal bottom sheet
|
||||
// so the user returns to the list,
|
||||
// when leaving the category editor
|
||||
Navigator.of(context).pop();
|
||||
)),
|
||||
// edit category
|
||||
ListTile(
|
||||
leading: const Icon(Icons.edit),
|
||||
title: Text(AppLocalizations.of(context)!.editCategory),
|
||||
subtitle:
|
||||
Text(AppLocalizations.of(context)!.editCategoryLong),
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
onTap: () {
|
||||
// close the modal bottom sheet
|
||||
// so the user returns to the list,
|
||||
// when leaving the category editor
|
||||
Navigator.of(context).pop();
|
||||
|
||||
// launch category editor
|
||||
context.pushNamed('edit-category', params: {
|
||||
'server': widget.room!.serverTag,
|
||||
'id': widget.room!.id,
|
||||
'category': item.id.toString()
|
||||
});
|
||||
},
|
||||
),
|
||||
// delete category
|
||||
ListTile(
|
||||
leading: const Icon(Icons.delete),
|
||||
title: Text(AppLocalizations.of(context)!.deleteCategory),
|
||||
subtitle: Text(
|
||||
// launch category editor
|
||||
context.pushNamed('edit-category', params: {
|
||||
'server': widget.room!.serverTag,
|
||||
'id': widget.room!.id,
|
||||
'category': item.id.toString()
|
||||
});
|
||||
},
|
||||
),
|
||||
// delete category
|
||||
ListTile(
|
||||
leading: const Icon(Icons.delete),
|
||||
title: Text(AppLocalizations.of(context)!.deleteCategory),
|
||||
subtitle: Text(
|
||||
AppLocalizations.of(context)!.deleteCategoryLong),
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
onTap: () {
|
||||
// show popup
|
||||
showDialog(
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
onTap: () {
|
||||
// show popup
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (ctx) => AlertDialog(
|
||||
icon: const Icon(Icons.delete),
|
||||
title: Text(AppLocalizations.of(context)!
|
||||
.deleteCategory),
|
||||
content: Text(AppLocalizations.of(context)!
|
||||
.deleteCategoryConfirm(item.name)),
|
||||
actions: [
|
||||
TextButton(
|
||||
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: () {
|
||||
icon: const Icon(Icons.delete),
|
||||
title: Text(AppLocalizations.of(context)!
|
||||
.deleteCategory),
|
||||
content: Text(AppLocalizations.of(context)!
|
||||
.deleteCategoryConfirm(item.name)),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
// close popup
|
||||
navInner.pop();
|
||||
// close modal bottom sheet
|
||||
nav.pop();
|
||||
});
|
||||
},
|
||||
child: Text(AppLocalizations.of(context)!
|
||||
.deleteCategory),
|
||||
)
|
||||
],
|
||||
));
|
||||
},
|
||||
),
|
||||
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
|
||||
navInner.pop();
|
||||
// close modal bottom sheet
|
||||
nav.pop();
|
||||
});
|
||||
},
|
||||
child: Text(AppLocalizations.of(context)!
|
||||
.deleteCategory),
|
||||
)
|
||||
],
|
||||
));
|
||||
},
|
||||
),
|
||||
]),
|
||||
onClosing: () {},
|
||||
),
|
||||
|
@ -206,50 +206,52 @@ class _RoomCategoriesPageState extends State<RoomCategoriesPage> {
|
|||
if (!((widget.info?.isAdmin ?? false) ||
|
||||
(widget.info?.isOwner ?? false) ||
|
||||
((widget.info?.permissions)! & RoomPermission.editRoomContent !=
|
||||
0))) {
|
||||
0))) {
|
||||
// user is not allowed to edit or delete categories
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
if (oldIndex < newIndex) {
|
||||
newIndex -= 1;
|
||||
}
|
||||
final item = list.removeAt(oldIndex);
|
||||
list.insert(newIndex, item);
|
||||
if (oldIndex < newIndex) {
|
||||
newIndex -= 1;
|
||||
}
|
||||
final item = list.removeAt(oldIndex);
|
||||
list.insert(newIndex, item);
|
||||
|
||||
// network request
|
||||
final user = context.read<User>();
|
||||
doNetworkRequest(ScaffoldMessenger.of(context),
|
||||
// network request
|
||||
final user = context.read<User>();
|
||||
doNetworkRequest(ScaffoldMessenger.of(context),
|
||||
req: () => postWithCreadentials(
|
||||
credentials: user,
|
||||
target: user.server,
|
||||
path: 'changeCategoriesOrder',
|
||||
body: {
|
||||
'room': widget.room?.id,
|
||||
'server': widget.room?.serverTag,
|
||||
'listCatIDs': list.map((item) => item.id).toList()
|
||||
}));
|
||||
credentials: user,
|
||||
target: user.server,
|
||||
path: 'changeCategoriesOrder',
|
||||
body: {
|
||||
'room': widget.room?.id,
|
||||
'server': widget.room?.serverTag,
|
||||
'listCatIDs': list.map((item) => item.id).toList()
|
||||
}));
|
||||
});
|
||||
},
|
||||
),
|
||||
floatingActionButton: (widget.info != null && ((widget.info?.isAdmin ?? false) ||
|
||||
(widget.info?.isOwner ?? false) ||
|
||||
((widget.info?.permissions)! & RoomPermission.editRoomContent !=
|
||||
0)))
|
||||
? FloatingActionButton.extended(
|
||||
icon: const Icon(Icons.add),
|
||||
label: Text(AppLocalizations.of(context)!.newCategoryShort),
|
||||
tooltip: AppLocalizations.of(context)!.newCategoryLong,
|
||||
onPressed: () {
|
||||
// show new category popup
|
||||
context.pushNamed('new-category', params: {
|
||||
'server': widget.room!.serverTag,
|
||||
'id': widget.room!.id,
|
||||
});
|
||||
},
|
||||
)
|
||||
: null,
|
||||
floatingActionButton: (widget.info != null &&
|
||||
((widget.info?.isAdmin ?? false) ||
|
||||
(widget.info?.isOwner ?? false) ||
|
||||
((widget.info?.permissions)! &
|
||||
RoomPermission.editRoomContent !=
|
||||
0)))
|
||||
? FloatingActionButton.extended(
|
||||
icon: const Icon(Icons.add),
|
||||
label: Text(AppLocalizations.of(context)!.newCategoryShort),
|
||||
tooltip: AppLocalizations.of(context)!.newCategoryLong,
|
||||
onPressed: () {
|
||||
// show new category popup
|
||||
context.pushNamed('new-category', params: {
|
||||
'server': widget.room!.serverTag,
|
||||
'id': widget.room!.id,
|
||||
});
|
||||
},
|
||||
)
|
||||
: null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,74 +36,74 @@ class _ShoppingListPageState extends State<ShoppingListPage> {
|
|||
// TODO: load cached items first
|
||||
|
||||
doNetworkRequest(ScaffoldMessenger.of(context),
|
||||
req: () => postWithCreadentials(
|
||||
credentials: user,
|
||||
target: user.server,
|
||||
path: 'getItems',
|
||||
body: {'room': widget.room?.id, 'server': widget.room?.serverTag}),
|
||||
onOK: (body) async {
|
||||
final resp = body['data']
|
||||
.map<RoomItem>((raw) => RoomItem.fromJSON(raw))
|
||||
.toList();
|
||||
req: () => postWithCreadentials(
|
||||
credentials: user,
|
||||
target: user.server,
|
||||
path: 'getItems',
|
||||
body: {'room': widget.room?.id, 'server': widget.room?.serverTag}),
|
||||
onOK: (body) async {
|
||||
final resp = body['data']
|
||||
.map<RoomItem>((raw) => RoomItem.fromJSON(raw))
|
||||
.toList();
|
||||
|
||||
final List<RoomItem> l = [];
|
||||
final List<RoomItem> c = [];
|
||||
final List<RoomItem> l = [];
|
||||
final List<RoomItem> c = [];
|
||||
|
||||
for (RoomItem item in resp) {
|
||||
if (item.state == 0) {
|
||||
l.add(item);
|
||||
} else {
|
||||
c.add(item);
|
||||
for (RoomItem item in resp) {
|
||||
if (item.state == 0) {
|
||||
l.add(item);
|
||||
} else {
|
||||
c.add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: cache items
|
||||
// TODO: cache items
|
||||
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
list = l;
|
||||
cart = c;
|
||||
|
||||
sortAll();
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void sortAll() {
|
||||
for (List<RoomItem> input in [list, cart]) {
|
||||
setState(() {
|
||||
input.sort((a, b) {
|
||||
if (a.category == b.category) {
|
||||
return 0;
|
||||
}
|
||||
if (a.category == null) {
|
||||
// b should be below
|
||||
return -1;
|
||||
}
|
||||
if (b.category == null) {
|
||||
// a should be below
|
||||
return 1;
|
||||
}
|
||||
input.sort((a, b) {
|
||||
if (a.category == b.category) {
|
||||
return 0;
|
||||
}
|
||||
if (a.category == null) {
|
||||
// b should be below
|
||||
return -1;
|
||||
}
|
||||
if (b.category == null) {
|
||||
// a should be below
|
||||
return 1;
|
||||
}
|
||||
|
||||
final weightA = weights[a.category];
|
||||
final weightB = weights[b.category];
|
||||
// both could be null now,
|
||||
// so we have to check agein
|
||||
if (weightA == weightB) {
|
||||
return 0;
|
||||
}
|
||||
if (weightA == null) {
|
||||
// b should be below
|
||||
return -1;
|
||||
}
|
||||
if (weightB == null) {
|
||||
// a should be below
|
||||
return 1;
|
||||
}
|
||||
final weightA = weights[a.category];
|
||||
final weightB = weights[b.category];
|
||||
// both could be null now,
|
||||
// so we have to check agein
|
||||
if (weightA == weightB) {
|
||||
return 0;
|
||||
}
|
||||
if (weightA == null) {
|
||||
// b should be below
|
||||
return -1;
|
||||
}
|
||||
if (weightB == null) {
|
||||
// a should be below
|
||||
return 1;
|
||||
}
|
||||
|
||||
return weightA.compareTo(weightB);
|
||||
});
|
||||
return weightA.compareTo(weightB);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -114,31 +114,31 @@ class _ShoppingListPageState extends State<ShoppingListPage> {
|
|||
// TODO: load cached categories first
|
||||
|
||||
doNetworkRequest(ScaffoldMessenger.of(context),
|
||||
req: () => postWithCreadentials(
|
||||
credentials: user,
|
||||
target: user.server,
|
||||
path: 'getCategories',
|
||||
body: {'room': widget.room?.id, 'server': widget.room?.serverTag}),
|
||||
onOK: (body) async {
|
||||
final resp = body['data']
|
||||
.map<RoomCategory>((raw) => RoomCategory.fromJSON(raw))
|
||||
.toList();
|
||||
req: () => postWithCreadentials(
|
||||
credentials: user,
|
||||
target: user.server,
|
||||
path: 'getCategories',
|
||||
body: {'room': widget.room?.id, 'server': widget.room?.serverTag}),
|
||||
onOK: (body) async {
|
||||
final resp = body['data']
|
||||
.map<RoomCategory>((raw) => RoomCategory.fromJSON(raw))
|
||||
.toList();
|
||||
|
||||
Map<int, int> map = {};
|
||||
Map<int?, RoomCategory> cat = {};
|
||||
for (int i = 0; i < resp.length; i++) {
|
||||
map[resp[i].id] = i;
|
||||
cat[resp[i].id] = resp[i];
|
||||
}
|
||||
Map<int, int> map = {};
|
||||
Map<int?, RoomCategory> cat = {};
|
||||
for (int i = 0; i < resp.length; i++) {
|
||||
map[resp[i].id] = i;
|
||||
cat[resp[i].id] = resp[i];
|
||||
}
|
||||
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
weights = map;
|
||||
categories = cat;
|
||||
sortAll();
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void fetchProducts() {
|
||||
|
@ -147,22 +147,22 @@ class _ShoppingListPageState extends State<ShoppingListPage> {
|
|||
// TODO: load cached products first
|
||||
|
||||
doNetworkRequest(ScaffoldMessenger.of(context),
|
||||
req: () => postWithCreadentials(
|
||||
credentials: user,
|
||||
target: user.server,
|
||||
path: 'getProducts',
|
||||
body: {'room': widget.room?.id, 'server': widget.room?.serverTag}),
|
||||
onOK: (body) async {
|
||||
final resp = body['data']
|
||||
.map<RoomProduct>((raw) => RoomProduct.fromJSON(raw))
|
||||
.toList();
|
||||
req: () => postWithCreadentials(
|
||||
credentials: user,
|
||||
target: user.server,
|
||||
path: 'getProducts',
|
||||
body: {'room': widget.room?.id, 'server': widget.room?.serverTag}),
|
||||
onOK: (body) async {
|
||||
final resp = body['data']
|
||||
.map<RoomProduct>((raw) => RoomProduct.fromJSON(raw))
|
||||
.toList();
|
||||
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
products = resp;
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -170,41 +170,41 @@ class _ShoppingListPageState extends State<ShoppingListPage> {
|
|||
super.initState();
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
fetchItems();
|
||||
fetchCategories();
|
||||
fetchProducts();
|
||||
fetchItems();
|
||||
fetchCategories();
|
||||
fetchProducts();
|
||||
});
|
||||
}
|
||||
|
||||
void changeItemState(RoomItem item) {
|
||||
final user = context.read<User>();
|
||||
doNetworkRequest(ScaffoldMessenger.of(context),
|
||||
req: () => postWithCreadentials(
|
||||
credentials: user,
|
||||
target: user.server,
|
||||
path: 'changeItemState',
|
||||
body: {
|
||||
'room': widget.room?.id,
|
||||
'server': widget.room?.serverTag,
|
||||
'listItemID': item.id,
|
||||
'state': item.state
|
||||
}));
|
||||
req: () => postWithCreadentials(
|
||||
credentials: user,
|
||||
target: user.server,
|
||||
path: 'changeItemState',
|
||||
body: {
|
||||
'room': widget.room?.id,
|
||||
'server': widget.room?.serverTag,
|
||||
'listItemID': item.id,
|
||||
'state': item.state
|
||||
}));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: ListView(children: [
|
||||
LabeledDivider(AppLocalizations.of(context)!.shoppingList),
|
||||
ListView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: const ClampingScrollPhysics(),
|
||||
itemCount: list.length,
|
||||
itemBuilder: (context, index) {
|
||||
final item = list[index];
|
||||
final cat =
|
||||
categories[item.category] ?? RoomCategory.other(context);
|
||||
return ShoppingListItem(
|
||||
LabeledDivider(AppLocalizations.of(context)!.shoppingList),
|
||||
ListView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: const ClampingScrollPhysics(),
|
||||
itemCount: list.length,
|
||||
itemBuilder: (context, index) {
|
||||
final item = list[index];
|
||||
final cat =
|
||||
categories[item.category] ?? RoomCategory.other(context);
|
||||
return ShoppingListItem(
|
||||
name: item.name,
|
||||
description: item.description,
|
||||
category: cat,
|
||||
|
@ -215,9 +215,9 @@ class _ShoppingListPageState extends State<ShoppingListPage> {
|
|||
item.state = 1;
|
||||
|
||||
setState(() {
|
||||
list.removeAt(index);
|
||||
cart.add(item);
|
||||
sortAll();
|
||||
list.removeAt(index);
|
||||
cart.add(item);
|
||||
sortAll();
|
||||
});
|
||||
|
||||
// network request
|
||||
|
@ -235,28 +235,28 @@ class _ShoppingListPageState extends State<ShoppingListPage> {
|
|||
// - delete item (if allowed)
|
||||
// - move to/from shopping cart?
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (context) => ShoppingListItemInfo(
|
||||
products: products,
|
||||
category: cat,
|
||||
info: widget.info,
|
||||
item: item,
|
||||
room: widget.room!.id,
|
||||
server: widget.room!.serverTag));
|
||||
});
|
||||
},
|
||||
),
|
||||
LabeledDivider(AppLocalizations.of(context)!.shoppingCart),
|
||||
ListView.builder(
|
||||
itemCount: cart.length,
|
||||
shrinkWrap: true,
|
||||
physics: const ClampingScrollPhysics(),
|
||||
itemBuilder: (context, index) {
|
||||
final item = cart[index];
|
||||
final cat =
|
||||
categories[item.category] ?? RoomCategory.other(context);
|
||||
context: context,
|
||||
builder: (context) => ShoppingListItemInfo(
|
||||
products: products,
|
||||
category: cat,
|
||||
info: widget.info,
|
||||
item: item,
|
||||
room: widget.room!.id,
|
||||
server: widget.room!.serverTag));
|
||||
});
|
||||
},
|
||||
),
|
||||
LabeledDivider(AppLocalizations.of(context)!.shoppingCart),
|
||||
ListView.builder(
|
||||
itemCount: cart.length,
|
||||
shrinkWrap: true,
|
||||
physics: const ClampingScrollPhysics(),
|
||||
itemBuilder: (context, index) {
|
||||
final item = cart[index];
|
||||
final cat =
|
||||
categories[item.category] ?? RoomCategory.other(context);
|
||||
|
||||
return ShoppingListItem(
|
||||
return ShoppingListItem(
|
||||
name: item.name,
|
||||
description: item.description,
|
||||
category: cat,
|
||||
|
@ -266,9 +266,9 @@ class _ShoppingListPageState extends State<ShoppingListPage> {
|
|||
// move back to list
|
||||
item.state = 0;
|
||||
setState(() {
|
||||
cart.removeAt(index);
|
||||
list.add(item);
|
||||
sortAll();
|
||||
cart.removeAt(index);
|
||||
list.add(item);
|
||||
sortAll();
|
||||
});
|
||||
|
||||
// network request
|
||||
|
@ -286,37 +286,37 @@ class _ShoppingListPageState extends State<ShoppingListPage> {
|
|||
// - delete item (if allowed)
|
||||
// - move to/from shopping cart?
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (context) => ShoppingListItemInfo(
|
||||
products: products,
|
||||
category: cat,
|
||||
item: item,
|
||||
info: widget.info,
|
||||
room: widget.room!.id,
|
||||
server: widget.room!.serverTag));
|
||||
});
|
||||
},
|
||||
)
|
||||
context: context,
|
||||
builder: (context) => ShoppingListItemInfo(
|
||||
products: products,
|
||||
category: cat,
|
||||
item: item,
|
||||
info: widget.info,
|
||||
room: widget.room!.id,
|
||||
server: widget.room!.serverTag));
|
||||
});
|
||||
},
|
||||
)
|
||||
]),
|
||||
floatingActionButton: (widget.info != null &&
|
||||
((widget.info?.isAdmin ?? false) ||
|
||||
(widget.info?.isOwner ?? false) ||
|
||||
((widget.info?.permissions)! &
|
||||
RoomPermission.addShoppingListItems !=
|
||||
0)))
|
||||
? FloatingActionButton.extended(
|
||||
icon: const Icon(Icons.add),
|
||||
label: Text(AppLocalizations.of(context)!.newItemShort),
|
||||
tooltip: AppLocalizations.of(context)!.newItemLong,
|
||||
onPressed: () {
|
||||
// show new category popup
|
||||
context.pushNamed('new-item', params: {
|
||||
'server': widget.room!.serverTag,
|
||||
'id': widget.room!.id,
|
||||
});
|
||||
},
|
||||
)
|
||||
: null,
|
||||
((widget.info?.isAdmin ?? false) ||
|
||||
(widget.info?.isOwner ?? false) ||
|
||||
((widget.info?.permissions)! &
|
||||
RoomPermission.addShoppingListItems !=
|
||||
0)))
|
||||
? FloatingActionButton.extended(
|
||||
icon: const Icon(Icons.add),
|
||||
label: Text(AppLocalizations.of(context)!.newItemShort),
|
||||
tooltip: AppLocalizations.of(context)!.newItemLong,
|
||||
onPressed: () {
|
||||
// show new category popup
|
||||
context.pushNamed('new-item', params: {
|
||||
'server': widget.room!.serverTag,
|
||||
'id': widget.room!.id,
|
||||
});
|
||||
},
|
||||
)
|
||||
: null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -331,14 +331,14 @@ class ShoppingListItem extends StatelessWidget {
|
|||
final Function()? onTap;
|
||||
|
||||
const ShoppingListItem(
|
||||
{required this.name,
|
||||
{required this.name,
|
||||
required this.category,
|
||||
required this.inCart,
|
||||
required this.description,
|
||||
required key,
|
||||
this.onDismiss,
|
||||
this.onTap})
|
||||
: _key = key;
|
||||
: _key = key;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -360,21 +360,21 @@ class ShoppingListItem extends StatelessWidget {
|
|||
return true;
|
||||
},
|
||||
background:
|
||||
Icon(!inCart ? Icons.shopping_cart : Icons.remove_shopping_cart),
|
||||
Icon(!inCart ? Icons.shopping_cart : Icons.remove_shopping_cart),
|
||||
child: Opacity(
|
||||
opacity: inCart ? 0.5 : 1.0,
|
||||
child: ListTile(
|
||||
title: Text(name),
|
||||
subtitle: Text(description),
|
||||
trailing: CategoryChip(
|
||||
category: category,
|
||||
),
|
||||
onTap: () {
|
||||
if (onTap != null) {
|
||||
onTap!();
|
||||
}
|
||||
},
|
||||
)),
|
||||
opacity: inCart ? 0.5 : 1.0,
|
||||
child: ListTile(
|
||||
title: Text(name),
|
||||
subtitle: Text(description),
|
||||
trailing: CategoryChip(
|
||||
category: category,
|
||||
),
|
||||
onTap: () {
|
||||
if (onTap != null) {
|
||||
onTap!();
|
||||
}
|
||||
},
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -388,7 +388,7 @@ class ShoppingListItemInfo extends StatelessWidget {
|
|||
final List<RoomProduct> products;
|
||||
|
||||
const ShoppingListItemInfo(
|
||||
{super.key,
|
||||
{super.key,
|
||||
this.info,
|
||||
required this.item,
|
||||
required this.server,
|
||||
|
@ -404,88 +404,88 @@ class ShoppingListItemInfo extends StatelessWidget {
|
|||
builder: (context) => Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(14),
|
||||
child: Center(
|
||||
child: Column(children: [
|
||||
Text(item.name, style: textTheme.headlineLarge),
|
||||
Text(item.description, style: textTheme.titleMedium),
|
||||
CategoryChip(
|
||||
category: category,
|
||||
),
|
||||
Text(Unit.fromId(item.unit).display(context, item.value))
|
||||
]))),
|
||||
padding: const EdgeInsets.all(14),
|
||||
child: Center(
|
||||
child: Column(children: [
|
||||
Text(item.name, style: textTheme.headlineLarge),
|
||||
Text(item.description, style: textTheme.titleMedium),
|
||||
CategoryChip(
|
||||
category: category,
|
||||
),
|
||||
Text(Unit.fromId(item.unit).display(context, item.value))
|
||||
]))),
|
||||
...(item.link != null)
|
||||
? [
|
||||
ListTile(
|
||||
title: Text(AppLocalizations.of(context)!
|
||||
.itemShowLinkedProductTitle),
|
||||
subtitle: Text(AppLocalizations.of(context)!
|
||||
.itemShowLinkedProductSubtitle),
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
onTap: () {
|
||||
// launch "view-product" page for specific product
|
||||
context.pushNamed('view-product', params: {
|
||||
'server': server,
|
||||
'id': room,
|
||||
'product': item.link.toString()
|
||||
});
|
||||
},
|
||||
)
|
||||
]
|
||||
: [],
|
||||
? [
|
||||
ListTile(
|
||||
title: Text(AppLocalizations.of(context)!
|
||||
.itemShowLinkedProductTitle),
|
||||
subtitle: Text(AppLocalizations.of(context)!
|
||||
.itemShowLinkedProductSubtitle),
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
onTap: () {
|
||||
// launch "view-product" page for specific product
|
||||
context.pushNamed('view-product', params: {
|
||||
'server': server,
|
||||
'id': room,
|
||||
'product': item.link.toString()
|
||||
});
|
||||
},
|
||||
)
|
||||
]
|
||||
: [],
|
||||
...(info != null &&
|
||||
((info?.isAdmin ?? false) ||
|
||||
(info?.isOwner ?? false) ||
|
||||
((info?.permissions)! &
|
||||
RoomPermission.addShoppingListItems !=
|
||||
0)))
|
||||
? [
|
||||
ListTile(
|
||||
title: Text(AppLocalizations.of(context)!.editItem),
|
||||
subtitle: Text(AppLocalizations.of(context)!.editItemLong),
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
onTap: () {
|
||||
context.pushNamed('edit-product', params: {
|
||||
'server': server,
|
||||
'id': room,
|
||||
'item': item.id.toString()
|
||||
});
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
title:
|
||||
Text(AppLocalizations.of(context)!.deleteItemTitle),
|
||||
subtitle: Text(
|
||||
AppLocalizations.of(context)!.deleteItemSubtitle),
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
onTap: () {
|
||||
// TODO: show confirm dialog
|
||||
}),
|
||||
]
|
||||
: [],
|
||||
((info?.isAdmin ?? false) ||
|
||||
(info?.isOwner ?? false) ||
|
||||
((info?.permissions)! &
|
||||
RoomPermission.addShoppingListItems !=
|
||||
0)))
|
||||
? [
|
||||
ListTile(
|
||||
title: Text(AppLocalizations.of(context)!.editItem),
|
||||
subtitle: Text(AppLocalizations.of(context)!.editItemLong),
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
onTap: () {
|
||||
context.pushNamed('edit-product', params: {
|
||||
'server': server,
|
||||
'id': room,
|
||||
'item': item.id.toString()
|
||||
});
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
title:
|
||||
Text(AppLocalizations.of(context)!.deleteItemTitle),
|
||||
subtitle: Text(
|
||||
AppLocalizations.of(context)!.deleteItemSubtitle),
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
onTap: () {
|
||||
// TODO: show confirm dialog
|
||||
}),
|
||||
]
|
||||
: [],
|
||||
ListTile(
|
||||
title: Text(item.state == 0
|
||||
? AppLocalizations.of(context)!.moveItemToCartTitle
|
||||
: AppLocalizations.of(context)!.moveItemToCartSubtitle),
|
||||
subtitle: Text(item.state == 0
|
||||
? AppLocalizations.of(context)!.removeItemFromCartTitle
|
||||
: AppLocalizations.of(context)!.removeItemFromCartSubtitle),
|
||||
onTap: () {
|
||||
// flip state
|
||||
item.state = (item.state - 1).abs();
|
||||
final user = context.read<User>();
|
||||
doNetworkRequest(ScaffoldMessenger.of(context),
|
||||
req: () => postWithCreadentials(
|
||||
credentials: user,
|
||||
target: user.server,
|
||||
path: 'changeItemState',
|
||||
body: {
|
||||
'room': room,
|
||||
'server': server,
|
||||
'listItemID': item.id,
|
||||
'state': item.state
|
||||
}));
|
||||
})
|
||||
title: Text(item.state == 0
|
||||
? AppLocalizations.of(context)!.moveItemToCartTitle
|
||||
: AppLocalizations.of(context)!.moveItemToCartSubtitle),
|
||||
subtitle: Text(item.state == 0
|
||||
? AppLocalizations.of(context)!.removeItemFromCartTitle
|
||||
: AppLocalizations.of(context)!.removeItemFromCartSubtitle),
|
||||
onTap: () {
|
||||
// flip state
|
||||
item.state = (item.state - 1).abs();
|
||||
final user = context.read<User>();
|
||||
doNetworkRequest(ScaffoldMessenger.of(context),
|
||||
req: () => postWithCreadentials(
|
||||
credentials: user,
|
||||
target: user.server,
|
||||
path: 'changeItemState',
|
||||
body: {
|
||||
'room': room,
|
||||
'server': server,
|
||||
'listItemID': item.id,
|
||||
'state': item.state
|
||||
}));
|
||||
})
|
||||
],
|
||||
),
|
||||
);
|
||||
|
|
|
@ -25,24 +25,24 @@ class _RoomProductsPageState extends State<RoomProductsPage> {
|
|||
final user = context.read<User>();
|
||||
|
||||
doNetworkRequest(ScaffoldMessenger.of(context),
|
||||
req: () => postWithCreadentials(
|
||||
credentials: user,
|
||||
target: user.server,
|
||||
path: 'getProducts',
|
||||
body: {'room': widget.room?.id, 'server': widget.room?.serverTag}),
|
||||
onOK: (body) async {
|
||||
final resp = body['data']
|
||||
.map<RoomProduct>((raw) => RoomProduct.fromJSON(raw))
|
||||
.toList();
|
||||
req: () => postWithCreadentials(
|
||||
credentials: user,
|
||||
target: user.server,
|
||||
path: 'getProducts',
|
||||
body: {'room': widget.room?.id, 'server': widget.room?.serverTag}),
|
||||
onOK: (body) async {
|
||||
final resp = body['data']
|
||||
.map<RoomProduct>((raw) => RoomProduct.fromJSON(raw))
|
||||
.toList();
|
||||
|
||||
// TODO: cache products
|
||||
// TODO: cache products
|
||||
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
products = resp;
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -73,31 +73,33 @@ class _RoomProductsPageState extends State<RoomProductsPage> {
|
|||
// where as reading the shopping item description,
|
||||
// might be a good idea
|
||||
context.pushNamed('view-product', params: {
|
||||
'server': widget.room!.serverTag,
|
||||
'id': widget.room!.id,
|
||||
'product': item.id.toString()
|
||||
'server': widget.room!.serverTag,
|
||||
'id': widget.room!.id,
|
||||
'product': item.id.toString()
|
||||
});
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
floatingActionButton: (widget.info != null && ((widget.info?.isAdmin ?? false) ||
|
||||
(widget.info?.isOwner ?? false) ||
|
||||
((widget.info?.permissions)! & RoomPermission.editRoomContent !=
|
||||
0)))
|
||||
? FloatingActionButton.extended(
|
||||
icon: const Icon(Icons.add),
|
||||
label: Text(AppLocalizations.of(context)!.newProductShort),
|
||||
tooltip: AppLocalizations.of(context)!.newProductLong,
|
||||
onPressed: () {
|
||||
// show new category popup
|
||||
context.pushNamed('new-product', params: {
|
||||
'server': widget.room!.serverTag,
|
||||
'id': widget.room!.id,
|
||||
});
|
||||
},
|
||||
)
|
||||
: null,
|
||||
floatingActionButton: (widget.info != null &&
|
||||
((widget.info?.isAdmin ?? false) ||
|
||||
(widget.info?.isOwner ?? false) ||
|
||||
((widget.info?.permissions)! &
|
||||
RoomPermission.editRoomContent !=
|
||||
0)))
|
||||
? FloatingActionButton.extended(
|
||||
icon: const Icon(Icons.add),
|
||||
label: Text(AppLocalizations.of(context)!.newProductShort),
|
||||
tooltip: AppLocalizations.of(context)!.newProductLong,
|
||||
onPressed: () {
|
||||
// show new category popup
|
||||
context.pushNamed('new-product', params: {
|
||||
'server': widget.room!.serverTag,
|
||||
'id': widget.room!.id,
|
||||
});
|
||||
},
|
||||
)
|
||||
: null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ class ViewProductPage extends StatefulWidget {
|
|||
final String server;
|
||||
final String room;
|
||||
const ViewProductPage(
|
||||
{required this.server,
|
||||
{required this.server,
|
||||
required this.room,
|
||||
required this.product,
|
||||
super.key});
|
||||
|
@ -39,14 +39,14 @@ class _ViewProductPageState extends State<ViewProductPage> {
|
|||
doNetworkRequest(
|
||||
sm,
|
||||
req: () => postWithCreadentials(
|
||||
path: 'getRoomInfo',
|
||||
credentials: user,
|
||||
target: user.server,
|
||||
body: {'room': widget.room, 'server': widget.server}),
|
||||
path: 'getRoomInfo',
|
||||
credentials: user,
|
||||
target: user.server,
|
||||
body: {'room': widget.room, 'server': widget.server}),
|
||||
onOK: (body) async {
|
||||
final info = RoomInfo.fromJSON(body['data']);
|
||||
setState(() {
|
||||
this.info = info;
|
||||
this.info = info;
|
||||
});
|
||||
return true;
|
||||
},
|
||||
|
@ -59,25 +59,25 @@ class _ViewProductPageState extends State<ViewProductPage> {
|
|||
// TODO: load cached categories first
|
||||
|
||||
doNetworkRequest(ScaffoldMessenger.of(context),
|
||||
req: () => postWithCreadentials(
|
||||
credentials: user,
|
||||
target: user.server,
|
||||
path: 'getCategories',
|
||||
body: {'room': widget.room, 'server': widget.server}),
|
||||
onOK: (body) async {
|
||||
final resp = body['data']
|
||||
.map<RoomCategory>((raw) => RoomCategory.fromJSON(raw))
|
||||
.toList();
|
||||
req: () => postWithCreadentials(
|
||||
credentials: user,
|
||||
target: user.server,
|
||||
path: 'getCategories',
|
||||
body: {'room': widget.room, 'server': widget.server}),
|
||||
onOK: (body) async {
|
||||
final resp = body['data']
|
||||
.map<RoomCategory>((raw) => RoomCategory.fromJSON(raw))
|
||||
.toList();
|
||||
|
||||
Map<int?, RoomCategory> map = {};
|
||||
Map<int?, RoomCategory> map = {};
|
||||
|
||||
for (RoomCategory cat in resp) {
|
||||
map[cat.id] = cat;
|
||||
}
|
||||
setState(() {
|
||||
for (RoomCategory cat in resp) {
|
||||
map[cat.id] = cat;
|
||||
}
|
||||
setState(() {
|
||||
categories = map;
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void fetchProducts() {
|
||||
|
@ -86,29 +86,29 @@ class _ViewProductPageState extends State<ViewProductPage> {
|
|||
// TODO: load cached products first
|
||||
|
||||
doNetworkRequest(ScaffoldMessenger.of(context),
|
||||
req: () => postWithCreadentials(
|
||||
credentials: user,
|
||||
target: user.server,
|
||||
path: 'getProducts',
|
||||
body: {'room': widget.room, 'server': widget.server}),
|
||||
onOK: (body) async {
|
||||
final resp = body['data']
|
||||
.map<RoomProduct>((raw) => RoomProduct.fromJSON(raw))
|
||||
.toList();
|
||||
req: () => postWithCreadentials(
|
||||
credentials: user,
|
||||
target: user.server,
|
||||
path: 'getProducts',
|
||||
body: {'room': widget.room, 'server': widget.server}),
|
||||
onOK: (body) async {
|
||||
final resp = body['data']
|
||||
.map<RoomProduct>((raw) => RoomProduct.fromJSON(raw))
|
||||
.toList();
|
||||
|
||||
for (RoomProduct prod in resp) {
|
||||
// load product info
|
||||
// for current product
|
||||
if (prod.id == widget.product) {
|
||||
setState(() {
|
||||
for (RoomProduct prod in resp) {
|
||||
// load product info
|
||||
// for current product
|
||||
if (prod.id == widget.product) {
|
||||
setState(() {
|
||||
product = prod;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
setState(() {
|
||||
setState(() {
|
||||
products = resp;
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -116,9 +116,9 @@ class _ViewProductPageState extends State<ViewProductPage> {
|
|||
super.initState();
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
fetchCategories();
|
||||
fetchProducts();
|
||||
fetchInfo();
|
||||
fetchCategories();
|
||||
fetchProducts();
|
||||
fetchInfo();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -131,10 +131,10 @@ class _ViewProductPageState extends State<ViewProductPage> {
|
|||
title: Text(product?.name ?? ''),
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: Column(children: [
|
||||
// display product into
|
||||
Center(
|
||||
child: Padding(
|
||||
child: Column(children: [
|
||||
// display product into
|
||||
Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(14),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
|
@ -142,68 +142,71 @@ class _ViewProductPageState extends State<ViewProductPage> {
|
|||
children: [
|
||||
Text(product?.name ?? '', style: textTheme.headlineLarge),
|
||||
Text(product?.description ?? '',
|
||||
style: textTheme.titleMedium),
|
||||
style: textTheme.titleMedium),
|
||||
Text(product?.ean ?? ''),
|
||||
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
|
||||
// edit product button
|
||||
...(info != null &&
|
||||
(info!.isAdmin ||
|
||||
info!.isOwner ||
|
||||
(info!.permissions & RoomPermission.editRoomContent != 0)))
|
||||
// show actions (if allowed / available
|
||||
// edit product button
|
||||
...(info != null &&
|
||||
(info!.isAdmin ||
|
||||
info!.isOwner ||
|
||||
(info!.permissions & RoomPermission.editRoomContent != 0)))
|
||||
? [
|
||||
ListTile(
|
||||
title: Text(AppLocalizations.of(context)!.editProductTitle),
|
||||
subtitle:
|
||||
Text(AppLocalizations.of(context)!.editProductSubtitle),
|
||||
onTap: () {
|
||||
context.pushNamed('edit-product', params: {
|
||||
ListTile(
|
||||
title: Text(AppLocalizations.of(context)!.editProductTitle),
|
||||
subtitle:
|
||||
Text(AppLocalizations.of(context)!.editProductSubtitle),
|
||||
onTap: () {
|
||||
context.pushNamed('edit-product', params: {
|
||||
'server': widget.server,
|
||||
'id': widget.room,
|
||||
'product': widget.product.toString()
|
||||
});
|
||||
},
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
),
|
||||
]
|
||||
});
|
||||
},
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
),
|
||||
]
|
||||
: [],
|
||||
// show parent?
|
||||
...(product?.parent != null)
|
||||
// show parent?
|
||||
...(product?.parent != null)
|
||||
? [
|
||||
ListTile(
|
||||
title: Text(
|
||||
AppLocalizations.of(context)!.viewParentProductTitle),
|
||||
subtitle: Text(
|
||||
AppLocalizations.of(context)!.viewParentProductSubtitle),
|
||||
onTap: () {
|
||||
context.pushNamed('view-product', params: {
|
||||
ListTile(
|
||||
title: Text(
|
||||
AppLocalizations.of(context)!.viewParentProductTitle),
|
||||
subtitle: Text(
|
||||
AppLocalizations.of(context)!.viewParentProductSubtitle),
|
||||
onTap: () {
|
||||
context.pushNamed('view-product', params: {
|
||||
'server': widget.server,
|
||||
'id': widget.room,
|
||||
'product': product!.parent.toString()
|
||||
});
|
||||
},
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
),
|
||||
]
|
||||
});
|
||||
},
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
),
|
||||
]
|
||||
: [],
|
||||
// show/manage children
|
||||
ListTile(
|
||||
title: Text(AppLocalizations.of(context)!.viewProductChildrenTitle),
|
||||
subtitle:
|
||||
// show/manage children
|
||||
ListTile(
|
||||
title: Text(AppLocalizations.of(context)!.viewProductChildrenTitle),
|
||||
subtitle:
|
||||
Text(AppLocalizations.of(context)!.viewProductChildrenSubtitle),
|
||||
onTap: () {
|
||||
context.pushNamed('view-product-children', params: {
|
||||
'server': widget.server,
|
||||
'id': widget.room,
|
||||
'product': widget.product.toString()
|
||||
});
|
||||
},
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
),
|
||||
onTap: () {
|
||||
context.pushNamed('view-product-children', params: {
|
||||
'server': widget.server,
|
||||
'id': widget.room,
|
||||
'product': widget.product.toString()
|
||||
});
|
||||
},
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
),
|
||||
])),
|
||||
);
|
||||
}
|
||||
|
|
1
lib/screens/server/dashboard.dart
Normal file
1
lib/screens/server/dashboard.dart
Normal file
|
@ -0,0 +1 @@
|
|||
|
|
@ -25,54 +25,57 @@ class _ChangePasswordDialogState extends State<ChangePasswordDialog> {
|
|||
title: Text(AppLocalizations.of(context)!.changeThemeTitle),
|
||||
icon: const Icon(Icons.password),
|
||||
content: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: TextField(
|
||||
controller: _ctrOldPassword,
|
||||
keyboardType: TextInputType.visiblePassword,
|
||||
obscureText: true,
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.lock),
|
||||
labelText: AppLocalizations.of(context)!.inputOldPasswordLabel,
|
||||
hintText: AppLocalizations.of(context)!.inputOldPasswordHint,
|
||||
helperText:AppLocalizations.of(context)!.inputOldPasswordHelp,
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: TextField(
|
||||
controller: _ctrOldPassword,
|
||||
keyboardType: TextInputType.visiblePassword,
|
||||
obscureText: true,
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.lock),
|
||||
labelText: AppLocalizations.of(context)!.inputOldPasswordLabel,
|
||||
hintText: AppLocalizations.of(context)!.inputOldPasswordHint,
|
||||
helperText: AppLocalizations.of(context)!.inputOldPasswordHelp,
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: TextField(
|
||||
controller: _ctrNewPassword,
|
||||
keyboardType: TextInputType.visiblePassword,
|
||||
obscureText: true,
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.lock),
|
||||
labelText: AppLocalizations.of(context)!.inputNewPasswordLabel,
|
||||
hintText: AppLocalizations.of(context)!.inputNewPasswordHint,
|
||||
helperText:AppLocalizations.of(context)!.inputNewPasswordHelp,
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: TextField(
|
||||
controller: _ctrNewPassword,
|
||||
keyboardType: TextInputType.visiblePassword,
|
||||
obscureText: true,
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.lock),
|
||||
labelText: AppLocalizations.of(context)!.inputNewPasswordLabel,
|
||||
hintText: AppLocalizations.of(context)!.inputNewPasswordHint,
|
||||
helperText: AppLocalizations.of(context)!.inputNewPasswordHelp,
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: TextField(
|
||||
controller: _ctrNewPasswordRepeat,
|
||||
keyboardType: TextInputType.visiblePassword,
|
||||
obscureText: true,
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.lock),
|
||||
labelText: AppLocalizations.of(context)!.inputNewPasswordRepeatLabel,
|
||||
hintText: AppLocalizations.of(context)!.inputNewPasswordRepeatHint,
|
||||
helperText:AppLocalizations.of(context)!.inputNewPasswordRepeatHelp,
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: TextField(
|
||||
controller: _ctrNewPasswordRepeat,
|
||||
keyboardType: TextInputType.visiblePassword,
|
||||
obscureText: true,
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.lock),
|
||||
labelText:
|
||||
AppLocalizations.of(context)!.inputNewPasswordRepeatLabel,
|
||||
hintText:
|
||||
AppLocalizations.of(context)!.inputNewPasswordRepeatHint,
|
||||
helperText:
|
||||
AppLocalizations.of(context)!.inputNewPasswordRepeatHelp,
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
)),
|
||||
actions: [
|
||||
TextButton(
|
||||
|
@ -93,8 +96,7 @@ class _ChangePasswordDialogState extends State<ChangePasswordDialog> {
|
|||
if (_ctrNewPassword.text.length < 6) {
|
||||
// password has to be at least 6 characters long
|
||||
showSimpleSnackbar(scaffMgr,
|
||||
text: trans!.errorPasswordLength,
|
||||
action: trans.dismiss);
|
||||
text: trans!.errorPasswordLength, action: trans.dismiss);
|
||||
|
||||
_ctrNewPasswordRepeat.clear();
|
||||
return;
|
||||
|
@ -102,7 +104,7 @@ class _ChangePasswordDialogState extends State<ChangePasswordDialog> {
|
|||
if (_ctrNewPassword.text != _ctrNewPasswordRepeat.text) {
|
||||
// new passwords do not match
|
||||
showSimpleSnackbar(scaffMgr,
|
||||
text: trans!.errorPasswordsDoNotMatch, action: trans.dismiss);
|
||||
text: trans!.errorPasswordsDoNotMatch, action: trans.dismiss);
|
||||
|
||||
_ctrNewPasswordRepeat.clear();
|
||||
return;
|
||||
|
@ -110,7 +112,7 @@ class _ChangePasswordDialogState extends State<ChangePasswordDialog> {
|
|||
if (hashPassword(_ctrOldPassword.text) != user.password) {
|
||||
// current password wrong
|
||||
showSimpleSnackbar(scaffMgr,
|
||||
text: trans!.errorOldPasswordWrong, action: trans.dismiss);
|
||||
text: trans!.errorOldPasswordWrong, action: trans.dismiss);
|
||||
|
||||
_ctrOldPassword.clear();
|
||||
return;
|
||||
|
@ -120,23 +122,23 @@ class _ChangePasswordDialogState extends State<ChangePasswordDialog> {
|
|||
|
||||
// send request
|
||||
doNetworkRequest(scaffMgr,
|
||||
req: () => postWithCreadentials(
|
||||
path: 'changePassword',
|
||||
target: user.server,
|
||||
body: {'accountKey': password},
|
||||
credentials: user),
|
||||
onOK: (_) async {
|
||||
// update local user struct
|
||||
final updatedUser = User(
|
||||
username: user.username,
|
||||
password: password,
|
||||
server: user.server);
|
||||
await updatedUser.toDisk();
|
||||
},
|
||||
after: () {
|
||||
// close popup
|
||||
nav.pop();
|
||||
});
|
||||
req: () => postWithCreadentials(
|
||||
path: 'changePassword',
|
||||
target: user.server,
|
||||
body: {'accountKey': password},
|
||||
credentials: user),
|
||||
onOK: (_) async {
|
||||
// update local user struct
|
||||
final updatedUser = User(
|
||||
username: user.username,
|
||||
password: password,
|
||||
server: user.server);
|
||||
await updatedUser.toDisk();
|
||||
},
|
||||
after: () {
|
||||
// close popup
|
||||
nav.pop();
|
||||
});
|
||||
},
|
||||
child: Text(AppLocalizations.of(context)!.changeThemeTitle),
|
||||
)
|
||||
|
|
|
@ -22,17 +22,17 @@ class _WelcomePageState extends State<WelcomePage> {
|
|||
|
||||
_currentPage = controller.initialPage;
|
||||
controller.addListener(() {
|
||||
setState(() {
|
||||
_currentPage = controller.page?.toInt() ?? controller.initialPage;
|
||||
});
|
||||
setState(() {
|
||||
_currentPage = controller.page?.toInt() ?? controller.initialPage;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final textTheme = Theme.of(context)
|
||||
.textTheme
|
||||
.apply(displayColor: Theme.of(context).colorScheme.onSurface);
|
||||
.textTheme
|
||||
.apply(displayColor: Theme.of(context).colorScheme.onSurface);
|
||||
|
||||
String fabText = AppLocalizations.of(context)!.next;
|
||||
if (_currentPage == 0) {
|
||||
|
@ -55,86 +55,80 @@ class _WelcomePageState extends State<WelcomePage> {
|
|||
body: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: PageView(
|
||||
controller: controller,
|
||||
children: <Widget>[
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
SvgPicture.asset(asset("undraw/undraw_shopping_app.svg"),
|
||||
child: PageView(
|
||||
controller: controller,
|
||||
children: <Widget>[
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
SvgPicture.asset(asset("undraw/undraw_shopping_app.svg"),
|
||||
fit: BoxFit.contain,
|
||||
width: smallest * 0.5,
|
||||
height: smallest * 0.5),
|
||||
Text(
|
||||
AppLocalizations.of(context)!.welcomeTitle,
|
||||
style: textTheme.displaySmall,
|
||||
),
|
||||
Text(
|
||||
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,
|
||||
Text(
|
||||
AppLocalizations.of(context)!.welcomeTitle,
|
||||
style: textTheme.displaySmall,
|
||||
),
|
||||
Text(AppLocalizations.of(context)!.welcomeSubtitle,
|
||||
style: textTheme.bodyMedium)
|
||||
],
|
||||
),
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
SvgPicture.asset(asset("undraw/undraw_online_connection.svg"),
|
||||
],
|
||||
),
|
||||
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)!.page3Title,
|
||||
style: textTheme.displaySmall,
|
||||
),
|
||||
Text(
|
||||
AppLocalizations.of(context)!.page3Subtitle,
|
||||
Text(
|
||||
AppLocalizations.of(context)!.page2Title,
|
||||
style: textTheme.displaySmall,
|
||||
),
|
||||
Text(AppLocalizations.of(context)!.page2Subtitle,
|
||||
style: textTheme.bodyMedium)
|
||||
],
|
||||
),
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
SvgPicture.asset(asset("undraw/undraw_online_groceries.svg"),
|
||||
],
|
||||
),
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
SvgPicture.asset(asset("undraw/undraw_online_connection.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,
|
||||
Text(
|
||||
AppLocalizations.of(context)!.page3Title,
|
||||
style: textTheme.displaySmall,
|
||||
),
|
||||
Text(AppLocalizations.of(context)!.page3Subtitle,
|
||||
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(
|
||||
onPressed: () {
|
||||
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 {
|
||||
// move to next page
|
||||
controller.nextPage(
|
||||
curve: Curves.easeInOut,
|
||||
duration: const Duration(milliseconds: 300));
|
||||
curve: Curves.easeInOut,
|
||||
duration: const Duration(milliseconds: 300));
|
||||
}
|
||||
},
|
||||
),
|
||||
|
|
Loading…
Reference in a new issue