onInit method
override
Called immediately after the widget is allocated in memory. You might use this to initialize something for the controller.
Implementation
@override
void onInit() {
Log.debug('onInit', '$runtimeType');
AudioUtils.ensureInitialized();
_initWebUtils();
List<String>? lastKeys = _settingsRepository
.applicationSettings
.value
?.muteKeys
?.toList();
_settingsWorker = ever(_settingsRepository.applicationSettings, (
ApplicationSettings? settings,
) {
if (!const ListEquality().equals(settings?.muteKeys, lastKeys)) {
lastKeys = settings?.muteKeys?.toList();
final bool shouldBind = _bind;
if (_bind) {
_unbindHotKey();
}
_hotKey = settings?.muteHotKey ?? MuteHotKeyExtension.defaultHotKey;
if (shouldBind) {
_bindHotKey();
}
}
});
bool wakelock = _callService.calls.isNotEmpty;
if (wakelock && !PlatformUtils.isLinux) {
WakelockPlus.enable().onError((_, _) => false);
}
if (PlatformUtils.isAndroid && !PlatformUtils.isWeb) {
_lifecycleWorker = ever(router.lifecycle, (e) async {
if (e.inForeground) {
if (_isCallKit) {
try {
await FlutterCallkitIncoming.endAllCalls();
} catch (_) {
// No-op.
}
}
_callService.calls.forEach((id, call) {
if (_answeredCalls.contains(id) && !call.value.isActive) {
_callService.join(id, withVideo: false);
_answeredCalls.remove(id);
}
});
}
});
}
Future<void> handle(ChatId key, OngoingCall c) async {
Future.delayed(Duration.zero, () {
// Ensure the call is displayed in the application before binding.
if (!c.background && c.state.value != OngoingCallState.ended) {
_bindHotKey();
}
});
if (c.state.value == OngoingCallState.pending ||
c.state.value == OngoingCallState.local) {
// Indicator whether it is us who are calling.
final bool outgoing =
(_callService.me == c.caller?.id ||
c.state.value == OngoingCallState.local) &&
c.conversationStartedAt == null;
final SharedPreferences prefs = await SharedPreferences.getInstance();
if (prefs.containsKey('answeredCall')) {
_answeredCalls.add(ChatId(prefs.getString('answeredCall')!));
prefs.remove('answeredCall');
}
final bool isInForeground = router.lifecycle.value.inForeground;
if (isInForeground && _answeredCalls.contains(c.chatId.value)) {
_callService.join(c.chatId.value, withVideo: false);
_answeredCalls.remove(c.chatId.value);
} else if (outgoing) {
play(_outgoing);
} else if (!PlatformUtils.isMobile || isInForeground) {
play(_incoming, fade: true);
Vibration.hasVibrator()
.then((bool? v) {
_vibrationTimer?.cancel();
if (v == true) {
Vibration.vibrate(
pattern: [500, 1000],
).onError((_, _) => false);
_vibrationTimer = Timer.periodic(
const Duration(milliseconds: 1500),
(timer) {
Vibration.vibrate(
pattern: [500, 1000],
repeat: 0,
).onError((_, _) => false);
},
);
}
})
.catchError((_, _) {
// No-op.
});
// Show a notification of an incoming call.
if (!outgoing && !PlatformUtils.isMobile && !_focused) {
final FutureOr<RxChat?> chat = _chatService.get(c.chatId.value);
void showIncomingCallNotification(RxChat? chat) {
// Displays a local notification via [NotificationService].
void notify() {
if (_myUser.value?.muted == null &&
chat?.chat.value.muted == null) {
final String? title = chat?.title ?? c.caller?.title;
_notificationService.show(
title ?? 'label_incoming_call'.l10n,
body: title == null ? null : 'label_incoming_call'.l10n,
payload: '${Routes.chats}/${c.chatId}',
icon: chat?.avatar.value?.original,
tag: '${c.chatId}_${c.call.value?.id}',
);
}
}
// If FCM wasn't initialized, show a local notification
// immediately.
if (!_notificationService.pushNotifications) {
notify();
} else if (PlatformUtils.isWeb && PlatformUtils.isDesktop) {
// [NotificationService] will not show the scheduled local
// notification, if a push with the same tag was already
// received.
Future.delayed(_pushTimeout, notify);
}
}
if (chat is RxChat?) {
showIncomingCallNotification(chat);
} else {
chat.then(showIncomingCallNotification);
}
}
}
}
if (_muted.value) {
c.setAudioEnabled(!_muted.value);
}
if (_isCallKit) {
_audioWorkers[key] = ever(c.audioState, (LocalTrackState state) async {
final ChatItemId? callId = c.call.value?.id;
if (callId != null) {
await FlutterCallkitIncoming.muteCall(
callId.val.base62ToUuid(),
isMuted: !state.isEnabled,
);
}
});
}
_workers[key] = ever(c.state, (OngoingCallState state) async {
final ChatItemId? callId = c.call.value?.id;
switch (state) {
case OngoingCallState.local:
case OngoingCallState.pending:
// No-op.
break;
case OngoingCallState.joining:
case OngoingCallState.active:
_workers.remove(key)?.dispose();
if (_workers.isEmpty) {
stop();
}
if (_isCallKit && callId != null) {
await FlutterCallkitIncoming.setCallConnected(
callId.val.base62ToUuid(),
);
}
break;
case OngoingCallState.ended:
_workers.remove(key)?.dispose();
if (_workers.isEmpty) {
stop();
}
if (_isCallKit && callId != null) {
await FlutterCallkitIncoming.endCall(callId.val.base62ToUuid());
}
break;
}
});
if (_isCallKit) {
final RxChat? chat = await _chatService.get(c.chatId.value);
final String id = (c.call.value?.id.val ?? c.chatId.value.val)
.base62ToUuid();
bool report = true;
final PreciseDateTime? accountedAt = await _callKitCalls.read(id);
if (accountedAt != null) {
report =
accountedAt.val.difference(DateTime.now()).abs() >=
_accountedTimeout;
}
if (report) {
final CallKitParams params = CallKitParams(
nameCaller: chat?.title ?? 'Call',
id: id,
handle: c.chatId.value.val,
extra: {'chatId': c.chatId.value.val},
);
switch (c.state.value) {
case OngoingCallState.pending:
Log.debug(
'onInit() -> ${c.state.value.name} -> FlutterCallkitIncoming.showCallkitIncoming($id)',
'$runtimeType',
);
await FlutterCallkitIncoming.showCallkitIncoming(params);
await _resubscribeTo(c.chatId.value);
break;
case OngoingCallState.local:
case OngoingCallState.joining:
case OngoingCallState.active:
Log.debug(
'onInit() -> ${c.state.value.name} -> FlutterCallkitIncoming.startCall($id)',
'$runtimeType',
);
await FlutterCallkitIncoming.startCall(params);
await FlutterCallkitIncoming.setCallConnected(id);
await _resubscribeTo(c.chatId.value);
break;
case OngoingCallState.ended:
// No-op.
break;
}
}
}
}
_subscription = _callService.calls.changes.listen((event) async {
if (!wakelock && _callService.calls.isNotEmpty) {
wakelock = true;
WakelockPlus.enable().onError((_, _) => false);
} else if (wakelock && _callService.calls.isEmpty) {
wakelock = false;
WakelockPlus.disable().onError((_, _) => false);
}
switch (event.op) {
case OperationKind.added:
if (event.key != null && event.value != null) {
await handle(event.key!, event.value!.value);
}
break;
case OperationKind.removed:
_answeredCalls.remove(event.key);
_audioWorkers.remove(event.key)?.dispose();
_workers.remove(event.key)?.dispose();
_eventsSubscriptions[event.key]?.cancel();
if (_workers.isEmpty) {
stop();
}
// Play an [_endCall] sound, when an [OngoingCall] with [myUser] ends.
final OngoingCall? call = event.value?.value;
if (call != null) {
final bool isActiveOrEnded =
call.state.value == OngoingCallState.active ||
call.state.value == OngoingCallState.ended;
final bool withMe = call.members.containsKey(call.me.id);
if (withMe && isActiveOrEnded && call.participated) {
play(_endCall);
}
if (_isCallKit) {
final ChatItemId? callId = call.call.value?.id;
if (callId != null) {
final String base62 = callId.val.base62ToUuid();
_callKitCalls.upsert(base62, PreciseDateTime.now());
await FlutterCallkitIncoming.endCall(base62);
}
await FlutterCallkitIncoming.endCall(
call.chatId.value.val.base62ToUuid(),
);
}
}
// Set the default speaker, when all the [OngoingCall]s are ended.
if (_callService.calls.isEmpty) {
_unbindHotKey();
try {
await AudioUtils.setDefaultSpeaker();
} on PlatformException {
// No-op.
}
if (_isCallKit) {
await FlutterCallkitIncoming.endAllCalls();
}
}
break;
default:
break;
}
});
for (Rx<OngoingCall> call in _callService.calls.values) {
handle(call.value.chatId.value, call.value);
}
if (_isCallKit) {
_callKitSubscription = FlutterCallkitIncoming.onEvent.listen((
CallEvent? event,
) async {
Log.debug('FlutterCallkitIncoming.onEvent -> $event', '$runtimeType');
switch (event!.event) {
case Event.actionCallAccept:
final String? chatId = event.body['extra']?['chatId'];
if (chatId != null) {
await _callService.join(ChatId(chatId));
}
break;
case Event.actionCallDecline:
final String? chatId = event.body['extra']?['chatId'];
if (chatId != null) {
_eventsSubscriptions[ChatId(chatId)]?.cancel();
await _callService.decline(ChatId(chatId));
}
break;
case Event.actionCallEnded:
case Event.actionCallTimeout:
final String? chatId = event.body['extra']?['chatId'];
if (chatId != null) {
_eventsSubscriptions[ChatId(chatId)]?.cancel();
_callService.remove(ChatId(chatId));
}
break;
case Event.actionCallToggleMute:
final bool? isMuted = event.body['isMuted'] as bool?;
if (isMuted != null) {
for (var e in _callService.calls.entries) {
e.value.value.setAudioEnabled(!isMuted);
}
}
break;
case Event.actionCallIncoming:
final String? extra = event.body['extra']?['chatId'];
final Credentials? credentials = _authService.credentials.value;
if (extra != null && credentials != null) {
final ChatId chatId = ChatId(extra);
await _resubscribeTo(chatId);
} else if (credentials == null) {
// We don't have `credentials`, thus no calls should be allowed.
await FlutterCallkitIncoming.endAllCalls();
}
break;
case Event.actionDidUpdateDevicePushTokenVoip:
case Event.actionCallStart:
case Event.actionCallCallback:
case Event.actionCallToggleHold:
case Event.actionCallToggleDmtf:
case Event.actionCallToggleGroup:
case Event.actionCallToggleAudioSession:
case Event.actionCallCustom:
case Event.actionCallConnected:
// No-op.
break;
}
});
// List the current active calls (e.g. if this app was launched as a
// result of VoIP notification received) and subscribe to the events.
FlutterCallkitIncoming.activeCalls().then((e) {
Log.debug(
'onInit() -> FlutterCallkitIncoming.activeCalls -> $e',
'$runtimeType',
);
if (e is List) {
for (var event in e) {
Log.debug(
'onInit() -> FlutterCallkitIncoming.activeCalls -> event -> ${event.runtimeType} -> ${event is Map}',
'$runtimeType',
);
if (event is Map) {
final String? chatId = event['extra']?['chatId'] as String?;
if (chatId != null) {
_resubscribeTo(ChatId(chatId));
}
}
}
}
});
}
_hotKey =
_settingsRepository.applicationSettings.value?.muteHotKey ??
MuteHotKeyExtension.defaultHotKey;
_callKitCalls.clear();
super.onInit();
}