Implement jitsi
This commit is contained in:
parent
4852fd42ad
commit
7f93dd2c97
|
@ -6,6 +6,7 @@ import 'package:fluffychat/utils/app_route.dart';
|
||||||
import 'package:fluffychat/views/chat_details.dart';
|
import 'package:fluffychat/views/chat_details.dart';
|
||||||
import 'package:fluffychat/views/chat_list.dart';
|
import 'package:fluffychat/views/chat_list.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
import 'dialogs/simple_dialogs.dart';
|
import 'dialogs/simple_dialogs.dart';
|
||||||
import 'matrix.dart';
|
import 'matrix.dart';
|
||||||
|
@ -29,6 +30,18 @@ class _ChatSettingsPopupMenuState extends State<ChatSettingsPopupMenu> {
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void startCallAction(BuildContext context) async {
|
||||||
|
final url =
|
||||||
|
'${Matrix.of(context).jitsiInstance}${Uri.encodeComponent(widget.room.id.localpart)}';
|
||||||
|
final success = await Matrix.of(context)
|
||||||
|
.tryRequestWithLoadingDialog(widget.room.sendEvent({
|
||||||
|
'msgtype': Matrix.callNamespace,
|
||||||
|
'body': url,
|
||||||
|
}));
|
||||||
|
if (success == false) return;
|
||||||
|
await launch(url);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
notificationChangeSub ??= Matrix.of(context)
|
notificationChangeSub ??= Matrix.of(context)
|
||||||
|
@ -49,6 +62,10 @@ class _ChatSettingsPopupMenuState extends State<ChatSettingsPopupMenu> {
|
||||||
value: "unmute",
|
value: "unmute",
|
||||||
child: Text(I18n.of(context).unmuteChat),
|
child: Text(I18n.of(context).unmuteChat),
|
||||||
),
|
),
|
||||||
|
PopupMenuItem<String>(
|
||||||
|
value: "call",
|
||||||
|
child: Text(I18n.of(context).videoCall),
|
||||||
|
),
|
||||||
PopupMenuItem<String>(
|
PopupMenuItem<String>(
|
||||||
value: "leave",
|
value: "leave",
|
||||||
child: Text(I18n.of(context).leave),
|
child: Text(I18n.of(context).leave),
|
||||||
|
@ -86,6 +103,9 @@ class _ChatSettingsPopupMenuState extends State<ChatSettingsPopupMenu> {
|
||||||
await Matrix.of(context).tryRequestWithLoadingDialog(
|
await Matrix.of(context).tryRequestWithLoadingDialog(
|
||||||
widget.room.setPushRuleState(PushRuleState.notify));
|
widget.room.setPushRuleState(PushRuleState.notify));
|
||||||
break;
|
break;
|
||||||
|
case "call":
|
||||||
|
startCallAction(context);
|
||||||
|
break;
|
||||||
case "details":
|
case "details":
|
||||||
await Navigator.of(context).push(
|
await Navigator.of(context).push(
|
||||||
AppRoute.defaultRoute(
|
AppRoute.defaultRoute(
|
||||||
|
|
|
@ -26,6 +26,7 @@ class SimpleDialogs {
|
||||||
content: TextField(
|
content: TextField(
|
||||||
controller: controller,
|
controller: controller,
|
||||||
autofocus: true,
|
autofocus: true,
|
||||||
|
autocorrect: false,
|
||||||
onSubmitted: (s) {
|
onSubmitted: (s) {
|
||||||
input = s;
|
input = s;
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
|
|
|
@ -10,6 +10,7 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||||
import 'package:localstorage/localstorage.dart';
|
import 'package:localstorage/localstorage.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
import '../i18n/i18n.dart';
|
import '../i18n/i18n.dart';
|
||||||
import '../utils/app_route.dart';
|
import '../utils/app_route.dart';
|
||||||
|
@ -18,8 +19,11 @@ import '../utils/event_extension.dart';
|
||||||
import '../utils/famedlysdk_store.dart';
|
import '../utils/famedlysdk_store.dart';
|
||||||
import '../utils/room_extension.dart';
|
import '../utils/room_extension.dart';
|
||||||
import '../views/chat.dart';
|
import '../views/chat.dart';
|
||||||
|
import 'avatar.dart';
|
||||||
|
|
||||||
class Matrix extends StatefulWidget {
|
class Matrix extends StatefulWidget {
|
||||||
|
static const String callNamespace = 'chat.fluffy.jitsi_call';
|
||||||
|
|
||||||
final Widget child;
|
final Widget child;
|
||||||
|
|
||||||
final String clientName;
|
final String clientName;
|
||||||
|
@ -52,6 +56,8 @@ class MatrixState extends State<Matrix> {
|
||||||
String activeRoomId;
|
String activeRoomId;
|
||||||
File wallpaper;
|
File wallpaper;
|
||||||
|
|
||||||
|
String jitsiInstance = 'https://meet.jit.si/';
|
||||||
|
|
||||||
void clean() async {
|
void clean() async {
|
||||||
if (!kIsWeb) return;
|
if (!kIsWeb) return;
|
||||||
|
|
||||||
|
@ -343,12 +349,73 @@ class MatrixState extends State<Matrix> {
|
||||||
};
|
};
|
||||||
|
|
||||||
StreamSubscription onRoomKeyRequestSub;
|
StreamSubscription onRoomKeyRequestSub;
|
||||||
|
StreamSubscription onJitsiCallSub;
|
||||||
|
|
||||||
|
void onJitsiCall(EventUpdate eventUpdate) {
|
||||||
|
final event = Event.fromJson(
|
||||||
|
eventUpdate.content, client.getRoomById(eventUpdate.roomID));
|
||||||
|
if (DateTime.now().millisecondsSinceEpoch -
|
||||||
|
event.time.millisecondsSinceEpoch >
|
||||||
|
1000 * 60 * 5) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final senderName = event.sender.calcDisplayname();
|
||||||
|
final senderAvatar = event.sender.avatarUrl;
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AlertDialog(
|
||||||
|
title: ListTile(
|
||||||
|
contentPadding: EdgeInsets.all(0),
|
||||||
|
leading: Avatar(senderAvatar, senderName),
|
||||||
|
title: Text(
|
||||||
|
senderName,
|
||||||
|
style: TextStyle(fontSize: 18),
|
||||||
|
),
|
||||||
|
subtitle:
|
||||||
|
event.room.isDirectChat ? null : Text(event.room.displayname),
|
||||||
|
),
|
||||||
|
content: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: <Widget>[
|
||||||
|
Divider(),
|
||||||
|
Row(
|
||||||
|
children: <Widget>[
|
||||||
|
Spacer(),
|
||||||
|
FloatingActionButton(
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
child: Icon(Icons.phone_missed),
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
),
|
||||||
|
Spacer(),
|
||||||
|
FloatingActionButton(
|
||||||
|
backgroundColor: Colors.green,
|
||||||
|
child: Icon(Icons.phone),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
launch(event.body);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Spacer(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
if (widget.client == null) {
|
if (widget.client == null) {
|
||||||
debugPrint("[Matrix] Init matrix client");
|
debugPrint("[Matrix] Init matrix client");
|
||||||
client = Client(widget.clientName, debug: false);
|
client = Client(widget.clientName, debug: false);
|
||||||
|
onJitsiCallSub ??= client.onEvent.stream
|
||||||
|
.where((e) =>
|
||||||
|
e.eventType == 'm.room.message' &&
|
||||||
|
e.content['content']['msgtype'] == Matrix.callNamespace &&
|
||||||
|
e.content['sender'] != client.userID)
|
||||||
|
.listen(onJitsiCall);
|
||||||
onRoomKeyRequestSub ??=
|
onRoomKeyRequestSub ??=
|
||||||
client.onRoomKeyRequest.stream.listen((RoomKeyRequest request) async {
|
client.onRoomKeyRequest.stream.listen((RoomKeyRequest request) async {
|
||||||
final Room room = request.room;
|
final Room room = request.room;
|
||||||
|
@ -368,6 +435,9 @@ class MatrixState extends State<Matrix> {
|
||||||
client = widget.client;
|
client = widget.client;
|
||||||
}
|
}
|
||||||
if (client.storeAPI != null) {
|
if (client.storeAPI != null) {
|
||||||
|
client.storeAPI
|
||||||
|
.getItem("chat.fluffy.jitsi_instance")
|
||||||
|
.then((final instance) => jitsiInstance = instance ?? jitsiInstance);
|
||||||
client.storeAPI.getItem("chat.fluffy.wallpaper").then((final path) async {
|
client.storeAPI.getItem("chat.fluffy.wallpaper").then((final path) async {
|
||||||
if (path == null) return;
|
if (path == null) return;
|
||||||
final file = File(path);
|
final file = File(path);
|
||||||
|
@ -382,6 +452,7 @@ class MatrixState extends State<Matrix> {
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
onRoomKeyRequestSub?.cancel();
|
onRoomKeyRequestSub?.cancel();
|
||||||
|
onJitsiCallSub?.cancel();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -99,6 +99,19 @@ class MessageContent extends StatelessWidget {
|
||||||
case MessageTypes.Notice:
|
case MessageTypes.Notice:
|
||||||
case MessageTypes.Emote:
|
case MessageTypes.Emote:
|
||||||
default:
|
default:
|
||||||
|
if (event.content['msgtype'] == Matrix.callNamespace) {
|
||||||
|
return RaisedButton(
|
||||||
|
color: Theme.of(context).backgroundColor,
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: <Widget>[
|
||||||
|
Icon(Icons.phone),
|
||||||
|
Text(I18n.of(context).videoCall),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
onPressed: () => launch(event.body),
|
||||||
|
);
|
||||||
|
}
|
||||||
return LinkText(
|
return LinkText(
|
||||||
text: event.getLocalizedBody(context, hideReply: true),
|
text: event.getLocalizedBody(context, hideReply: true),
|
||||||
textStyle: TextStyle(
|
textStyle: TextStyle(
|
||||||
|
|
|
@ -371,6 +371,8 @@ class I18n {
|
||||||
|
|
||||||
String get isTyping => Intl.message("is typing...");
|
String get isTyping => Intl.message("is typing...");
|
||||||
|
|
||||||
|
String get editJitsiInstance => Intl.message('Edit Jitsi instance');
|
||||||
|
|
||||||
String joinedTheChat(String username) => Intl.message(
|
String joinedTheChat(String username) => Intl.message(
|
||||||
"$username joined the chat",
|
"$username joined the chat",
|
||||||
name: "joinedTheChat",
|
name: "joinedTheChat",
|
||||||
|
@ -714,6 +716,8 @@ class I18n {
|
||||||
|
|
||||||
String get verify => Intl.message("Verify");
|
String get verify => Intl.message("Verify");
|
||||||
|
|
||||||
|
String get videoCall => Intl.message('Video call');
|
||||||
|
|
||||||
String get visibleForAllParticipants =>
|
String get visibleForAllParticipants =>
|
||||||
Intl.message("Visible for all participants");
|
Intl.message("Visible for all participants");
|
||||||
|
|
||||||
|
|
|
@ -49,6 +49,21 @@ class _SettingsState extends State<Settings> {
|
||||||
AppRoute.defaultRoute(context, SignUp()), (r) => false);
|
AppRoute.defaultRoute(context, SignUp()), (r) => false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setJitsiInstanceAction(BuildContext context) async {
|
||||||
|
var jitsi = await SimpleDialogs(context).enterText(
|
||||||
|
titleText: I18n.of(context).editJitsiInstance,
|
||||||
|
hintText: Matrix.of(context).jitsiInstance,
|
||||||
|
labelText: I18n.of(context).editJitsiInstance,
|
||||||
|
);
|
||||||
|
if (jitsi == null) return;
|
||||||
|
if (!jitsi.endsWith('/')) {
|
||||||
|
jitsi += '/';
|
||||||
|
}
|
||||||
|
final MatrixState matrix = Matrix.of(context);
|
||||||
|
await matrix.client.storeAPI.setItem('chat.fluffy.jitsi_instance', jitsi);
|
||||||
|
matrix.jitsiInstance = jitsi;
|
||||||
|
}
|
||||||
|
|
||||||
void setDisplaynameAction(BuildContext context) async {
|
void setDisplaynameAction(BuildContext context) async {
|
||||||
final String displayname = await SimpleDialogs(context).enterText(
|
final String displayname = await SimpleDialogs(context).enterText(
|
||||||
titleText: I18n.of(context).editDisplayname,
|
titleText: I18n.of(context).editDisplayname,
|
||||||
|
@ -205,6 +220,12 @@ class _SettingsState extends State<Settings> {
|
||||||
subtitle: Text(profile?.displayname ?? client.userID.localpart),
|
subtitle: Text(profile?.displayname ?? client.userID.localpart),
|
||||||
onTap: () => setDisplaynameAction(context),
|
onTap: () => setDisplaynameAction(context),
|
||||||
),
|
),
|
||||||
|
ListTile(
|
||||||
|
trailing: Icon(Icons.phone),
|
||||||
|
title: Text(I18n.of(context).editJitsiInstance),
|
||||||
|
subtitle: Text(Matrix.of(context).jitsiInstance),
|
||||||
|
onTap: () => setJitsiInstanceAction(context),
|
||||||
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
trailing: Icon(Icons.devices_other),
|
trailing: Icon(Icons.devices_other),
|
||||||
title: Text(I18n.of(context).devices),
|
title: Text(I18n.of(context).devices),
|
||||||
|
|
37
pubspec.lock
37
pubspec.lock
|
@ -21,28 +21,28 @@ packages:
|
||||||
name: archive
|
name: archive
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.13"
|
version: "2.0.11"
|
||||||
args:
|
args:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: args
|
name: args
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.6.0"
|
version: "1.5.2"
|
||||||
async:
|
async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: async
|
name: async
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.1"
|
version: "2.4.0"
|
||||||
boolean_selector:
|
boolean_selector:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: boolean_selector
|
name: boolean_selector
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.0"
|
version: "1.0.5"
|
||||||
bubble:
|
bubble:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -63,14 +63,14 @@ packages:
|
||||||
name: charcode
|
name: charcode
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.3"
|
version: "1.1.2"
|
||||||
collection:
|
collection:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: collection
|
name: collection
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.14.12"
|
version: "1.14.11"
|
||||||
convert:
|
convert:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -91,7 +91,7 @@ packages:
|
||||||
name: crypto
|
name: crypto
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.4"
|
version: "2.1.3"
|
||||||
csslib:
|
csslib:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -260,7 +260,7 @@ packages:
|
||||||
name: image
|
name: image
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.12"
|
version: "2.1.4"
|
||||||
image_picker:
|
image_picker:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -274,7 +274,7 @@ packages:
|
||||||
name: intl
|
name: intl
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.16.1"
|
version: "0.16.0"
|
||||||
intl_translation:
|
intl_translation:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -502,7 +502,7 @@ packages:
|
||||||
name: quiver
|
name: quiver
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.3"
|
version: "2.0.5"
|
||||||
receive_sharing_intent:
|
receive_sharing_intent:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -570,7 +570,7 @@ packages:
|
||||||
name: source_span
|
name: source_span
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.7.0"
|
version: "1.5.5"
|
||||||
sqflite:
|
sqflite:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -619,21 +619,21 @@ packages:
|
||||||
name: test
|
name: test
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.13.0"
|
version: "1.9.4"
|
||||||
test_api:
|
test_api:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_api
|
name: test_api
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.2.15"
|
version: "0.2.11"
|
||||||
test_core:
|
test_core:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_core
|
name: test_core
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.3.1"
|
version: "0.2.15"
|
||||||
typed_data:
|
typed_data:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -718,13 +718,6 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.0"
|
version: "1.1.0"
|
||||||
webkit_inspection_protocol:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: webkit_inspection_protocol
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "0.5.0+1"
|
|
||||||
webview_flutter:
|
webview_flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -738,7 +731,7 @@ packages:
|
||||||
name: xml
|
name: xml
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.6.1"
|
version: "3.5.0"
|
||||||
yaml:
|
yaml:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
Loading…
Reference in a new issue