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