Simplified network requests & snackbars
and rewrote the component's network requests. showSimpleSnackbar, allows displaying a simple snackbar, with text and one action button, that can be clicked. doNetworkRequest is supposed to be a wrapper for the already existing post* functions. It aims to make network requests and error handling easier, by containing all the try&catch blocks and being able to show snackbars.
This commit is contained in:
parent
eabb6b93ae
commit
5d333522a5
11 changed files with 637 additions and 751 deletions
|
@ -187,10 +187,10 @@ class RoomIcon {
|
||||||
}
|
}
|
||||||
|
|
||||||
class Room {
|
class Room {
|
||||||
final String id;
|
String id;
|
||||||
final String serverTag;
|
String serverTag;
|
||||||
final String name;
|
String name;
|
||||||
final String description;
|
String description;
|
||||||
RoomIcon? icon = RoomIcon.other;
|
RoomIcon? icon = RoomIcon.other;
|
||||||
RoomVisibility? visibility = RoomVisibility.private;
|
RoomVisibility? visibility = RoomVisibility.private;
|
||||||
|
|
||||||
|
@ -202,6 +202,17 @@ class Room {
|
||||||
this.icon,
|
this.icon,
|
||||||
this.visibility});
|
this.visibility});
|
||||||
|
|
||||||
|
int compareTo(Room r) {
|
||||||
|
final me = humanReadable;
|
||||||
|
final other = r.humanReadable;
|
||||||
|
|
||||||
|
return me.compareTo(other);
|
||||||
|
}
|
||||||
|
|
||||||
|
String get humanReadable {
|
||||||
|
return '$id@$serverTag';
|
||||||
|
}
|
||||||
|
|
||||||
// get list of all known rooms
|
// get list of all known rooms
|
||||||
static Future<List<Room>> listRooms() async {
|
static Future<List<Room>> listRooms() async {
|
||||||
final db = Localstore.instance;
|
final db = Localstore.instance;
|
||||||
|
|
|
@ -30,6 +30,12 @@ class User {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Future<void> removeDisk() async {
|
||||||
|
final db = Localstore.instance;
|
||||||
|
await db.collection('meta').doc('auth').delete();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
static listen(Function(Map<String, dynamic>) cb) async {
|
static listen(Function(Map<String, dynamic>) cb) async {
|
||||||
final db = Localstore.instance;
|
final db = Localstore.instance;
|
||||||
final stream = db.collection('meta').stream;
|
final stream = db.collection('meta').stream;
|
||||||
|
@ -55,12 +61,11 @@ class AccountMeta {
|
||||||
|
|
||||||
factory AccountMeta.fromJSON(dynamic json) {
|
factory AccountMeta.fromJSON(dynamic json) {
|
||||||
return AccountMeta(
|
return AccountMeta(
|
||||||
permissions: json['rights'],
|
permissions: json['rights'],
|
||||||
username: json['name'],
|
username: json['name'],
|
||||||
maxRoomSize: json['maxRoomSize'],
|
maxRoomSize: json['maxRoomSize'],
|
||||||
maxRoomCount: json['maxRooms'],
|
maxRoomCount: json['maxRooms'],
|
||||||
maxRoomMemberCount: json['maxUsersPerRoom'],
|
maxRoomMemberCount: json['maxUsersPerRoom'],
|
||||||
discvoverable: json['viewable'] == 1
|
discvoverable: json['viewable'] == 1);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:outbag_app/backend/user.dart';
|
import 'package:outbag_app/backend/user.dart';
|
||||||
import 'package:outbag_app/screens/room/join.dart';
|
import 'package:outbag_app/screens/room/join.dart';
|
||||||
import 'package:outbag_app/screens/room/new.dart';
|
import 'package:outbag_app/screens/room/new.dart';
|
||||||
|
import 'package:outbag_app/tools/fetch_wrapper.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import './screens/home.dart';
|
import './screens/home.dart';
|
||||||
import './screens/welcome.dart';
|
import './screens/welcome.dart';
|
||||||
|
@ -58,47 +59,48 @@ class _OutbagAppState extends State {
|
||||||
// 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
|
||||||
(() async {
|
doNetworkRequest(
|
||||||
User credentials;
|
null,
|
||||||
try {
|
req: (user) => postWithCreadentials(
|
||||||
credentials = await User.fromDisk();
|
target: (user?.server)!,
|
||||||
} catch (_) {
|
path: 'getMyAccount',
|
||||||
// invalid credentials
|
credentials: user!,
|
||||||
// log out
|
body: {}),
|
||||||
setState(() {
|
onOK: (body) {
|
||||||
isAuthorized = false;
|
final info = AccountMeta.fromJSON(body['data']);
|
||||||
});
|
setState(() {
|
||||||
return;
|
isAuthorized = true;
|
||||||
}
|
this.info = info;
|
||||||
try {
|
});
|
||||||
final resp = await postWithCreadentials(
|
},
|
||||||
target: credentials.server,
|
onServerErr: (body) {
|
||||||
path: 'getMyAccount',
|
// credentials are wrong
|
||||||
credentials: credentials,
|
// log out
|
||||||
body: {});
|
|
||||||
if (resp.res == Result.ok) {
|
setState(() {
|
||||||
final info = AccountMeta.fromJSON(resp.body['data']);
|
isAuthorized = false;
|
||||||
setState(() {
|
});
|
||||||
isAuthorized = true;
|
return true;
|
||||||
this.info = info;
|
},
|
||||||
});
|
onNetworkErr: () {
|
||||||
} else {
|
// user is currently offline
|
||||||
// credentials are wrong
|
// approve login,
|
||||||
// log out
|
// until user goes back offline
|
||||||
setState(() {
|
// NOTE TODO: check user data once online
|
||||||
isAuthorized = false;
|
setState(() {
|
||||||
});
|
isAuthorized = true;
|
||||||
}
|
});
|
||||||
} catch (_) {
|
return true;
|
||||||
// user is currently offline
|
},
|
||||||
// approve login,
|
onUserErr: () {
|
||||||
// until user goes back offline
|
// invalid credentials
|
||||||
// NOTE TODO: check user data once online
|
// log out
|
||||||
setState(() {
|
setState(() {
|
||||||
isAuthorized = true;
|
isAuthorized = false;
|
||||||
});
|
});
|
||||||
}
|
return true;
|
||||||
})();
|
}
|
||||||
|
);
|
||||||
|
|
||||||
// wait for user to be authorized
|
// wait for user to be authorized
|
||||||
User.listen((data) async {
|
User.listen((data) async {
|
||||||
|
@ -115,7 +117,9 @@ class _OutbagAppState extends State {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MultiProvider(
|
return MultiProvider(
|
||||||
providers: [
|
providers: [
|
||||||
Provider<AccountMeta?>.value(value: info,),
|
Provider<AccountMeta?>.value(
|
||||||
|
value: info,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
child: MaterialApp.router(
|
child: MaterialApp.router(
|
||||||
title: "Outbag",
|
title: "Outbag",
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
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/user.dart';
|
import 'package:outbag_app/backend/user.dart';
|
||||||
|
import 'package:outbag_app/tools/fetch_wrapper.dart';
|
||||||
|
import 'package:outbag_app/tools/snackbar.dart';
|
||||||
import 'package:routemaster/routemaster.dart';
|
import 'package:routemaster/routemaster.dart';
|
||||||
import '../backend/resolve_url.dart';
|
import '../backend/resolve_url.dart';
|
||||||
import '../backend/errors.dart';
|
import '../backend/errors.dart';
|
||||||
|
@ -173,6 +175,8 @@ class _AuthPageState extends State<AuthPage> {
|
||||||
showSpinner = true;
|
showSpinner = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
final scaffMgr = ScaffoldMessenger.of(context);
|
||||||
|
|
||||||
// verify that both passwords are the same
|
// verify that both passwords are the same
|
||||||
if (widget.mode != Mode.signin) {
|
if (widget.mode != Mode.signin) {
|
||||||
if (_ctrPassword.text != _ctrPasswordRpt.text) {
|
if (_ctrPassword.text != _ctrPasswordRpt.text) {
|
||||||
|
@ -180,19 +184,8 @@ class _AuthPageState extends State<AuthPage> {
|
||||||
showSpinner = false;
|
showSpinner = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
final snackBar = SnackBar(
|
showSimpleSnackbar(scaffMgr,
|
||||||
behavior: SnackBarBehavior.floating,
|
text: 'Passwords do not match', action: 'Dismiss');
|
||||||
content: const Text('Passwords do not match'),
|
|
||||||
action: SnackBarAction(
|
|
||||||
label: 'Dismiss',
|
|
||||||
onPressed: () {
|
|
||||||
ScaffoldMessenger.of(context).hideCurrentSnackBar();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
ScaffoldMessenger.of(context).hideCurrentSnackBar();
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
|
||||||
|
|
||||||
_ctrPasswordRpt.clear();
|
_ctrPasswordRpt.clear();
|
||||||
return;
|
return;
|
||||||
|
@ -205,28 +198,15 @@ class _AuthPageState extends State<AuthPage> {
|
||||||
showSpinner = false;
|
showSpinner = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
final snackBar = SnackBar(
|
showSimpleSnackbar(scaffMgr,
|
||||||
behavior: SnackBarBehavior.floating,
|
text: 'Password has to be at least 6 characters longs',
|
||||||
content: const Text(
|
action: 'Dismiss');
|
||||||
'Password has to be at least 6 characters long'),
|
|
||||||
action: SnackBarAction(
|
|
||||||
label: 'Dismiss',
|
|
||||||
onPressed: () {
|
|
||||||
ScaffoldMessenger.of(context).hideCurrentSnackBar();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
ScaffoldMessenger.of(context).hideCurrentSnackBar();
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
|
||||||
|
|
||||||
_ctrPasswordRpt.clear();
|
_ctrPasswordRpt.clear();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final scaffMgr = ScaffoldMessenger.of(context);
|
// resolve homeserver url
|
||||||
|
|
||||||
// TODO: resolve homeserver url
|
|
||||||
OutbagServer server;
|
OutbagServer server;
|
||||||
try {
|
try {
|
||||||
server = await getOutbagServerUrl(_ctrServer.text);
|
server = await getOutbagServerUrl(_ctrServer.text);
|
||||||
|
@ -238,50 +218,43 @@ class _AuthPageState extends State<AuthPage> {
|
||||||
showSpinner = false;
|
showSpinner = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
final snackBar = SnackBar(
|
showSimpleSnackbar(scaffMgr,
|
||||||
behavior: SnackBarBehavior.floating,
|
text:
|
||||||
content: Text(
|
'Unable to find valid outbag server on ${_ctrServer.text}',
|
||||||
'Unable to find valid outbag server on ${_ctrServer.text}'),
|
action: 'Dismiss');
|
||||||
action: SnackBarAction(
|
|
||||||
label: 'Dismiss',
|
|
||||||
onPressed: () {
|
|
||||||
scaffMgr.hideCurrentSnackBar();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
scaffMgr.hideCurrentSnackBar();
|
|
||||||
scaffMgr.showSnackBar(snackBar);
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// hash password
|
||||||
var bytes = utf8.encode(_ctrPassword.text);
|
var bytes = utf8.encode(_ctrPassword.text);
|
||||||
final password = sha256.convert(bytes).toString();
|
final password = sha256.convert(bytes).toString();
|
||||||
try {
|
|
||||||
Response resp;
|
doNetworkRequest(
|
||||||
// validate account
|
scaffMgr,
|
||||||
if (widget.mode == Mode.signin) {
|
needUser: false,
|
||||||
resp = await postUnauthorized(
|
req: (_) {
|
||||||
|
if (widget.mode == Mode.signin) {
|
||||||
|
return postUnauthorized(
|
||||||
target: server,
|
target: server,
|
||||||
path: 'signin',
|
path: 'signin',
|
||||||
body: {
|
body: {
|
||||||
'name': _ctrUsername.text,
|
'name': _ctrUsername.text,
|
||||||
'server': server.tag,
|
'server': server.tag,
|
||||||
'accountKey': password
|
'accountKey': password
|
||||||
});
|
});
|
||||||
} else if (widget.mode == Mode.signup) {
|
} else if (widget.mode == Mode.signup) {
|
||||||
resp = await postUnauthorized(
|
return postUnauthorized(
|
||||||
target: server,
|
target: server,
|
||||||
path: 'signup',
|
path: 'signup',
|
||||||
body: {
|
body: {
|
||||||
'name': _ctrUsername.text,
|
'name': _ctrUsername.text,
|
||||||
'server': server.tag,
|
'server': server.tag,
|
||||||
'accountKey': password
|
'accountKey': password
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// signup OTA
|
// signup OTA
|
||||||
resp = await postUnauthorized(
|
return postUnauthorized(
|
||||||
target: server,
|
target: server,
|
||||||
path: 'signupOTA',
|
path: 'signupOTA',
|
||||||
body: {
|
body: {
|
||||||
|
@ -289,51 +262,23 @@ class _AuthPageState extends State<AuthPage> {
|
||||||
'server': server.tag,
|
'server': server.tag,
|
||||||
'accountKey': password,
|
'accountKey': password,
|
||||||
'OTA': _ctrOTA.text
|
'OTA': _ctrOTA.text
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
},
|
||||||
if (resp.res == Result.err) {
|
onOK: (body) async {
|
||||||
// error
|
|
||||||
final snackBar = SnackBar(
|
|
||||||
behavior: SnackBarBehavior.floating,
|
|
||||||
content: Text(errorAsString(resp.body)),
|
|
||||||
action: SnackBarAction(
|
|
||||||
label: 'Dismiss',
|
|
||||||
onPressed: () {
|
|
||||||
scaffMgr.hideCurrentSnackBar();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
scaffMgr.hideCurrentSnackBar();
|
|
||||||
scaffMgr.showSnackBar(snackBar);
|
|
||||||
} else {
|
|
||||||
// authorize user
|
// authorize user
|
||||||
await User(
|
await User(
|
||||||
username: _ctrUsername.text,
|
username: _ctrUsername.text,
|
||||||
password: password,
|
password: password,
|
||||||
server: server)
|
server: server)
|
||||||
.toDisk();
|
.toDisk();
|
||||||
|
},
|
||||||
|
after: () {
|
||||||
|
setState(() {
|
||||||
|
showSpinner = false;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} catch (_) {
|
);
|
||||||
final snackBar = SnackBar(
|
|
||||||
behavior: SnackBarBehavior.floating,
|
|
||||||
content: const Text('Network error'),
|
|
||||||
action: SnackBarAction(
|
|
||||||
label: 'Dismiss',
|
|
||||||
onPressed: () {
|
|
||||||
scaffMgr.hideCurrentSnackBar();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
scaffMgr.hideCurrentSnackBar();
|
|
||||||
scaffMgr.showSnackBar(snackBar);
|
|
||||||
}
|
|
||||||
|
|
||||||
setState(() {
|
|
||||||
showSpinner = false;
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
label: Text(modeName),
|
label: Text(modeName),
|
||||||
icon: const Icon(Icons.check),
|
icon: const Icon(Icons.check),
|
||||||
|
|
|
@ -5,6 +5,7 @@ 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/user.dart';
|
import 'package:outbag_app/backend/user.dart';
|
||||||
|
import 'package:outbag_app/tools/fetch_wrapper.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:routemaster/routemaster.dart';
|
import 'package:routemaster/routemaster.dart';
|
||||||
import '../backend/room.dart';
|
import '../backend/room.dart';
|
||||||
|
@ -31,43 +32,38 @@ class _HomePageState extends State<HomePage> {
|
||||||
});
|
});
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeDependencies() {
|
||||||
|
super.didChangeDependencies();
|
||||||
|
|
||||||
|
final sm = ScaffoldMessenger.of(context);
|
||||||
|
|
||||||
// load cached rooms
|
// load cached rooms
|
||||||
(() async {
|
(() async {
|
||||||
try {
|
try {
|
||||||
final newRooms = await Room.listRooms();
|
final newRooms = await Room.listRooms();
|
||||||
setState(() {
|
setState(() {
|
||||||
rooms = newRooms;
|
rooms = newRooms;
|
||||||
});
|
});
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
// fetch room list
|
doNetworkRequest(sm,
|
||||||
(() async {
|
req: (user) => postWithCreadentials(
|
||||||
User user;
|
path: 'listRooms',
|
||||||
try {
|
credentials: user!,
|
||||||
user = await User.fromDisk();
|
target: user.server,
|
||||||
} catch (_) {
|
body: {}),
|
||||||
// probably not logged in
|
onOK: (body) async {
|
||||||
return;
|
final List<Room> list = body['data'].map<Room>((json) {
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
final resp = await postWithCreadentials(
|
|
||||||
path: 'listRooms',
|
|
||||||
credentials: user,
|
|
||||||
target: user.server,
|
|
||||||
body: {});
|
|
||||||
if (resp.res == Result.ok) {
|
|
||||||
final List<Room> list = resp.body['data'].map<Room>((json){
|
|
||||||
return Room.fromJSON(json);
|
return Room.fromJSON(json);
|
||||||
}).toList();
|
}).toList();
|
||||||
for (Room r in list) {
|
for (Room r in list) {
|
||||||
await r.toDisk();
|
await r.toDisk();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (_) {}
|
});
|
||||||
})();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -105,16 +101,20 @@ class _HomePageState extends State<HomePage> {
|
||||||
// show settings screen
|
// show settings screen
|
||||||
Routemaster.of(context).push("/settings");
|
Routemaster.of(context).push("/settings");
|
||||||
}),
|
}),
|
||||||
...(context.watch<AccountMeta?>() != null &&
|
...(context.watch<AccountMeta?>() != null &&
|
||||||
(context.watch<AccountMeta?>()?.permissions)! & ServerPermission.allManagement != 0)?[
|
(context.watch<AccountMeta?>()?.permissions)! &
|
||||||
MenuItemButton(
|
ServerPermission.allManagement !=
|
||||||
leadingIcon: const Icon(Icons.dns),
|
0)
|
||||||
child: const Text('Server Dashboard'),
|
? [
|
||||||
onPressed: () {
|
MenuItemButton(
|
||||||
// show settings screen
|
leadingIcon: const Icon(Icons.dns),
|
||||||
Routemaster.of(context).push("/server");
|
child: const Text('Server Dashboard'),
|
||||||
}),
|
onPressed: () {
|
||||||
]:[],
|
// show settings screen
|
||||||
|
Routemaster.of(context).push("/server");
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
: [],
|
||||||
MenuItemButton(
|
MenuItemButton(
|
||||||
leadingIcon: const Icon(Icons.info_rounded),
|
leadingIcon: const Icon(Icons.info_rounded),
|
||||||
child: const Text('About'),
|
child: const Text('About'),
|
||||||
|
|
|
@ -4,6 +4,7 @@ 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/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:routemaster/routemaster.dart';
|
import 'package:routemaster/routemaster.dart';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
|
@ -17,45 +18,42 @@ class JoinRoomPage extends StatefulWidget {
|
||||||
class _JoinRoomPageState extends State {
|
class _JoinRoomPageState extends State {
|
||||||
List<Room> rooms = [];
|
List<Room> rooms = [];
|
||||||
|
|
||||||
void fetchData() async {
|
@override
|
||||||
User user;
|
void didChangeDependencies() {
|
||||||
try {
|
super.didChangeDependencies();
|
||||||
user = await User.fromDisk();
|
|
||||||
} catch (_) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
final sm = ScaffoldMessenger.of(context);
|
||||||
final resp = await postWithCreadentials(
|
|
||||||
|
doNetworkRequest(null,
|
||||||
|
req: (user) => postWithCreadentials(
|
||||||
path: 'listPublicRooms',
|
path: 'listPublicRooms',
|
||||||
credentials: user,
|
credentials: user!,
|
||||||
target: user.server,
|
target: user.server,
|
||||||
body: {});
|
body: {}),
|
||||||
if (resp.res == Result.ok) {
|
onOK: (body) async {
|
||||||
// parse rooms
|
// parse rooms
|
||||||
final list = resp.body['data'];
|
final list = body['data'];
|
||||||
|
|
||||||
// try to fetch a list of rooms the user is a member of
|
// try to fetch a list of rooms the user is a member of
|
||||||
// use an empty blacklist when request is not successful
|
// use an empty blacklist when request is not successful
|
||||||
final blacklist = [];
|
final List<Room> blacklist = [];
|
||||||
try {
|
doNetworkRequest(sm,
|
||||||
final resp = await postWithCreadentials(
|
req: (user) => postWithCreadentials(
|
||||||
path: 'listRooms',
|
path: 'listRooms',
|
||||||
credentials: user,
|
credentials: user!,
|
||||||
target: user.server,
|
target: user.server,
|
||||||
body: {});
|
body: {}),
|
||||||
if (resp.res == Result.ok) {
|
onOK: (body) {
|
||||||
final List<Room> list = resp.body['data'].map<Room>((json) {
|
final List<Room> list = body['data'].map<Room>((json) {
|
||||||
return Room.fromJSON(json);
|
return Room.fromJSON(json);
|
||||||
}).toList();
|
}).toList();
|
||||||
for (Room r in list) {
|
for (Room r in list) {
|
||||||
blacklist.add(r);
|
blacklist.add(r);
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
} catch (_) {}
|
|
||||||
|
|
||||||
// process the list of public rooms
|
// process the list of public rooms
|
||||||
List<Room> builder = [];
|
final List<Room> builder = [];
|
||||||
processor:
|
processor:
|
||||||
for (dynamic raw in list) {
|
for (dynamic raw in list) {
|
||||||
try {
|
try {
|
||||||
|
@ -65,7 +63,7 @@ class _JoinRoomPageState extends State {
|
||||||
// only add room to list,
|
// only add room to list,
|
||||||
// if not on blacklist
|
// if not on blacklist
|
||||||
for (Room r in blacklist) {
|
for (Room r in blacklist) {
|
||||||
if (room.serverTag == r.serverTag && room.id == r.id) {
|
if (r.compareTo(room) == 0) {
|
||||||
// server on white list
|
// server on white list
|
||||||
// move to next iteration on outer for loop
|
// move to next iteration on outer for loop
|
||||||
continue processor;
|
continue processor;
|
||||||
|
@ -77,32 +75,22 @@ class _JoinRoomPageState extends State {
|
||||||
// ignore room
|
// ignore room
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
builder.sort();
|
|
||||||
setState(() {
|
setState(() {
|
||||||
rooms = builder;
|
rooms = builder;
|
||||||
});
|
});
|
||||||
} else {
|
});
|
||||||
throw Error();
|
|
||||||
}
|
|
||||||
} catch (_) {
|
|
||||||
// network error
|
|
||||||
// unable to load room list
|
|
||||||
// NOTE: might want to show snackbar
|
|
||||||
// with warning
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
fetchData();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final textTheme = Theme.of(context)
|
final textTheme = Theme.of(context)
|
||||||
.textTheme
|
.textTheme
|
||||||
.apply(displayColor: Theme.of(context).colorScheme.onSurface);
|
.apply(displayColor: Theme.of(context).colorScheme.onSurface);
|
||||||
|
|
||||||
double width = MediaQuery.of(context).size.width;
|
double width = MediaQuery.of(context).size.width;
|
||||||
double height = MediaQuery.of(context).size.height;
|
double height = MediaQuery.of(context).size.height;
|
||||||
|
@ -133,241 +121,178 @@ class _JoinRoomPageState extends State {
|
||||||
tooltip: "Refresh",
|
tooltip: "Refresh",
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
// fetch public rooms again
|
// fetch public rooms again
|
||||||
fetchData();
|
didChangeDependencies();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
MenuAnchor(
|
MenuAnchor(
|
||||||
builder: (ctx, controller, child) {
|
builder: (ctx, controller, child) {
|
||||||
return IconButton(
|
return IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (controller.isOpen) {
|
if (controller.isOpen) {
|
||||||
controller.close();
|
controller.close();
|
||||||
} else {
|
} else {
|
||||||
controller.open();
|
controller.open();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.more_vert),
|
icon: const Icon(Icons.more_vert),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
menuChildren: [
|
menuChildren: [
|
||||||
MenuItemButton(
|
MenuItemButton(
|
||||||
leadingIcon: const Icon(Icons.drafts),
|
leadingIcon: const Icon(Icons.drafts),
|
||||||
child: const Text('Join invite-only room'),
|
child: const Text('Join invite-only room'),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
// show settings screen
|
// show settings screen
|
||||||
Routemaster.of(context).push("/add-room/by-id");
|
Routemaster.of(context).push("/add-room/by-id");
|
||||||
}),
|
}),
|
||||||
])
|
])
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: rooms.isEmpty
|
body: rooms.isEmpty
|
||||||
? Center(
|
? Center(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Text('No new Rooms found', style: textTheme.titleLarge),
|
Text('No new Rooms found', style: textTheme.titleLarge),
|
||||||
],
|
],
|
||||||
))
|
))
|
||||||
: ListView.builder(
|
: ListView.builder(
|
||||||
itemCount: rooms.length,
|
itemCount: rooms.length,
|
||||||
itemBuilder: (ctx, i) {
|
itemBuilder: (ctx, i) {
|
||||||
final room = rooms[i];
|
final room = rooms[i];
|
||||||
return Card(
|
return Card(
|
||||||
margin: const EdgeInsets.all(8.0),
|
margin: const EdgeInsets.all(8.0),
|
||||||
clipBehavior: Clip.antiAliasWithSaveLayer,
|
clipBehavior: Clip.antiAliasWithSaveLayer,
|
||||||
semanticContainer: true,
|
semanticContainer: true,
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
// TODO: show modalBottomSheet
|
// TODO: show modalBottomSheet
|
||||||
// with room information
|
// with room information
|
||||||
// and join button
|
// and join button
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
context: ctx,
|
context: ctx,
|
||||||
builder: (ctx) {
|
builder: (ctx) {
|
||||||
return BottomSheet(
|
return BottomSheet(
|
||||||
onClosing: () {},
|
onClosing: () {},
|
||||||
builder: (ctx) {
|
builder: (ctx) {
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment:
|
crossAxisAlignment:
|
||||||
CrossAxisAlignment.center,
|
CrossAxisAlignment.center,
|
||||||
mainAxisAlignment:
|
mainAxisAlignment:
|
||||||
MainAxisAlignment.center,
|
MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(14),
|
padding: const EdgeInsets.all(14),
|
||||||
child: Column(children: [
|
child: Column(children: [
|
||||||
// room icon
|
// room icon
|
||||||
SvgPicture.asset(
|
SvgPicture.asset(
|
||||||
(room.icon?.img)!,
|
(room.icon?.img)!,
|
||||||
width: smallest * 0.2,
|
width: smallest * 0.2,
|
||||||
height: smallest * 0.2,
|
height: smallest * 0.2,
|
||||||
),
|
),
|
||||||
// room name
|
// room name
|
||||||
Text(
|
Text(
|
||||||
room.name,
|
room.name,
|
||||||
style: textTheme.displayMedium,
|
style: textTheme.displayMedium,
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'${room.id}@${room.serverTag}',
|
'${room.id}@${room.serverTag}',
|
||||||
style: textTheme.labelSmall,
|
style: textTheme.labelSmall,
|
||||||
),
|
),
|
||||||
// description
|
// description
|
||||||
Text(room.description,
|
Text(room.description,
|
||||||
style: textTheme.bodyLarge),
|
style: textTheme.bodyLarge),
|
||||||
// visibility
|
// visibility
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment:
|
mainAxisAlignment:
|
||||||
MainAxisAlignment.center,
|
MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Icon(room.visibility?.icon),
|
Icon(room.visibility?.icon),
|
||||||
Text((room
|
Text((room
|
||||||
.visibility?.text)!),
|
.visibility?.text)!),
|
||||||
]),
|
]),
|
||||||
])),
|
])),
|
||||||
// action buttons
|
// action buttons
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment:
|
mainAxisAlignment:
|
||||||
MainAxisAlignment.center,
|
MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
// cancel button
|
// cancel button
|
||||||
Padding(
|
Padding(
|
||||||
padding:
|
padding:
|
||||||
const EdgeInsets.all(14),
|
const EdgeInsets.all(14),
|
||||||
child: ElevatedButton.icon(
|
child: ElevatedButton.icon(
|
||||||
icon:
|
icon:
|
||||||
const Icon(Icons.close),
|
const Icon(Icons.close),
|
||||||
label: const Text('Cancel'),
|
label: const Text('Cancel'),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
// close sheet
|
// close sheet
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
},
|
},
|
||||||
)),
|
)),
|
||||||
// join room button
|
// join room button
|
||||||
Padding(
|
Padding(
|
||||||
padding:
|
padding:
|
||||||
const EdgeInsets.all(14),
|
const EdgeInsets.all(14),
|
||||||
child: FilledButton.icon(
|
child: FilledButton.icon(
|
||||||
icon:
|
icon:
|
||||||
const Icon(Icons.check),
|
const Icon(Icons.check),
|
||||||
label: const Text('Join'),
|
label: const Text('Join'),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final scaffMgr =
|
final scaffMgr =
|
||||||
ScaffoldMessenger.of(
|
ScaffoldMessenger.of(
|
||||||
context);
|
context);
|
||||||
final rmaster =
|
final rmaster =
|
||||||
Routemaster.of(
|
Routemaster.of(
|
||||||
context);
|
context);
|
||||||
final nav =
|
final nav =
|
||||||
Navigator.of(context);
|
Navigator.of(context);
|
||||||
|
|
||||||
// join room & close screen
|
doNetworkRequest(scaffMgr,
|
||||||
User user;
|
req: (user) =>
|
||||||
try {
|
postWithCreadentials(
|
||||||
user = await User
|
credentials:
|
||||||
.fromDisk();
|
user!,
|
||||||
} catch (_) {
|
target: user
|
||||||
// user data invalid
|
.server,
|
||||||
// NOTE: shouldn't happen
|
path:
|
||||||
// because the main.dart watches the auth meta data
|
'joinPublicRoom',
|
||||||
// and auto logs-out the user
|
body: {
|
||||||
return;
|
'room':
|
||||||
}
|
room.id,
|
||||||
|
'server': room
|
||||||
try {
|
.serverTag
|
||||||
final resp =
|
}),
|
||||||
await postWithCreadentials(
|
onOK: (body) async {
|
||||||
target:
|
await room.toDisk();
|
||||||
user.server,
|
nav.pop();
|
||||||
path:
|
rmaster.replace(
|
||||||
'joinPublicRoom',
|
'/r/${room.serverTag}/${room.id}');
|
||||||
body: {
|
});
|
||||||
'room':
|
},
|
||||||
room.id,
|
))
|
||||||
'server': room
|
])
|
||||||
.serverTag
|
],
|
||||||
},
|
);
|
||||||
credentials:
|
},
|
||||||
user);
|
);
|
||||||
if (resp.res ==
|
});
|
||||||
Result.ok) {
|
},
|
||||||
// successfully joined room
|
child: Container(
|
||||||
await room.toDisk();
|
padding: const EdgeInsets.fromLTRB(10, 5, 5, 10),
|
||||||
nav.pop();
|
child: ListTile(
|
||||||
rmaster.replace(
|
title: Text(room.name),
|
||||||
'/r/${room.serverTag}/${room.id}');
|
visualDensity: const VisualDensity(vertical: 3),
|
||||||
} else {
|
subtitle: Text(room.description),
|
||||||
// server error
|
leading: AspectRatio(
|
||||||
final snackBar =
|
aspectRatio: 1 / 1,
|
||||||
SnackBar(
|
child: SvgPicture.asset("${room.icon?.img}"),
|
||||||
behavior:
|
),
|
||||||
SnackBarBehavior
|
hoverColor: Colors.transparent,
|
||||||
.floating,
|
))));
|
||||||
content: Text(
|
|
||||||
errorAsString(
|
|
||||||
resp.body)),
|
|
||||||
action:
|
|
||||||
SnackBarAction(
|
|
||||||
label: 'Dismiss',
|
|
||||||
onPressed: () {
|
|
||||||
scaffMgr
|
|
||||||
.hideCurrentSnackBar();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
scaffMgr
|
|
||||||
.hideCurrentSnackBar();
|
|
||||||
scaffMgr.showSnackBar(
|
|
||||||
snackBar);
|
|
||||||
}
|
|
||||||
} catch (_) {
|
|
||||||
// network error
|
|
||||||
final snackBar =
|
|
||||||
SnackBar(
|
|
||||||
behavior:
|
|
||||||
SnackBarBehavior
|
|
||||||
.floating,
|
|
||||||
content: const Text(
|
|
||||||
'Network error'),
|
|
||||||
action:
|
|
||||||
SnackBarAction(
|
|
||||||
label: 'Dismiss',
|
|
||||||
onPressed: () {
|
|
||||||
scaffMgr
|
|
||||||
.hideCurrentSnackBar();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
scaffMgr
|
|
||||||
.hideCurrentSnackBar();
|
|
||||||
scaffMgr.showSnackBar(
|
|
||||||
snackBar);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
))
|
|
||||||
])
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
child: Container(
|
),
|
||||||
padding: const EdgeInsets.fromLTRB(10, 5, 5, 10),
|
|
||||||
child: ListTile(
|
|
||||||
title: Text(room.name),
|
|
||||||
visualDensity: const VisualDensity(vertical: 3),
|
|
||||||
subtitle: Text(room.description),
|
|
||||||
leading: AspectRatio(
|
|
||||||
aspectRatio: 1 / 1,
|
|
||||||
child: SvgPicture.asset("${room.icon?.img}"),
|
|
||||||
),
|
|
||||||
hoverColor: Colors.transparent,
|
|
||||||
))));
|
|
||||||
},
|
|
||||||
),
|
|
||||||
floatingActionButton: FloatingActionButton.extended(
|
floatingActionButton: FloatingActionButton.extended(
|
||||||
label: const Text('New'),
|
label: const Text('New'),
|
||||||
icon: const Icon(Icons.add),
|
icon: const Icon(Icons.add),
|
||||||
|
|
|
@ -6,6 +6,7 @@ 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:routemaster/routemaster.dart';
|
import 'package:routemaster/routemaster.dart';
|
||||||
|
|
||||||
class RoomPage extends StatefulWidget {
|
class RoomPage extends StatefulWidget {
|
||||||
|
@ -27,83 +28,81 @@ class _RoomPageState extends State<RoomPage> {
|
||||||
|
|
||||||
// fetch room information
|
// fetch room information
|
||||||
void fetchInfo() async {
|
void fetchInfo() async {
|
||||||
bool foundData = false;
|
final sm = ScaffoldMessenger.of(context);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final diskRoom =
|
final diskRoom =
|
||||||
await Room.fromDisk(serverTag: widget.server, id: widget.tag);
|
await Room.fromDisk(serverTag: widget.server, id: widget.tag);
|
||||||
foundData = true;
|
|
||||||
setState(() {
|
setState(() {
|
||||||
room = diskRoom;
|
room = diskRoom;
|
||||||
});
|
});
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
|
|
||||||
// fetch additional data from web
|
doNetworkRequest(sm,
|
||||||
User user;
|
req: (user) => postWithCreadentials(
|
||||||
try {
|
path: 'getRoomInfo',
|
||||||
user = await User.fromDisk();
|
credentials: user!,
|
||||||
} catch (_) {
|
target: (user.server),
|
||||||
// probably not logged in
|
body: {'room': widget.tag, 'server': widget.server}),
|
||||||
return;
|
onOK: (body) async {
|
||||||
}
|
final info = RoomInfo.fromJSON(body['data']);
|
||||||
|
final room = Room.fromJSON(body['data']);
|
||||||
try {
|
|
||||||
final resp = await postWithCreadentials(
|
|
||||||
path: 'getRoomInfo',
|
|
||||||
credentials: user,
|
|
||||||
target: user.server,
|
|
||||||
body: {'room': widget.tag, 'server': widget.server});
|
|
||||||
if (resp.res == Result.ok) {
|
|
||||||
final info = RoomInfo.fromJSON(resp.body['data']);
|
|
||||||
final room = Room.fromJSON(resp.body['data']);
|
|
||||||
|
|
||||||
room.toDisk();
|
room.toDisk();
|
||||||
foundData = true;
|
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
this.info = info;
|
this.info = info;
|
||||||
});
|
});
|
||||||
}
|
return true;
|
||||||
} catch (_) {}
|
},
|
||||||
|
onNetworkErr: () {
|
||||||
if (!foundData) {
|
// user offline
|
||||||
// no room data available
|
if (room == null) {
|
||||||
// TODO: close room
|
// no room data available
|
||||||
// or show snackbar
|
// NOTE: close room?
|
||||||
// BUG: there is currently no way of implementing this,
|
}
|
||||||
// because there is no context available here
|
return true;
|
||||||
}
|
},
|
||||||
|
onServerErr: (json) {
|
||||||
|
// user no longer in room
|
||||||
|
// TODO: close room
|
||||||
|
return true;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
|
||||||
// schedule info-get
|
|
||||||
fetchInfo();
|
|
||||||
|
|
||||||
_ctr.addListener(() {
|
_ctr.addListener(() {
|
||||||
setState(() {
|
setState(() {
|
||||||
page = _ctr.page?.toInt() ?? _ctr.initialPage;
|
page = _ctr.page?.toInt() ?? _ctr.initialPage;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
Room.listen((_) async {
|
Room.listen((_) async {
|
||||||
// rooms changed on disk
|
// rooms changed on disk
|
||||||
// probably this one,
|
// probably this one,
|
||||||
// because it is currently open
|
// because it is currently open
|
||||||
// NOTE: might be a different room
|
// NOTE: might be a different room
|
||||||
// (if a background listener is implemented at some point,
|
// (if a background listener is implemented at some point,
|
||||||
// checking if this room changed might improve performance)
|
// checking if this room changed might improve performance)
|
||||||
try {
|
try {
|
||||||
final r = await Room.fromDisk(serverTag: widget.server, id: widget.tag);
|
final r = await Room.fromDisk(serverTag: widget.server, id: widget.tag);
|
||||||
setState(() {
|
setState(() {
|
||||||
room = r;
|
room = r;
|
||||||
});
|
});
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeDependencies() {
|
||||||
|
super.didChangeDependencies();
|
||||||
|
// schedule info-get
|
||||||
|
fetchInfo();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
|
@ -130,27 +129,27 @@ class _RoomPageState extends State<RoomPage> {
|
||||||
bottomNavigationBar: NavigationBar(
|
bottomNavigationBar: NavigationBar(
|
||||||
onDestinationSelected: (int index) {
|
onDestinationSelected: (int index) {
|
||||||
_ctr.animateToPage(index,
|
_ctr.animateToPage(index,
|
||||||
curve: Curves.easeInOut,
|
curve: Curves.easeInOut,
|
||||||
duration: const Duration(milliseconds: 300));
|
duration: const Duration(milliseconds: 300));
|
||||||
},
|
},
|
||||||
selectedIndex: page,
|
selectedIndex: page,
|
||||||
destinations: const [
|
destinations: const [
|
||||||
NavigationDestination(
|
NavigationDestination(
|
||||||
icon: Icon(Icons.list),
|
icon: Icon(Icons.list),
|
||||||
label: "List",
|
label: "List",
|
||||||
tooltip: 'View shopping list'),
|
tooltip: 'View shopping list'),
|
||||||
NavigationDestination(
|
NavigationDestination(
|
||||||
icon: Icon(Icons.inventory_2),
|
icon: Icon(Icons.inventory_2),
|
||||||
label: "Products",
|
label: "Products",
|
||||||
tooltip: 'View saved items'),
|
tooltip: 'View saved items'),
|
||||||
NavigationDestination(
|
NavigationDestination(
|
||||||
icon: Icon(Icons.category),
|
icon: Icon(Icons.category),
|
||||||
label: "Categories",
|
label: "Categories",
|
||||||
tooltip: 'View categories'),
|
tooltip: 'View categories'),
|
||||||
NavigationDestination(
|
NavigationDestination(
|
||||||
icon: Icon(Icons.info_rounded),
|
icon: Icon(Icons.info_rounded),
|
||||||
label: "About",
|
label: "About",
|
||||||
tooltip: 'View room info'),
|
tooltip: 'View room info'),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -4,6 +4,8 @@ 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/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/snackbar.dart';
|
||||||
import 'package:routemaster/routemaster.dart';
|
import 'package:routemaster/routemaster.dart';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
|
@ -175,118 +177,73 @@ class _NewRoomPageState extends State {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)))),
|
)))),
|
||||||
floatingActionButton: FloatingActionButton.extended(
|
floatingActionButton: FloatingActionButton.extended(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final scaffMgr = ScaffoldMessenger.of(context);
|
final scaffMgr = ScaffoldMessenger.of(context);
|
||||||
final rmaster = Routemaster.of(context);
|
final rmaster = Routemaster.of(context);
|
||||||
|
|
||||||
// ID should be at least three characters long
|
// ID should be at least three characters long
|
||||||
if (_ctrID.text.length < 3) {
|
if (_ctrID.text.length < 3) {
|
||||||
final snackBar = SnackBar(
|
showSimpleSnackbar(scaffMgr,
|
||||||
behavior: SnackBarBehavior.floating,
|
text: _ctrID.text.isEmpty
|
||||||
content: Text(_ctrID.text.isEmpty
|
? 'Please specify a Room ID'
|
||||||
? 'Please specify a Room ID'
|
: 'Room ID has to be at least three characters long',
|
||||||
: 'Room ID has to be at least three characters long'),
|
action: 'OK');
|
||||||
action: SnackBarAction(
|
|
||||||
label: 'Ok',
|
|
||||||
onPressed: () {
|
|
||||||
scaffMgr.hideCurrentSnackBar();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
scaffMgr.hideCurrentSnackBar();
|
return;
|
||||||
scaffMgr.showSnackBar(snackBar);
|
}
|
||||||
|
|
||||||
return;
|
// name may not be empty
|
||||||
}
|
if (_ctrName.text.isEmpty) {
|
||||||
|
showSimpleSnackbar(
|
||||||
|
scaffMgr,
|
||||||
|
text: 'Please specify a room name',
|
||||||
|
action: 'OK'
|
||||||
|
);
|
||||||
|
|
||||||
// name may not be empty
|
return;
|
||||||
if (_ctrName.text.isEmpty) {
|
}
|
||||||
final snackBar = SnackBar(
|
|
||||||
behavior: SnackBarBehavior.floating,
|
|
||||||
content: const Text('Please specify a room name'),
|
|
||||||
action: SnackBarAction(
|
|
||||||
label: 'Ok',
|
|
||||||
onPressed: () {
|
|
||||||
scaffMgr.hideCurrentSnackBar();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
scaffMgr.hideCurrentSnackBar();
|
User user;
|
||||||
scaffMgr.showSnackBar(snackBar);
|
try {
|
||||||
|
user = await User.fromDisk();
|
||||||
|
} catch (_) {
|
||||||
|
// user data invalid
|
||||||
|
// shouldn't happen
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
final room = Room(
|
||||||
}
|
id: _ctrID.text,
|
||||||
|
serverTag: user.server.tag,
|
||||||
|
name: _ctrName.text,
|
||||||
|
description: _ctrDescription.text,
|
||||||
|
icon: _ctrIcon,
|
||||||
|
visibility: _ctrVis);
|
||||||
|
|
||||||
User user;
|
doNetworkRequest(scaffMgr,
|
||||||
try {
|
req: (_) => postWithCreadentials(
|
||||||
user = await User.fromDisk();
|
target: user.server,
|
||||||
} catch (_) {
|
credentials: user,
|
||||||
// user data invalid
|
path: 'createRoom',
|
||||||
// shouldn't happen
|
body: {
|
||||||
return;
|
'room': room.id,
|
||||||
}
|
'title': room.name,
|
||||||
|
'description': room.description,
|
||||||
final room = Room(
|
'icon': room.icon?.type,
|
||||||
id: _ctrID.text,
|
'visibility': room.visibility?.type
|
||||||
serverTag: user.server.tag,
|
}),
|
||||||
name: _ctrName.text,
|
onOK: (_) async {
|
||||||
description: _ctrDescription.text,
|
// room was created
|
||||||
icon: _ctrIcon,
|
// save room
|
||||||
visibility: _ctrVis);
|
await room.toDisk();
|
||||||
try {
|
// move to home page
|
||||||
final resp = await postWithCreadentials(
|
rmaster.replace('/');
|
||||||
target: user.server,
|
},
|
||||||
credentials: user,
|
// we manually fetch the user data above
|
||||||
path: 'createRoom',
|
// because we need the serverTag
|
||||||
body: {
|
needUser: false);
|
||||||
'room': room.id,
|
|
||||||
'title': room.name,
|
|
||||||
'description': room.description,
|
|
||||||
'icon': room.icon?.type,
|
|
||||||
'visibility': room.visibility?.type
|
|
||||||
}
|
|
||||||
);
|
|
||||||
if (resp.res == Result.ok) {
|
|
||||||
// room was created
|
|
||||||
// save room
|
|
||||||
await room.toDisk();
|
|
||||||
// move to home page
|
|
||||||
rmaster.replace('/');
|
|
||||||
} else {
|
|
||||||
// error
|
|
||||||
final snackBar = SnackBar(
|
|
||||||
behavior: SnackBarBehavior.floating,
|
|
||||||
content: Text(errorAsString(resp.body)),
|
|
||||||
action: SnackBarAction(
|
|
||||||
label: 'Dismiss',
|
|
||||||
onPressed: () {
|
|
||||||
scaffMgr.hideCurrentSnackBar();
|
|
||||||
},
|
},
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
scaffMgr.hideCurrentSnackBar();
|
|
||||||
scaffMgr.showSnackBar(snackBar);
|
|
||||||
}
|
|
||||||
} catch (_) {
|
|
||||||
final snackBar = SnackBar(
|
|
||||||
behavior: SnackBarBehavior.floating,
|
|
||||||
content: const Text('Network error'),
|
|
||||||
action: SnackBarAction(
|
|
||||||
label: 'Dismiss',
|
|
||||||
onPressed: () {
|
|
||||||
scaffMgr.hideCurrentSnackBar();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
scaffMgr.hideCurrentSnackBar();
|
|
||||||
scaffMgr.showSnackBar(snackBar);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
label: const Text('Create'),
|
label: const Text('Create'),
|
||||||
icon: const Icon(Icons.add)),
|
icon: const Icon(Icons.add)),
|
||||||
);
|
);
|
||||||
|
|
|
@ -8,6 +8,7 @@ import 'dart:math';
|
||||||
|
|
||||||
import 'package:outbag_app/backend/user.dart';
|
import 'package:outbag_app/backend/user.dart';
|
||||||
import 'package:outbag_app/screens/room/edit.dart';
|
import 'package:outbag_app/screens/room/edit.dart';
|
||||||
|
import 'package:outbag_app/tools/fetch_wrapper.dart';
|
||||||
import 'package:routemaster/routemaster.dart';
|
import 'package:routemaster/routemaster.dart';
|
||||||
|
|
||||||
class AboutRoomPage extends StatefulWidget {
|
class AboutRoomPage extends StatefulWidget {
|
||||||
|
@ -109,71 +110,28 @@ class _AboutRoomPageState extends State<AboutRoomPage> {
|
||||||
ScaffoldMessenger.of(context);
|
ScaffoldMessenger.of(context);
|
||||||
final nav = Navigator.of(context);
|
final nav = Navigator.of(context);
|
||||||
|
|
||||||
User user;
|
doNetworkRequest(scaffMgr,
|
||||||
try {
|
req: (user) =>
|
||||||
user = await User.fromDisk();
|
postWithCreadentials(
|
||||||
} catch (_) {
|
path: 'setVisibility',
|
||||||
// probably not logged in
|
target: (user?.server)!,
|
||||||
nav.pop();
|
body: {
|
||||||
return;
|
'room':
|
||||||
}
|
widget.room?.id,
|
||||||
|
'server': (widget.room
|
||||||
try {
|
?.serverTag)!,
|
||||||
final resp =
|
'visibility':
|
||||||
await postWithCreadentials(
|
vset.first
|
||||||
path: 'setVisibility',
|
},
|
||||||
target: user.server,
|
credentials: user!),
|
||||||
body: {
|
onOK: (_) {
|
||||||
'room': widget.room?.id,
|
Room r = widget.room!;
|
||||||
'server': (widget
|
r.visibility = vis;
|
||||||
.room?.serverTag)!,
|
r.toDisk();
|
||||||
'visibility': vset.first
|
},
|
||||||
},
|
after: () {
|
||||||
credentials: user);
|
nav.pop();
|
||||||
if (resp.res == Result.ok) {
|
});
|
||||||
Room r = widget.room!;
|
|
||||||
r.visibility = vis;
|
|
||||||
r.toDisk();
|
|
||||||
} else {
|
|
||||||
// server error
|
|
||||||
final snackBar = SnackBar(
|
|
||||||
behavior:
|
|
||||||
SnackBarBehavior.floating,
|
|
||||||
content: Text(
|
|
||||||
errorAsString(resp.body)),
|
|
||||||
action: SnackBarAction(
|
|
||||||
label: 'Dismiss',
|
|
||||||
onPressed: () {
|
|
||||||
scaffMgr
|
|
||||||
.hideCurrentSnackBar();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
scaffMgr.hideCurrentSnackBar();
|
|
||||||
scaffMgr.showSnackBar(snackBar);
|
|
||||||
}
|
|
||||||
} catch (_) {
|
|
||||||
// network error
|
|
||||||
final snackBar = SnackBar(
|
|
||||||
behavior:
|
|
||||||
SnackBarBehavior.floating,
|
|
||||||
content:
|
|
||||||
const Text('Network error'),
|
|
||||||
action: SnackBarAction(
|
|
||||||
label: 'Dismiss',
|
|
||||||
onPressed: () {
|
|
||||||
scaffMgr
|
|
||||||
.hideCurrentSnackBar();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
scaffMgr.hideCurrentSnackBar();
|
|
||||||
scaffMgr.showSnackBar(snackBar);
|
|
||||||
}
|
|
||||||
|
|
||||||
nav.pop();
|
|
||||||
},
|
},
|
||||||
child: const Text('Ok'),
|
child: const Text('Ok'),
|
||||||
)
|
)
|
||||||
|
@ -209,11 +167,10 @@ class _AboutRoomPageState extends State<AboutRoomPage> {
|
||||||
onTap: () {
|
onTap: () {
|
||||||
// show edit room screen
|
// show edit room screen
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context)=>Dialog.fullscreen(
|
builder: (context) => Dialog.fullscreen(
|
||||||
child: EditRoomPage(widget.room!),
|
child: EditRoomPage(widget.room!),
|
||||||
)
|
));
|
||||||
);
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@ -284,28 +241,20 @@ class _AboutRoomPageState extends State<AboutRoomPage> {
|
||||||
final nav = Navigator.of(ctx);
|
final nav = Navigator.of(ctx);
|
||||||
final rmaster = Routemaster.of(ctx);
|
final rmaster = Routemaster.of(ctx);
|
||||||
|
|
||||||
User user;
|
doNetworkRequest(
|
||||||
try {
|
scaffMgr,
|
||||||
user = await User.fromDisk();
|
req: (user)=>postWithCreadentials(
|
||||||
} catch (_) {
|
path: ((widget.info?.isOwner)!)
|
||||||
// probably not logged in
|
? 'deleteRoom'
|
||||||
nav.pop();
|
: 'leaveRoom',
|
||||||
return;
|
target: (user?.server)!,
|
||||||
}
|
body: {
|
||||||
|
'room': widget.room?.id,
|
||||||
try {
|
'server':
|
||||||
final resp = await postWithCreadentials(
|
(widget.room?.serverTag)!,
|
||||||
path: ((widget.info?.isOwner)!)
|
},
|
||||||
? 'deleteRoom'
|
credentials: user!),
|
||||||
: 'leaveRoom',
|
onOK: (_) async {
|
||||||
target: user.server,
|
|
||||||
body: {
|
|
||||||
'room': widget.room?.id,
|
|
||||||
'server':
|
|
||||||
(widget.room?.serverTag)!,
|
|
||||||
},
|
|
||||||
credentials: user);
|
|
||||||
if (resp.res == Result.ok) {
|
|
||||||
// try delete room from disk
|
// try delete room from disk
|
||||||
try {
|
try {
|
||||||
await widget.room?.removeDisk();
|
await widget.room?.removeDisk();
|
||||||
|
@ -313,42 +262,12 @@ class _AboutRoomPageState extends State<AboutRoomPage> {
|
||||||
|
|
||||||
// go back home
|
// go back home
|
||||||
rmaster.replace('/');
|
rmaster.replace('/');
|
||||||
} else {
|
},
|
||||||
// server error
|
after: () {
|
||||||
final snackBar = SnackBar(
|
// close popup
|
||||||
behavior: SnackBarBehavior.floating,
|
nav.pop();
|
||||||
content:
|
|
||||||
Text(errorAsString(resp.body)),
|
|
||||||
action: SnackBarAction(
|
|
||||||
label: 'Dismiss',
|
|
||||||
onPressed: () {
|
|
||||||
scaffMgr.hideCurrentSnackBar();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
scaffMgr.hideCurrentSnackBar();
|
|
||||||
scaffMgr.showSnackBar(snackBar);
|
|
||||||
}
|
}
|
||||||
} catch (_) {
|
);
|
||||||
// network error
|
|
||||||
final snackBar = SnackBar(
|
|
||||||
behavior: SnackBarBehavior.floating,
|
|
||||||
content: const Text('Network error'),
|
|
||||||
action: SnackBarAction(
|
|
||||||
label: 'Dismiss',
|
|
||||||
onPressed: () {
|
|
||||||
scaffMgr.hideCurrentSnackBar();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
scaffMgr.hideCurrentSnackBar();
|
|
||||||
scaffMgr.showSnackBar(snackBar);
|
|
||||||
}
|
|
||||||
|
|
||||||
// close popup
|
|
||||||
nav.pop();
|
|
||||||
},
|
},
|
||||||
child: Text(((widget.info?.isOwner)!)
|
child: Text(((widget.info?.isOwner)!)
|
||||||
? 'Delete'
|
? 'Delete'
|
||||||
|
|
100
lib/tools/fetch_wrapper.dart
Normal file
100
lib/tools/fetch_wrapper.dart
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:outbag_app/backend/errors.dart';
|
||||||
|
import 'package:outbag_app/backend/request.dart';
|
||||||
|
import 'package:outbag_app/backend/user.dart';
|
||||||
|
import 'package:outbag_app/tools/snackbar.dart';
|
||||||
|
|
||||||
|
void doNetworkRequest(ScaffoldMessengerState? sm,
|
||||||
|
{required Future<Response> Function(User?) req,
|
||||||
|
Function(Map<String, dynamic>)? onOK,
|
||||||
|
bool Function()? onNetworkErr,
|
||||||
|
bool Function()? onUnknownErr,
|
||||||
|
bool Function()? onUserErr,
|
||||||
|
Function()? after,
|
||||||
|
bool Function(Map<String, dynamic>)? onServerErr,
|
||||||
|
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 (after != null) {
|
||||||
|
after();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Response res;
|
||||||
|
try {
|
||||||
|
res = await req(user);
|
||||||
|
} catch (_) {
|
||||||
|
// network error
|
||||||
|
bool showBar = true;
|
||||||
|
|
||||||
|
if (onNetworkErr != null) {
|
||||||
|
showBar = onNetworkErr();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showBar && sm != null) {
|
||||||
|
showSimpleSnackbar(sm, text: 'Network Error', action: 'Dismiss');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (after != null) {
|
||||||
|
after();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (res.res == Result.ok) {
|
||||||
|
if (onOK != null) {
|
||||||
|
await onOK(res.body);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// server error
|
||||||
|
bool showBar = true;
|
||||||
|
|
||||||
|
if (onServerErr != null) {
|
||||||
|
showBar = onServerErr(res.body);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showBar && sm != null) {
|
||||||
|
showSimpleSnackbar(sm, text: errorAsString(res.body), action: 'OK');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
bool showBar = true;
|
||||||
|
|
||||||
|
print(e);
|
||||||
|
|
||||||
|
if (onUnknownErr != null) {
|
||||||
|
showBar = onUnknownErr();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showBar && sm != null) {
|
||||||
|
showSimpleSnackbar(sm, text: 'Unknown Error', action: 'OK');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (after != null) {
|
||||||
|
after();
|
||||||
|
}
|
||||||
|
}
|
21
lib/tools/snackbar.dart
Normal file
21
lib/tools/snackbar.dart
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
void showSimpleSnackbar(ScaffoldMessengerState scaffMgr,
|
||||||
|
{required String text, required String action, Function? onTap}) {
|
||||||
|
final snackBar = SnackBar(
|
||||||
|
behavior: SnackBarBehavior.floating,
|
||||||
|
content: Text(text),
|
||||||
|
action: SnackBarAction(
|
||||||
|
label: action,
|
||||||
|
onPressed: () {
|
||||||
|
scaffMgr.hideCurrentSnackBar();
|
||||||
|
if (onTap != null) {
|
||||||
|
onTap();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
scaffMgr.hideCurrentSnackBar();
|
||||||
|
scaffMgr.showSnackBar(snackBar);
|
||||||
|
}
|
Loading…
Reference in a new issue