* chore: Nicer invite selection view
* chore: Do not request thousands of users on invite page
* build(deps): bump rexml from 3.3.6 to 3.3.9 in /ios
Bumps [rexml](https://github.com/ruby/rexml) from 3.3.6 to 3.3.9.
- [Release notes](https://github.com/ruby/rexml/releases)
- [Changelog](https://github.com/ruby/rexml/blob/master/NEWS.md)
- [Commits](https://github.com/ruby/rexml/compare/v3.3.6...v3.3.9)
---
updated-dependencies:
- dependency-name: rexml
dependency-type: indirect
...
Signed-off-by: dependabot[bot] <support@github.com>
* design: Highlight emoji only messages
* chore: Follow up emoji only messages
* Translated using Weblate (Galician)
Currently translated at 100.0% (672 of 672 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/gl/
* Translated using Weblate (Russian)
Currently translated at 99.7% (670 of 672 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/ru/
* design: New login design
* chore: Improve spaces design
* chore: Improve spaces design
* chore: Improved UX for creating groups and spaces
* Translated using Weblate (German)
Currently translated at 100.0% (672 of 672 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/de/
* feat: Better wallpapers with blur and opacity sliders and improved styles page
* chore: Follow up wallpaper configs
* chore: Add max length to state messages
* chore: Follow up wallpaper design
* feat: Open account manage url when using MAS
* chore: follow up wellknown fetch
* Translated using Weblate (Arabic)
Currently translated at 100.0% (674 of 674 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/ar/
* Translated using Weblate (Estonian)
Currently translated at 100.0% (674 of 674 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/et/
* Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (674 of 674 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/zh_Hans/
* Translated using Weblate (Indonesian)
Currently translated at 100.0% (674 of 674 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/id/
* Translated using Weblate (Finnish)
Currently translated at 79.0% (533 of 674 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/fi/
* Translated using Weblate (Latvian)
Currently translated at 100.0% (674 of 674 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/lv/
* build: Add links to snapcraft.yaml file
* chore: Nicer empty page
* chore: Polish chat bubble colors
* chore: Follow up chat bubble design
* refactor: Remove unnecessary builder widget
* chore: Design adjustments
* chore: Follow up design
* refactor: Display two lines on new messages
* chore: Design follow up
* Translated using Weblate (Arabic)
Currently translated at 100.0% (678 of 678 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/ar/
* Translated using Weblate (German)
Currently translated at 100.0% (678 of 678 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/de/
* Translated using Weblate (Estonian)
Currently translated at 99.7% (676 of 678 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/et/
* Translated using Weblate (Basque)
Currently translated at 100.0% (678 of 678 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/eu/
* Translated using Weblate (Ukrainian)
Currently translated at 100.0% (678 of 678 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/uk/
* Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (678 of 678 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/zh_Hans/
* chore: Follow up message bubbles
* chore: Follow up design
* chore: Follow up design
* chore: Follow up colors
* chore: Follow up homeserverpicker UX
* chore: Design follow up
* feat: Add about server page
* chore: Follow up update snackbar
* chore: Polish login design
* chore: Follow up login page
* chore: Follow up homeserver picker
* chore: Follow up appbar shadow
* refactor: Performance boost for avatar widget
* Revert "refactor: Performance boost for avatar widget"
This reverts commit 58577bb9e8.
* Translated using Weblate (Estonian)
Currently translated at 100.0% (678 of 678 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/et/
* Translated using Weblate (Ukrainian)
Currently translated at 100.0% (678 of 678 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/uk/
* Translated using Weblate (Latvian)
Currently translated at 100.0% (678 of 678 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/lv/
* Translated using Weblate (Arabic)
Currently translated at 100.0% (687 of 687 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/ar/
* Translated using Weblate (Estonian)
Currently translated at 100.0% (687 of 687 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/et/
* Translated using Weblate (Basque)
Currently translated at 100.0% (687 of 687 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/eu/
* Translated using Weblate (Galician)
Currently translated at 100.0% (687 of 687 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/gl/
* Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (687 of 687 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/zh_Hans/
* Translated using Weblate (Indonesian)
Currently translated at 100.0% (687 of 687 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/id/
* Translated using Weblate (Latvian)
Currently translated at 100.0% (687 of 687 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/lv/
* Translated using Weblate (Ukrainian)
Currently translated at 100.0% (687 of 687 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/uk/
* Translated using Weblate (Korean)
Currently translated at 100.0% (687 of 687 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/ko/
* chore: Follow up homeserver input field
* refactor: Move to upstream geolocator
* chore: Follow up send file dialog
* Translated using Weblate (Spanish)
Currently translated at 74.6% (513 of 687 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/es/
* refactor: Migrate to newer keyboard shortcuts package
* refactor: Remove keyboard shortcuts
This package right now
makes the web app
nearly unusable as it
throws multiple errors on
every key press. The
package seems to be
unmaintained. I tried
two other packages
and none of them worked.
* build: Update matrix dart sdk to 0.35.0
* chore: Better FluffyChat Logo for PWA
* build: (deps): bump samuelmeuli/action-snapcraft from 2 to 3
Bumps [samuelmeuli/action-snapcraft](https://github.com/samuelmeuli/action-snapcraft) from 2 to 3.
- [Release notes](https://github.com/samuelmeuli/action-snapcraft/releases)
- [Commits](https://github.com/samuelmeuli/action-snapcraft/compare/v2...v3)
---
updated-dependencies:
- dependency-name: samuelmeuli/action-snapcraft
dependency-type: direct:production
update-type: version-update:semver-major
...
Signed-off-by: dependabot[bot] <support@github.com>
* chore: Follow up send file dialog
* feat: Add markdown context actions for text input
* build: Update flutter to 3.24.5
* build: Remove snapcraft build workaround
* chore: Better error message when join room failed
* chore: Follow up join room
* chore: Make error dialog show full error
* chore: Follow up loading dialog
* chore: Follow up loading dialog
* build: Snapcraft from local build file
* chore: Follow up build snap
* chore: Follow up snapcraft in ci
* build: Revert build snapcraft changes
* build: Try downgrading flutter web auth
* chore: add hint in pubspec.yaml regarding flutter_web_auth_2
* Translated using Weblate (Estonian)
Currently translated at 100.0% (688 of 688 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/et/
* Translated using Weblate (Galician)
Currently translated at 100.0% (688 of 688 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/gl/
* Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (688 of 688 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/zh_Hans/
* Translated using Weblate (Indonesian)
Currently translated at 100.0% (688 of 688 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/id/
* Translated using Weblate (Irish)
Currently translated at 100.0% (688 of 688 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/ga/
* Translated using Weblate (Arabic)
Currently translated at 100.0% (688 of 688 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/ar/
* Translated using Weblate (Basque)
Currently translated at 100.0% (688 of 688 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/eu/
* Translated using Weblate (Ukrainian)
Currently translated at 100.0% (688 of 688 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/uk/
* Translated using Weblate (Latvian)
Currently translated at 100.0% (688 of 688 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/lv/
* Translated using Weblate (Italian)
Currently translated at 100.0% (688 of 688 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/it/
* Translated using Weblate (Estonian)
Currently translated at 100.0% (694 of 694 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/et/
* Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (694 of 694 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/zh_Hans/
* Translated using Weblate (Arabic)
Currently translated at 100.0% (694 of 694 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/ar/
* Translated using Weblate (Basque)
Currently translated at 100.0% (694 of 694 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/eu/
* Translated using Weblate (Irish)
Currently translated at 100.0% (694 of 694 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/ga/
* Translated using Weblate (Indonesian)
Currently translated at 100.0% (694 of 694 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/id/
* Translated using Weblate (Latvian)
Currently translated at 100.0% (694 of 694 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/lv/
* Translated using Weblate (Arabic)
Currently translated at 100.0% (695 of 695 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/ar/
* Translated using Weblate (Estonian)
Currently translated at 100.0% (695 of 695 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/et/
* Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (695 of 695 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/zh_Hans/
* Translated using Weblate (Irish)
Currently translated at 99.8% (694 of 695 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/ga/
* Translated using Weblate (German)
Currently translated at 99.5% (692 of 695 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/de/
* fix: dont use thumbnails for emoticons
* chore: Improve presence performance
* Translated using Weblate (Basque)
Currently translated at 100.0% (695 of 695 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/eu/
* Translated using Weblate (Galician)
Currently translated at 100.0% (695 of 695 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/gl/
* Translated using Weblate (Italian)
Currently translated at 100.0% (695 of 695 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/it/
* Translated using Weblate (Irish)
Currently translated at 100.0% (695 of 695 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/ga/
* Translated using Weblate (Russian)
Currently translated at 100.0% (695 of 695 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/ru/
* Translated using Weblate (Ukrainian)
Currently translated at 100.0% (695 of 695 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/uk/
* Translated using Weblate (Catalan)
Currently translated at 95.1% (661 of 695 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/ca/
* build: Bump version
* chore: Follow up send file dialog for images
* chore: Follow up send multiple images
* build: Add android build workaround for new flutter version
* build: Use file selector to save files
* chore: Follow up save file on desktop
* chore: Adjust default linux window height
* refactor: Update to new receive sharing intent package
* fluffychat merge
* fluffychat merge
* fluffychat merge
* fix android build
* fluffychat merge
* fluffychat merge
---------
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Krille <c.kussowski@famedly.com>
Co-authored-by: Krille-chan <christian-kussowski@posteo.de>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: josé m <correoxm@disroot.org>
Co-authored-by: v1s7 <v1s7@users.noreply.hosted.weblate.org>
Co-authored-by: Christian <christian-pauly@posteo.de>
Co-authored-by: Rex_sa <rex.sa@pm.me>
Co-authored-by: Priit Jõerüüt <hwlate@joeruut.com>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: Edgars Andersons <Edgars+Weblate@gaitenis.id.lv>
Co-authored-by: xabirequejo <xabi.rn@gmail.com>
Co-authored-by: Bezruchenko Simon <worcposj44@gmail.com>
Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: Bruno Roh <kane.roh429@gmail.com>
Co-authored-by: Kimby <kimisaes@naver.com>
Co-authored-by: Aindriú Mac Giolla Eoin <aindriu80@gmail.com>
Co-authored-by: Angelo Schirinzi <muten619@hotmail.it>
Co-authored-by: Marek Vospěl <marek@vospel.cz>
Co-authored-by: Александр (Alexandr1995) <stupino19951406@gmail.com>
626 lines
17 KiB
Dart
626 lines
17 KiB
Dart
/*
|
|
* Famedly
|
|
* Copyright (C) 2019, 2020, 2021 Famedly GmbH
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as
|
|
* published by the Free Software Foundation, either version 3 of the
|
|
* License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
import 'dart:async';
|
|
import 'dart:math';
|
|
|
|
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
|
|
import 'package:fluffychat/utils/platform_infos.dart';
|
|
import 'package:fluffychat/utils/voip/video_renderer.dart';
|
|
import 'package:fluffychat/widgets/avatar.dart';
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:flutter_foreground_task/flutter_foreground_task.dart';
|
|
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
|
import 'package:flutter_webrtc/flutter_webrtc.dart' hide VideoRenderer;
|
|
import 'package:just_audio/just_audio.dart';
|
|
import 'package:matrix/matrix.dart';
|
|
import 'package:wakelock_plus/wakelock_plus.dart';
|
|
|
|
import 'pip/pip_view.dart';
|
|
|
|
class _StreamView extends StatelessWidget {
|
|
const _StreamView(
|
|
this.wrappedStream, {
|
|
this.mainView = false,
|
|
required this.matrixClient,
|
|
});
|
|
|
|
final WrappedMediaStream wrappedStream;
|
|
final Client matrixClient;
|
|
|
|
final bool mainView;
|
|
|
|
Uri? get avatarUrl => wrappedStream.getUser().avatarUrl;
|
|
|
|
String? get displayName => wrappedStream.displayName;
|
|
|
|
String get avatarName => wrappedStream.avatarName;
|
|
|
|
bool get isLocal => wrappedStream.isLocal();
|
|
|
|
bool get mirrored =>
|
|
wrappedStream.isLocal() &&
|
|
wrappedStream.purpose == SDPStreamMetadataPurpose.Usermedia;
|
|
|
|
bool get audioMuted => wrappedStream.audioMuted;
|
|
|
|
bool get videoMuted => wrappedStream.videoMuted;
|
|
|
|
bool get isScreenSharing =>
|
|
wrappedStream.purpose == SDPStreamMetadataPurpose.Screenshare;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Container(
|
|
decoration: const BoxDecoration(
|
|
color: Colors.black54,
|
|
),
|
|
child: Stack(
|
|
alignment: Alignment.center,
|
|
children: <Widget>[
|
|
VideoRenderer(
|
|
wrappedStream,
|
|
mirror: mirrored,
|
|
fit: RTCVideoViewObjectFit.RTCVideoViewObjectFitContain,
|
|
),
|
|
if (videoMuted) ...[
|
|
Container(color: Colors.black54),
|
|
Positioned(
|
|
child: Avatar(
|
|
mxContent: avatarUrl,
|
|
name: displayName,
|
|
size: mainView ? 96 : 48,
|
|
client: matrixClient,
|
|
// textSize: mainView ? 36 : 24,
|
|
// matrixClient: matrixClient,
|
|
),
|
|
),
|
|
],
|
|
if (!isScreenSharing)
|
|
Positioned(
|
|
left: 4.0,
|
|
bottom: 4.0,
|
|
child: Icon(
|
|
audioMuted ? Icons.mic_off : Icons.mic,
|
|
color: Colors.white,
|
|
size: 18.0,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class Calling extends StatefulWidget {
|
|
final VoidCallback? onClear;
|
|
final BuildContext context;
|
|
final String callId;
|
|
final CallSession call;
|
|
final Client client;
|
|
|
|
const Calling({
|
|
required this.context,
|
|
required this.call,
|
|
required this.client,
|
|
required this.callId,
|
|
this.onClear,
|
|
super.key,
|
|
});
|
|
|
|
@override
|
|
MyCallingPage createState() => MyCallingPage();
|
|
}
|
|
|
|
class MyCallingPage extends State<Calling> {
|
|
Room? get room => call.room;
|
|
|
|
String get displayName => call.room.getLocalizedDisplayname(
|
|
MatrixLocals(L10n.of(widget.context)),
|
|
);
|
|
|
|
String get callId => widget.callId;
|
|
|
|
CallSession get call => widget.call;
|
|
|
|
MediaStream? get localStream {
|
|
if (call.localUserMediaStream != null) {
|
|
return call.localUserMediaStream!.stream!;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
MediaStream? get remoteStream {
|
|
if (call.getRemoteStreams.isNotEmpty) {
|
|
return call.getRemoteStreams[0].stream!;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
bool get isMicrophoneMuted => call.isMicrophoneMuted;
|
|
|
|
bool get isLocalVideoMuted => call.isLocalVideoMuted;
|
|
|
|
bool get isScreensharingEnabled => call.screensharingEnabled;
|
|
|
|
bool get isRemoteOnHold => call.remoteOnHold;
|
|
|
|
bool get voiceonly => call.type == CallType.kVoice;
|
|
|
|
bool get connecting => call.state == CallState.kConnecting;
|
|
|
|
bool get connected => call.state == CallState.kConnected;
|
|
|
|
double? _localVideoHeight;
|
|
double? _localVideoWidth;
|
|
EdgeInsetsGeometry? _localVideoMargin;
|
|
CallState? _state;
|
|
|
|
void _playCallSound() async {
|
|
const path = 'assets/sounds/call.ogg';
|
|
if (kIsWeb || PlatformInfos.isMobile || PlatformInfos.isMacOS) {
|
|
final player = AudioPlayer();
|
|
await player.setAsset(path);
|
|
player.play();
|
|
} else {
|
|
Logs().w('Playing sound not implemented for this platform!');
|
|
}
|
|
}
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
initialize();
|
|
_playCallSound();
|
|
}
|
|
|
|
void initialize() async {
|
|
final call = this.call;
|
|
call.onCallStateChanged.stream.listen(_handleCallState);
|
|
call.onCallEventChanged.stream.listen((event) {
|
|
if (event == CallStateChange.kFeedsChanged) {
|
|
setState(() {
|
|
call.tryRemoveStopedStreams();
|
|
});
|
|
} else if (event == CallStateChange.kLocalHoldUnhold ||
|
|
event == CallStateChange.kRemoteHoldUnhold) {
|
|
setState(() {});
|
|
Logs().i(
|
|
'Call hold event: local ${call.localHold}, remote ${call.remoteOnHold}',
|
|
);
|
|
}
|
|
});
|
|
_state = call.state;
|
|
|
|
if (call.type == CallType.kVideo) {
|
|
try {
|
|
// Enable wakelock (keep screen on)
|
|
unawaited(WakelockPlus.enable());
|
|
} catch (_) {}
|
|
}
|
|
}
|
|
|
|
void cleanUp() {
|
|
Timer(
|
|
const Duration(seconds: 2),
|
|
() => widget.onClear?.call(),
|
|
);
|
|
if (call.type == CallType.kVideo) {
|
|
try {
|
|
unawaited(WakelockPlus.disable());
|
|
} catch (_) {}
|
|
}
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
super.dispose();
|
|
call.cleanUp.call();
|
|
}
|
|
|
|
void _resizeLocalVideo(Orientation orientation) {
|
|
final shortSide = min(
|
|
MediaQuery.of(widget.context).size.width,
|
|
MediaQuery.of(widget.context).size.height,
|
|
);
|
|
_localVideoMargin = remoteStream != null
|
|
? const EdgeInsets.only(top: 20.0, right: 20.0)
|
|
: EdgeInsets.zero;
|
|
_localVideoWidth = remoteStream != null
|
|
? shortSide / 3
|
|
: MediaQuery.of(widget.context).size.width;
|
|
_localVideoHeight = remoteStream != null
|
|
? shortSide / 4
|
|
: MediaQuery.of(widget.context).size.height;
|
|
}
|
|
|
|
void _handleCallState(CallState state) {
|
|
Logs().v('CallingPage::handleCallState: ${state.toString()}');
|
|
if ({CallState.kConnected, CallState.kEnded}.contains(state)) {
|
|
HapticFeedback.heavyImpact();
|
|
}
|
|
|
|
if (mounted) {
|
|
setState(() {
|
|
_state = state;
|
|
if (_state == CallState.kEnded) cleanUp();
|
|
});
|
|
}
|
|
}
|
|
|
|
void _answerCall() {
|
|
setState(() {
|
|
call.answer();
|
|
});
|
|
}
|
|
|
|
void _hangUp() {
|
|
setState(() {
|
|
if (call.isRinging) {
|
|
call.reject();
|
|
} else {
|
|
call.hangup(reason: CallErrorCode.userHangup);
|
|
}
|
|
});
|
|
}
|
|
|
|
void _muteMic() {
|
|
setState(() {
|
|
call.setMicrophoneMuted(!call.isMicrophoneMuted);
|
|
});
|
|
}
|
|
|
|
void _screenSharing() async {
|
|
if (PlatformInfos.isAndroid) {
|
|
if (!call.screensharingEnabled) {
|
|
FlutterForegroundTask.init(
|
|
androidNotificationOptions: AndroidNotificationOptions(
|
|
channelId: 'notification_channel_id',
|
|
channelName: 'Foreground Notification',
|
|
channelDescription:
|
|
L10n.of(widget.context).foregroundServiceRunning,
|
|
),
|
|
iosNotificationOptions: const IOSNotificationOptions(),
|
|
foregroundTaskOptions: const ForegroundTaskOptions(),
|
|
);
|
|
FlutterForegroundTask.startService(
|
|
notificationTitle: L10n.of(widget.context).screenSharingTitle,
|
|
notificationText: L10n.of(widget.context).screenSharingDetail,
|
|
);
|
|
} else {
|
|
FlutterForegroundTask.stopService();
|
|
}
|
|
}
|
|
|
|
setState(() {
|
|
call.setScreensharingEnabled(!call.screensharingEnabled);
|
|
});
|
|
}
|
|
|
|
void _remoteOnHold() {
|
|
setState(() {
|
|
call.setRemoteOnHold(!call.remoteOnHold);
|
|
});
|
|
}
|
|
|
|
void _muteCamera() {
|
|
setState(() {
|
|
call.setLocalVideoMuted(!call.isLocalVideoMuted);
|
|
});
|
|
}
|
|
|
|
void _switchCamera() async {
|
|
if (call.localUserMediaStream != null) {
|
|
await Helper.switchCamera(
|
|
call.localUserMediaStream!.stream!.getVideoTracks()[0],
|
|
);
|
|
}
|
|
setState(() {});
|
|
}
|
|
|
|
/*
|
|
void _switchSpeaker() {
|
|
setState(() {
|
|
session.setSpeakerOn();
|
|
});
|
|
}
|
|
*/
|
|
|
|
List<Widget> _buildActionButtons(bool isFloating) {
|
|
if (isFloating) {
|
|
return [];
|
|
}
|
|
|
|
final switchCameraButton = FloatingActionButton(
|
|
heroTag: 'switchCamera',
|
|
onPressed: _switchCamera,
|
|
backgroundColor: Colors.black45,
|
|
child: const Icon(Icons.switch_camera),
|
|
);
|
|
/*
|
|
var switchSpeakerButton = FloatingActionButton(
|
|
heroTag: 'switchSpeaker',
|
|
child: Icon(_speakerOn ? Icons.volume_up : Icons.volume_off),
|
|
onPressed: _switchSpeaker,
|
|
foregroundColor: Colors.black54,
|
|
backgroundColor: Theme.of(widget.context).backgroundColor,
|
|
);
|
|
*/
|
|
final hangupButton = FloatingActionButton(
|
|
heroTag: 'hangup',
|
|
onPressed: _hangUp,
|
|
tooltip: 'Hangup',
|
|
backgroundColor: _state == CallState.kEnded ? Colors.black45 : Colors.red,
|
|
child: const Icon(Icons.call_end),
|
|
);
|
|
|
|
final answerButton = FloatingActionButton(
|
|
heroTag: 'answer',
|
|
onPressed: _answerCall,
|
|
tooltip: 'Answer',
|
|
backgroundColor: Colors.green,
|
|
child: const Icon(Icons.phone),
|
|
);
|
|
|
|
final muteMicButton = FloatingActionButton(
|
|
heroTag: 'muteMic',
|
|
onPressed: _muteMic,
|
|
foregroundColor: isMicrophoneMuted ? Colors.black26 : Colors.white,
|
|
backgroundColor: isMicrophoneMuted ? Colors.white : Colors.black45,
|
|
child: Icon(isMicrophoneMuted ? Icons.mic_off : Icons.mic),
|
|
);
|
|
|
|
final screenSharingButton = FloatingActionButton(
|
|
heroTag: 'screenSharing',
|
|
onPressed: _screenSharing,
|
|
foregroundColor: isScreensharingEnabled ? Colors.black26 : Colors.white,
|
|
backgroundColor: isScreensharingEnabled ? Colors.white : Colors.black45,
|
|
child: const Icon(Icons.desktop_mac),
|
|
);
|
|
|
|
final holdButton = FloatingActionButton(
|
|
heroTag: 'hold',
|
|
onPressed: _remoteOnHold,
|
|
foregroundColor: isRemoteOnHold ? Colors.black26 : Colors.white,
|
|
backgroundColor: isRemoteOnHold ? Colors.white : Colors.black45,
|
|
child: const Icon(Icons.pause),
|
|
);
|
|
|
|
final muteCameraButton = FloatingActionButton(
|
|
heroTag: 'muteCam',
|
|
onPressed: _muteCamera,
|
|
foregroundColor: isLocalVideoMuted ? Colors.black26 : Colors.white,
|
|
backgroundColor: isLocalVideoMuted ? Colors.white : Colors.black45,
|
|
child: Icon(isLocalVideoMuted ? Icons.videocam_off : Icons.videocam),
|
|
);
|
|
|
|
switch (_state) {
|
|
case CallState.kRinging:
|
|
case CallState.kInviteSent:
|
|
case CallState.kCreateAnswer:
|
|
case CallState.kConnecting:
|
|
return call.isOutgoing
|
|
? <Widget>[hangupButton]
|
|
: <Widget>[answerButton, hangupButton];
|
|
case CallState.kConnected:
|
|
return <Widget>[
|
|
muteMicButton,
|
|
//switchSpeakerButton,
|
|
if (!voiceonly && !kIsWeb) switchCameraButton,
|
|
if (!voiceonly) muteCameraButton,
|
|
if (PlatformInfos.isMobile || PlatformInfos.isWeb)
|
|
screenSharingButton,
|
|
holdButton,
|
|
hangupButton,
|
|
];
|
|
case CallState.kEnded:
|
|
return <Widget>[
|
|
hangupButton,
|
|
];
|
|
case CallState.kFledgling:
|
|
case CallState.kWaitLocalMedia:
|
|
case CallState.kCreateOffer:
|
|
case CallState.kEnding:
|
|
case null:
|
|
break;
|
|
}
|
|
return <Widget>[];
|
|
}
|
|
|
|
List<Widget> _buildContent(Orientation orientation, bool isFloating) {
|
|
final stackWidgets = <Widget>[];
|
|
|
|
final call = this.call;
|
|
if (call.callHasEnded) {
|
|
return stackWidgets;
|
|
}
|
|
|
|
if (call.localHold || call.remoteOnHold) {
|
|
var title = '';
|
|
if (call.localHold) {
|
|
title = '${call.room.getLocalizedDisplayname(
|
|
MatrixLocals(L10n.of(widget.context)),
|
|
)} held the call.';
|
|
} else if (call.remoteOnHold) {
|
|
title = 'You held the call.';
|
|
}
|
|
stackWidgets.add(
|
|
Center(
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
const Icon(
|
|
Icons.pause,
|
|
size: 48.0,
|
|
color: Colors.white,
|
|
),
|
|
Text(
|
|
title,
|
|
style: const TextStyle(
|
|
color: Colors.white,
|
|
fontSize: 24.0,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
return stackWidgets;
|
|
}
|
|
|
|
var primaryStream = call.remoteScreenSharingStream ??
|
|
call.localScreenSharingStream ??
|
|
call.remoteUserMediaStream ??
|
|
call.localUserMediaStream;
|
|
|
|
if (!connected) {
|
|
primaryStream = call.localUserMediaStream;
|
|
}
|
|
|
|
if (primaryStream != null) {
|
|
stackWidgets.add(
|
|
Center(
|
|
child: _StreamView(
|
|
primaryStream,
|
|
mainView: true,
|
|
matrixClient: widget.client,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
if (isFloating || !connected) {
|
|
return stackWidgets;
|
|
}
|
|
|
|
_resizeLocalVideo(orientation);
|
|
|
|
if (call.getRemoteStreams.isEmpty) {
|
|
return stackWidgets;
|
|
}
|
|
|
|
final secondaryStreamViews = <Widget>[];
|
|
|
|
if (call.remoteScreenSharingStream != null) {
|
|
final remoteUserMediaStream = call.remoteUserMediaStream;
|
|
secondaryStreamViews.add(
|
|
SizedBox(
|
|
width: _localVideoWidth,
|
|
height: _localVideoHeight,
|
|
child:
|
|
_StreamView(remoteUserMediaStream!, matrixClient: widget.client),
|
|
),
|
|
);
|
|
secondaryStreamViews.add(const SizedBox(height: 10));
|
|
}
|
|
|
|
final localStream =
|
|
call.localUserMediaStream ?? call.localScreenSharingStream;
|
|
if (localStream != null && !isFloating) {
|
|
secondaryStreamViews.add(
|
|
SizedBox(
|
|
width: _localVideoWidth,
|
|
height: _localVideoHeight,
|
|
child: _StreamView(localStream, matrixClient: widget.client),
|
|
),
|
|
);
|
|
secondaryStreamViews.add(const SizedBox(height: 10));
|
|
}
|
|
|
|
if (call.localScreenSharingStream != null && !isFloating) {
|
|
secondaryStreamViews.add(
|
|
SizedBox(
|
|
width: _localVideoWidth,
|
|
height: _localVideoHeight,
|
|
child: _StreamView(
|
|
call.remoteUserMediaStream!,
|
|
matrixClient: widget.client,
|
|
),
|
|
),
|
|
);
|
|
secondaryStreamViews.add(const SizedBox(height: 10));
|
|
}
|
|
|
|
if (secondaryStreamViews.isNotEmpty) {
|
|
stackWidgets.add(
|
|
Container(
|
|
padding: const EdgeInsets.fromLTRB(0, 20, 0, 120),
|
|
alignment: Alignment.bottomRight,
|
|
child: Container(
|
|
width: _localVideoWidth,
|
|
margin: _localVideoMargin,
|
|
child: Column(
|
|
children: secondaryStreamViews,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
return stackWidgets;
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return PIPView(
|
|
builder: (context, isFloating) {
|
|
return Scaffold(
|
|
resizeToAvoidBottomInset: !isFloating,
|
|
floatingActionButtonLocation:
|
|
FloatingActionButtonLocation.centerFloat,
|
|
floatingActionButton: SizedBox(
|
|
width: 320.0,
|
|
height: 150.0,
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
|
children: _buildActionButtons(isFloating),
|
|
),
|
|
),
|
|
body: OrientationBuilder(
|
|
builder: (BuildContext context, Orientation orientation) {
|
|
return Container(
|
|
decoration: const BoxDecoration(
|
|
color: Colors.black87,
|
|
),
|
|
child: Stack(
|
|
children: [
|
|
..._buildContent(orientation, isFloating),
|
|
if (!isFloating)
|
|
Positioned(
|
|
top: 24.0,
|
|
left: 24.0,
|
|
child: IconButton(
|
|
color: Colors.black45,
|
|
icon: const Icon(Icons.arrow_back),
|
|
onPressed: () {
|
|
PIPView.of(context)?.setFloating(true);
|
|
},
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
}
|