added basic split view for tablet devices

This commit is contained in:
Elias Müller 2023-09-11 23:00:56 +02:00
parent e01bb38af7
commit 482bf8dd0b
6 changed files with 209 additions and 183 deletions

@ -562,6 +562,13 @@
</list>
</value>
</entry>
<entry key="flutter_split_view">
<value>
<list>
<option value="$USER_HOME$/.pub-cache/hosted/pub.dev/flutter_split_view-0.1.2/lib" />
</list>
</value>
</entry>
<entry key="flutter_test">
<value>
<list>
@ -1512,6 +1519,7 @@
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dev/flutter_login-4.2.1/lib" />
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dev/flutter_native_splash-2.3.2/lib" />
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dev/flutter_plugin_android_lifecycle-2.0.16/lib" />
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dev/flutter_split_view-0.1.2/lib" />
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dev/fluttertoast-8.2.2/lib" />
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dev/font_awesome_flutter-10.5.0/lib" />
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dev/frontend_server_client-3.2.0/lib" />

@ -32,4 +32,8 @@ class ChatProps extends DataHolder {
run();
}
String currentToken() {
return _queryToken;
}
}

@ -2,6 +2,7 @@
import 'dart:async';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';
import 'package:flutter_split_view/flutter_split_view.dart';
import 'package:provider/provider.dart';
import '../../../api/marianumcloud/talk/createRoom/createRoom.dart';
@ -72,68 +73,70 @@ class _ChatListState extends State<ChatList> {
Widget build(BuildContext context) {
ChatListProps? latestData;
return Scaffold(
appBar: AppBar(
title: const Text("Talk"),
actions: [
IconButton(
icon: const Icon(Icons.search),
onPressed: () async {
if(latestData == null) return;
showSearch(context: context, delegate: SearchChat(latestData!.getRoomsResponse.data.toList()));
},
)
],
),
floatingActionButton: FloatingActionButton(
heroTag: "createChat",
backgroundColor: Theme.of(context).primaryColor,
onPressed: () async {
showSearch(context: context, delegate: JoinChat()).then((username) {
if(username == null) return;
ConfirmDialog(
title: "Chat starten",
content: "Möchtest du einen Chat mit Nutzer '$username' starten?",
confirmButton: "Chat starten",
onConfirm: () {
CreateRoom(CreateRoomParams(
roomType: 1,
invite: username,
)).run().then((value) {
_query(renew: true);
});
return SplitView.material(
child: Scaffold(
appBar: AppBar(
title: const Text("Talk"),
actions: [
IconButton(
icon: const Icon(Icons.search),
onPressed: () async {
if(latestData == null) return;
showSearch(context: context, delegate: SearchChat(latestData!.getRoomsResponse.data.toList()));
},
).asDialog(context);
});
},
child: const Icon(Icons.add_comment_outlined),
),
body: Consumer<ChatListProps>(
builder: (context, data, child) {
)
],
),
floatingActionButton: FloatingActionButton(
heroTag: "createChat",
backgroundColor: Theme.of(context).primaryColor,
onPressed: () async {
showSearch(context: context, delegate: JoinChat()).then((username) {
if(username == null) return;
if(data.primaryLoading()) return const LoadingSpinner();
latestData = data;
List<ChatTile> chats = [];
for (var chatRoom in data.getRoomsResponse.sortBy(
lastActivity: true,
favoritesToTop: Provider.of<SettingsProvider>(context).val().talkSettings.sortFavoritesToTop,
unreadToTop: Provider.of<SettingsProvider>(context).val().talkSettings.sortUnreadToTop,
)
) {
bool hasDraft = settings.val().talkSettings.drafts.containsKey(chatRoom.token);
chats.add(ChatTile(data: chatRoom, query: _query, hasDraft: hasDraft));
}
ConfirmDialog(
title: "Chat starten",
content: "Möchtest du einen Chat mit Nutzer '$username' starten?",
confirmButton: "Chat starten",
onConfirm: () {
CreateRoom(CreateRoomParams(
roomType: 1,
invite: username,
)).run().then((value) {
_query(renew: true);
});
},
).asDialog(context);
});
},
child: const Icon(Icons.add_comment_outlined),
),
body: Consumer<ChatListProps>(
builder: (context, data, child) {
return RefreshIndicator(
color: Theme.of(context).primaryColor,
onRefresh: () {
_query(renew: true);
return Future.delayed(const Duration(seconds: 3));
},
child: ListView(children: chats),
);
},
if(data.primaryLoading()) return const LoadingSpinner();
latestData = data;
List<ChatTile> chats = [];
for (var chatRoom in data.getRoomsResponse.sortBy(
lastActivity: true,
favoritesToTop: Provider.of<SettingsProvider>(context).val().talkSettings.sortFavoritesToTop,
unreadToTop: Provider.of<SettingsProvider>(context).val().talkSettings.sortUnreadToTop,
)
) {
bool hasDraft = settings.val().talkSettings.drafts.containsKey(chatRoom.token);
chats.add(ChatTile(data: chatRoom, query: _query, hasDraft: hasDraft));
}
return RefreshIndicator(
color: Theme.of(context).primaryColor,
onRefresh: () {
_query(renew: true);
return Future.delayed(const Duration(seconds: 3));
},
child: ListView(children: chats),
);
},
),
),
);
}

@ -1,7 +1,9 @@
import 'package:flutter/material.dart';
import 'package:flutter_split_view/flutter_split_view.dart';
import 'package:jiffy/jiffy.dart';
import 'package:marianum_mobile/widget/userAvatar.dart';
import 'package:persistent_bottom_nav_bar/persistent_tab_view.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../../api/marianumcloud/talk/chat/richObjectStringProcessor.dart';
@ -10,6 +12,7 @@ import '../../../api/marianumcloud/talk/room/getRoomResponse.dart';
import '../../../api/marianumcloud/talk/setFavorite/setFavorite.dart';
import '../../../api/marianumcloud/talk/setReadMarker/setReadMarker.dart';
import '../../../api/marianumcloud/talk/setReadMarker/setReadMarkerParams.dart';
import '../../../model/chatList/chatProps.dart';
import '../../../widget/confirmDialog.dart';
import '../../../widget/debug/debugTile.dart';
import 'chatView.dart';
@ -52,134 +55,139 @@ class _ChatTileState extends State<ChatTile> {
bool isGroup = widget.data.type == GetRoomResponseObjectConversationType.oneToOne;
UserAvatar circleAvatar = UserAvatar(username: widget.data.name, isGroup: isGroup);
return ListTile(
leading: Stack(
children: [
circleAvatar,
Visibility(
visible: widget.data.isFavorite,
child: Positioned(
right: 0,
bottom: 0,
child: Container(
padding: const EdgeInsets.all(1),
decoration: BoxDecoration(
color: Theme.of(context).primaryColor.withAlpha(200),
borderRadius: BorderRadius.circular(90.0),
),
child: const Icon(Icons.star, color: Colors.amberAccent, size: 15),
),
),
)
],
),
title: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(widget.data.displayName),
if(widget.hasDraft) ...[
const SizedBox(width: 5),
const Icon(Icons.edit_outlined, size: 15),
],
],
),
subtitle: Text("${Jiffy.parseFromMillisecondsSinceEpoch(widget.data.lastMessage.timestamp * 1000).fromNow()}: ${RichObjectStringProcessor.parseToString(widget.data.lastMessage.message.replaceAll("\n", " "), widget.data.lastMessage.messageParameters)}", overflow: TextOverflow.ellipsis),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
Visibility(
visible: widget.data.unreadMessages > 0,
child: Container(
padding: const EdgeInsets.all(1),
decoration: BoxDecoration(
color: Theme.of(context).primaryColor,
borderRadius: BorderRadius.circular(30),
),
constraints: const BoxConstraints(
minWidth: 20,
minHeight: 20,
),
child: Text(
"${widget.data.unreadMessages}",
style: const TextStyle(
color: Colors.white,
fontSize: 15,
),
textAlign: TextAlign.center,
),
),
),
],
),
onTap: () async {
setCurrentAsRead();
PersistentNavBarNavigator.pushNewScreen(
context,
screen: ChatView(room: widget.data, selfId: username, avatar: circleAvatar),
withNavBar: false
);
},
onLongPress: () {
if(widget.disableContextActions) return;
showDialog(context: context, builder: (context) => SimpleDialog(
return Consumer<ChatProps>(builder: (context, chatData, child) {
return ListTile(
style: ListTileStyle.list,
tileColor: chatData.currentToken() == widget.data.token
? Theme.of(context).primaryColor.withAlpha(100)
: null,
leading: Stack(
children: [
Visibility(
visible: widget.data.unreadMessages > 0,
replacement: ListTile(
leading: const Icon(Icons.mark_chat_unread_outlined),
title: const Text("Als ungelesen markieren"),
onTap: () {
SetReadMarker(widget.data.token, false).run().then((value) => widget.query(renew: true));
Navigator.of(context).pop();
},
),
child: ListTile(
leading: const Icon(Icons.mark_chat_read_outlined),
title: const Text("Als gelesen markieren"),
onTap: () {
setCurrentAsRead();
Navigator.of(context).pop();
},
),
),
circleAvatar,
Visibility(
visible: widget.data.isFavorite,
replacement: ListTile(
leading: const Icon(Icons.star_outline),
title: const Text("Zu Favoriten hinzufügen"),
onTap: () {
SetFavorite(widget.data.token, true).run().then((value) => widget.query(renew: true));
Navigator.of(context).pop();
},
),
child: ListTile(
leading: const Icon(Icons.stars_outlined),
title: const Text("Von Favoriten entfernen"),
onTap: () {
SetFavorite(widget.data.token, false).run().then((value) => widget.query(renew: true));
Navigator.of(context).pop();
},
child: Positioned(
right: 0,
bottom: 0,
child: Container(
padding: const EdgeInsets.all(1),
decoration: BoxDecoration(
color: Theme.of(context).primaryColor.withAlpha(200),
borderRadius: BorderRadius.circular(90.0),
),
child: const Icon(Icons.star, color: Colors.amberAccent, size: 15),
),
),
)
],
),
title: Row(
mainAxisSize: MainAxisSize.min,
children: [
Flexible(
child: Text(widget.data.displayName, overflow: TextOverflow.ellipsis),
),
ListTile(
leading: const Icon(Icons.delete_outline),
title: const Text("Konversation verlassen"),
onTap: () {
ConfirmDialog(
title: "Chat verlassen",
content: "Du benötigst ggf. eine Einladung um erneut beizutreten.",
confirmButton: "Löschen",
onConfirm: () {
LeaveRoom(widget.data.token).run().then((value) => widget.query(renew: true));
if(widget.hasDraft) ...[
const SizedBox(width: 5),
const Icon(Icons.edit_outlined, size: 15),
],
],
),
subtitle: Text("${Jiffy.parseFromMillisecondsSinceEpoch(widget.data.lastMessage.timestamp * 1000).fromNow()}: ${RichObjectStringProcessor.parseToString(widget.data.lastMessage.message.replaceAll("\n", " "), widget.data.lastMessage.messageParameters)}", overflow: TextOverflow.ellipsis),
trailing: widget.data.unreadMessages <= 0
? null
: Container(
padding: const EdgeInsets.all(1),
decoration: BoxDecoration(
color: Theme.of(context).primaryColor,
borderRadius: BorderRadius.circular(30),
),
constraints: const BoxConstraints(
minWidth: 20,
minHeight: 20,
),
child: Text(
"${widget.data.unreadMessages}",
style: const TextStyle(
color: Colors.white,
fontSize: 15,
),
textAlign: TextAlign.center,
),
),
onTap: () async {
setCurrentAsRead();
SplitView.of(context).setSecondary(ChatView(room: widget.data, selfId: username, avatar: circleAvatar));
Provider.of<ChatProps>(context, listen: false).setQueryToken(widget.data.token);
// PersistentNavBarNavigator.pushNewScreen(
// context,
// screen: ChatView(room: widget.data, selfId: username, avatar: circleAvatar),
// withNavBar: false
// );
},
onLongPress: () {
if(widget.disableContextActions) return;
showDialog(context: context, builder: (context) => SimpleDialog(
children: [
Visibility(
visible: widget.data.unreadMessages > 0,
replacement: ListTile(
leading: const Icon(Icons.mark_chat_unread_outlined),
title: const Text("Als ungelesen markieren"),
onTap: () {
SetReadMarker(widget.data.token, false).run().then((value) => widget.query(renew: true));
Navigator.of(context).pop();
},
).asDialog(context);
},
),
DebugTile(context).jsonData(widget.data.toJson()),
],
));
},
);
),
child: ListTile(
leading: const Icon(Icons.mark_chat_read_outlined),
title: const Text("Als gelesen markieren"),
onTap: () {
setCurrentAsRead();
Navigator.of(context).pop();
},
),
),
Visibility(
visible: widget.data.isFavorite,
replacement: ListTile(
leading: const Icon(Icons.star_outline),
title: const Text("Zu Favoriten hinzufügen"),
onTap: () {
SetFavorite(widget.data.token, true).run().then((value) => widget.query(renew: true));
Navigator.of(context).pop();
},
),
child: ListTile(
leading: const Icon(Icons.stars_outlined),
title: const Text("Von Favoriten entfernen"),
onTap: () {
SetFavorite(widget.data.token, false).run().then((value) => widget.query(renew: true));
Navigator.of(context).pop();
},
),
),
ListTile(
leading: const Icon(Icons.delete_outline),
title: const Text("Konversation verlassen"),
onTap: () {
ConfirmDialog(
title: "Chat verlassen",
content: "Du benötigst ggf. eine Einladung um erneut beizutreten.",
confirmButton: "Löschen",
onConfirm: () {
LeaveRoom(widget.data.token).run().then((value) => widget.query(renew: true));
Navigator.of(context).pop();
},
).asDialog(context);
},
),
DebugTile(context).jsonData(widget.data.toJson()),
],
));
},
);
});
}
}

@ -1,4 +1,6 @@
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:loader_overlay/loader_overlay.dart';
import 'package:provider/provider.dart';
@ -30,7 +32,7 @@ class _ChatViewState extends State<ChatView> {
@override
void initState() {
super.initState();
log("init state for ${widget.room.name}");
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
_query();
});

@ -88,6 +88,7 @@ dependencies:
fluttertoast: ^8.2.2
fast_rsa: ^3.6.1
share_plus: ^7.1.0
flutter_split_view: ^0.1.2
dev_dependencies:
flutter_test: