Add session invites to message view

This commit is contained in:
Nutcake 2023-05-02 22:50:35 +02:00
parent 158fccbf3f
commit 38205fe8e1
5 changed files with 241 additions and 115 deletions

View file

@ -4,6 +4,7 @@ import 'dart:developer';
import 'package:contacts_plus_plus/api_client.dart';
import 'package:contacts_plus_plus/apis/message_api.dart';
import 'package:contacts_plus_plus/auxiliary.dart';
import 'package:contacts_plus_plus/models/session.dart';
import 'package:uuid/uuid.dart';
enum MessageType {

View file

@ -9,10 +9,11 @@ class Session {
final String description;
final List<String> tags;
final bool headlessHost;
final String hostUsername;
Session({required this.id, required this.name, required this.sessionUsers, required this.thumbnail,
required this.maxUsers, required this.hasEnded, required this.isValid, required this.description,
required this.tags, required this.headlessHost,
required this.tags, required this.headlessHost, required this.hostUsername,
});
factory Session.fromMap(Map map) {
@ -21,12 +22,13 @@ class Session {
name: map["name"],
sessionUsers: (map["sessionUsers"] as List? ?? []).map((entry) => SessionUser.fromMap(entry)).toList(),
thumbnail: map["thumbnail"] ?? "",
maxUsers: map["maxUsers"],
hasEnded: map["hasEnded"],
isValid: map["isValid"],
maxUsers: map["maxUsers"] ?? 0,
hasEnded: map["hasEnded"] ?? false,
isValid: map["isValid"] ?? true,
description: map["description"] ?? "",
tags: ((map["tags"] as List?) ?? []).map((e) => e.toString()).toList(),
headlessHost: map["headlessHost"],
headlessHost: map["headlessHost"] ?? false,
hostUsername: map["hostUsername"] ?? "",
);
}

View file

@ -0,0 +1,83 @@
import 'dart:convert';
import 'package:contacts_plus_plus/api_client.dart';
import 'package:contacts_plus_plus/auxiliary.dart';
import 'package:contacts_plus_plus/models/message.dart';
import 'package:contacts_plus_plus/models/session.dart';
import 'package:contacts_plus_plus/widgets/generic_avatar.dart';
import 'package:contacts_plus_plus/widgets/messages.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
class MessageSessionInvite extends StatelessWidget {
MessageSessionInvite({required this.message, super.key});
final DateFormat _dateFormat = DateFormat.Hm();
final Message message;
@override
Widget build(BuildContext context) {
final sessionInfo = Session.fromMap(jsonDecode(message.content));
return TextButton(
onPressed: () {
showDialog(context: context, builder: (context) => SessionPopup(session: sessionInfo));
},
style: TextButton.styleFrom(padding: EdgeInsets.zero),
child: Container(
padding: const EdgeInsets.only(left: 10),
constraints: const BoxConstraints(maxWidth: 300),
child: Column(
children: [
Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(sessionInfo.name, maxLines: null, softWrap: true, style: Theme.of(context).textTheme.titleMedium,),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: [
GenericAvatar(
imageUri: Aux.neosDbToHttp(Aux.neosDbToHttp(sessionInfo.thumbnail)),
placeholderIcon: Icons.no_photography,
),
const SizedBox(height: 4,),
Text("${sessionInfo.sessionUsers.length}/${sessionInfo.maxUsers}")
],
),
),
],
),
const SizedBox(height: 8,),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(child: Text("Hosted by ${sessionInfo.hostUsername}", overflow: TextOverflow.ellipsis, style: Theme.of(context).textTheme.bodySmall?.copyWith(color: Colors.white60),)),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4.0),
child: Text(
_dateFormat.format(message.sendTime),
style: Theme
.of(context)
.textTheme
.labelMedium
?.copyWith(color: Colors.white54),
),
),
const SizedBox(width: 4,),
if (message.senderId == ClientHolder.of(context).client.userId) Padding(
padding: const EdgeInsets.only(right: 12.0),
child: MessageStateIndicator(messageState: message.state),
),
],
)
],
),
),
);
}
}

View file

@ -1,4 +1,3 @@
import 'dart:convert';
import 'dart:developer';
import 'package:cached_network_image/cached_network_image.dart';
@ -7,8 +6,9 @@ import 'package:contacts_plus_plus/auxiliary.dart';
import 'package:contacts_plus_plus/models/friend.dart';
import 'package:contacts_plus_plus/models/message.dart';
import 'package:contacts_plus_plus/models/session.dart';
import 'package:contacts_plus_plus/widgets/audio_clip_player.dart';
import 'package:contacts_plus_plus/widgets/message_audio_player.dart';
import 'package:contacts_plus_plus/widgets/generic_avatar.dart';
import 'package:contacts_plus_plus/widgets/message_session_invite.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
@ -338,8 +338,26 @@ class MyMessageBubble extends StatelessWidget {
var content = message.content;
switch (message.type) {
case MessageType.sessionInvite:
content = "[Session Invite]";
continue rawText;
return Row(
mainAxisAlignment: MainAxisAlignment.end,
mainAxisSize: MainAxisSize.min,
children: [
Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
color: Theme
.of(context)
.colorScheme
.primaryContainer,
margin: const EdgeInsets.only(left: 32, bottom: 16, right: 12),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 12),
child: MessageSessionInvite(message: message,),
),
),
],
);
case MessageType.object:
content = "[Asset]";
continue rawText;
@ -435,17 +453,28 @@ class OtherMessageBubble extends StatelessWidget {
@override
Widget build(BuildContext context) {
var content = message.content;
if (message.type == MessageType.sessionInvite) {
content = "[Session Invite]";
} else if (message.type == MessageType.sound) {
content = "[Voice Message]";
} else if (message.type == MessageType.object) {
content = "[Asset]";
}
switch (message.type) {
case MessageType.sessionInvite:
content = "[Session Invite]";
continue rawText;
return Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
color: Theme
.of(context)
.colorScheme
.secondaryContainer,
margin: const EdgeInsets.only(right: 32, bottom: 16, left: 12),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
child: MessageSessionInvite(message: message,),
),
),
],
);
case MessageType.object:
content = "[Asset]";
continue rawText;
@ -500,7 +529,8 @@ class OtherMessageBubble extends StatelessWidget {
return Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: [Card(
children: [
Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
@ -547,6 +577,110 @@ class MessageStateIndicator extends StatelessWidget {
}
}
class SessionPopup extends StatelessWidget {
const SessionPopup({required this.session, super.key});
final Session session;
@override
Widget build(BuildContext context) {
final ScrollController userListScrollController = ScrollController();
final thumbnailUri = Aux.neosDbToHttp(session.thumbnail);
return Dialog(
insetPadding: const EdgeInsets.all(32),
child: Container(
constraints: const BoxConstraints(maxHeight: 400),
padding: const EdgeInsets.all(24),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Expanded(
child: Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: ListView(
children: [
Text(session.name, style: Theme.of(context).textTheme.titleMedium),
Text(session.description.isEmpty ? "No description." : session.description, style: Theme.of(context).textTheme.labelMedium),
Text("Tags: ${session.tags.isEmpty ? "None" : session.tags.join(", ")}",
style: Theme.of(context).textTheme.labelMedium,
softWrap: true,
),
Text("Users: ${session.sessionUsers.length}", style: Theme.of(context).textTheme.labelMedium),
Text("Maximum users: ${session.maxUsers}", style: Theme.of(context).textTheme.labelMedium),
Text("Headless: ${session.headlessHost ? "Yes" : "No"}", style: Theme.of(context).textTheme.labelMedium),
],
),
),
if (session.sessionUsers.isNotEmpty) Expanded(
child: Scrollbar(
trackVisibility: true,
controller: userListScrollController,
thumbVisibility: true,
child: ListView.builder(
controller: userListScrollController,
shrinkWrap: true,
itemCount: session.sessionUsers.length,
itemBuilder: (context, index) {
final user = session.sessionUsers[index];
return ListTile(
dense: true,
title: Text(user.username, textAlign: TextAlign.end,),
subtitle: Text(user.isPresent ? "Active" : "Inactive", textAlign: TextAlign.end,),
);
},
),
),
) else Expanded(
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: const [
Icon(Icons.person_remove_alt_1_rounded),
Padding(
padding: EdgeInsets.all(16.0),
child: Text("No one is currently playing.", textAlign: TextAlign.center,),
)
],
),
),
),
],
),
),
Expanded(
child: Center(
child: CachedNetworkImage(
imageUrl: thumbnailUri,
placeholder: (context, url) {
return const CircularProgressIndicator();
},
errorWidget: (context, error, what) => Column(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: const [
Icon(Icons.no_photography),
Padding(
padding: EdgeInsets.all(16.0),
child: Text("Failed to load Image"),
)
],
),
),
),
)
],
),
),
);
}
}
class SessionTile extends StatelessWidget {
const SessionTile({required this.session, super.key});
final Session session;
@ -555,101 +689,7 @@ class SessionTile extends StatelessWidget {
Widget build(BuildContext context) {
return TextButton(
onPressed: () {
showDialog(context: context, builder: (context) {
final ScrollController userListScrollController = ScrollController();
final thumbnailUri = Aux.neosDbToHttp(session.thumbnail);
return Dialog(
insetPadding: const EdgeInsets.all(32),
child: Container(
constraints: const BoxConstraints(maxHeight: 400),
padding: const EdgeInsets.all(24),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Expanded(
child: Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: ListView(
children: [
Text(session.name, style: Theme.of(context).textTheme.titleMedium),
Text(session.description.isEmpty ? "No description." : session.description, style: Theme.of(context).textTheme.labelMedium),
Text("Tags: ${session.tags.isEmpty ? "None" : session.tags.join(", ")}",
style: Theme.of(context).textTheme.labelMedium,
softWrap: true,
),
Text("Users: ${session.sessionUsers.length}", style: Theme.of(context).textTheme.labelMedium),
Text("Maximum users: ${session.maxUsers}", style: Theme.of(context).textTheme.labelMedium),
Text("Headless: ${session.headlessHost ? "Yes" : "No"}", style: Theme.of(context).textTheme.labelMedium),
],
),
),
if (session.sessionUsers.isNotEmpty) Expanded(
child: Scrollbar(
trackVisibility: true,
controller: userListScrollController,
thumbVisibility: true,
child: ListView.builder(
controller: userListScrollController,
shrinkWrap: true,
itemCount: session.sessionUsers.length,
itemBuilder: (context, index) {
final user = session.sessionUsers[index];
return ListTile(
dense: true,
title: Text(user.username, textAlign: TextAlign.end,),
subtitle: Text(user.isPresent ? "Active" : "Inactive", textAlign: TextAlign.end,),
);
},
),
),
) else Expanded(
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: const [
Icon(Icons.person_remove_alt_1_rounded),
Padding(
padding: EdgeInsets.all(16.0),
child: Text("No one is currently playing.", textAlign: TextAlign.center,),
)
],
),
),
),
],
),
),
Expanded(
child: Center(
child: thumbnailUri.isEmpty ? const Text("No Image") : CachedNetworkImage(
imageUrl: thumbnailUri,
placeholder: (context, url) {
return const CircularProgressIndicator();
},
errorWidget: (context, error, what) => Column(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: const [
Icon(Icons.no_photography),
Padding(
padding: EdgeInsets.all(16.0),
child: Text("Failed to load Image"),
)
],
),
),
),
)
],
),
),
);
});
showDialog(context: context, builder: (context) => SessionPopup(session: session));
},
child: Row(
mainAxisAlignment: MainAxisAlignment.center,