Skip to content

release: 1.15.1#1219

Draft
SERDUN wants to merge 338 commits intomainfrom
release/1.15.1
Draft

release: 1.15.1#1219
SERDUN wants to merge 338 commits intomainfrom
release/1.15.1

Conversation

@SERDUN
Copy link
Copy Markdown
Member

@SERDUN SERDUN commented Apr 28, 2026

Release 1.15.1

Changes since 1.15.0

SERDUN and others added 30 commits February 2, 2026 13:42
* style: set background color for expansion panels

* docs: add TODOs explaining the dark mode background override
…911)

* feat: add theme override capabilities to ThemedScaffold

Introduces ContentThemeOverride to allow forcing light or dark modes
on the scaffold or its body independently.

- Add ContentThemeOverride enum (auto, light, dark).
- Add contentThemeOverride and ignoreAppBarOverride parameters.
- Implement _resolveThemeOverride to dynamically generate theme data.
- Update build logic to conditionally wrap the scaffold or body in a Theme widget.
- Refactor background decoration logic for better readability.

* refactor: replace Scaffold with ThemedScaffold across main feature screens

* chore: add theme override configurations and documentation to page configs

* feat: add theme override configuration for pages

* feat(theme): implement comprehensive screen theming and override system

Overhauls the screen styling architecture to support dynamic background styles
and explicit theme mode overrides (Light/Dark) across all main features.

- Implement ThemeOverrideConfig to manage theme mode forcing and AppBar
  synchronization.
- Introduce screen-specific styles (Contacts, Embedded, Favorites,
  Conversations, Recents) with background and theme override support.
- Refactor ThemedScaffold to use applyToAppBar (positive logic) for
  better readability and predictable theme propagation.
- Add StyleFactories for all migrated screens to map declarative
  JSON configurations to UI styles.
- Update BottomMenuTab models to include theme override data for
  tab-level consistency.
- Standardize MainAppBar behavior to handle complex backgrounds (gradients/images)
  by dynamically adjusting transparency and elevation.

* chore(theme): synchronize theme override keys and visibility in page configs

* fix: prevent content overlap with AppBar on complex backgrounds

* docs: align applyToAppBar documentation and default behavior
* fix: fixed formatting on about screen

Transferred core url after version and divided app information and device information.

* refactor: optimize layout and decompose widgets

* feat: add documentation and enrich appInfo output in AppMetadataProvider

---------

Co-authored-by: Dmytro Serdun <d.serdun@webtrit.com>
Added collate method with no case option when sorting contacts
…upport (#914)

* refactor: update dark theme configuration and enable mode support

* feat: implement supported features schema and unify theme mode

* feat: apply forced theme mode from FeatureAccess configuration

* refactor: swap page and widget theme configurations to correct files
* refactor: rename ThemeModeConfig.auto to ThemeModeConfig.system

* refactor: replace custom ContentThemeOverride with standard ThemeMode

* refactor: rename toContentThemeOverride to toThemeMode

* fix: correctly handle nullable contentThemeOverride
…rides (#916)

* refactor: rename ThemeModeConfig.auto to ThemeModeConfig.system

* refactor: replace custom ContentThemeOverride with standard ThemeMode

* refactor: use ThemeProvider for theme overrides

Replaces the manual theme generation logic in ThemedScaffold with
direct retrieval from ThemeProvider. This ensures that when a theme
mode is forced (Light/Dark), the resulting ThemeData includes all
custom extensions and component styles defined in the app.
* style: remove temporary background color overrides in MediaSettingsScreen

* fix: wrap active thumbnail in Card for overlay support

The active call thumbnail is rendered inside an overlay, which is detached
from the main Scaffold tree. Without a Material ancestor, the widget lacks
theme context and elevation.

This change wraps the content in a Card to ensure proper Material styling
and shadows when displayed as a floating thumbnail.

* refactor(settings): use theme colors for microphone warning
* refactor(app): implement safe teardown sequence for user logout

Introduces a dedicated `Teardown` application lifecycle state to ensure safe resource cleanup.

- Add `AppLifecycleStatus` to `AppBloc` to manage authentication states.
- Create `TeardownScreen` to hold the UI context while the `MainShell` unmounts.
- Implement `UserSessionCleanupResolver` to centralize repository and database clearing.
- Remove `onLogout` callback from `SessionRepository` and `main.dart`.
- Refactor `AppRouter` guards to handle the new teardown navigation flow.

* refactor: implement best-effort strategy for user session cleanup

* refactor: rename AppLogined to AppLoggedIn and refine logout documentation

* feat(autoprovision): pass system info when logging in via autoprovision

* refactor: implement memory-first session strategy and remove reactive streams

* fix: promote SessionRepository to a singleton in bootstrap

* refactor: implement intent-based logout orchestration in AppBloc

* style: optimize event instantiation and refine code comments

* style: reorder members in AppEvent classes

* feat: localize TeardownScreen progress text

* feat: explicitly handle storage cleanup during session updates

* refactor: refine teardown screen navigation guard

* docs: refine _saveSession documentation to match delegation pattern
* feat: add ButtonStyle and Geometry configuration models

* feat: implement configurable ButtonStyle for LoginSwitchScreen

* chore: add configurable SegmentedButton styling for login switch

* feat: add disabled state colors to ButtonStyleConfig

* feat: support disabled states and improve border side resolution in button styles

* refactor: migrate primary elevated button to ButtonStyleConfig
…ead badges (#924)

* refactor: replace custom tab buttons with standard TabBar and add unread badges

* refactor: remove redundant TabButtonsBar widget

* refactor: optimize tab selection logic and state updates
* fix(theme): propagate defaultFontFamily to theme factories

Updated theme style factories and configuration extensions to accept and apply
a `defaultFontFamily`. This ensures consistent font rendering across the app
by explicitly passing the font family derived from the global TextTheme into
custom component styles.

- Added `defaultFontFamily` parameter to `TextStyleConfig.toTextStyle` and
  related style conversion methods.
- Modified `ThemeStyleFactoryProvider` to initialize a `defaultTextTheme` and
  pass its font family to various screen and widget factories.
- Refactored multiple factories (Keypad, CallScreen, Login, Settings, etc.)
  to support the new font propagation logic.
- Replaced redundant `createTextTheme()` calls with a cached `defaultTextTheme`.

* refactor: improve initials text style mapping and reduce log verbosity
* refactor: move ActionPadWidgetConfig from widget to page configuration

* refactor: add defaultVerticalAlignment to TextButtonsTable

* refactor(keypad): transition Actionpad to semantic button styling

Redesign Actionpad styling logic to move away from function-specific
identifiers toward semantic roles. This decoupling allows for more
flexible UI configurations and cleaner style inheritance.

* fix: correct button style merging and disabled states in ActionPad
* refactor: remove hardcoded timeouts in PeerConnectionManager

* refactor: increase RtpTrafficMonitor check interval to 15s

Update the default monitorCheckInterval from 2s to 15s to optimize log
storage and prevent quota exhaustion. This adjustment reduces log
volume by approximately 85% while maintaining sufficient granularity
for monitoring network quality trends.
…930)

* refactor: consolidate remote config service implementations

* refactor: enhance remote config service and caching logic

* refactor: move remote config service to services directory

* refactor: rename FirebaseRemoteConfigService to CachedRemoteConfigService

* feat: add remote config update stream support

* feat: integrate remote config overrides into FeatureAccess

* refactor: implement Snapshot pattern for RemoteConfigService

* refactor: update RemoteConfigService architecture and snapshot handling

* refactor: improve FeatureAccess reactivity and StreamUtils robustness

* refactor: add disposal guards to CachedRemoteConfigService and clean up redundant comments

* refactor: introduce FeatureAccessStreamFactory and decouple stream logic from RootApp

* refactor: update FeatureAccess architecture with specialized factories and rxdart integration

* refactor: ensure deterministic equality in CoreSupportImpl by sorting flags
…ronization (#931)

* feat: implement dynamic RTP monitoring configuration

- Added MonitoringConfig model and MonitoringMapper to handle monitor intervals.
- Updated PeerConnectionManager to support runtime configuration updates.
- Introduced CallConfigSynchronizer to reactively update CallBloc when FeatureAccess changes.
- Added 'monitorConfig' to SupportedFeature for static app configuration.
- Integrated Remote Config override for monitor check interval via 'feature_monitor_check_interval_sec'.
- Enabled voicemail settings by default in app.config.json.

* fix: align app.config.json key with SupportedMonitorConfig model

- Rename 'checkInterval' to 'checkIntervalSec' in app.config.json.
- Fixes an issue where the static monitor configuration was ignored due to a naming mismatch with the generated SupportedMonitorConfig model.
- Ensures the default 15-second interval is correctly read from the assets.

* fix(data): add monitoringConfig to FeatureAccess Equatable props

* fix(data): validate remote config monitor interval to prevent zero or negative values

* fix(call): allow disabling RTP monitor via zero interval

* test(data): verify monitor interval remote overrides and refactor validation

* docs: fix parameter name in SupportedFeature.monitorConfig doc comment

* fix: allow zero interval to propagate for RTP monitor disablement

* test(call): add unit tests for RtpTrafficMonitor

- Verified that checkInterval <= 0 successfully acts as a kill switch, preventing timer initialization and native calls.
- Verified that a valid positive interval correctly triggers periodic WebRTC stats fetching.
- Verified that monitoring automatically stops when RTCPeerConnection enters a closed state.
- Covered edge cases for zero and negative durations to ensure stability against invalid configurations.
…ng (#932)

* feat: add reboot dialog

Added dialog for user to reboot user when timeout error on starting a call if it is Xiaomi phone

* refactor: replace AppMetadataProvider with DeviceInfo in DiagnosticService

* refactor: inject DeviceInfo into DiagnosticService in AppShell

* feat: include Huawei in emergency reboot diagnostic logic

* chore: update system error dialog localizations

* refactor(diagnostic): improve error handling and decouple UI logic

* refactor: simplify system error dialog and unify diagnostic launchers

* refactor: unify diagnostic flow and streamline reporting

* chore: remove trailing periods from system error dialog titles

---------

Co-authored-by: Alex Maudza <o.maudza@webtrit.com>
…ry saving (#933)

* refactor: add controller to HistoryAutocompleteField for manual history saving

- Implement HistoryAutocompleteController to allow manual triggers for saving input to history.
- Convert LoginCoreUrlAssignScreen to a StatefulWidget to manage the controller lifecycle.
- Update HistoryAutocompleteField to support the new controller and internal logic extraction.
- Refactor callbacks into private methods and simplify widget building logic.

* fix: avoid redundant history saving in HistoryAutocompleteField
…ures (#934)

Refactor feature configuration by introducing systemNotifications and
sipPresence as variants of SupportedFeature. This change initiates
the transition away from legacy flat properties in AppConfigMain.
digiboridev and others added 29 commits April 22, 2026 10:13
…ion (WT-1401) (#1193)

* fix(call): skip ICE restart for calls without initialized PeerConnection

_onConnectivityResultChanged called retrieve() unconditionally for all
active calls. For calls still in pre-PC states (outgoingConnectingToSignaling,
incomingInitializingMedia, etc.) the completer was never completed, causing
retrieve() to block for 5 s then throw TimeoutException — breaking the
entire handler loop and silently skipping ICE restart for all subsequent calls.

Adds hasPeerConnectionReady getter to CallProcessingStatus covering the states
where complete() is guaranteed to have been called (outgoingOfferSent,
outgoingRinging, connected, disconnecting). The guard skips retrieve() for
calls that have no PC yet while preserving the TimeoutException signal for
calls that should have a PC but don't.

Fixes: WT-1401 (secondary bug)

* fix(call): log skipped ICE restart when PC not ready

* test(call): add unit tests for CallProcessingStatus.hasPeerConnectionReady

* fix(call): remove disconnecting from hasPeerConnectionReady, add status to ICE restart log
…o prevent native eviction (#1196)

* fix(WT-1398): detach pooled track from stream before dispose

On iOS and Android, streamDispose iterates the stream's track list and
removes each track from the native localTracks registry. When a pooled
audio/video track is shared between the main call and a consultation
call, disposing the consultation call's local stream evicts the track
from localTracks — even though the main call still holds a reference.
The next consultation attempt calls addTrack() on the same pooled track
object, which fails with "Track is nil" / "track is null" because the
track is no longer in localTracks.

Fix: in release(), call removeTrack() on the pooled track before
stream.dispose() when references > 1. mediaStreamRemoveTrack removes
the track from the stream's own audioTracks/videoTracks list without
touching localTracks, so streamDispose no longer sees it and does not
evict it from the registry.

Confirmed by reading the native plugin source:
- iOS: FlutterWebRTCPlugin.m streamDispose / mediaStreamRemoveTrack
- Android: MethodCallHandlerImpl.java streamDispose / mediaStreamRemoveTrack

* test(user-media-builder): stub removeTrack and add detach verification

Adds missing removeTrack stub to _TestMediaStreamFactory so the new
_detachIfStillPooled path does not fail with a null return from the mock.

Adds a test verifying that pooled tracks are detached from a stream
before dispose when another borrower still holds a reference, and that
the last borrower skips detach (tracks are stopped and disposed anyway).
…ions (#1200)

* fix(signaling): ignore late server ACK for already-timed-out transactions

When a signaling transaction times out the client removes it from the
_transactions map and tears down the call. If the server responds after
the timeout (observed up to 63 s under network congestion), the missing
map entry previously triggered _onError(WebtritSignalingTransactionUnavailableException)
which emitted a false-positive SignalingConnectionFailed and caused an
unnecessary reconnect while other calls may still be in progress.

Replace the error path with a warning-level log so a late ACK is a no-op.

* refactor(signaling): remove WebtritSignalingTransactionUnavailableException

Class became dead code after late-ACK handling was changed to log a warning
instead of calling _onError. Remove the class, its hub codec encode/decode
branches, the constant, and all test references.
…tification (#1203)

* fix(wt-1416): show caller number instead of Unknown in missed call notification

When IncomingCallEvent is missing (race condition where push isolate connects
after the event has passed through the hub), the caller number now falls back
to _metadata.handle.value from FCM push metadata instead of the hardcoded
'unknown' string.

The missed-call notification display name resolution now also falls back to
the caller phone number when neither signaling nor push metadata provide a
display name.

* fix(wt-1416): replace unknown string fallback with empty string

Use empty string instead of hardcoded 'unknown' as final fallback for
caller number, allowing isNotEmpty check to handle all cases cleanly.

* refactor(wt-1416): simplify _getDisplayNameForMissedCall with firstWhere
* fix(signaling): align hub execute timeout with full retry cycle

_executeTimeout in SignalingHubClient was set to 15 s ("slightly longer
than the 10 s transaction timeout") but did not account for
SignalingRequestQueue retrying a timed-out transaction up to
defaultMaxRetryCount times. The hub timeout fired mid-retry, causing the
main isolate to receive a TimeoutException while the background isolate
was still working, which triggered a false wasAccepted==false → Decline.

Fix: derive _executeTimeout dynamically from the actual retry budget —
transactionTimeout × (maxRetryCount + 1) + 5 s overhead (45 s with
defaults). The hub timeout now only fires when the background isolate
stops responding entirely (hub death), which is the intended behaviour.
Hub death detection via ping/pong remains unchanged as a parallel guard.

Add SignalingRequestQueue.defaultMaxRetryCount constant so both sides
reference the same source of truth without hardcoding.

* test(signaling): add coverage for hub execute timeout alignment

- hub_client_integration_test: new 'execute timeout alignment' group with 4
  tests proving that _executeTimeout must cover the full retry cycle
  (transactionTimeout × (maxRetryCount + 1)); fixes const → final for
  Duration arithmetic expressions
- signaling_request_queue_test: add 3 tests for defaultMaxRetryCount
  constant value, default constructor usage, and custom override

* refactor(signaling): address PR review comments

- add explicit int type to SignalingRequestQueue.defaultMaxRetryCount
- clarify test comment: fake client delays directly, no real retry happens
…1205)

CallController now requires ConnectivityService; mocked it in tests.
Timeout path now emits GeneralUnableToCallNotification, not NoInternetConnectionNotification.
…1403) (#1206)

* fix(signaling): reset _errorHandled when new client connects (WT-1403)

When _onError fired for clientA, _errorHandled was left true after
clientB successfully connected. ClientB's subsequent disconnect was
silently suppressed, preventing any reconnect from being triggered.

Adds regression tests covering both the with-stale-disconnect and
direct error→reconnect→disconnect scenarios.

* fix(signaling): close stale-callback guard hole and add ghost-state detection (WT-1403)

Bug #2 — _activeClientId token replaces the old _client != null guard in
SignalingModuleImpl. The previous guard passed stale callbacks when _client
was already null (e.g. a late _wsOnDone after _onError had cleared _client),
delivering spurious SignalingDisconnected(delay=null) that silenced the
reconnect loop. The new per-client Object token is cleared in both _onError
and _onDisconnect, so any late callback from the same or a prior client is
discarded regardless of _client's current value.

Bug #3 — ghost-state detection in WebtritSignalingService.execute(). When
the main-isolate believes it is connected (_isConnected=true) but the FGS
hub is actually disconnected, execute() now receives NotConnectedException
instead of a generic StateError. The exception is encoded through the isolate
codec (signaling_hub_codec) and caught in a catchError chain; on detection
_isConnected is reset to false and connect() is called to trigger recovery.

Tests: 8 new tests for Bug #2 (4 BUG + 4 after-fix) and 5 new tests for
Bug #3 (ghost-state reset, reconnect trigger, rethrow, normal path, non-NCE).

* fix(signaling): fix null-aware operator and local variable naming lint warnings

* refactor(signaling-tests): address reviewer feedback — merge duplicate helpers and rename BUG→regression prefix

- Extract buildModuleWith/buildModuleWith2 local functions into a single
  top-level _buildModuleFromClients helper to remove duplication
- Rename test prefix 'BUG:' → 'regression:' for clarity: regression tests
  reproduce the bug scenario and are expected to fail on unfixed code,
  not assert buggy behavior
- Update library docstring to match the new naming convention
#1207)

* fix(call): show missed call notification on Android when call ends via signaling

When the app is alive and an incoming call is never answered, the HangupEvent
arrives through the signaling path in CallBloc, which only logged the call and
reported end to callkeep — no OS notification was posted. The push isolate path
already handled this correctly via localPushRepository.displayPush.

Added localPushRepository dep to CallBloc and _showMissedCallNotification helper.
Notification is guarded to Android only since iOS handles missed calls via CallKit.

* feat: add AppLocalPush.missedCall factory to centralise missed call notification construction

Both push isolate and CallBloc were constructing the same AppLocalPush payload
manually with duplicated callId.hashCode, title string, and payload keys.
Moved the construction into a named factory so callers pass only callId and
callerName.

* fix(call): suppress missed call notification when declined on another device

When code == declineCall the call was actively rejected by another registered
device, so posting a missed call notification is misleading. Guard the
notification behind code != declineCall.
Adds missing error codes to handleError switch so users see
actionable messages instead of silent failures:
- incorrect_credentials → "Incorrect username or password"
- user_not_found → "User not found"
- unconfigured_bundle_id, validation_error, parameters_apply_issue,
  empty_email — previously handled only in deprecated LoginErrorNotification

Adds 7 new notification classes and 2 new l10n keys (EN/UK/IT).

Fixes: WT-1417
…#1209)

ConnectivityResult.vpn was falling through to the default case and
returning NetworkStatus.none, causing the app to show "waiting network"
and skip signaling reconnect when VPN was active.
* fix(wt-1417): debounce ICE restart on connectivity change to allow interface init

* refactor(wt-1417): extract debounce-per-key logic into reusable DebounceMap utility

- Add DebounceMap<K> to lib/utils/ — cancel+reschedule timer per key, dispose all on teardown
- Replace raw Map<String, Timer> + Timer in CallBloc with DebounceMap<String>
- Cover DebounceMap with 8 unit tests using fake_async

* refactor(wt-1417): address review — dispatch internal event from ICE restart timer

- Add _IceRestartTriggered event; timer now calls add() instead of doing async work directly
- _onIceRestartTriggered handles retrieve+restartIce inside the BLoC event loop
- Cancel pending ICE restart on call teardown in __onResetStateEventCompleteCall
- Fix debounce_map.dart export position in utils.dart (alphabetical order)
)

* fix(wt-1428): show user not found notification on OTP sign-in 404

Convert 404 from otp-create endpoint to UserNotFoundException in the
API client, then handle it explicitly in LoginCubit.handleError to
show LoginUserNotFoundNotification instead of logging as unexpected error.

* fix(wt-1428): fix log style and add test for 404 UserNotFoundException

Align warning log to use string interpolation consistent with existing
handleError style. Add unit test for createSessionOtp 404 → UserNotFoundException.
* fix: safe renegotiate - pc ready guard

* fix: _safeRenegotiate move pc retrieve on top to avoid other guards inconsistency after retrieve await

* fix: skip remote updating if disconnecting
…uting (#1216)

* feat(call): introduce CallMediaManager for centralized audio/video routing

Replaces scattered direct audio plugin calls in CallBloc with a single
CallMediaManager facade that encapsulates both webtrit_callkeep and
flutter_webrtc audio APIs.

Key changes:
- CallMediaManager: single coordinator for all audio routing decisions
  (device selection, iOS session lifecycle, video-triggered speaker logic)
- CallAudioDevice moved from call_state.dart (part file) to
  models/call_audio_device.dart to resolve circular import
- Android: earpiece-first preferredOutputOrder set at app init via
  AndroidNativeAudioManagement.setAndroidAudioConfiguration, fixing
  speaker auto-activation on cold-start voice calls
- onVideoEnabled/onVideoDisabled: symmetric speaker↔earpiece routing
  on both platforms when camera is toggled mid-call
- _handleVideoStateTransitions in onChange: reacts to mid-call video
  flag transitions (prevCall != null guard prevents firing on initial
  call creation before PhoneConnection exists)
- _onVideoStreamReady helper: called after getUserMedia completes for
  initial video calls, when AudioSwitch and PhoneConnection are both ready

* chore: update pubspec.lock to pick up flutter-webrtc preferredOutputOrder (#8)

* refactor(call): call mediaManager directly instead of reactive onChange detection

* fix(call): route speaker via AudioSwitch in onVideoEnabled/Disabled

* fix(call): route all Android audio device changes through AudioSwitch in setDevice

* refactor(call): extract reusable _enableSpeaker/_disableSpeaker helpers in CallMediaManager

* refactor(call): remove duplicate comment in CallMediaManager speaker helpers

* fix(lint): add curly braces to if statements in CallMediaManager

* refactor(call): move CallMediaManager from services/ to utils/

* chore: restore network_security_config.xml to match develop

Remove local dev IP (192.168.0.9) committed by mistake; file now matches develop exactly.

* chore: remove auto-generated assets.dart committed by mistake

* refactor(call): inline lifecycle mediaManager calls, remove wrapper methods

_onFirstCallStarted/_onLastCallEnded were thin wrappers with no logic of their own.

* refactor(call): move lifecycle logging into CallMediaManager, remove bloc logs

* refactor(call): remove redundant platform guards in CallMediaManager lifecycle methods

* refactor(call): replace lifecycle methods with direct commands in CallMediaManager

* refactor(call): make enableSpeaker/disableSpeaker public, remove resetSpeaker wrapper

* refactor(call): merge enableSpeaker/disableSpeaker into single setSpeaker method

* refactor(call): move setUseManualAudio to _configure, remove onCallStarted

* revert(call): revert instance fields for static-only classes, keep static calls

* docs(call): restore doc comments lost during CallMediaManager refactor

* fix(call): address reviewer issues in CallMediaManager

- Add Platform.isAndroid guard to clearCommunicationDevice
- Fix double setSpeakerphoneOn(false) on iOS in onVideoDisabled
- Route Android non-speaker setDevice through setSpeaker for consistency
- Add catchError logging to fire-and-forget audio session callbacks
- Remove navigation comment from call_state.dart

* fix(call): address second round of reviewer issues

- Replace byName with exhaustive switch in fromCallkeep/toCallkeep
- Make setSpeaker/setDevice properly async with await
- Fix double Telecom call in Android non-speaker setDevice path
- Fix fire-and-forget audio session callbacks with unawaited+try/catch
- Rename mediaManager to _mediaManager (encapsulation)

* docs(call): update CallMediaManager class doc to reflect current responsibilities

* docs(call): document AudioSwitch activation constraint in onVideoEnabled
* fix: pass actual device ID to Telecom when switching audio on Android

setSpeaker() was creating an anonymous CallAudioDevice (id=null) and
passing it to Callkeep.setAudioDevice(), causing "Cannot set audio device:
null device id" on every speaker switch during a call.

setDevice() Android speaker branch now calls both Helper.setSpeakerphoneOn
and _callkeep.setAudioDevice with the real device (preserving its UUID).
Same fix applied to onVideoEnabled/onVideoDisabled which had the same
null-id pattern via setSpeaker(callId, ...).

setSpeaker() is now WebRTC-only (no Telecom call) since all live-call
routing goes through setDevice or the video helpers with the actual device.

* fix: revert Telecom routing from video helpers, use AudioSwitch only

onVideoEnabled/onVideoDisabled called _callkeep.setAudioDevice which
competed with AudioSwitch and prevented the speaker from switching on
Android when the user enabled video on an established audio call.

Video-triggered audio routing goes through AudioSwitch (setSpeakerphoneOn)
only — the same path used before PR #1216. Telecom routing via setAudioDevice
is reserved for explicit user-triggered device changes in setDevice().

* fix: restore Telecom routing in onVideoEnabled/onVideoDisabled

Log confirms AudioSwitch alone is insufficient: Telecom holds earpiece
routing (uid 1000) and overrides setSpeakerphoneOn(true) from the app
(uid 11391). Both APIs must be called to switch to speaker on video enable.

Restores _callkeep.setAudioDevice with the actual device ID from
availableAudioDevices, which was the missing piece to update the Telecom
route and let the app win the audio routing conflict.

* fix(call): skip speaker auto-enable for incoming video calls

Only route audio to speaker in _onVideoStreamReady when the call
direction is outgoing. Incoming video calls do not initiate video —
auto-enabling speaker on answer is unexpected for the user.

* fix(call): explicitly set VideoChat mode on iOS when video is enabled

With setUseManualAudio(true) active, WebRTC does not auto-switch the
AVAudioSession mode. onVideoEnabled now mirrors the reverse of
onVideoDisabled: sets AppleAudioMode.videoChat then calls setSpeakerphoneOn(true).

* fix(call): enable speaker for all video calls, not just outgoing

Speaker auto-enable on video applies to both incoming and outgoing
video calls. Reverts the outgoing-only direction guard added earlier.

* fix(call): update setSpeaker docstring and add warning on missing earpiece device

- Clarify that setSpeaker is also used for live iOS routing in setDevice
  (iOS has no Telecom layer, so Helper.setSpeakerphoneOn is sufficient there)
- Add logger.warning in onVideoDisabled when earpieceDevice is null on Android
  so a silent Telecom-skip is observable in field logs
#1226)

RenegotiationHandler.handle now calls getSenders() and getReceivers()
before createOffer to determine anyoneHasVideo, but the affected tests
did not stub these methods — causing a TypeError that short-circuited
execution before the verify assertions could pass.
Update flutter-webrtc resolved ref and webtrit_callkeep version to 1.0.1+0.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants