Add session invites to message view
This commit is contained in:
parent
158fccbf3f
commit
38205fe8e1
5 changed files with 241 additions and 115 deletions
|
@ -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 {
|
||||
|
@ -165,4 +166,4 @@ class AudioClipContent {
|
|||
assetUri: map["assetUri"],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"] ?? "",
|
||||
);
|
||||
}
|
||||
|
||||
|
|
83
lib/widgets/message_session_invite.dart
Normal file
83
lib/widgets/message_session_invite.dart
Normal 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),
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -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,
|
||||
|
|
Loading…
Reference in a new issue