Moved User into application wide conntext.

Every page (after login) has access
to the User object via context.read/watch<User>().

This reduces localstore and asnyc operations,
as the screens do not have to load the user every time.
Additionally this prevents anyone from
using the without a user object.
This commit is contained in:
Jakob Meier 2023-03-25 17:18:46 +01:00
parent 42b6da7461
commit bb9a8621a0
No known key found for this signature in database
GPG key ID: 66BDC7E6A01A6152
13 changed files with 471 additions and 527 deletions

View file

@ -16,47 +16,6 @@ import './screens/auth.dart';
import './backend/request.dart'; import './backend/request.dart';
import 'package:routemaster/routemaster.dart'; import 'package:routemaster/routemaster.dart';
// routes when user is not logged in
final routesUnauthorized = RouteMap(routes: {
'/welcome/': (_) => const MaterialPage(child: WelcomePage()),
'/signup': (_) => const MaterialPage(child: AuthPage(mode: Mode.signup)),
'/signupOTA': (_) =>
const MaterialPage(child: AuthPage(mode: Mode.signupOTA)),
'/signin': (_) => const MaterialPage(child: AuthPage(mode: Mode.signin)),
}, onUnknownRoute: (_) => const MaterialPage(child: WelcomePage()));
// routes when user is logged in
final routesLoggedIn = RouteMap(routes: {
'/': (_) => const MaterialPage(child: HomePage()),
'/settings': (_) => const MaterialPage(child: SettingsPage()),
'/add-room/new': (_) => const MaterialPage(child: NewRoomPage()),
'/add-room': (_) => const MaterialPage(child: JoinRoomPage()),
'/r/:server/:tag/': (info) {
final server = info.pathParameters['server'] ?? "";
final tag = info.pathParameters['tag'] ?? "";
return MaterialPage(child: RoomPage(server, tag));
},
'/r/:server/:tag/edit': (info) {
final server = info.pathParameters['server'] ?? "";
final tag = info.pathParameters['tag'] ?? "";
return MaterialPage(child: EditRoomPage(server, tag));
},
'/r/:server/:tag/members': (info) {
final server = info.pathParameters['server'] ?? "";
final tag = info.pathParameters['tag'] ?? "";
return MaterialPage(child: ManageRoomMembersPage(server, tag));
},
'/r/:server/:tag/permissions': (info) {
final server = info.pathParameters['server'] ?? "";
final tag = info.pathParameters['tag'] ?? "";
return MaterialPage(child: EditRoomPermissionSetPage(server, tag));
},
}, onUnknownRoute: (_) => const Redirect('/'));
void main() { void main() {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
runApp(const OutbagApp()); runApp(const OutbagApp());
@ -73,8 +32,8 @@ class _OutbagAppState extends State {
// assume user is logged in // assume user is logged in
// unless not userdata is found // unless not userdata is found
// or the userdata turns out to be wrong // or the userdata turns out to be wrong
bool isAuthorized = true;
AccountMeta? info; AccountMeta? info;
User? user;
AppTheme theme = AppTheme.auto; AppTheme theme = AppTheme.auto;
@ -85,11 +44,17 @@ class _OutbagAppState extends State {
// wait for user to be authorized // wait for user to be authorized
User.listen((_) async { User.listen((_) async {
try { try {
await User.fromDisk(); final user = await User.fromDisk();
setState(() { setState(() {
isAuthorized = true; this.user = user;
}); });
} catch (_) {} fetchInfo();
} catch (_) {
// no userdata found
setState(() {
user = null;
});
}
}); });
AppTheme.listen((_) async { AppTheme.listen((_) async {
@ -106,15 +71,30 @@ class _OutbagAppState extends State {
}); });
})(); })();
WidgetsBinding.instance.addPostFrameCallback((_) => fetchInfo()); WidgetsBinding.instance.addPostFrameCallback((_) async {
try {
final user = await User.fromDisk();
setState(() {
this.user = user;
});
fetchInfo();
} catch (_) {
// user unavailable
// invalid credentials
// log out
setState(() {
user = null;
});
}
});
} }
void fetchInfo() { void fetchInfo() async {
// try to obtain user account information // try to obtain user account information
// with existing details // with existing details
// NOTE: also functions as a way to verify ther data // NOTE: also functions as a way to verify ther data
doNetworkRequest(null, doNetworkRequest(null,
req: (user) => postWithCreadentials( req: () => postWithCreadentials(
target: (user?.server)!, target: (user?.server)!,
path: 'getMyAccount', path: 'getMyAccount',
credentials: user!, credentials: user!,
@ -122,16 +102,16 @@ class _OutbagAppState extends State {
onOK: (body) { onOK: (body) {
final info = AccountMeta.fromJSON(body['data']); final info = AccountMeta.fromJSON(body['data']);
setState(() { setState(() {
isAuthorized = true;
this.info = info; this.info = info;
}); });
}, },
onServerErr: (body) { onServerErr: (_) {
// credentials are wrong // credentials are wrong
// log out // log out
setState(() { setState(() {
isAuthorized = false; info = null;
user = null;
}); });
return true; return true;
}, },
@ -140,20 +120,7 @@ class _OutbagAppState extends State {
// approve login, // approve login,
// until user goes back offline // until user goes back offline
// NOTE TODO: check user data once online // NOTE TODO: check user data once online
setState(() {
isAuthorized = true;
});
return true; return true;
},
onUserErr: () {
// invalid credentials
// log out
setState(() {
isAuthorized = false;
});
// do not show snackbar,
// because the user was probably never logged in
return false;
}); });
} }
@ -164,7 +131,12 @@ class _OutbagAppState extends State {
Provider<AccountMeta?>.value( Provider<AccountMeta?>.value(
value: info, value: info,
), ),
Provider<AppTheme>.value(value: theme) Provider<AppTheme>.value(value: theme),
// conditional user provider
// used to only provide a user outside of the login area
...(user!=null)?[
Provider<User>.value(value:user!)
]:[]
], ],
child: MaterialApp.router( child: MaterialApp.router(
title: "Outbag", title: "Outbag",
@ -172,8 +144,74 @@ class _OutbagAppState extends State {
theme: ThemeData(useMaterial3: true, brightness: Brightness.light), theme: ThemeData(useMaterial3: true, brightness: Brightness.light),
darkTheme: ThemeData(useMaterial3: true, brightness: Brightness.dark), darkTheme: ThemeData(useMaterial3: true, brightness: Brightness.dark),
routerDelegate: RoutemasterDelegate( routerDelegate: RoutemasterDelegate(
routesBuilder: (context) => routesBuilder: (context) => RouteMap(
isAuthorized ? routesLoggedIn : routesUnauthorized), routes: {
// pre authentification routes
...(user == null)
? {
'/welcome/': (_) =>
const MaterialPage(child: WelcomePage()),
'/signup': (_) => const MaterialPage(
child: AuthPage(mode: Mode.signup)),
'/signupOTA': (_) => const MaterialPage(
child: AuthPage(mode: Mode.signupOTA)),
'/signin': (_) => const MaterialPage(
child: AuthPage(mode: Mode.signin)),
}
: {},
// routes that need the user
// to exis
...(user!=null)?{
'/': (_) => const MaterialPage(child: HomePage()),
// user-space settings
'/settings': (_) =>
const MaterialPage(child: SettingsPage()),
// server dashboard
// TODO: add permission check
// so routes are only available
// if user is allowed to view dashboard
...{
},
// room related settings
'/add-room/new': (_) =>
const MaterialPage(child: NewRoomPage()),
'/add-room': (_) =>
const MaterialPage(child: JoinRoomPage()),
'/r/:server/:tag/': (info) {
final server = info.pathParameters['server'] ?? "";
final tag = info.pathParameters['tag'] ?? "";
return MaterialPage(child: RoomPage(server, tag));
},
'/r/:server/:tag/edit': (info) {
final server = info.pathParameters['server'] ?? "";
final tag = info.pathParameters['tag'] ?? "";
return MaterialPage(child: EditRoomPage(server, tag));
},
'/r/:server/:tag/members': (info) {
final server = info.pathParameters['server'] ?? "";
final tag = info.pathParameters['tag'] ?? "";
return MaterialPage(
child: ManageRoomMembersPage(server, tag));
},
'/r/:server/:tag/permissions': (info) {
final server = info.pathParameters['server'] ?? "";
final tag = info.pathParameters['tag'] ?? "";
return MaterialPage(
child: EditRoomPermissionSetPage(server, tag));
},
}:{}
},
onUnknownRoute: (_) => (user == null)
? const MaterialPage(child: WelcomePage())
: const Redirect('/'))),
routeInformationParser: const RoutemasterParser(), routeInformationParser: const RoutemasterParser(),
)); ));
} }

View file

@ -6,8 +6,6 @@ import 'package:outbag_app/tools/fetch_wrapper.dart';
import 'package:outbag_app/tools/snackbar.dart'; import 'package:outbag_app/tools/snackbar.dart';
import 'package:routemaster/routemaster.dart'; import 'package:routemaster/routemaster.dart';
import '../backend/resolve_url.dart'; import '../backend/resolve_url.dart';
import 'package:crypto/crypto.dart';
import 'dart:convert';
enum Mode { enum Mode {
signin, signin,
@ -225,7 +223,7 @@ class _AuthPageState extends State<AuthPage> {
// hash password // hash password
final password = hashPassword(_ctrPassword.text); final password = hashPassword(_ctrPassword.text);
doNetworkRequest(scaffMgr, needUser: false, req: (_) { doNetworkRequest(scaffMgr, req: () {
if (widget.mode == Mode.signin) { if (widget.mode == Mode.signin) {
return postUnauthorized( return postUnauthorized(
target: server, target: server,

View file

@ -36,6 +36,7 @@ class _HomePageState extends State<HomePage> {
void fetchList() async { void fetchList() async {
final sm = ScaffoldMessenger.of(context); final sm = ScaffoldMessenger.of(context);
final user = context.read<User>();
// load cached rooms // load cached rooms
try { try {
@ -45,12 +46,10 @@ class _HomePageState extends State<HomePage> {
}); });
} catch (_) {} } catch (_) {}
doNetworkRequest(sm, doNetworkRequest(
req: (user) => postWithCreadentials( sm,
path: 'listRooms', req: () => postWithCreadentials(
credentials: user!, path: 'listRooms', credentials: user, target: user.server, body: {}),
target: user.server,
body: {}),
onOK: (body) async { onOK: (body) async {
final List<Room> list = body['data'].map<Room>((json) { final List<Room> list = body['data'].map<Room>((json) {
return Room.fromJSON(json); return Room.fromJSON(json);
@ -59,7 +58,6 @@ class _HomePageState extends State<HomePage> {
await r.toDisk(); await r.toDisk();
} }
}, },
onUserErr: ()=>false
); );
} }

View file

@ -5,6 +5,7 @@ import 'package:outbag_app/backend/request.dart';
import 'package:outbag_app/backend/room.dart'; import 'package:outbag_app/backend/room.dart';
import 'package:outbag_app/backend/user.dart'; import 'package:outbag_app/backend/user.dart';
import 'package:outbag_app/tools/fetch_wrapper.dart'; import 'package:outbag_app/tools/fetch_wrapper.dart';
import 'package:provider/provider.dart';
import 'package:routemaster/routemaster.dart'; import 'package:routemaster/routemaster.dart';
import 'dart:math'; import 'dart:math';
@ -44,13 +45,14 @@ class _EditRoomPageState extends State<EditRoomPage> {
void fetchInfo() { void fetchInfo() {
final sm = ScaffoldMessenger.of(context); final sm = ScaffoldMessenger.of(context);
final rmaster = Routemaster.of(context); final rmaster = Routemaster.of(context);
final user = context.read<User>();
doNetworkRequest( doNetworkRequest(
sm, sm,
req: (user) => postWithCreadentials( req: () => postWithCreadentials(
path: 'getRoomInfo', path: 'getRoomInfo',
credentials: user!, credentials: user,
target: (user.server), target: user.server,
body: {'room': widget.tag, 'server': widget.server}), body: {'room': widget.tag, 'server': widget.server}),
onOK: (body) async { onOK: (body) async {
final room = Room.fromJSON(body['data']); final room = Room.fromJSON(body['data']);
@ -139,7 +141,8 @@ class _EditRoomPageState extends State<EditRoomPage> {
tooltip: "Go back", tooltip: "Go back",
), ),
), ),
body: SingleChildScrollView(child: Center( body: SingleChildScrollView(
child: Center(
child: Padding( child: Padding(
padding: const EdgeInsets.all(14), padding: const EdgeInsets.all(14),
child: ConstrainedBox( child: ConstrainedBox(
@ -159,8 +162,8 @@ class _EditRoomPageState extends State<EditRoomPage> {
showDialog( showDialog(
context: context, context: context,
builder: (ctx) => AlertDialog( builder: (ctx) => AlertDialog(
title: title: const Text(
const Text('Choose a room Icon'), 'Choose a room Icon'),
actions: const [], actions: const [],
content: SizedBox( content: SizedBox(
width: smallest * 0.3 * 3, width: smallest * 0.3 * 3,
@ -174,15 +177,19 @@ class _EditRoomPageState extends State<EditRoomPage> {
icon: SvgPicture icon: SvgPicture
.asset( .asset(
icon.img, icon.img,
width: smallest * width:
smallest *
0.3, 0.3,
height: smallest * height:
smallest *
0.3, 0.3,
), ),
tooltip: icon.text, tooltip:
icon.text,
onPressed: () { onPressed: () {
setState(() { setState(() {
_ctrIcon = icon; _ctrIcon =
icon;
}); });
Navigator.of( Navigator.of(
context) context)
@ -239,11 +246,7 @@ class _EditRoomPageState extends State<EditRoomPage> {
), ),
), ),
], ],
) ))))),
)
)
)
),
floatingActionButton: FloatingActionButton.extended( floatingActionButton: FloatingActionButton.extended(
onPressed: () async { onPressed: () async {
final scaffMgr = ScaffoldMessenger.of(context); final scaffMgr = ScaffoldMessenger.of(context);

View file

@ -2,7 +2,9 @@ import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:outbag_app/backend/request.dart'; import 'package:outbag_app/backend/request.dart';
import 'package:outbag_app/backend/room.dart'; import 'package:outbag_app/backend/room.dart';
import 'package:outbag_app/backend/user.dart';
import 'package:outbag_app/tools/fetch_wrapper.dart'; import 'package:outbag_app/tools/fetch_wrapper.dart';
import 'package:provider/provider.dart';
import 'package:routemaster/routemaster.dart'; import 'package:routemaster/routemaster.dart';
import 'dart:math'; import 'dart:math';
@ -18,11 +20,12 @@ class _JoinRoomPageState extends State {
void fetchRooms() { void fetchRooms() {
final sm = ScaffoldMessenger.of(context); final sm = ScaffoldMessenger.of(context);
final user = context.read<User>();
doNetworkRequest(null, doNetworkRequest(null,
req: (user) => postWithCreadentials( req: () => postWithCreadentials(
path: 'listPublicRooms', path: 'listPublicRooms',
credentials: user!, credentials: user,
target: user.server, target: user.server,
body: {}), body: {}),
onOK: (body) async { onOK: (body) async {
@ -33,9 +36,9 @@ class _JoinRoomPageState extends State {
// use an empty blacklist when request is not successful // use an empty blacklist when request is not successful
final List<Room> blacklist = []; final List<Room> blacklist = [];
doNetworkRequest(sm, doNetworkRequest(sm,
req: (user) => postWithCreadentials( req: () => postWithCreadentials(
path: 'listRooms', path: 'listRooms',
credentials: user!, credentials: user,
target: user.server, target: user.server,
body: {}), body: {}),
onOK: (body) { onOK: (body) {
@ -245,12 +248,14 @@ class _JoinRoomPageState extends State {
context); context);
final nav = final nav =
Navigator.of(context); Navigator.of(context);
final user =
context.read<User>();
doNetworkRequest(scaffMgr, doNetworkRequest(scaffMgr,
req: (user) => req: () =>
postWithCreadentials( postWithCreadentials(
credentials: credentials:
user!, user,
target: user target: user
.server, .server,
path: path:

View file

@ -1,11 +1,13 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:outbag_app/backend/request.dart'; import 'package:outbag_app/backend/request.dart';
import 'package:outbag_app/backend/room.dart'; import 'package:outbag_app/backend/room.dart';
import 'package:outbag_app/backend/user.dart';
import 'package:outbag_app/screens/room/pages/about.dart'; import 'package:outbag_app/screens/room/pages/about.dart';
import 'package:outbag_app/screens/room/pages/categories.dart'; import 'package:outbag_app/screens/room/pages/categories.dart';
import 'package:outbag_app/screens/room/pages/products.dart'; import 'package:outbag_app/screens/room/pages/products.dart';
import 'package:outbag_app/screens/room/pages/list.dart'; import 'package:outbag_app/screens/room/pages/list.dart';
import 'package:outbag_app/tools/fetch_wrapper.dart'; import 'package:outbag_app/tools/fetch_wrapper.dart';
import 'package:provider/provider.dart';
import 'package:routemaster/routemaster.dart'; import 'package:routemaster/routemaster.dart';
class RoomPage extends StatefulWidget { class RoomPage extends StatefulWidget {
@ -28,6 +30,7 @@ class _RoomPageState extends State<RoomPage> {
// fetch room information // fetch room information
void fetchInfo() async { void fetchInfo() async {
final sm = ScaffoldMessenger.of(context); final sm = ScaffoldMessenger.of(context);
final user = context.read<User>();
try { try {
final diskRoom = final diskRoom =
@ -38,10 +41,10 @@ class _RoomPageState extends State<RoomPage> {
} catch (_) {} } catch (_) {}
doNetworkRequest(sm, doNetworkRequest(sm,
req: (user) => postWithCreadentials( req: () => postWithCreadentials(
path: 'getRoomInfo', path: 'getRoomInfo',
credentials: user!, credentials: user,
target: (user.server), target: user.server,
body: {'room': widget.tag, 'server': widget.server}), body: {'room': widget.tag, 'server': widget.server}),
onOK: (body) async { onOK: (body) async {
final info = RoomInfo.fromJSON(body['data']); final info = RoomInfo.fromJSON(body['data']);

View file

@ -2,7 +2,9 @@ import 'package:flutter/material.dart';
import 'package:outbag_app/backend/permissions.dart'; import 'package:outbag_app/backend/permissions.dart';
import 'package:outbag_app/backend/request.dart'; import 'package:outbag_app/backend/request.dart';
import 'package:outbag_app/backend/room.dart'; import 'package:outbag_app/backend/room.dart';
import 'package:outbag_app/backend/user.dart';
import 'package:outbag_app/tools/fetch_wrapper.dart'; import 'package:outbag_app/tools/fetch_wrapper.dart';
import 'package:provider/provider.dart';
import 'package:routemaster/routemaster.dart'; import 'package:routemaster/routemaster.dart';
class ManageRoomMembersPage extends StatefulWidget { class ManageRoomMembersPage extends StatefulWidget {
@ -22,13 +24,14 @@ class _ManageRoomMembersPageState extends State<ManageRoomMembersPage> {
void fetchUserInfo() { void fetchUserInfo() {
final rmaster = Routemaster.of(context); final rmaster = Routemaster.of(context);
final sm = ScaffoldMessenger.of(context); final sm = ScaffoldMessenger.of(context);
final user = context.read<User>();
doNetworkRequest( doNetworkRequest(
sm, sm,
req: (user) => postWithCreadentials( req: () => postWithCreadentials(
path: 'getRoomInfo', path: 'getRoomInfo',
credentials: user!, credentials: user,
target: (user.server), target: user.server,
body: {'room': widget.tag, 'server': widget.server}), body: {'room': widget.tag, 'server': widget.server}),
onAnyErr: () { onAnyErr: () {
// user should not be here // user should not be here
@ -49,10 +52,11 @@ class _ManageRoomMembersPageState extends State<ManageRoomMembersPage> {
void fetchMembers() { void fetchMembers() {
final rmaster = Routemaster.of(context); final rmaster = Routemaster.of(context);
final sm = ScaffoldMessenger.of(context); final sm = ScaffoldMessenger.of(context);
final user = context.read<User>();
doNetworkRequest(sm, doNetworkRequest(sm,
req: (user) => postWithCreadentials( req: () => postWithCreadentials(
credentials: user!, credentials: user,
target: user.server, target: user.server,
path: 'getRoomMembers', path: 'getRoomMembers',
body: {'room': widget.tag, 'server': widget.server}), body: {'room': widget.tag, 'server': widget.server}),
@ -140,7 +144,9 @@ class _ManageRoomMembersPageState extends State<ManageRoomMembersPage> {
title: Text(item.humanReadableName), title: Text(item.humanReadableName),
subtitle: Text(role), subtitle: Text(role),
leading: const Icon(Icons.person), leading: const Icon(Icons.person),
onTap: !enable?null:() { onTap: !enable
? null
: () {
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
builder: (context) => BottomSheet( builder: (context) => BottomSheet(
@ -158,7 +164,8 @@ class _ManageRoomMembersPageState extends State<ManageRoomMembersPage> {
...((info?.isAdmin)! || ...((info?.isAdmin)! ||
(info?.isOwner)! || (info?.isOwner)! ||
((info?.permissions)! & ((info?.permissions)! &
RoomPermission.changeAdmin != RoomPermission
.changeAdmin !=
0)) 0))
? [ ? [
ListTile( ListTile(
@ -175,8 +182,10 @@ class _ManageRoomMembersPageState extends State<ManageRoomMembersPage> {
showDialog( showDialog(
context: context, context: context,
builder: builder:
(context) => AlertDialog( (context) =>
icon: const Icon(Icons AlertDialog(
icon: const Icon(
Icons
.supervisor_account), .supervisor_account),
title: Text(item title: Text(item
.isAdmin .isAdmin
@ -188,12 +197,12 @@ class _ManageRoomMembersPageState extends State<ManageRoomMembersPage> {
: 'Do you really want to make ${item.humanReadableName} admin?'), : 'Do you really want to make ${item.humanReadableName} admin?'),
actions: [ actions: [
TextButton( TextButton(
onPressed: () { onPressed:
() {
// close popup // close popup
// NOTE: cancel only closes the dialog // NOTE: cancel only closes the dialog
// whilst OK closes both // whilst OK closes both
Navigator.of( Navigator.of(context)
context)
.pop(); .pop();
}, },
child: const Text( child: const Text(
@ -204,20 +213,16 @@ class _ManageRoomMembersPageState extends State<ManageRoomMembersPage> {
() async { () async {
// send request // send request
final scaffMgr = final scaffMgr =
ScaffoldMessenger.of( ScaffoldMessenger.of(context);
context);
final nav = final nav =
Navigator.of( Navigator.of(context);
context); final user =
context.read<User>();
doNetworkRequest( doNetworkRequest(
scaffMgr, scaffMgr,
req: (user) => req: () =>
postWithCreadentials( postWithCreadentials(path: 'setAdminStatus', credentials: user, target: user.server, body: {
path: 'setAdminStatus',
credentials: user!,
target: user.server,
body: {
'room': widget.tag, 'room': widget.tag,
'roomServer': widget.server, 'roomServer': widget.server,
'server': item.serverTag, 'server': item.serverTag,
@ -233,8 +238,7 @@ class _ManageRoomMembersPageState extends State<ManageRoomMembersPage> {
nav.pop(); nav.pop();
}); });
}, },
child: child: const Text(
const Text(
'OK'), 'OK'),
) )
], ],
@ -251,9 +255,10 @@ class _ManageRoomMembersPageState extends State<ManageRoomMembersPage> {
0)) 0))
? [ ? [
ListTile( ListTile(
leading: leading: const Icon(
const Icon(Icons.person_remove), Icons.person_remove),
title: const Text('Kick User'), title:
const Text('Kick User'),
subtitle: const Text( subtitle: const Text(
"Temporarrily remove user from server (they'll be able to join the room again)"), "Temporarrily remove user from server (they'll be able to join the room again)"),
onTap: () { onTap: () {
@ -261,8 +266,10 @@ class _ManageRoomMembersPageState extends State<ManageRoomMembersPage> {
showDialog( showDialog(
context: context, context: context,
builder: builder:
(context) => AlertDialog( (context) =>
icon: const Icon(Icons AlertDialog(
icon: const Icon(
Icons
.person_remove), .person_remove),
title: const Text( title: const Text(
'Kick User'), 'Kick User'),
@ -270,13 +277,13 @@ class _ManageRoomMembersPageState extends State<ManageRoomMembersPage> {
'Do you really want to kick ${item.humanReadableName}?'), 'Do you really want to kick ${item.humanReadableName}?'),
actions: [ actions: [
TextButton( TextButton(
onPressed: () { onPressed:
() {
// close popup // close popup
// NOTE: cancel only closes the dialog // NOTE: cancel only closes the dialog
// whilst OK closes both // whilst OK closes both
Navigator.of( Navigator.of(context)
context)
.pop(); .pop();
}, },
child: const Text( child: const Text(
@ -287,18 +294,18 @@ class _ManageRoomMembersPageState extends State<ManageRoomMembersPage> {
() async { () async {
// send request // send request
final scaffMgr = final scaffMgr =
ScaffoldMessenger.of( ScaffoldMessenger.of(context);
context);
final nav = final nav =
Navigator.of( Navigator.of(context);
context); final user =
context.read<User>();
doNetworkRequest( doNetworkRequest(
scaffMgr, scaffMgr,
req: (user) => req: () =>
postWithCreadentials( postWithCreadentials(
path: 'kickMember', path: 'kickMember',
credentials: user!, credentials: user,
target: user.server, target: user.server,
body: { body: {
'room': widget.tag, 'room': widget.tag,

View file

@ -5,6 +5,7 @@ import 'package:outbag_app/backend/room.dart';
import 'package:outbag_app/backend/user.dart'; import 'package:outbag_app/backend/user.dart';
import 'package:outbag_app/tools/fetch_wrapper.dart'; import 'package:outbag_app/tools/fetch_wrapper.dart';
import 'package:outbag_app/tools/snackbar.dart'; import 'package:outbag_app/tools/snackbar.dart';
import 'package:provider/provider.dart';
import 'package:routemaster/routemaster.dart'; import 'package:routemaster/routemaster.dart';
import 'dart:math'; import 'dart:math';
@ -77,8 +78,8 @@ class _NewRoomPageState extends State {
showDialog( showDialog(
context: context, context: context,
builder: (ctx) => AlertDialog( builder: (ctx) => AlertDialog(
title: title: const Text(
const Text('Choose a room Icon'), 'Choose a room Icon'),
actions: const [], actions: const [],
content: SizedBox( content: SizedBox(
width: smallest * 0.3 * 3, width: smallest * 0.3 * 3,
@ -92,15 +93,19 @@ class _NewRoomPageState extends State {
icon: SvgPicture icon: SvgPicture
.asset( .asset(
icon.img, icon.img,
width: smallest * width:
smallest *
0.3, 0.3,
height: smallest * height:
smallest *
0.3, 0.3,
), ),
tooltip: icon.text, tooltip:
icon.text,
onPressed: () { onPressed: () {
setState(() { setState(() {
_ctrIcon = icon; _ctrIcon =
icon;
}); });
Navigator.of( Navigator.of(
context) context)
@ -177,11 +182,7 @@ class _NewRoomPageState extends State {
selectedIcon: Icon(_ctrVis.icon), selectedIcon: Icon(_ctrVis.icon),
), ),
], ],
) ))))),
)
)
)
),
floatingActionButton: FloatingActionButton.extended( floatingActionButton: FloatingActionButton.extended(
onPressed: () async { onPressed: () async {
final scaffMgr = ScaffoldMessenger.of(context); final scaffMgr = ScaffoldMessenger.of(context);
@ -200,24 +201,13 @@ class _NewRoomPageState extends State {
// name may not be empty // name may not be empty
if (_ctrName.text.isEmpty) { if (_ctrName.text.isEmpty) {
showSimpleSnackbar( showSimpleSnackbar(scaffMgr,
scaffMgr, text: 'Please specify a room name', action: 'OK');
text: 'Please specify a room name',
action: 'OK'
);
return; return;
} }
User user; final user = context.read<User>();
try {
user = await User.fromDisk();
} catch (_) {
// user data invalid
// shouldn't happen
return;
}
final room = Room( final room = Room(
id: _ctrID.text, id: _ctrID.text,
serverTag: user.server.tag, serverTag: user.server.tag,
@ -227,7 +217,7 @@ class _NewRoomPageState extends State {
visibility: _ctrVis); visibility: _ctrVis);
doNetworkRequest(scaffMgr, doNetworkRequest(scaffMgr,
req: (_) => postWithCreadentials( req: () => postWithCreadentials(
target: user.server, target: user.server,
credentials: user, credentials: user,
path: 'createRoom', path: 'createRoom',
@ -244,10 +234,7 @@ class _NewRoomPageState extends State {
await room.toDisk(); await room.toDisk();
// move to home page // move to home page
rmaster.replace('/'); rmaster.replace('/');
}, });
// we manually fetch the user data above
// because we need the serverTag
needUser: false);
}, },
label: const Text('Create'), label: const Text('Create'),
icon: const Icon(Icons.add)), icon: const Icon(Icons.add)),

View file

@ -3,8 +3,10 @@ import 'package:flutter_svg/flutter_svg.dart';
import 'package:outbag_app/backend/permissions.dart'; import 'package:outbag_app/backend/permissions.dart';
import 'package:outbag_app/backend/request.dart'; import 'package:outbag_app/backend/request.dart';
import 'package:outbag_app/backend/room.dart'; import 'package:outbag_app/backend/room.dart';
import 'package:outbag_app/backend/user.dart';
import 'dart:math'; import 'dart:math';
import 'package:outbag_app/tools/fetch_wrapper.dart'; import 'package:outbag_app/tools/fetch_wrapper.dart';
import 'package:provider/provider.dart';
import 'package:routemaster/routemaster.dart'; import 'package:routemaster/routemaster.dart';
class AboutRoomPage extends StatefulWidget { class AboutRoomPage extends StatefulWidget {
@ -29,7 +31,7 @@ class _AboutRoomPageState extends State<AboutRoomPage> {
double smallest = min(width, height); double smallest = min(width, height);
return SingleChildScrollView( return SingleChildScrollView(
child:Center( child: Center(
child: Column(children: [ child: Column(children: [
// room meta display // room meta display
...(widget.room != null) ...(widget.room != null)
@ -51,7 +53,8 @@ class _AboutRoomPageState extends State<AboutRoomPage> {
'${widget.room?.id}@${widget.room?.serverTag}', '${widget.room?.id}@${widget.room?.serverTag}',
style: textTheme.bodySmall, style: textTheme.bodySmall,
), ),
Text(widget.room?.description ?? '', Text(
widget.room?.description ?? '',
style: textTheme.bodyMedium, style: textTheme.bodyMedium,
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
@ -103,21 +106,19 @@ class _AboutRoomPageState extends State<AboutRoomPage> {
final scaffMgr = final scaffMgr =
ScaffoldMessenger.of(context); ScaffoldMessenger.of(context);
final nav = Navigator.of(context); final nav = Navigator.of(context);
final user = context.read<User>();
doNetworkRequest(scaffMgr, doNetworkRequest(scaffMgr,
req: (user) => req: () => postWithCreadentials(
postWithCreadentials(
path: 'setVisibility', path: 'setVisibility',
target: (user?.server)!, target: user.server,
body: { body: {
'room': 'room': widget.room?.id,
widget.room?.id, 'server': (widget
'server': (widget.room .room?.serverTag)!,
?.serverTag)!, 'visibility': vset.first
'visibility':
vset.first
}, },
credentials: user!), credentials: user),
onOK: (_) { onOK: (_) {
Room r = widget.room!; Room r = widget.room!;
r.visibility = vis; r.visibility = vis;
@ -262,20 +263,20 @@ class _AboutRoomPageState extends State<AboutRoomPage> {
ScaffoldMessenger.of(ctx); ScaffoldMessenger.of(ctx);
final nav = Navigator.of(ctx); final nav = Navigator.of(ctx);
final rmaster = Routemaster.of(ctx); final rmaster = Routemaster.of(ctx);
final user = ctx.read<User>();
doNetworkRequest( doNetworkRequest(scaffMgr,
scaffMgr, req: () => postWithCreadentials(
req: (user)=>postWithCreadentials(
path: ((widget.info?.isOwner)!) path: ((widget.info?.isOwner)!)
? 'deleteRoom' ? 'deleteRoom'
: 'leaveRoom', : 'leaveRoom',
target: (user?.server)!, target: user.server,
body: { body: {
'room': widget.room?.id, 'room': widget.room?.id,
'server': 'server':
(widget.room?.serverTag)!, (widget.room?.serverTag)!,
}, },
credentials: user!), credentials: user),
onOK: (_) async { onOK: (_) async {
// try delete room from disk // try delete room from disk
try { try {
@ -288,8 +289,7 @@ class _AboutRoomPageState extends State<AboutRoomPage> {
after: () { after: () {
// close popup // close popup
nav.pop(); nav.pop();
} });
);
}, },
child: Text(((widget.info?.isOwner)!) child: Text(((widget.info?.isOwner)!)
? 'Delete' ? 'Delete'
@ -301,9 +301,6 @@ class _AboutRoomPageState extends State<AboutRoomPage> {
)) ))
] ]
: [], : [],
] ])));
)
)
);
} }
} }

View file

@ -4,7 +4,9 @@ import 'package:flutter/material.dart';
import 'package:outbag_app/backend/permissions.dart'; import 'package:outbag_app/backend/permissions.dart';
import 'package:outbag_app/backend/request.dart'; import 'package:outbag_app/backend/request.dart';
import 'package:outbag_app/backend/room.dart'; import 'package:outbag_app/backend/room.dart';
import 'package:outbag_app/backend/user.dart';
import 'package:outbag_app/tools/fetch_wrapper.dart'; import 'package:outbag_app/tools/fetch_wrapper.dart';
import 'package:provider/provider.dart';
import 'package:routemaster/routemaster.dart'; import 'package:routemaster/routemaster.dart';
class EditRoomPermissionSetPage extends StatefulWidget { class EditRoomPermissionSetPage extends StatefulWidget {
@ -24,13 +26,14 @@ class _EditRoomPermissionSetPageState extends State<EditRoomPermissionSetPage> {
void fetchInfo() { void fetchInfo() {
final rmaster = Routemaster.of(context); final rmaster = Routemaster.of(context);
final sm = ScaffoldMessenger.of(context); final sm = ScaffoldMessenger.of(context);
final user = context.read<User>();
doNetworkRequest( doNetworkRequest(
sm, sm,
req: (user) => postWithCreadentials( req: () => postWithCreadentials(
path: 'getRoomInfo', path: 'getRoomInfo',
credentials: user!, credentials: user,
target: (user.server), target: user.server,
body: {'room': widget.tag, 'server': widget.server}), body: {'room': widget.tag, 'server': widget.server}),
onAnyErr: () { onAnyErr: () {
// user should not be here // user should not be here
@ -94,11 +97,13 @@ class _EditRoomPermissionSetPageState extends State<EditRoomPermissionSetPage> {
onPressed: () { onPressed: () {
final rmaster = Routemaster.of(context); final rmaster = Routemaster.of(context);
final sm = ScaffoldMessenger.of(context); final sm = ScaffoldMessenger.of(context);
final user = context.read<User>();
// update permissions // update permissions
doNetworkRequest(sm, doNetworkRequest(sm,
req: (user) => postWithCreadentials( req: () => postWithCreadentials(
path: 'setRoomRight', path: 'setRoomRight',
credentials: user!, credentials: user,
target: user.server, target: user.server,
body: { body: {
'room': widget.tag, 'room': widget.tag,

View file

@ -4,7 +4,7 @@ import 'package:outbag_app/backend/request.dart';
import 'package:outbag_app/backend/user.dart'; import 'package:outbag_app/backend/user.dart';
import 'package:outbag_app/tools/fetch_wrapper.dart'; import 'package:outbag_app/tools/fetch_wrapper.dart';
import 'package:outbag_app/tools/snackbar.dart'; import 'package:outbag_app/tools/snackbar.dart';
import 'package:routemaster/routemaster.dart'; import 'package:provider/provider.dart';
class ChangePasswordDialog extends StatefulWidget { class ChangePasswordDialog extends StatefulWidget {
const ChangePasswordDialog({super.key}); const ChangePasswordDialog({super.key});
@ -18,39 +18,6 @@ class _ChangePasswordDialogState extends State<ChangePasswordDialog> {
final TextEditingController _ctrNewPassword = TextEditingController(); final TextEditingController _ctrNewPassword = TextEditingController();
final TextEditingController _ctrNewPasswordRepeat = TextEditingController(); final TextEditingController _ctrNewPasswordRepeat = TextEditingController();
User? user;
void loadUser() async {
final rmaster = Routemaster.of(context);
try {
final u = await User.fromDisk();
setState(() {
user = u;
});
} catch (_) {
// logout user
await User.removeDisk();
// move to welcome screen
rmaster.replace('/');
}
}
@override
void initState() {
super.initState();
User.listen((data) async {
try {
final u = await User.fromDisk();
setState(() {
user = u;
});
} catch (_) {}
});
WidgetsBinding.instance.addPostFrameCallback((_) => loadUser());
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return AlertDialog( return AlertDialog(
@ -120,6 +87,7 @@ class _ChangePasswordDialogState extends State<ChangePasswordDialog> {
onPressed: () async { onPressed: () async {
final scaffMgr = ScaffoldMessenger.of(context); final scaffMgr = ScaffoldMessenger.of(context);
final nav = Navigator.of(context); final nav = Navigator.of(context);
final user = context.read<User>();
// validate password // validate password
if (_ctrNewPassword.text.length < 6) { if (_ctrNewPassword.text.length < 6) {
@ -139,7 +107,7 @@ class _ChangePasswordDialogState extends State<ChangePasswordDialog> {
_ctrNewPasswordRepeat.clear(); _ctrNewPasswordRepeat.clear();
return; return;
} }
if (hashPassword(_ctrOldPassword.text) != user?.password) { if (hashPassword(_ctrOldPassword.text) != user.password) {
// current password wrong // current password wrong
showSimpleSnackbar(scaffMgr, showSimpleSnackbar(scaffMgr,
text: 'Old password is wrong', action: 'Dismiss'); text: 'Old password is wrong', action: 'Dismiss');
@ -152,18 +120,17 @@ class _ChangePasswordDialogState extends State<ChangePasswordDialog> {
// send request // send request
doNetworkRequest(scaffMgr, doNetworkRequest(scaffMgr,
needUser: false, req: () => postWithCreadentials(
req: (_) => postWithCreadentials(
path: 'changePassword', path: 'changePassword',
target: (user?.server)!, target: user.server,
body: {'accountKey': password}, body: {'accountKey': password},
credentials: user!), credentials: user),
onOK: (_) async { onOK: (_) async {
// update local user struct // update local user struct
final updatedUser = User( final updatedUser = User(
username: (user?.username)!, username: user.username,
password: password, password: password,
server: (user?.server)!); server: user.server);
await updatedUser.toDisk(); await updatedUser.toDisk();
}, },
after: () { after: () {

View file

@ -15,41 +15,15 @@ class SettingsPage extends StatefulWidget {
} }
class _SettingsPageState extends State<SettingsPage> { class _SettingsPageState extends State<SettingsPage> {
User? user;
AccountMeta? meta;
void fetchMeta() {
doNetworkRequest(ScaffoldMessenger.of(context), req: (user) {
setState(() {
this.user = user;
});
return postWithCreadentials(
path: 'getMyAccount',
credentials: user!,
target: user.server,
body: {});
}, onOK: (body) {
final meta = AccountMeta.fromJSON(body['data']);
setState(() {
this.meta = meta;
});
});
}
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) => fetchMeta());
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final textTheme = Theme.of(context) final textTheme = Theme.of(context)
.textTheme .textTheme
.apply(displayColor: Theme.of(context).colorScheme.onSurface); .apply(displayColor: Theme.of(context).colorScheme.onSurface);
final user = context.watch<User>();
final meta = context.watch<AccountMeta?>();
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('Settings'), title: const Text('Settings'),
@ -75,7 +49,7 @@ class _SettingsPageState extends State<SettingsPage> {
children: [ children: [
Padding( Padding(
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
child: Text('${user?.humanReadable}', child: Text(user.humanReadable,
style: textTheme.titleLarge)), style: textTheme.titleLarge)),
ListTile( ListTile(
title: const Text('Room count limit:'), title: const Text('Room count limit:'),
@ -119,8 +93,7 @@ class _SettingsPageState extends State<SettingsPage> {
builder: (context) => AlertDialog( builder: (context) => AlertDialog(
title: const Text('Change Theme'), title: const Text('Change Theme'),
content: SingleChildScrollView( content: SingleChildScrollView(
child: Column( child: Column(children: [
children: [
const Padding( const Padding(
padding: EdgeInsets.all(8), padding: EdgeInsets.all(8),
child: Text('Choose your preferred theme'), child: Text('Choose your preferred theme'),
@ -143,9 +116,7 @@ class _SettingsPageState extends State<SettingsPage> {
} catch (_) {} } catch (_) {}
}, },
), ),
] ])),
)
),
actions: [ actions: [
FilledButton( FilledButton(
child: const Text('Close'), child: const Text('Close'),
@ -214,11 +185,11 @@ class _SettingsPageState extends State<SettingsPage> {
final rmaster = Routemaster.of(ctx); final rmaster = Routemaster.of(ctx);
doNetworkRequest(scaffMgr, doNetworkRequest(scaffMgr,
req: (user) => postWithCreadentials( req: () => postWithCreadentials(
path: 'deleteAccount', path: 'deleteAccount',
target: (user?.server)!, target: user.server,
body: {}, body: {},
credentials: user!), credentials: user),
onOK: (_) async { onOK: (_) async {
// delete everything // delete everything
// delete user data (meta) // delete user data (meta)

View file

@ -1,54 +1,19 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:outbag_app/backend/errors.dart'; import 'package:outbag_app/backend/errors.dart';
import 'package:outbag_app/backend/request.dart'; import 'package:outbag_app/backend/request.dart';
import 'package:outbag_app/backend/user.dart';
import 'package:outbag_app/tools/snackbar.dart'; import 'package:outbag_app/tools/snackbar.dart';
void doNetworkRequest(ScaffoldMessengerState? sm, void doNetworkRequest(ScaffoldMessengerState? sm,
{required Future<Response> Function(User?) req, {required Future<Response> Function() req,
Function(Map<String, dynamic>)? onOK, Function(Map<String, dynamic> body)? onOK,
bool Function()? onNetworkErr, bool Function()? onNetworkErr,
bool Function()? onUnknownErr, bool Function()? onUnknownErr,
bool Function()? onUserErr,
Function()? onAnyErr, Function()? onAnyErr,
Function()? after, Function()? after,
bool Function(Map<String, dynamic>)? onServerErr, bool Function(Map<String, dynamic>)? onServerErr}) async {
bool needUser = true}) async {
User? user;
try {
user = await User.fromDisk();
} catch (_) {
// no user data available
if (needUser) {
// show error & quit
bool showBar = true;
if (onUserErr != null) {
showBar = onUserErr();
}
if (showBar && sm != null) {
showSimpleSnackbar(sm, text: 'No user found', action: 'Authorize',
onTap: () {
try {
// try to remove broken user
User.removeDisk();
} catch (_) {}
});
}
if (onAnyErr != null) {
onAnyErr();
}
if (after != null) {
after();
}
return;
}
}
Response res; Response res;
try { try {
res = await req(user); res = await req();
} catch (_) { } catch (_) {
// network error // network error
bool showBar = true; bool showBar = true;