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:
Jakob Meier 2023-03-23 14:48:53 +01:00
parent eabb6b93ae
commit 5d333522a5
No known key found for this signature in database
GPG key ID: 66BDC7E6A01A6152
11 changed files with 637 additions and 751 deletions

View file

@ -187,10 +187,10 @@ class RoomIcon {
}
class Room {
final String id;
final String serverTag;
final String name;
final String description;
String id;
String serverTag;
String name;
String description;
RoomIcon? icon = RoomIcon.other;
RoomVisibility? visibility = RoomVisibility.private;
@ -202,6 +202,17 @@ class Room {
this.icon,
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
static Future<List<Room>> listRooms() async {
final db = Localstore.instance;

View file

@ -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 {
final db = Localstore.instance;
final stream = db.collection('meta').stream;
@ -60,7 +66,6 @@ class AccountMeta {
maxRoomSize: json['maxRoomSize'],
maxRoomCount: json['maxRooms'],
maxRoomMemberCount: json['maxUsersPerRoom'],
discvoverable: json['viewable'] == 1
);
discvoverable: json['viewable'] == 1);
}
}

View file

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:outbag_app/backend/user.dart';
import 'package:outbag_app/screens/room/join.dart';
import 'package:outbag_app/screens/room/new.dart';
import 'package:outbag_app/tools/fetch_wrapper.dart';
import 'package:provider/provider.dart';
import './screens/home.dart';
import './screens/welcome.dart';
@ -58,38 +59,30 @@ class _OutbagAppState extends State {
// try to obtain user account information
// with existing details
// NOTE: also functions as a way to verify ther data
(() async {
User credentials;
try {
credentials = await User.fromDisk();
} catch (_) {
// invalid credentials
// log out
setState(() {
isAuthorized = false;
});
return;
}
try {
final resp = await postWithCreadentials(
target: credentials.server,
doNetworkRequest(
null,
req: (user) => postWithCreadentials(
target: (user?.server)!,
path: 'getMyAccount',
credentials: credentials,
body: {});
if (resp.res == Result.ok) {
final info = AccountMeta.fromJSON(resp.body['data']);
credentials: user!,
body: {}),
onOK: (body) {
final info = AccountMeta.fromJSON(body['data']);
setState(() {
isAuthorized = true;
this.info = info;
});
} else {
},
onServerErr: (body) {
// credentials are wrong
// log out
setState(() {
isAuthorized = false;
});
}
} catch (_) {
return true;
},
onNetworkErr: () {
// user is currently offline
// approve login,
// until user goes back offline
@ -97,8 +90,17 @@ class _OutbagAppState extends State {
setState(() {
isAuthorized = true;
});
return true;
},
onUserErr: () {
// invalid credentials
// log out
setState(() {
isAuthorized = false;
});
return true;
}
})();
);
// wait for user to be authorized
User.listen((data) async {
@ -115,7 +117,9 @@ class _OutbagAppState extends State {
Widget build(BuildContext context) {
return MultiProvider(
providers: [
Provider<AccountMeta?>.value(value: info,),
Provider<AccountMeta?>.value(
value: info,
),
],
child: MaterialApp.router(
title: "Outbag",

View file

@ -1,6 +1,8 @@
import 'package:flutter/material.dart';
import 'package:outbag_app/backend/request.dart';
import 'package:outbag_app/backend/user.dart';
import 'package:outbag_app/tools/fetch_wrapper.dart';
import 'package:outbag_app/tools/snackbar.dart';
import 'package:routemaster/routemaster.dart';
import '../backend/resolve_url.dart';
import '../backend/errors.dart';
@ -173,6 +175,8 @@ class _AuthPageState extends State<AuthPage> {
showSpinner = true;
});
final scaffMgr = ScaffoldMessenger.of(context);
// verify that both passwords are the same
if (widget.mode != Mode.signin) {
if (_ctrPassword.text != _ctrPasswordRpt.text) {
@ -180,19 +184,8 @@ class _AuthPageState extends State<AuthPage> {
showSpinner = false;
});
final snackBar = SnackBar(
behavior: SnackBarBehavior.floating,
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);
showSimpleSnackbar(scaffMgr,
text: 'Passwords do not match', action: 'Dismiss');
_ctrPasswordRpt.clear();
return;
@ -205,28 +198,15 @@ class _AuthPageState extends State<AuthPage> {
showSpinner = false;
});
final snackBar = SnackBar(
behavior: SnackBarBehavior.floating,
content: const Text(
'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);
showSimpleSnackbar(scaffMgr,
text: 'Password has to be at least 6 characters longs',
action: 'Dismiss');
_ctrPasswordRpt.clear();
return;
}
final scaffMgr = ScaffoldMessenger.of(context);
// TODO: resolve homeserver url
// resolve homeserver url
OutbagServer server;
try {
server = await getOutbagServerUrl(_ctrServer.text);
@ -238,31 +218,24 @@ class _AuthPageState extends State<AuthPage> {
showSpinner = false;
});
final snackBar = SnackBar(
behavior: SnackBarBehavior.floating,
content: Text(
'Unable to find valid outbag server on ${_ctrServer.text}'),
action: SnackBarAction(
label: 'Dismiss',
onPressed: () {
scaffMgr.hideCurrentSnackBar();
},
),
);
scaffMgr.hideCurrentSnackBar();
scaffMgr.showSnackBar(snackBar);
showSimpleSnackbar(scaffMgr,
text:
'Unable to find valid outbag server on ${_ctrServer.text}',
action: 'Dismiss');
return;
}
// hash password
var bytes = utf8.encode(_ctrPassword.text);
final password = sha256.convert(bytes).toString();
try {
Response resp;
// validate account
doNetworkRequest(
scaffMgr,
needUser: false,
req: (_) {
if (widget.mode == Mode.signin) {
resp = await postUnauthorized(
return postUnauthorized(
target: server,
path: 'signin',
body: {
@ -271,7 +244,7 @@ class _AuthPageState extends State<AuthPage> {
'accountKey': password
});
} else if (widget.mode == Mode.signup) {
resp = await postUnauthorized(
return postUnauthorized(
target: server,
path: 'signup',
body: {
@ -281,7 +254,7 @@ class _AuthPageState extends State<AuthPage> {
});
} else {
// signup OTA
resp = await postUnauthorized(
return postUnauthorized(
target: server,
path: 'signupOTA',
body: {
@ -291,49 +264,21 @@ class _AuthPageState extends State<AuthPage> {
'OTA': _ctrOTA.text
});
}
if (resp.res == Result.err) {
// 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 {
onOK: (body) async {
// authorize user
await User(
username: _ctrUsername.text,
password: password,
server: server)
.toDisk();
}
} catch (_) {
final snackBar = SnackBar(
behavior: SnackBarBehavior.floating,
content: const Text('Network error'),
action: SnackBarAction(
label: 'Dismiss',
onPressed: () {
scaffMgr.hideCurrentSnackBar();
},
),
);
scaffMgr.hideCurrentSnackBar();
scaffMgr.showSnackBar(snackBar);
}
after: () {
setState(() {
showSpinner = false;
});
}
);
},
label: Text(modeName),
icon: const Icon(Icons.check),

View file

@ -5,6 +5,7 @@ import 'package:flutter_svg/flutter_svg.dart';
import 'package:outbag_app/backend/permissions.dart';
import 'package:outbag_app/backend/request.dart';
import 'package:outbag_app/backend/user.dart';
import 'package:outbag_app/tools/fetch_wrapper.dart';
import 'package:provider/provider.dart';
import 'package:routemaster/routemaster.dart';
import '../backend/room.dart';
@ -31,6 +32,13 @@ class _HomePageState extends State<HomePage> {
});
} catch (_) {}
});
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
final sm = ScaffoldMessenger.of(context);
// load cached rooms
(() async {
@ -42,32 +50,20 @@ class _HomePageState extends State<HomePage> {
} catch (_) {}
})();
// fetch room list
(() async {
User user;
try {
user = await User.fromDisk();
} catch (_) {
// probably not logged in
return;
}
try {
final resp = await postWithCreadentials(
doNetworkRequest(sm,
req: (user) => postWithCreadentials(
path: 'listRooms',
credentials: user,
credentials: user!,
target: user.server,
body: {});
if (resp.res == Result.ok) {
final List<Room> list = resp.body['data'].map<Room>((json){
body: {}),
onOK: (body) async {
final List<Room> list = body['data'].map<Room>((json) {
return Room.fromJSON(json);
}).toList();
for (Room r in list) {
await r.toDisk();
}
}
} catch (_) {}
})();
});
}
@override
@ -106,7 +102,10 @@ class _HomePageState extends State<HomePage> {
Routemaster.of(context).push("/settings");
}),
...(context.watch<AccountMeta?>() != null &&
(context.watch<AccountMeta?>()?.permissions)! & ServerPermission.allManagement != 0)?[
(context.watch<AccountMeta?>()?.permissions)! &
ServerPermission.allManagement !=
0)
? [
MenuItemButton(
leadingIcon: const Icon(Icons.dns),
child: const Text('Server Dashboard'),
@ -114,7 +113,8 @@ class _HomePageState extends State<HomePage> {
// show settings screen
Routemaster.of(context).push("/server");
}),
]:[],
]
: [],
MenuItemButton(
leadingIcon: const Icon(Icons.info_rounded),
child: const Text('About'),

View file

@ -4,6 +4,7 @@ import 'package:outbag_app/backend/errors.dart';
import 'package:outbag_app/backend/request.dart';
import 'package:outbag_app/backend/room.dart';
import 'package:outbag_app/backend/user.dart';
import 'package:outbag_app/tools/fetch_wrapper.dart';
import 'package:routemaster/routemaster.dart';
import 'dart:math';
@ -17,45 +18,42 @@ class JoinRoomPage extends StatefulWidget {
class _JoinRoomPageState extends State {
List<Room> rooms = [];
void fetchData() async {
User user;
try {
user = await User.fromDisk();
} catch (_) {
return;
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
try {
final resp = await postWithCreadentials(
final sm = ScaffoldMessenger.of(context);
doNetworkRequest(null,
req: (user) => postWithCreadentials(
path: 'listPublicRooms',
credentials: user,
credentials: user!,
target: user.server,
body: {});
if (resp.res == Result.ok) {
body: {}),
onOK: (body) async {
// parse rooms
final list = resp.body['data'];
final list = body['data'];
// try to fetch a list of rooms the user is a member of
// use an empty blacklist when request is not successful
final blacklist = [];
try {
final resp = await postWithCreadentials(
final List<Room> blacklist = [];
doNetworkRequest(sm,
req: (user) => postWithCreadentials(
path: 'listRooms',
credentials: user,
credentials: user!,
target: user.server,
body: {});
if (resp.res == Result.ok) {
final List<Room> list = resp.body['data'].map<Room>((json) {
body: {}),
onOK: (body) {
final List<Room> list = body['data'].map<Room>((json) {
return Room.fromJSON(json);
}).toList();
for (Room r in list) {
blacklist.add(r);
}
}
} catch (_) {}
});
// process the list of public rooms
List<Room> builder = [];
final List<Room> builder = [];
processor:
for (dynamic raw in list) {
try {
@ -65,7 +63,7 @@ class _JoinRoomPageState extends State {
// only add room to list,
// if not on blacklist
for (Room r in blacklist) {
if (room.serverTag == r.serverTag && room.id == r.id) {
if (r.compareTo(room) == 0) {
// server on white list
// move to next iteration on outer for loop
continue processor;
@ -77,25 +75,15 @@ class _JoinRoomPageState extends State {
// ignore room
}
}
builder.sort();
setState(() {
rooms = builder;
});
} else {
throw Error();
}
} catch (_) {
// network error
// unable to load room list
// NOTE: might want to show snackbar
// with warning
}
});
}
@override
void initState() {
super.initState();
fetchData();
}
@override
@ -133,7 +121,7 @@ class _JoinRoomPageState extends State {
tooltip: "Refresh",
onPressed: () {
// fetch public rooms again
fetchData();
didChangeDependencies();
},
),
MenuAnchor(
@ -261,24 +249,13 @@ class _JoinRoomPageState extends State {
final nav =
Navigator.of(context);
// join room & close screen
User user;
try {
user = await User
.fromDisk();
} catch (_) {
// user data invalid
// NOTE: shouldn't happen
// because the main.dart watches the auth meta data
// and auto logs-out the user
return;
}
try {
final resp =
await postWithCreadentials(
target:
user.server,
doNetworkRequest(scaffMgr,
req: (user) =>
postWithCreadentials(
credentials:
user!,
target: user
.server,
path:
'joinPublicRoom',
body: {
@ -286,65 +263,13 @@ class _JoinRoomPageState extends State {
room.id,
'server': room
.serverTag
},
credentials:
user);
if (resp.res ==
Result.ok) {
// successfully joined room
}),
onOK: (body) async {
await room.toDisk();
nav.pop();
rmaster.replace(
'/r/${room.serverTag}/${room.id}');
} 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);
}
});
},
))
])

View file

@ -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/products.dart';
import 'package:outbag_app/screens/room/pages/list.dart';
import 'package:outbag_app/tools/fetch_wrapper.dart';
import 'package:routemaster/routemaster.dart';
class RoomPage extends StatefulWidget {
@ -27,61 +28,52 @@ class _RoomPageState extends State<RoomPage> {
// fetch room information
void fetchInfo() async {
bool foundData = false;
final sm = ScaffoldMessenger.of(context);
try {
final diskRoom =
await Room.fromDisk(serverTag: widget.server, id: widget.tag);
foundData = true;
setState(() {
room = diskRoom;
});
} catch (_) {}
// fetch additional data from web
User user;
try {
user = await User.fromDisk();
} catch (_) {
// probably not logged in
return;
}
try {
final resp = await postWithCreadentials(
doNetworkRequest(sm,
req: (user) => 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']);
credentials: user!,
target: (user.server),
body: {'room': widget.tag, 'server': widget.server}),
onOK: (body) async {
final info = RoomInfo.fromJSON(body['data']);
final room = Room.fromJSON(body['data']);
room.toDisk();
foundData = true;
setState(() {
this.info = info;
});
}
} catch (_) {}
if (!foundData) {
return true;
},
onNetworkErr: () {
// user offline
if (room == null) {
// no room data available
// TODO: close room
// or show snackbar
// BUG: there is currently no way of implementing this,
// because there is no context available here
// NOTE: close room?
}
return true;
},
onServerErr: (json) {
// user no longer in room
// TODO: close room
return true;
});
}
@override
void initState() {
super.initState();
// schedule info-get
fetchInfo();
_ctr.addListener(() {
setState(() {
page = _ctr.page?.toInt() ?? _ctr.initialPage;
@ -104,6 +96,13 @@ class _RoomPageState extends State<RoomPage> {
});
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
// schedule info-get
fetchInfo();
}
@override
Widget build(BuildContext context) {
return Scaffold(

View file

@ -4,6 +4,8 @@ import 'package:outbag_app/backend/errors.dart';
import 'package:outbag_app/backend/request.dart';
import 'package:outbag_app/backend/room.dart';
import 'package:outbag_app/backend/user.dart';
import 'package:outbag_app/tools/fetch_wrapper.dart';
import 'package:outbag_app/tools/snackbar.dart';
import 'package:routemaster/routemaster.dart';
import 'dart:math';
@ -182,41 +184,23 @@ class _NewRoomPageState extends State {
// ID should be at least three characters long
if (_ctrID.text.length < 3) {
final snackBar = SnackBar(
behavior: SnackBarBehavior.floating,
content: Text(_ctrID.text.isEmpty
showSimpleSnackbar(scaffMgr,
text: _ctrID.text.isEmpty
? 'Please specify a Room ID'
: 'Room ID has to be at least three characters long'),
action: SnackBarAction(
label: 'Ok',
onPressed: () {
scaffMgr.hideCurrentSnackBar();
},
),
);
scaffMgr.hideCurrentSnackBar();
scaffMgr.showSnackBar(snackBar);
: 'Room ID has to be at least three characters long',
action: 'OK');
return;
}
// name may not be empty
if (_ctrName.text.isEmpty) {
final snackBar = SnackBar(
behavior: SnackBarBehavior.floating,
content: const Text('Please specify a room name'),
action: SnackBarAction(
label: 'Ok',
onPressed: () {
scaffMgr.hideCurrentSnackBar();
},
),
showSimpleSnackbar(
scaffMgr,
text: 'Please specify a room name',
action: 'OK'
);
scaffMgr.hideCurrentSnackBar();
scaffMgr.showSnackBar(snackBar);
return;
}
@ -236,8 +220,9 @@ class _NewRoomPageState extends State {
description: _ctrDescription.text,
icon: _ctrIcon,
visibility: _ctrVis);
try {
final resp = await postWithCreadentials(
doNetworkRequest(scaffMgr,
req: (_) => postWithCreadentials(
target: user.server,
credentials: user,
path: 'createRoom',
@ -247,45 +232,17 @@ class _NewRoomPageState extends State {
'description': room.description,
'icon': room.icon?.type,
'visibility': room.visibility?.type
}
);
if (resp.res == Result.ok) {
}),
onOK: (_) async {
// 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);
}
// we manually fetch the user data above
// because we need the serverTag
needUser: false);
},
label: const Text('Create'),
icon: const Icon(Icons.add)),

View file

@ -8,6 +8,7 @@ import 'dart:math';
import 'package:outbag_app/backend/user.dart';
import 'package:outbag_app/screens/room/edit.dart';
import 'package:outbag_app/tools/fetch_wrapper.dart';
import 'package:routemaster/routemaster.dart';
class AboutRoomPage extends StatefulWidget {
@ -109,71 +110,28 @@ class _AboutRoomPageState extends State<AboutRoomPage> {
ScaffoldMessenger.of(context);
final nav = Navigator.of(context);
User user;
try {
user = await User.fromDisk();
} catch (_) {
// probably not logged in
nav.pop();
return;
}
try {
final resp =
await postWithCreadentials(
doNetworkRequest(scaffMgr,
req: (user) =>
postWithCreadentials(
path: 'setVisibility',
target: user.server,
target: (user?.server)!,
body: {
'room': widget.room?.id,
'server': (widget
.room?.serverTag)!,
'visibility': vset.first
'room':
widget.room?.id,
'server': (widget.room
?.serverTag)!,
'visibility':
vset.first
},
credentials: user);
if (resp.res == Result.ok) {
credentials: user!),
onOK: (_) {
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);
}
after: () {
nav.pop();
});
},
child: const Text('Ok'),
)
@ -210,10 +168,9 @@ class _AboutRoomPageState extends State<AboutRoomPage> {
// show edit room screen
showDialog(
context: context,
builder: (context)=>Dialog.fullscreen(
builder: (context) => Dialog.fullscreen(
child: EditRoomPage(widget.room!),
)
);
));
},
),
]
@ -284,28 +241,20 @@ class _AboutRoomPageState extends State<AboutRoomPage> {
final nav = Navigator.of(ctx);
final rmaster = Routemaster.of(ctx);
User user;
try {
user = await User.fromDisk();
} catch (_) {
// probably not logged in
nav.pop();
return;
}
try {
final resp = await postWithCreadentials(
doNetworkRequest(
scaffMgr,
req: (user)=>postWithCreadentials(
path: ((widget.info?.isOwner)!)
? 'deleteRoom'
: 'leaveRoom',
target: user.server,
target: (user?.server)!,
body: {
'room': widget.room?.id,
'server':
(widget.room?.serverTag)!,
},
credentials: user);
if (resp.res == Result.ok) {
credentials: user!),
onOK: (_) async {
// try delete room from disk
try {
await widget.room?.removeDisk();
@ -313,42 +262,12 @@ class _AboutRoomPageState extends State<AboutRoomPage> {
// go back home
rmaster.replace('/');
} 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);
}
after: () {
// close popup
nav.pop();
}
);
},
child: Text(((widget.info?.isOwner)!)
? 'Delete'

View 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
View 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);
}