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() {
  Log.debug('onInit', '$runtimeType');

  AudioUtils.ensureInitialized();

  if (!WebUtils.isPopup) {
    _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();
      }
    });

    _workers.remove(key)?.dispose();
    _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(),
            );

            final String base62 = callId.val.base62ToUuid();
            await _callKitCalls.upsert(base62, PreciseDateTime.now());
          }
          break;

        case OngoingCallState.ended:
          _workers.remove(key)?.dispose();
          if (_workers.isEmpty) {
            stop();
          }

          if (_isCallKit && callId != null) {
            Log.debug(
              'c.state($state) -> invoking `FlutterCallkitIncoming.endCall($callId)` due to state being `ended`',
              '$runtimeType',
            );

            await FlutterCallkitIncoming.endCall(callId.val.base62ToUuid());
          }
          break;
      }
    });

    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 (_workers.isNotEmpty &&
          (!PlatformUtils.isMobile || isInForeground)) {
        play(_incoming, fade: true);

        // 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 != true) {
              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,
          );
        }
      });
    }

    if (_isCallKit) {
      _eventsSubscriptions.remove(c.chatId.value)?.cancel();
      _resubscribeTo(c.chatId.value);

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

          case OngoingCallState.ended:
            // No-op.
            break;
        }
      }
    }
  }

  if (!WebUtils.isPopup) {
    _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.remove(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;

              Log.debug(
                '_callService.calls.changes($event) -> invoking FlutterCallkitIncoming.endCall() due to call being `removed` from `calls`',
                '$runtimeType',
              );

              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.remove(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.remove(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;

  if (!WebUtils.isPopup) {
    _callKitCalls.clear();
  }

  final List<ConnectivityResult> previous = _sessionRepository.connectivity
      .toList();
  ever(_sessionRepository.connectivity, (connections) async {
    if (previous.isEmpty && connections.isNotEmpty) {
      return previous.addAll(connections.toList());
    }

    if (!const ListEquality().equals(previous, connections)) {
      Log.debug(
        '_sessionRepository.connectivity -> $previous != $connections',
        '$runtimeType',
      );

      previous.clear();
      previous.addAll(connections.toList());

      if (connections.every((e) => e != ConnectivityResult.none)) {
        final int seconds =
            _lastConnectedAt?.difference(DateTime.now()).abs().inSeconds ??
            10;

        if (_lastConnectedAt == null || seconds >= 5) {
          _lastConnectedAt = DateTime.now();

          for (var e in _callService.calls.values) {
            e.value.notify(ConnectionLostNotification());
          }

          await MediaUtils.ensureReconnected();

          for (var e in _callService.calls.values) {
            e.value.notify(ConnectionRestoredNotification());
          }
        }
      }
    }
  });

  super.onInit();
}