8fffafde47
Translations are provided in *.arb* format. Some keys have descriptions (indicated by leading @-symbol). Descriptions should not be copied into the translation itself. Currently only English is supported (app_en.arb), but German is planned. Apparently weblate merged .arb support at some time, so it would be nice to enable community translations at some point.
308 lines
11 KiB
Dart
308 lines
11 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_svg/flutter_svg.dart';
|
|
import 'package:go_router/go_router.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:provider/provider.dart';
|
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
|
import 'dart:math';
|
|
|
|
class JoinRoomPage extends StatefulWidget {
|
|
const JoinRoomPage({super.key});
|
|
|
|
@override
|
|
State<StatefulWidget> createState() => _JoinRoomPageState();
|
|
}
|
|
|
|
class _JoinRoomPageState extends State {
|
|
List<Room> rooms = [];
|
|
|
|
void fetchRooms() {
|
|
final sm = ScaffoldMessenger.of(context);
|
|
final user = context.read<User>();
|
|
|
|
doNetworkRequest(null,
|
|
req: () => postWithCreadentials(
|
|
path: 'listPublicRooms',
|
|
credentials: user,
|
|
target: user.server,
|
|
body: {}),
|
|
onOK: (body) async {
|
|
// parse rooms
|
|
final list = body['data'];
|
|
|
|
// try to fetch a list of rooms the user is a member of
|
|
// use an empty blacklist when request is not successful
|
|
final List<Room> blacklist = [];
|
|
doNetworkRequest(sm,
|
|
req: () => postWithCreadentials(
|
|
path: 'listRooms',
|
|
credentials: user,
|
|
target: user.server,
|
|
body: {}),
|
|
onOK: (body) {
|
|
final List<Room> list = body['data'].map<Room>((json) {
|
|
return Room.fromJSON(json);
|
|
}).toList();
|
|
for (Room r in list) {
|
|
blacklist.add(r);
|
|
}
|
|
});
|
|
|
|
// process the list of public rooms
|
|
final List<Room> builder = [];
|
|
processor:
|
|
for (dynamic raw in list) {
|
|
try {
|
|
final room = Room.fromJSON(raw);
|
|
|
|
// figure out if room is on blacklist
|
|
// only add room to list,
|
|
// if not on blacklist
|
|
for (Room r in blacklist) {
|
|
if (r.compareTo(room) == 0) {
|
|
// server on white list
|
|
// move to next iteration on outer for loop
|
|
continue processor;
|
|
}
|
|
}
|
|
|
|
builder.add(room);
|
|
} catch (_) {
|
|
// ignore room
|
|
}
|
|
}
|
|
setState(() {
|
|
rooms = builder;
|
|
});
|
|
});
|
|
}
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
|
|
WidgetsBinding.instance.addPostFrameCallback((_) => fetchRooms());
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final textTheme = Theme.of(context)
|
|
.textTheme
|
|
.apply(displayColor: Theme.of(context).colorScheme.onSurface);
|
|
|
|
double width = MediaQuery.of(context).size.width;
|
|
double height = MediaQuery.of(context).size.height;
|
|
double smallest = min(width, height);
|
|
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
title: const Text('Join Room'),
|
|
actions: [
|
|
IconButton(
|
|
icon: const Icon(Icons.search),
|
|
tooltip: AppLocalizations.of(context)!.search,
|
|
onPressed: () {
|
|
// show searchbar
|
|
// NOTE: location currently unknown
|
|
},
|
|
),
|
|
IconButton(
|
|
icon: const Icon(Icons.refresh),
|
|
tooltip: AppLocalizations.of(context)!.refresh,
|
|
onPressed: () {
|
|
// fetch public rooms again
|
|
didChangeDependencies();
|
|
},
|
|
),
|
|
MenuAnchor(
|
|
builder: (ctx, controller, child) {
|
|
return IconButton(
|
|
onPressed: () {
|
|
if (controller.isOpen) {
|
|
controller.close();
|
|
} else {
|
|
controller.open();
|
|
}
|
|
},
|
|
icon: const Icon(Icons.more_vert),
|
|
);
|
|
},
|
|
menuChildren: [
|
|
MenuItemButton(
|
|
leadingIcon: const Icon(Icons.drafts),
|
|
child: Text(AppLocalizations.of(context)!.joinRoomInvite),
|
|
onPressed: () {
|
|
// show settings screen
|
|
context.goNamed('join-room-ota');
|
|
}),
|
|
])
|
|
],
|
|
),
|
|
body: rooms.isEmpty
|
|
? Center(
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
children: [
|
|
Text(AppLocalizations.of(context)!.noNewRoomsFound, style: textTheme.titleLarge),
|
|
],
|
|
))
|
|
: ListView.builder(
|
|
itemCount: rooms.length,
|
|
itemBuilder: (ctx, i) {
|
|
final room = rooms[i];
|
|
return Card(
|
|
margin: const EdgeInsets.all(8.0),
|
|
clipBehavior: Clip.antiAliasWithSaveLayer,
|
|
semanticContainer: true,
|
|
child: InkWell(
|
|
onTap: () {
|
|
// show modalBottomSheet
|
|
// with room information
|
|
// and join button
|
|
showModalBottomSheet(
|
|
context: ctx,
|
|
builder: (ctx) {
|
|
return BottomSheet(
|
|
onClosing: () {},
|
|
builder: (ctx) {
|
|
return Column(
|
|
crossAxisAlignment:
|
|
CrossAxisAlignment.center,
|
|
mainAxisAlignment:
|
|
MainAxisAlignment.center,
|
|
children: [
|
|
Padding(
|
|
padding: const EdgeInsets.all(14),
|
|
child: Column(children: [
|
|
// room icon
|
|
SvgPicture.asset(
|
|
(room.icon?.img)!,
|
|
width: smallest * 0.2,
|
|
height: smallest * 0.2,
|
|
),
|
|
// room name
|
|
Text(
|
|
room.name,
|
|
style: textTheme.displayMedium,
|
|
),
|
|
Text(
|
|
'${room.id}@${room.serverTag}',
|
|
style: textTheme.labelSmall,
|
|
),
|
|
// description
|
|
Text(room.description,
|
|
style: textTheme.bodyLarge),
|
|
// visibility
|
|
Row(
|
|
mainAxisAlignment:
|
|
MainAxisAlignment.center,
|
|
children: [
|
|
Icon(room.visibility?.icon),
|
|
Text((room
|
|
.visibility?.text(context))!),
|
|
]),
|
|
])),
|
|
// action buttons
|
|
Row(
|
|
mainAxisAlignment:
|
|
MainAxisAlignment.center,
|
|
children: [
|
|
// cancel button
|
|
Padding(
|
|
padding:
|
|
const EdgeInsets.all(14),
|
|
child: ElevatedButton.icon(
|
|
icon:
|
|
const Icon(Icons.close),
|
|
label: Text(AppLocalizations.of(context)!.cancel),
|
|
onPressed: () {
|
|
// close sheet
|
|
Navigator.pop(context);
|
|
},
|
|
)),
|
|
// join room button
|
|
Padding(
|
|
padding:
|
|
const EdgeInsets.all(14),
|
|
child: FilledButton.icon(
|
|
icon:
|
|
const Icon(Icons.check),
|
|
label: Text(AppLocalizations.of(context)!.joinRoom),
|
|
onPressed: () async {
|
|
final scaffMgr =
|
|
ScaffoldMessenger.of(
|
|
context);
|
|
|
|
final nav =
|
|
Navigator.of(context);
|
|
final user =
|
|
context.read<User>();
|
|
final router =
|
|
GoRouter.of(context);
|
|
|
|
doNetworkRequest(scaffMgr,
|
|
req: () =>
|
|
postWithCreadentials(
|
|
credentials:
|
|
user,
|
|
target: user
|
|
.server,
|
|
path:
|
|
'joinPublicRoom',
|
|
body: {
|
|
'room':
|
|
room.id,
|
|
'server': room
|
|
.serverTag
|
|
}),
|
|
onOK: (body) async {
|
|
await room.toDisk();
|
|
nav.pop();
|
|
router
|
|
.pushReplacementNamed(
|
|
'room',
|
|
params: {
|
|
'server': room
|
|
.serverTag,
|
|
'id': room.id
|
|
});
|
|
});
|
|
},
|
|
))
|
|
])
|
|
],
|
|
);
|
|
},
|
|
);
|
|
});
|
|
},
|
|
child: Container(
|
|
padding: const EdgeInsets.fromLTRB(10, 5, 5, 10),
|
|
child: ListTile(
|
|
title: Text(room.name),
|
|
visualDensity: const VisualDensity(vertical: 3),
|
|
subtitle: Text(room.description),
|
|
leading: AspectRatio(
|
|
aspectRatio: 1 / 1,
|
|
child: SvgPicture.asset("${room.icon?.img}"),
|
|
),
|
|
hoverColor: Colors.transparent,
|
|
))));
|
|
},
|
|
),
|
|
floatingActionButton: FloatingActionButton.extended(
|
|
label: Text(AppLocalizations.of(context)!.newRoom),
|
|
icon: const Icon(Icons.add),
|
|
onPressed: () {
|
|
// create new room
|
|
context.goNamed('new-room');
|
|
},
|
|
tooltip: AppLocalizations.of(context)!.createRoomShort,
|
|
),
|
|
);
|
|
}
|
|
}
|