onInit method

  1. @override
void onInit()
override

Called immediately after the widget is allocated in memory. You might use this to initialize something for the controller.

Implementation

@override
void onInit() {
  AudioUtils.ensureInitialized();
  _initWebUtils();

  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);
          }
        });
      }
    });
  }

  _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:
        final OngoingCall c = event.value!.value;

        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[event.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[event.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(event.key!)?.dispose();
              if (_workers.isEmpty) {
                stop();
              }

              if (_isCallKit && callId != null) {
                await FlutterCallkitIncoming.setCallConnected(
                  callId.val.base62ToUuid(),
                );
              }
              break;

            case OngoingCallState.ended:
              _workers.remove(event.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);

          await FlutterCallkitIncoming.startCall(
            CallKitParams(
              nameCaller: chat?.title ?? 'Call',
              id: (c.call.value?.id.val ?? c.chatId.value.val).base62ToUuid(),
              handle: c.chatId.value.val,
              extra: {'chatId': c.chatId.value.val},
            ),
          );
        }
        break;

      case OperationKind.removed:
        _answeredCalls.remove(event.key);
        _audioWorkers.remove(event.key)?.dispose();
        _workers.remove(event.key)?.dispose();
        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) {
              await FlutterCallkitIncoming.endCall(callId.val.base62ToUuid());
            }

            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;
    }
  });

  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) {
            await _callService.decline(ChatId(chatId));
          }
          break;

        case Event.actionCallEnded:
        case Event.actionCallTimeout:
          final String? chatId = event.body['extra']?['chatId'];
          if (chatId != null) {
            _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);

            final GraphQlProvider provider = GraphQlProvider();
            provider.token = credentials.access.secret;

            _eventsSubscriptions[chatId]?.cancel();
            _eventsSubscriptions[chatId] = provider.chatEvents(chatId, null, () => null).listen((
              e,
            ) async {
              var events =
                  ChatEvents$Subscription.fromJson(e.data!).chatEvents;
              if (events.$$typename == 'ChatEventsVersioned') {
                var mixin =
                    events
                        as ChatEvents$Subscription$ChatEvents$ChatEventsVersioned;

                for (var e in mixin.events) {
                  if (e.$$typename == 'EventChatCallFinished') {
                    final node =
                        e as ChatEventsVersionedMixin$Events$EventChatCallFinished;

                    if (_isCallKit) {
                      await FlutterCallkitIncoming.endCall(
                        node.call.id.val.base62ToUuid(),
                      );
                    }
                  } else if (e.$$typename == 'EventChatCallMemberJoined') {
                    final node =
                        e
                            as ChatEventsVersionedMixin$Events$EventChatCallMemberJoined;
                    final call = _callService.calls[chatId];

                    if (node.user.id == credentials.userId &&
                        call?.value.connected != true) {
                      if (_isCallKit) {
                        await FlutterCallkitIncoming.endCall(
                          node.call.id.val.base62ToUuid(),
                        );
                      }
                    }
                  } else if (e.$$typename == 'EventChatCallDeclined') {
                    final node =
                        e as ChatEventsVersionedMixin$Events$EventChatCallDeclined;
                    if (node.user.id == credentials.userId) {
                      if (_isCallKit) {
                        await FlutterCallkitIncoming.endCall(
                          node.call.id.val.base62ToUuid(),
                        );
                      }
                    }
                  } else if (e.$$typename ==
                      'EventChatCallAnswerTimeoutPassed') {
                    final node =
                        e
                            as ChatEventsVersionedMixin$Events$EventChatCallAnswerTimeoutPassed;
                    if (node.userId == credentials.userId) {
                      if (_isCallKit) {
                        await FlutterCallkitIncoming.endCall(
                          node.callId.val.base62ToUuid(),
                        );
                      }
                    }
                  }
                }
              }
            });
          }
          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:
          // No-op.
          break;
      }
    });
  }

  _hotKey =
      _settingsRepository.applicationSettings.value?.muteHotKey ??
      MuteHotKeyExtension.defaultHotKey;

  super.onInit();
}