fix: fixed return button not working#935
Merged
SERDUN merged 1 commit intoMar 2, 2026
Merged
Conversation
Member
|
Need to clarify for not affect another flow |
- Remove canPop: false from EmbeddedRequestErrorDialog — default is true, and blocking pop prevented the system back button from working in Settings → Terms & Conditions when the page failed to load (WT-890). - Add onPopInvokedWithResult to call onBack when system back dismisses the dialog in Mode B (pushed route), ensuring _errorDialogShown is reset. - Reset _errorDialogShown in _handleEmbeddedErrorState when error is cleared, so repeated errors after system-back dismissal are shown again.
741646c to
612c1ca
Compare
SERDUN
added a commit
that referenced
this pull request
Apr 6, 2026
- Remove canPop: false from EmbeddedRequestErrorDialog — default is true, and blocking pop prevented the system back button from working in Settings → Terms & Conditions when the page failed to load (WT-890). - Add onPopInvokedWithResult to call onBack when system back dismisses the dialog in Mode B (pushed route), ensuring _errorDialogShown is reset. - Reset _errorDialogShown in _handleEmbeddedErrorState when error is cleared, so repeated errors after system-back dismissal are shown again. Co-authored-by: Dmytro Serdun <d.serdun@webtrit.com>
SERDUN
added a commit
that referenced
this pull request
Apr 10, 2026
* fix: add missed mock MicrophoneStatusBloc for screenshot builds (#806) * fix: change ice in media settings to abbreviation (#807) In media settings user can see ice and Ice. It was changed to ICE because it is abbreviation. Also settings_iceSettings_Section_tooltip in Italian language was changed because with abbreviation meaning was changed. * fix: restore contact name display from phone number lookup (#805) * fix: fixed displaying wrong name in keypad Fixed displaying inconsistent name hint in keypad view. Added storing raw number and sanitized versions together. * fix: fix comments * feat: remove trimming whitespaces from number * style: fix styling * fix: fixed not showing Google contacts in Android (#813) In this pull requests bug with not showing Google contacts in Android was not showing. App only displays contacts that saved on device. * fix: prevent forcing speakerphone when headphones are connected on iOS (#812) * feat: introduce configurable theme images with alignment and fit (#810) * feat: add style enums and extend ImageRenderSpec with alignment and fit - Introduce common style enums (BorderType, AlignmentConfig, BoxFitConfig) - Export new style_types module from common models - Extend ImageRenderSpec to support alignment and BoxFit options - Update JSON serialization/deserialization and Freezed models - Improve image rendering flexibility for theme-driven layouts * chore: add alignment to onboarding image render config - Set explicit center alignment for onboarding logo images - Update both light and dark theme JSON configs - Align theme configuration with new ImageRenderSpec capabilities * feat: add configurable theme image support with alignment and fit - Add AlignmentConfig and BoxFitConfig extensions for Flutter mapping - Introduce ThemeImageStyle and factory for theme-driven image styling - Implement ConfigurableThemeImage widget for rendering themed SVG assets - Wire new style and extensions into theme exports - Enable alignment, fit, padding, and scaling via theme configuration * refactor: migrate remaining onboarding/logo usage to ConfigurableThemeImage - Replace OnboardingLogo/OnboardingPictureLogo usages with ConfigurableThemeImage - Extend login screen styles to use ThemeImageStyle + onboarding text style - Refactor About screen to render themed logo via ThemeImageStyleFactory - Update style factories to build pictureLogoStyle from mainLogo config - Remove legacy onboarding logo widgets and exports * style: migrate login screenshots to ConfigurableThemeImage * feat: add background support to screen styles using ThemedScaffold (#814) * feat: add page background models and settings page config * feat: add gradient page backgrounds to login/settings/keypad configs * feat: add page background config > style mapping * feat: allow passing backgroundColor and elevation to MainAppBar * feat: introduce BackgroundStyle and ThemedScaffold * feat: add background support to screen styles and use ThemedScaffold * feat: introduce BackgroundStyle and ThemedScaffold * refactor: remove extension for external contact (#816) Extension for external contact was removed. Fetter for safeSourceId was tranferred to ExternalContact model. * feat: extend AppBar and TabBar theming via config-driven factories (#815) * refactor: extract and extend appearance theme config models * refactor: improve GroupTitleList style merging and theming * feat: extend AppBar and TabBar theming via config-driven factories - Add IconThemeConfig extensions to map config to Flutter IconThemeData - Replace string-based border types with BorderTypeConfig enums - Expand AppBarThemeDataFactory to support full AppBarConfig options - Enhance TabBarThemeDataFactory with configurable indicators, alignment, animation, and splash - Wire new AppBarConfig and TabBarConfig through ThemeStyleFactoryProvider - Remove hardcoded TabBar indicator in ExtTabBar in favor of theme-driven styling * docs: document TabBarConfig and AppBarConfig usage * docs: clarify BorderConfig documentation and formatting * refactor: replace TabBar string options with enums and typed extensions * refactor: unify AppBar config mapping and update TabBarConfig JSON model (#818) * fix: not available media (#817) Fixed bug with no available media for 20 seconds in Firefox after enabling video. * feat: add themed SettingsScreen styles and support item iconColor (#819) * feat: add themed SettingsScreen styles and support item iconColor * fix: use resolved effective settings style for icons and text * refactor: fix typo by renaming GroutTitleListStyle to GroupTitleListStyle * docs: add documentation for SettingScreenStyle * fix: include background in SettingScreenStyle merge and lerp * chore: fix import to use GroupTitleListStyle definition * docs: document SettingsTile and tidy widgets exports * chore: reorder imports and constructor parameters for consistency * fix: harden Firebase background message handling and error reporting (#820) * fix: guard background message handler with runZonedGuarded * refactor: pass logger into background message handler * fix: guard contact lookup in background isolate with fallback on DB errors * fix: report background handler errors to Crashlytics - rename _initFirebase to _initFirebaseApp for clarity - initialize Firebase in background isolate before Crashlytics usage - record unhandled bg isolate errors via Crashlytics + logger * fix: initialize Firebase before guarded background handler execution * fix: await Crashlytics error reporting in background handler (#821) * feat: unknown sdp profiles filter (#822) * fix: sdp profiles filter hotfix (#824) * refactor: clarify camera soft-mute strategy and RecvOnly limitation (#823) * fix: hide local camera preview when video is logically muted * docs: document soft-mute camera strategy and RecvOnly server issue * docs: fix typos and complete camera preview comment * fix: hide initial online snackbar (#826) * feat: conversation screen title align (#825) * refactor: improve local camera preview overlay UI (#827) * refactor: extract local camera preview overlay widget * refactor: tweak camera preview overlay switch icon position * refactor: round local camera preview overlay corners * style: fix formatting in LocalCameraPreviewOverlay * refactor: rename localePlaceholderBuilder to localPlaceholderBuilder * docs: expand LocalCameraPreviewOverlay API documentation * refactor: styled chat messages improvements (#828) * fix: stabilize draggable thumbnail by tracking offset state (#831) * fix: stabilize draggable thumbnail by tracking offset state * fix: add drag safety checks to prevent thumbnail jumping * docs: add explanatory comments for draggable thumbnail bounds and snapping * feat: auto-hide call controls with compact mode controller (#829) * test: add fake_async as a dev dependency * refactor: export extensions from call barrel file * feat: add ActiveCall list auto-compact extension * feat: add CompactAutoResetController for auto-hide UI controls * test: add coverage for CompactAutoResetController and auto-compact extension * feat: add auto-hide compact controller to CallActiveScaffold * refactor: simplify CallActions callbacks and remove redundant setState * feat: add remote video fit toggle and blurred background (#830) * feat: add auto-hide compact controller to CallActiveScaffold * refactor: simplify CallActions callbacks and remove redundant setState * feat: add configurable objectFit to RTCStreamView * feat: add remote video fit toggle via RTCVideoViewObjectFit extension * refactor: extract RemoteVideoViewOverlay widget * feat: add blurred background for remote video in contain mode * feat: add video view options menu for fit and blur toggles * refactor: show blur toggle only for contain fit * feat: add video background modes and refactor remote video options * refactor: clarify fit/background mode extensions and rename toggle helpers * feat: localize call video view and background toggle labels * refactor(mesaging): better space reuse (#832) * chore: sync generated localizations mapper (#834) * fix: emit state update when toggling existing video track (#833) Previously, when toggling the camera on an active call with an existing video track, the track's enabled state was modified, but the BLoC did not emit the updated state. This caused the UI button to become desynchronized from the actual camera state (e.g., camera turns off, but button stays active). This commit adds the missing emit call to ensure the activeCall.video property is updated in the state, triggering the necessary UI rebuilds. * fix: fix not selectable slider value (#838) Fixed not selectable value on slider * fix: fix core version label (#835) Added label for core version on About screen. * fix: messaging push notifications (#841) * fix: wrong source identifier * fix: remote push structure * refactor: single DB creation point for isolates + lifecycle holder + IsolateDatabase.use() (#840) * fix: pass app documents directory to IsolateDatabase.create - initialize AppPath in background handlers and call services isolate - update IsolateDatabase.create to require directoryPath - ensure drift DB uses correct documents location in isolates * refactor: remove cached instance from IsolateDatabase.create * refactor: validate create() args with asserts * refactor: make IsolateDatabase a sync factory and add lifecycle holder - Convert IsolateDatabase to an abstract final helper that returns AppDatabase (no caching, sync create()). - Update background/workmanager + isolate services to use sync create() (no await). - Provide AppDatabase via AppDatabaseLifecycleHolder to close DB on app detach; add refactor notes in main. * refactor: add IsolateDatabase.use() helper for safe per-isolate DB access - Introduce IsolateDatabase.use<T>() to open, run action, and always close the database (with optional onError fallback). - Refactor background FCM handlers to persist pushes / resolve contacts via IsolateDatabase.use(). - Simplify WorkManager system notifications task flow and wrap local DB usage in IsolateDatabase.use() with robust logging. * refactor: cache AppDatabase and add common dependency disposal - Keep a shared AppDatabase instance in the call isolate to reuse across repositories. - Add _disposeCommonDependencies() to close DB/isolate managers and reset cached singletons. - Release resources via _disposeCommonDependencies() on push sync releaseResources callback. * docs: clarify isolate DB usage and lifecycle handling - Expand IsolateDatabase docs with concurrency/locking guidance and recommended patterns. - Add TODO note for deterministic cleanup in signaling isolate callback. - Simplify/condense main.dart Provider comments for AppDatabase lifecycle holder wiring. * refactor: introduce AppDatabaseScope and reorganize isolate DB helpers - Add AppDatabaseScope for scoped/try/fallback DB access (use, tryUse, useOrNull) - Replace IsolateDatabase.use usages with AppDatabaseScope in bootstrap background flows - Move IsolateDatabase into common/db module and re-export via common.dart - Remove scoped-use logic from IsolateDatabase, keeping it as a pure factory helper * fix: return false on unhandled task error to allow retries WorkManager tasks now report failure on unexpected exceptions so backoff/retry policy can apply; add inline comment explaining why. * docs: warn about unawaited db.close() on app detach * docs: refine AppDatabaseScope/IsolateDatabase comments * fix: fixed non-alphabetic contacts ordering (#808) * fix: fixed non-alphabetic contacts ordering Implement sorting of contacts by display name. * fix: added sorting by phone number Added sorting by phone number in case when alias name, first name and last name are null. * fix fix changing info icon color (#836) * fix fix changing info icon color Fixed bug when info icon color changes when user opens and closes menu section in media settings. * refactor: use color from color scheme * fix: enable WAL + busy_timeout and add isolate DB concurrency stress tests (#843) * feat: enable WAL and busy_timeout for background NativeDatabase connection * test: add DB concurrency stress tests with isolate writes and WAL file cleanup * test: simplify db concurrency test writes * feat: make WAL and busy_timeout configurable for drift connections * docs: clarify WAL/timeout options are ignored on web connection * test: cleanup temp app dir after DB concurrency tests * test: use path.join for database file cleanup helper * refactor: rename busyTimeoutMilliseconds to busyTimeoutMs and simplify native config * refactor: make native busyTimeoutMs nullable to match cross-platform connection API * refactor: increase write jitter and document concurrency test constants * refactor: log cleanup failures for temp DB files and test directories * fix: fixed wrong ordering of recent calls (#845) Fixed bug that sometimes recent calls is sorted incorrectly * fix: fixed label recent calls (#839) Fixed labels when user does not have recent and missed calls. * refactor: centralize contact synchronization logic in repository layer (#844) * refactor: move local/external sync logic into ContactsRepository data source * test: add fixtures and unit coverage for ContactsLocalDataSource sync/upsert * refactor: use utils barrel in contacts sync blocs * style: reorder contactsRepository param for consistency * docs: clarify external contacts sync legacy cleanup * refactor: batch delete contacts by sourceId list during sync * refactor: parallelize contact sync and batch upsert phones/emails * fix: use explicit conflict targets for batch upserts of contact phones/emails * test: extend ContactsLocalDataSource coverage for emails and sync details * fix: throw when ContactsLocalDataSource is missing for sync methods * fix: handle user info fetch errors before syncing external contacts * fix: diff external sync by safeSourceId and update fixtures/tests for null id * test: add unit tests for ExternalContactsSyncBloc and LocalContactsSyncBloc * docs: add TODO to clarify external contacts self-filtering logic * fix: close menu section on opening other (#837) * fix: close menu section on opening other Added closing menu section when user opens another in media settings. * refactor: migrate media settings sections to ExpansionPanelList and enhance HeadingSection --------- Co-authored-by: Dmytro Serdun <d.serdun@webtrit.com> * refactor: centralize local contact phone number sanitization in ContactsLocalDataSource (#846) * chore: sync translations and regenerate localizations (#847) * fix: presence params in group add dialog (#848) * fix: ensure auto-compact mode only triggers for the current active call (#849) * refactor: connectivity check (#853) * fix: presence indicator empty color (#854) * fix: external contacts sync kind overwrite (#850) * fix: chat appbar name doubling (#851) * refactor: hide personal data and actualize code in dialog info (#852) * refactor: expose contact kind * refactor: actualize dialog info ui * fix: presence settings presets style (#855) * refactor: extract sip presence indicator * fix: remove leading avatar + handle overflow for preset dropdown * fix: missed contact kind commit (#856) * fix: presence settings hint clip (#857) * refactor: contact presence view (#859) * feat: add draggable thumbnail overlay manager and local camera preview overlay (#858) * refactor: extract DraggableThumbnail into widgets * refactor: extract thumbnail avatar overlay helpers * docs: add documentation for CallDisplay enum * feat: refactor floating thumbnail overlay and extract StreamThumbnail * refactor: extract thumbnail overlay manager and simplify CallActiveThumbnail * refactor: simplify local camera preview overlay for draggable use * feat: manage thumbnail overlays via manager + add shimmer placeholder * refactor: move CallActiveThumbnail to widgets and update imports * refactor: rename LocalCameraPreviewOverlay to LocalCameraPreviewThumbnail * feat: extract thumbnail layout sizing utils * docs: improve thumbnail widget docs and fix navigatePath comment * test: add integration coverage for draggable thumbnail, overlay manager, and shimmer widget * refactor: improve DraggableThumbnail docs and rename internals for clarity * test: add StreamThumbnail widget tests with mocked FlutterWebRTC channels * refactor: rename Shimmer state class for clarity * docs: clarify ThumbnailOverlayManager overlay update behavior * fix: guard thumbnail overlay rendering when no active calls * feat: add material list padding constant and sticky overlay padding * fix: update overlay video listener to use cameraEnabled * refactor: use StreamThumbnail in LocalCameraPreviewThumbnail * fix: guard StreamThumbnail renderer init/dispose race * refactor: split camera state into track presence and active flag * fix: refactor StreamThumbnail sizing to resolve parent layout conflicts * feat: add ContactResolver for thumbnail avatar overlay * refactor: centralize shimmer placeholder config for call thumbnails * refactor: simplify StreamThumbnail and move overlay to CallActiveThumbnail * refactor: derive local preview theme values inside LocalCameraPreviewThumbnail * refactor: rename CallCard variables to Thumbnail for consistency * test: update expected base color to surfaceContainerHighest * refactor: use semantic naming (portrait/auto) and optimize utils (#860) * refactor: rename PreferredOrientation values * refactor: extract preferred orientation lists into constants * feat: respect system auto-rotate setting in auto mode (#861) * feat: add Flutter plugin to detect system auto-rotate setting on Android * feat: respect system auto-rotate in auto mode and add integration tests - inject DeviceRotationUtil into OrientationsBloc and listen to auto-rotate stream - apply portrait vs all orientations based on ACCELEROMETER_ROTATION when in Auto mode - cancel rotation subscription on bloc close - move orientations helpers under features/orientations and export utils barrel - wire OrientationsBloc in App with DeviceRotationUtil - add OrientationsBloc integration test covering portrait/auto behavior and stream updates - add device_auto_rotate as a path dependency * docs: clarify stream error handling default behavior * chore: fix pubspec build number length (#863) * fix: stabilize error group id and extract DiagnosticType labels (#868) - extracted DiagnosticType.label into a dedicated extension for reuse and readability - built error group from sorted DiagnosticType labels to keep grouping deterministic - removed timestamp-based uniqueness to avoid creating a new Crashlytics issue on every occurrence * fix: change ice in media settings to abbreviation (#869) In media settings user can see ice and Ice. It was changed to ICE because it is abbreviation. Also settings_iceSettings_Section_title and settings_iceSettings_Section_tooltip in Italian language was changed because with abbreviation meaning was changed. * fix: add additional check for setting source object (#866) * fix: add additional check for setting source object Added additional check when setting source object for renderer to prevent error when call initialize before setting the stream * refactor: move condition for dispose and _updateSrcObject to getter * fix: fixed delaying when updating seen status (#867) * fix: fixed delaying when updating seen status Fixed delaying when changing seen status flag for voicemails * test: added test for changing voicemails seen status * refactor: replace seen flag with ReadStatus and harden state emits * docs: document SizedCircularProgressIndicator sizing behavior * refactor: extract subtitle and animate unread indicator * fix: disable mark-as-read toggle while status is unknown * refactor: centralize cached emission and restore db status on fetch failure --------- Co-authored-by: Dmytro Serdun <d.serdun@webtrit.com> * refactor: move common isolate logic into SignalingManager (#873) * refactor: move common isolate logic into SignalingManager * refactor: rename NotificationIsolateManager to IsolateManager * fix: await signaling manager launch/dispose in foreground isolate sync * feat: add call log event lookup and incoming video SDP helper extensions (#874) * feat: add call log event lookup and incoming video SDP helper extensions * chore: add TODO notes for call line logs extensions tests * feat: show missed call notification and log call from isolate hangup events (#875) * refactor: rename isolate manager callbacks/services for clarity and unify error logging (#877) * refactor: remove deprecated performSetSpeaker stub from CallBloc (#878) * fix: improve list separation and multi-select deletion UX (#880) * fix: fixed voicemails separation Added divider to show voicemail separation clearly. * feat: added deletion of selected voicemails * test: added tests for multiple voicemails deletion * feat: add PlainListTile widget * style: tighten AudioSlider time labels layout * feat: add ReadStatus l10n/toggle extension * refactor: migrate VoicemailTile to PlainListTile and ReadStatus extensions * feat: show selected count badge on delete action --------- Co-authored-by: Alex Maudza <o.maudza@webtrit.com> * chore: remove AppBlocObserver wiring (#881) * feat: add LoggerPretty extension with chunking and safe formatting (#879) * feat: add LoggerPretty extension with chunking and safe formatting * test: cover null payload handling in LoggerPretty * feat: enhance PeerConnectionManager with RTP traffic monitoring, disposal barrier, and pretty JSON logging (#864) * refactor: extract PeerConnectionManager logic and implement disposal barrier - Extract `RTCPeerConnection` lifecycle management (create, retrieve, dispose) into `PeerConnectionManager`. - Implement `PeerConnectionFactory` interface to abstract native WebRTC calls for testability. - Add "Disposal Barrier" mechanism to prevent race conditions during rapid call cycling. - Remove inline `Completer` logic from `CallBloc`, simplifying the state machine. - Add unit and integration tests covering race conditions, zombie connections, and timeouts. * test: migrate PeerConnectionManager tests to integration_test binding * refactor: make DefaultPeerConnectionFactory configurable and reuse default ICE config * docs: clarify PeerConnectionManager factory responsibility for connection configuration * test: update PeerConnectionManager unit tests for factory API and verify close on replacement * fix: log dispose errors when cleaning up peer connections * feat: add RTP traffic monitoring with scoped logging and wire into PeerConnectionManager * refactor: simplify RTP monitor logging and adjust configuration notes * refactor: clarify PeerConnectionManager variable naming and improve event logging * docs: document non-awaited peer connection disposal and disposal barrier rationale * refactor: simplify RTP traffic logging delegate configuration * refactor: inject PeerConnectionManager from MainShell * fix: align logPretty calls with new (data, tag) signature after rebase * fix: reset iOS audio route on call session start/end to prevent sticky speaker (#876) * refactor: migrate FeatureAccess from *Feature to *Config with mappers (#882) * feat: implement configurable call actions in chat contact info (#883) * feat: add configuration support for video call button in contact info * chore: enable video call button in chat contact info by default * refactor(messaging): conversations screen improvements (#884) * chore: remove unused PendingCall subscription cleanup (#888) * fix: evaluate feature flag dynamically in FeatureGuard (#886) * refactor: make configs equatable for reliable state comparison (#887) * refactor: make configs equatable for reliable state comparison * refactor: enforce immutability for feature and embedded configs * refactor: make FeatureAccess reactive to SystemInfo changes via StreamProvider (#885) * refactor: make FeatureAccess reactive to SystemInfo changes via StreamProvider * refactor: support dynamic configuration updates in AppRouter * refactor: derive voicemailsEnabled in SettingsConfig * refactor: simplify FeatureChecker and remove resolver indirection * refactor: watch FeatureAccess in widget tree and add logging * refactor: extract FeatureAccess stream creation into helper method * refactor: remove unused FeatureResolver typedef * fix: audio device button interaction and visual selection state (#889) * fix: prevent disabled state for audio device selection button * fix: sync audio button selection state in popup menu mode * refactor: improve voicemail audio playback stability and error handling (#890) * refactor: improve voicemail audio playback stability and error handling * refactor: enhance audio player disposal and retry logic * fix: handle session_missing error explicitly (#891) * chore: disable overwrite and reviewed flags on upload (#892) * fix: solve "sticky speaker" issue by localizing audio state (#895) Moved `speakerOnBeforeMinimize` from global `CallState` to `ActiveCall` model to ensure audio preferences are call-specific and properly disposed of when a call ends. - Fixes a bug where a new audio call would erroneously start on speakerphone if a previous call (or transfer session) had speaker enabled. - Updated `CallBloc` to preserve and restore speaker state using the specific `ActiveCall` instance during minimization, transfers, and screen transitions. - Cleaned up `CallState` to prevent stale audio state leakage between unrelated call sessions. * fix: prevent duplicate outgoing calls using hybrid event transformer (#897) * fix: change CallControlEvent transformer to droppable * fix: use hybrid transformer for CallControlEvent to prevent duplicate calls * fix: expand small touch target size for menu button (#894) * fix: expand small touch target size for menu button Fixed small tap target zone for popup menu button in voicemail tile * refactor: improve voicemail menu button interaction and hover shape Replaced the 'child' property with 'icon' in PopupMenuButton to fix the square hover effect. This change ensures a standard Material circular splash and restores the correct touch target size. --------- Co-authored-by: Dmytro Serdun <d.serdun@webtrit.com> * refactor: cache CallBloc to avoid unsafe context access (#899) * refactor: cache CallBloc to avoid unsafe context access * chore: sync project metadata and adjust initialization order * chore: sync lockfiles and regenerate localization mappers (#900) * fix: prioritize config color for avatar initials (#901) Previously, the LeadingAvatarStyleFactory forcibly overrode the text color with `onSecondaryContainer`, ignoring the value from the configuration. This change ensures the configuration color is used if provided, falling back to the theme default only when necessary. * style: apply initials text color to loading and placeholder icons (#902) * feat: support initial expansion state in MediaSettingsScreen (#903) - Add initialOpenSection parameter to MediaSettingsScreen - Implement MockMediaSettingsCubit for screenshot testing - Register MediaSettingsScreenScreenshot in the screenshot router * fix: media settings panel color (#906) * style: set background color for expansion panels * docs: add TODOs explaining the dark mode background override * feat: implement ProgressIndicatorThemeData with Material 3 surface mapping (#907) * fix: correct foreground color for app bar actions (#908) * refactor(messaging): enhance GroupAvatar styling and logic (#910) * chore(lefthook): move heavy checks to pre-push and add format check (#912) * feat: implement declarative screen styling and theme override system (#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: revert changes in bottom_menu_feature.dart (#913) * fix: fixed formatting on about screen (#905) * 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> * fix: fixed contacts ordering (#904) Added collate method with no case option when sorting contacts * feat: implement feature configuration system and unified dark theme support (#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: standardize theme mode types and naming (#915) * refactor: rename ThemeModeConfig.auto to ThemeModeConfig.system * refactor: replace custom ContentThemeOverride with standard ThemeMode * refactor: rename toContentThemeOverride to toThemeMode * fix: correctly handle nullable contentThemeOverride * refactor: utilize ThemeProvider in ThemedScaffold for consistent overrides (#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. * fix: support dark mode in screenshot generation (#918) * fix: remove hardcoded colors to allow context-based inheritance (#917) * refactor: migrate login screens to ThemedScaffold and enhance styling models (#919) * fix: polish theming and fix active call overlay (#920) * 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: sync localizely keys and update plural logic (#921) * refactor(app): implement safe teardown sequence for user logout (#922) * 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: Implement dynamic styling for LoginSwitch segmented button (#925) * 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 * refactor: convert CallPopupMenuButton to StatelessWidget and disable click effects (#923) * refactor: replace custom tab buttons with standard TabBar and add unread 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 (#926) * 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 * fix(contacts): prevent race condition in LocalContactsSyncBloc during teardown (#927) * refactor: re-engineer actionpad styling to semantic roles (#928) * 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 * fix(call): increase monitor interval to optimize log quota (#929) * 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. * feat: enhance RemoteConfigService with snapshots and stream support (#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 * feat: dynamic RTP traffic monitoring interval and configuration synchronization (#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. * feat: generalize emergency reboot logic and refine diagnostic reporting (#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> * refactor: add controller to HistoryAutocompleteField for manual history 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 * feat: migrate system notifications and SIP presence to supported features (#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. * fix: collect outbound rtp stats (#937) * fix: description for Media encoding configs (#938) Fixed description for Media encoding configs in Media settings. Full Flex option was removed, other options were renamed. * fix(call): preserve widget context during auto-compact transition (#939) Refactor CallActiveScaffold to use AnimatedOpacity and IgnorePointer instead of conditional widget removal. This ensures that CallActions remains in the widget tree, preventing callback failures (e.g., onAudioDeviceChanged) when the UI compacts while a popup is open. * fix: fixed localization issues in network settings (#936) Fixed issues with localization in network settings. Also added radio buttons and checkbox for 'Incoming Call Type' and 'SMS Fallback' accordingly * chore: actualize l10n code generation (#940) * chore: actualize configurations (#942) * chore: implement modular rules system (#943) * chore: implement modular rules system and copilot instructions (#946) * chore: implement modular rules system and copilot instructions * chore: add makefile helpers for copilot branch management * feat: add theme switching settings (#951) * chore: implement modular rules system and copilot instructions * feat(settings): expose theme mode switching in settings --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> * feat: remote cli settings (#944) * feat: support metadata for missed call display name in isolate (#953) * feat: support metadata for missed call display name in isolate - Introduce getDisplayNameForMissedCall in IsolateManager to resolve caller names. - Update PushNotificationIsolateManager to utilize CallkeepIncomingCallMetadata for missed call notifications. - Pass metadata through onPushNotificationSyncCallback and onSignalingSyncCallback. - Ensure LocalPushRepository returns the future from the notification plugin. * docs: add documentation for IsolateManager properties * refactor: improve missed call display name logic and logging * feat(call): override _onNoActiveLines in PushNotificationIsolateManager for missed call handling (#959) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: SERDUN <26858237+SERDUN@users.noreply.github.com> * chore: ai agent pipeline refinement (#954) * chore: ignore agent files * docs: clarify commit message convention * chore: enforce lowercase commit descriptions and update rules docs * docs: refine AI agent execution pipeline and commit rules * fix: user id migration (#960) * refactor: replace SupportedMonitorConfig with SupportedLoggingConfig (#957) * refactor: replace SupportedMonitorConfig with SupportedLoggingConfig * refactor(logging): decouple AppLogger from RemoteConfigSnapshot - make kLogLevelKey private in FeatureOverridesFactory - add remoteLoggingEnabled to FeatureOverrides and LoggingConfig - rewrite AppLogger.init() to accept LoggingConfig instead of RemoteConfigSnapshot - add watchFeatureAccess() to observe loggingConfig changes via FeatureAccess stream - add LoggingMapper.mapFromOverridesOnly() for isolate/background contexts - update bootstrap.dart and services_isolate.dart call sites Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor(logging): replace direct FeatureAccess stream with applyConfig in AppLogger - remove watchFeatureAccess() and StreamSubscription from AppLogger - add applyConfig(LoggingConfig) as the single public update point - call applyConfig in App.didChangeDependencies() via existing FeatureAccess watch - remove redundant feature_access.dart import from app_logger.dart Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor(logging): narrow AppLogger.applyConfig to accept Level directly - applyConfig now takes Level instead of LoggingConfig - caller extracts loggingConfig.logLevel before passing to AppLogger Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor(logging): remove LoggingConfig dependency from AppLogger - init() now accepts Level and bool remoteLoggingEnabled as primitives - remove models.dart import from app_logger.dart - extract primitives from LoggingConfig at each call site Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor(logging): remove EnvironmentConfig dependency from AppLogger - init() now accepts logzioLogLevel and pre-built List<RemoteLoggingService> - store logzioLogLevel as field for use in applyConfig - move _buildRemoteLoggingServices to bootstrap.dart and services_isolate.dart - remove environment_config.dart import from app_logger.dart Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor(logging): simplify AppLogger by replacing List<RemoteLoggingService> with LogzioLoggingService? - add LogzioLoggingService.fromEnvironment() factory to encapsulate EnvironmentConfig logic - AppLogger.init() now accepts LogzioLoggingService? instead of List<RemoteLoggingService> - remove _buildRemoteLoggingServices helpers from bootstrap.dart and services_isolate.dart Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor(logging): move logzioLogLevel resolution into LogzioLoggingService.fromEnvironment - fromEnvironment() now reads REMOTE_LOGZIO_LOG_LEVEL internally, no longer takes minLevel param - AppLogger.init() drops logzioLogLevel parameter, uses _logzioService?.minLevel in applyConfig - remove environment_config.dart import from services_isolate.dart Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(logging): add unit tests for LoggingMapper and FeatureAccessStreamFactory logging fields Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(call): compare monitorCheckInterval directly to avoid spurious CallBloc updates Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * chore: update Flutter to 3.41.2 and suppress experimental_member_use warnings (#962) * chore: update Flutter to 3.41.2 and suppress experimental_member_use warnings Both LockCachingAudioSource (just_audio) and TableMigration (drift) are the only available APIs for their respective tasks with no stable alternatives. Added ignore directives with explanatory comments. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: bump minimum Flutter SDK constraint to 3.41.2 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * chore: add claude code team settings (#965) * chore: add Claude Code team settings and documentation Add team-level `.claude/settings.json` with deny rules for keystores, signing keys, and environment files. Add `.claude/settings.local.json` to `.gitignore` and document settings levels in `docs/development.md`. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: add safe tool allow rules to team settings Allow flutter test, analyze, dart format, dart fix, pub get, and gen-l10n commands in team settings for all developers. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * fix: remove unused import in messaging_shell.dart (#966) Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * fix(settings): add optimistic toggle with spinner for register status switch (#967) Replaces the blocking register status toggle with an optimistic update pattern — the switch flips immediately, shows a spinner, disables during the API call, and reverts on failure. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * fix: skip static payloads sanitizing if no rtpmap (#968) * fix: media preset translation (#969) * fix: fix splash config generation and add android_12 image support (#865) - Escape # in Makefile color variables to prevent shell comment issues - Remove redundant shell parameter expansion, use Make-only expansion - Add ANDROID_12_SPLASH_IMAGE variable for separate Android 12 splash - Use single quotes in echo to avoid shell interpretation problems Co-authored-by: Dmytro Serdun <d.serdun@webtrit.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * chore: add diagnostic logging for recents number mismatch (#970) Add hash-based and raw value logging to trace why recent calls list shows two different phone numbers per entry. Raw values are visible when Logz.io anonymization is disabled, hashes work with it enabled. * feat: add support a blurred appbar surface (#971) * feat: add BlurredSurface widget and apply blur effect to all app bars Extract common ClipRect+BackdropFilter pattern into reusable BlurredSurface widget. Apply consistent blur background with extendBodyBehindAppBar to all screens using MainAppBar or AppBar. Remove isComplexBackground conditional logic. * refactor: standardize MainAppBar field order across screens * feat: apply blurred appbar surface to ConversationsScreen * fix: remove unused theme/theme.dart imports * fix: add top padding to prevent content overlap with blurred appbar Screens using extendBodyBehindAppBar lacked compensating padding, causing body content to be hidden behind the blurred AppBar. Added appropriate top padding to recents, favorites, about, contacts, and CDR screens consistent with the existing settings screen approach. * feat(screenshots): make IgnorePointer configurable in ScreenshotApp (#972) Add ignorePointer parameter (default true) to allow enabling interaction for specific screenshots that require it. * feat: add screenshot mocks and docs for all features (#973) * feat: add screenshot mocks for all features Add ~20 new screenshot definitions covering all user-visible screens: - Core flows: contact, chat conversation, SMS conversation, system notifications, recent CDRs, number CDRs, call log - Settings: network, language, diagnostic, caller ID, presence, theme mode, voicemail - Login: switch screen - Utility: log records console, contacts agreement, teardown, permissions, user agreement Includes shared mock data, mock blocs/cubits/repositories, screenshot widgets, router entries, and integration test registrations. * docs: add screenshots package documentation Add docs for screenshots architecture, mock reference, data reference, and step-by-step guide for adding new screenshots. Update README with links to the new documentation. * fix: address review feedback for screenshot mocks and docs - Add provider as direct dependency in screenshots/pubspec.yaml and remove ignore: depend_on_referenced_packages from 7 files - Fix mock type/factory mismatches in mocks.md documentation - Fix broken code formatting in mocks.md and adding_screenshots.md - Remove shadowed ChatTypingCubit/SmsTypingCubit BlocProviders from conversation screenshot wrappers - Guard registerFallbackValue with static flag to prevent duplicate registration - Fix duplicated phrase in README.md Firebase Hosting section - Document both flutter test and flutter drive workflows in overview.md * feat: update keystores path to new applications subdirectory (#974) * feat: add text style background decoration support (#975) * feat: add backgroundBorderRadius and backgroundPadding to TextStyleConfig * feat: add greetingTextStyle to LoginModeSelectPageConfig * feat: add ExtendedText widget with background decoration support * feat: add greetingTextStyle to login.modeSelect docs * feat: add tests for ExtendedText widget * feat: update theme template configs with missing components and fixes (#976) - Fix calStatuses typo to callStatuses in both widget configs - Add missing widget sections as examples: button, group, bar, input, text, confirmDialog - Fix non-existent dark SVG asset references to use existing logos - Remove unsupported fontFeatures from page text styles - Clean up dark page config duplicate keys in login.switchPage - Add greetingTextStyle for dark login modeSelect visibility - Update dark widget decoration gradient config * refactor: replace deprecated GradientColorsConfig with PageBackground (#977) * refactor: replace deprecated GradientColorsConfig with PageBackground Remove GradientColorsConfig, DecorationConfig, GradientsStyleFactory, and Gradients theme extension. Migrate login and call screens to use PageBackground via ThemedScaffold. * refactor: enable gradient backgrounds for login and call page configs Activate background for login modeSelect and add gradient background to dialing section using colors from the removed GradientColorsConfig. * refactor: adjust dark login gradient to contrast with buttons Change bottom gradient color from #000000 to #0A1929 to avoid blending with neutralOnDark button surface color. * refactor: reverse dark login gradient direction Start with dark color on top, lighter blue on bottom to keep contrast with buttons at the bottom of the screen. * refactor: rename misleading onSurface variable to surfaceColor * feat: appBar blurred surface config and theme updates (#979) * feat: extract appBar background color and blurred surface to config pipeline Add configurable appBarBackgroundColor and appBarBlurredSurface (color, sigmaX, sigmaY) to the theme config→style pipeline across all 7 main screens, replacing hardcoded values. * feat: update page configuration docs with appBar fields Add Common page fields section documenting appBarBackgroundColor and appBarBlurredSurface, update Keypad page example. * feat: remove appBarBackgroundColor from config pipeline AppBar backgroundColor is already handled by MainAppBar fallback chain (explicit → appBarTheme → canvasColor.withAlpha). Remove redundant appBarBackgroundColor from BasePageConfig, all page configs, screen styles, factories, and screens to avoid confusion. * feat: add BlurredSurface.fromStyle factory and simplify screen usage Replace inline BlurredSurface construction with fromStyle() factory that returns null when no config is present (no blur applied) or a configured BlurredSurface with resolved sigma defaults of 10. * feat: enable appBar blur and activate widget config sections - Add appBarBlurredSurface to all 7 page sections (dark/light) - Activate previously ignored widget config sections (_bar, _button, _group, _input, _text -> remove underscore prefix) - Set appBar backgroundColor/surfaceTintColor to transparent for blur - Fix tab indicator label contrast and remove divider line * feat: update light theme appBarBlurredSurface color to soft blue-white * feat: set dark status bar icons for light theme appBar * feat: fix appBarBlurredSurface sigma defaults and add tests - Make sigmaX/sigmaY nullable in BlurredSurfaceConfig so omitted fields resolve to 10 via BlurredSurface.fromStyle instead of staying 0 - Regenerate Freezed/JSON for BlurredSurfaceConfig - Restore blur on RecentCdrs and SystemNotifications screens by passing sigmaX: 10, sigmaY: 10 explicitly to const BlurredSurface() - Explicit Colors.transparent fallback in BlurredSurface Container child - Update docs to reflect null defaults with 10 resolved at widget layer - Add BlurredSurface.fromStyle widget tests (4 cases) * fix: system back button blocked in EmbeddedRequestErrorDialog (#935) - Remove canPop: false from EmbeddedRequestErrorDialog — default is true, and blocking pop prevented the system back button from working in Settings → Terms & Conditions when the page failed to load (WT-890). - Add onPopInvokedWithResult to call onBack when system back dismisses the dialog in Mode B (pushed route), ensuring _errorDialogShown is reset. - Reset _errorDialogShown in _handleEmbeddedErrorState when error is cleared, so repeated errors after system-back dismissal are shown again. Co-authored-by: Dmytro Serdun <d.serdun@webtrit.com> * fix: prevent keepalive write-after-close and harden transaction lifecycle (#870) * fix: prevent keepalive write-after-close and add regression test * fix: clean up transaction on send failure in _executeTransaction Move _addMessage inside the try-catch block so that if writing to a closed socket throws WebtritSignalingBadStateException, the transaction is properly removed from the _transactions map instead of leaking. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * test: add transaction cleanup and keepalive timer tests Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: add comment for closeCode guard in keepalive loop Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address Copilot review comments on keepalive integration tests - Fix compile error: replace called(greaterThan(0)) with called(1) - Fix lint warning: await streamController.close() in tearDown - Fix false-positive test: move closeCode mock before flushMicrotasks and add verify closeCode called(2) to catch timer-restart regression --------- Co-authored-by: Dmytro Serdun <d.serdun@webtrit.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * fix: fixed app is locked in Bluetooth call profile after first call (#982) * fix: fixed app is locked in Bluetooth call profile after first call Fixed situation when user returns to YouTube or music playback, the audio remains degraded, like during a call. This happens only on Android. * fix: improve comment for _onLastCallEnded to cover iOS and Android audio routing --------- Co-authored-by: Dmytro Serdun <d.serdun@webtrit.com> * feat: add melos v7 integration (#980) * feat: add melos integration with codegen, test, format, and dependency scripts * feat: fix melos scripts syntax for v7 and rename format to fmt * feat: configure melos v7 with workspace and scripts in pubspec.yaml - Add workspace: list to pubspec.yaml for Dart pub workspace / Melos v7 package discovery - Add melos: section to pubspec.yaml with scripts for codegen, tests, formatting, dependencies - Add resolution: workspace to all sub-package pubspec.yaml files - Fix ssl_certificates publish_to typo (none; → 'none') - Update screenshots SDK constraint to ^3.8.0 - Remove melos.yaml (ignored by Melos v7 — config lives in pubspec.yaml) * feat: include root package in melos workspace via useRootAsPackage * fix: replace non-ASCII em dashes with hyphens in pubspec.yaml comments * feat: migrate makefile targets to melos scripts and mark deprecated * fix: replace __ with _ in screenshots to fix unnecessary_underscores lint * fix: rename melos script from run to start to avoid CLI conflict The melos v7 CLI command `melos run <script>` conflicted with the script named `run`. When running `melos run fmt`, melos treated `run` as the script name and `fmt` as an argument, causing flutter to look for a file named `fmt` instead of formatting code. Renaming the script to `start` (and `run:ios` to `start:ios`) resolves the ambiguity. Use `melos run start` to launch the app. * feat: add melos smoke tests and update docs with melos commands - Add tool/scripts/melos_smoke_test.sh for smoke testing safe scripts - Add smoke:test melos script to pubspec.yaml - Rename start to start:android for clarity - Replace Makefile references with melos commands in docs/build.md - Rewrite docs/make_file.md as full melos commands reference - Update README.md to link to Melos Commands * fix: address Copilot review comments - Switch get/upgrade/outdated melos scripts from exec to run to correctly resolve from workspace root (not per-package) - Align screenshots flutter constraint with workspace root (^3.41.2) to fix sdk/flutter constraint inconsistency (Dart 3.8 requires Flutter 3.41+) * fix: reverse date divider in chat (#983) Placed date divider before bunch of messages. Also changed displaying date in this divider. Added displaying options "Today", "Yesterday", day of week, like "Monday", and date in E, d MMM format, like "Tue, 6 Jan" , depending on comparison between date of message and today's date. * feat: claude code setup (#981) * feat: replace .rules with CLAUDE.md for Claude Code * feat: configure .claude with hooks and allowed commands * feat: add melos usage rules and update common commands * feat: reduce CLAUDE.md duplication and soften melos usage rules * feat: add webtrit_callkeep docs and package-level CLAUDE.md files * feat: document call architecture, flows, isolates, and key patterns in CLAUDE.md * feat: restructure AI agent memory files (AGENTS.md + CLAUDE.md split) - Add root AGENTS.md (<100 lines): universal instructions for any AI tool - Slim root CLAUDE.md (<50 lines): @imports + Claude-specific gotchas only - Extract CallBloc architecture to docs/call_architecture.md - Add AGENTS.md for all 10 packages (shared content, any agent) - Remove package CLAUDE.md where content fully moved to AGENTS.md - Keep package CLAUDE.md only where Claude-specific gotchas exist (data, ssl_certificates) - Add CLAUDE.local.md to .gitignore * feat: address Copilot review comments in PR #981 - Fix md_formatter.py docstring: was 'prettier', now correctly says 'markdownlint-cli2 --fix' - Expand settings.json deny list to cover keystores (.jks, .keystore, .p12) and signing keys (.pem, .key, .p8) - Fix AGENTS.md import groups: two '2.' entries corrected to sequential 1–6 numbering - Fix packages/data/CLAUDE.md migration steps: misnumbered list corrected to 1–5 - Add working directory note to packages/data/AGENTS.md commands section - Fix _web_socket_channel/AGENTS.md: 'pinned to' → 'constrained to' for caret constraint * feat: remove webtrit_phone_keystores from deny list (dir is outside project scope) * feat: configure gitignore, formatter, and analysis for generated files (#985) * feat: configure gitignore, formatter, and analysis for generated files - Add **/build/ and **/.claude/ to .gitignore - Update lefthook pre-commit to skip *.g.dart / *.freezed.dart / *.gr.dart - Replace melos fmt/fmt:check with find-based scripts that exclude generated files * feat: update melos v7 integration across features, DAOs, and screenshots * feat: allow feat/ as valid branch prefix alongside feature/ (#987) * feat: allow feat/ as valid branch prefix alongside feature/ Update branch-name-check.sh and git-lint.yml to accept both feat/ and feature/ prefixes. Add CONTRIBUTING.md documenting branch naming rules and commit conventions. * docs: fix table alignment in CONTRIBUTING.md * docs: consolidate git conventions into CONTRIBUTING.md as single source of truth - Remove .rules.md (referenced non-existent .rules/ directory) - Update .github/copilot-instructions.md to reference CONTRIBUTING.md and AGENTS.md - Add feat/ to branch pattern in copilot-instructions.md - Fix pre-commit hook description in docs/development.md (dart format, not flutter analyze) - Add CONTRIBUTING.md reference in docs/development.md - Add docs/development.md pointer in CONTRIBUTING.md hooks section * feat: favorites remote syncable (#984) * refactor: cdr ui improvements, disconnect reason translations (#989) * fix: use firstWhere instead of first in getAllContacts test to avoid ordering assumption (#991) * fix: change contact_phones UNIQUE constraint to (number, label, contact_id) (#992) * feat: change contact_phones UNIQUE constraint to (number, label, contact_id) * feat: update ContactPhonesDao to use (number, label) pairs for stale-row deletion * feat: persist per-label phone rows in ContactsLocalDataSource for DID-only accounts * feat: add tests for DID-only contact phone handling and schema migration v20 * docs: add inline comment explaining grouping and merge logic in displayPhones * refactor: rename lambda param p to phone in deleteOtherContactPhonesOfContactId call * style: apply dart format to changed files * docs: add DartDoc to deleteOtherContactPhonesOfContactId * docs: add inline comments to deleteOtherContactPhonesOfContactId body * style: replace non-ASCII arrow with hyphen in comment * fix: use canonical label in displayPhones to prevent merged label from reaching favorites * fix: recreate contact_phones via new table to preserve favorites FK reference * test: add favorites FK integrity assertion for migration v20 * fix: resolve canonical ContactPhone by id before passing to favorites * docs: document merged label display and canonical lab…
SERDUN
added a commit
that referenced
this pull request
Apr 28, 2026
* chore: disable overwrite and reviewed flags on upload (#892) * fix: solve "sticky speaker" issue by localizing audio state (#895) Moved `speakerOnBeforeMinimize` from global `CallState` to `ActiveCall` model to ensure audio preferences are call-specific and properly disposed of when a call ends. - Fixes a bug where a new audio call would erroneously start on speakerphone if a previous call (or transfer session) had speaker enabled. - Updated `CallBloc` to preserve and restore speaker state using the specific `ActiveCall` instance during minimization, transfers, and screen transitions. - Cleaned up `CallState` to prevent stale audio state leakage between unrelated call sessions. * fix: prevent duplicate outgoing calls using hybrid event transformer (#897) * fix: change CallControlEvent transformer to droppable * fix: use hybrid transformer for CallControlEvent to prevent duplicate calls * fix: expand small touch target size for menu button (#894) * fix: expand small touch target size for menu button Fixed small tap target zone for popup menu button in voicemail tile * refactor: improve voicemail menu button interaction and hover shape Replaced the 'child' property with 'icon' in PopupMenuButton to fix the square hover effect. This change ensures a standard Material circular splash and restores the correct touch target size. --------- Co-authored-by: Dmytro Serdun <d.serdun@webtrit.com> * refactor: cache CallBloc to avoid unsafe context access (#899) * refactor: cache CallBloc to avoid unsafe context access * chore: sync project metadata and adjust initialization order * chore: sync lockfiles and regenerate localization mappers (#900) * fix: prioritize config color for avatar initials (#901) Previously, the LeadingAvatarStyleFactory forcibly overrode the text color with `onSecondaryContainer`, ignoring the value from the configuration. This change ensures the configuration color is used if provided, falling back to the theme default only when necessary. * style: apply initials text color to loading and placeholder icons (#902) * feat: support initial expansion state in MediaSettingsScreen (#903) - Add initialOpenSection parameter to MediaSettingsScreen - Implement MockMediaSettingsCubit for screenshot testing - Register MediaSettingsScreenScreenshot in the screenshot router * fix: media settings panel color (#906) * style: set background color for expansion panels * docs: add TODOs explaining the dark mode background override * feat: implement ProgressIndicatorThemeData with Material 3 surface mapping (#907) * fix: correct foreground color for app bar actions (#908) * refactor(messaging): enhance GroupAvatar styling and logic (#910) * chore(lefthook): move heavy checks to pre-push and add format check (#912) * feat: implement declarative screen styling and theme override system (#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: revert changes in bottom_menu_feature.dart (#913) * fix: fixed formatting on about screen (#905) * 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> * fix: fixed contacts ordering (#904) Added collate method with no case option when sorting contacts * feat: implement feature configuration system and unified dark theme support (#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: standardize theme mode types and naming (#915) * refactor: rename ThemeModeConfig.auto to ThemeModeConfig.system * refactor: replace custom ContentThemeOverride with standard ThemeMode * refactor: rename toContentThemeOverride to toThemeMode * fix: correctly handle nullable contentThemeOverride * refactor: utilize ThemeProvider in ThemedScaffold for consistent overrides (#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. * fix: support dark mode in screenshot generation (#918) * fix: remove hardcoded colors to allow context-based inheritance (#917) * refactor: migrate login screens to ThemedScaffold and enhance styling models (#919) * fix: polish theming and fix active call overlay (#920) * 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: sync localizely keys and update plural logic (#921) * refactor(app): implement safe teardown sequence for user logout (#922) * 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: Implement dynamic styling for LoginSwitch segmented button (#925) * 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 * refactor: convert CallPopupMenuButton to StatelessWidget and disable click effects (#923) * refactor: replace custom tab buttons with standard TabBar and add unread 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 (#926) * 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 * fix(contacts): prevent race condition in LocalContactsSyncBloc during teardown (#927) * refactor: re-engineer actionpad styling to semantic roles (#928) * 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 * fix(call): increase monitor interval to optimize log quota (#929) * 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. * feat: enhance RemoteConfigService with snapshots and stream support (#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 * feat: dynamic RTP traffic monitoring interval and configuration synchronization (#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. * feat: generalize emergency reboot logic and refine diagnostic reporting (#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> * refactor: add controller to HistoryAutocompleteField for manual history 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 * feat: migrate system notifications and SIP presence to supported features (#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. * fix: collect outbound rtp stats (#937) * fix: description for Media encoding configs (#938) Fixed description for Media encoding configs in Media settings. Full Flex option was removed, other options were renamed. * fix(call): preserve widget context during auto-compact transition (#939) Refactor CallActiveScaffold to use AnimatedOpacity and IgnorePointer instead of conditional widget removal. This ensures that CallActions remains in the widget tree, preventing callback failures (e.g., onAudioDeviceChanged) when the UI compacts while a popup is open. * fix: fixed localization issues in network settings (#936) Fixed issues with localization in network settings. Also added radio buttons and checkbox for 'Incoming Call Type' and 'SMS Fallback' accordingly * chore: actualize l10n code generation (#940) * chore: actualize configurations (#942) * chore: implement modular rules system (#943) * chore: implement modular rules system and copilot instructions (#946) * chore: implement modular rules system and copilot instructions * chore: add makefile helpers for copilot branch management * feat: add theme switching settings (#951) * chore: implement modular rules system and copilot instructions * feat(settings): expose theme mode switching in settings --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> * feat: remote cli settings (#944) * feat: support metadata for missed call display name in isolate (#953) * feat: support metadata for missed call display name in isolate - Introduce getDisplayNameForMissedCall in IsolateManager to resolve caller names. - Update PushNotificationIsolateManager to utilize CallkeepIncomingCallMetadata for missed call notifications. - Pass metadata through onPushNotificationSyncCallback and onSignalingSyncCallback. - Ensure LocalPushRepository returns the future from the notification plugin. * docs: add documentation for IsolateManager properties * refactor: improve missed call display name logic and logging * feat(call): override _onNoActiveLines in PushNotificationIsolateManager for missed call handling (#959) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: SERDUN <26858237+SERDUN@users.noreply.github.com> * chore: ai agent pipeline refinement (#954) * chore: ignore agent files * docs: clarify commit message convention * chore: enforce lowercase commit descriptions and update rules docs * docs: refine AI agent execution pipeline and commit rules * fix: user id migration (#960) * refactor: replace SupportedMonitorConfig with SupportedLoggingConfig (#957) * refactor: replace SupportedMonitorConfig with SupportedLoggingConfig * refactor(logging): decouple AppLogger from RemoteConfigSnapshot - make kLogLevelKey private in FeatureOverridesFactory - add remoteLoggingEnabled to FeatureOverrides and LoggingConfig - rewrite AppLogger.init() to accept LoggingConfig instead of RemoteConfigSnapshot - add watchFeatureAccess() to observe loggingConfig changes via FeatureAccess stream - add LoggingMapper.mapFromOverridesOnly() for isolate/background contexts - update bootstrap.dart and services_isolate.dart call sites Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor(logging): replace direct FeatureAccess stream with applyConfig in AppLogger - remove watchFeatureAccess() and StreamSubscription from AppLogger - add applyConfig(LoggingConfig) as the single public update point - call applyConfig in App.didChangeDependencies() via existing FeatureAccess watch - remove redundant feature_access.dart import from app_logger.dart Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor(logging): narrow AppLogger.applyConfig to accept Level directly - applyConfig now takes Level instead of LoggingConfig - caller extracts loggingConfig.logLevel before passing to AppLogger Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor(logging): remove LoggingConfig dependency from AppLogger - init() now accepts Level and bool remoteLoggingEnabled as primitives - remove models.dart import from app_logger.dart - extract primitives from LoggingConfig at each call site Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor(logging): remove EnvironmentConfig dependency from AppLogger - init() now accepts logzioLogLevel and pre-built List<RemoteLoggingService> - store logzioLogLevel as field for use in applyConfig - move _buildRemoteLoggingServices to bootstrap.dart and services_isolate.dart - remove environment_config.dart import from app_logger.dart Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor(logging): simplify AppLogger by replacing List<RemoteLoggingService> with LogzioLoggingService? - add LogzioLoggingService.fromEnvironment() factory to encapsulate EnvironmentConfig logic - AppLogger.init() now accepts LogzioLoggingService? instead of List<RemoteLoggingService> - remove _buildRemoteLoggingServices helpers from bootstrap.dart and services_isolate.dart Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor(logging): move logzioLogLevel resolution into LogzioLoggingService.fromEnvironment - fromEnvironment() now reads REMOTE_LOGZIO_LOG_LEVEL internally, no longer takes minLevel param - AppLogger.init() drops logzioLogLevel parameter, uses _logzioService?.minLevel in applyConfig - remove environment_config.dart import from services_isolate.dart Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(logging): add unit tests for LoggingMapper and FeatureAccessStreamFactory logging fields Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(call): compare monitorCheckInterval directly to avoid spurious CallBloc updates Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * chore: update Flutter to 3.41.2 and suppress experimental_member_use warnings (#962) * chore: update Flutter to 3.41.2 and suppress experimental_member_use warnings Both LockCachingAudioSource (just_audio) and TableMigration (drift) are the only available APIs for their respective tasks with no stable alternatives. Added ignore directives with explanatory comments. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: bump minimum Flutter SDK constraint to 3.41.2 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * chore: add claude code team settings (#965) * chore: add Claude Code team settings and documentation Add team-level `.claude/settings.json` with deny rules for keystores, signing keys, and environment files. Add `.claude/settings.local.json` to `.gitignore` and document settings levels in `docs/development.md`. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: add safe tool allow rules to team settings Allow flutter test, analyze, dart format, dart fix, pub get, and gen-l10n commands in team settings for all developers. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * fix: remove unused import in messaging_shell.dart (#966) Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * fix(settings): add optimistic toggle with spinner for register status switch (#967) Replaces the blocking register status toggle with an optimistic update pattern — the switch flips immediately, shows a spinner, disables during the API call, and reverts on failure. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * fix: skip static payloads sanitizing if no rtpmap (#968) * fix: media preset translation (#969) * fix: fix splash config generation and add android_12 image support (#865) - Escape # in Makefile color variables to prevent shell comment issues - Remove redundant shell parameter expansion, use Make-only expansion - Add ANDROID_12_SPLASH_IMAGE variable for separate Android 12 splash - Use single quotes in echo to avoid shell interpretation problems Co-authored-by: Dmytro Serdun <d.serdun@webtrit.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * chore: add diagnostic logging for recents number mismatch (#970) Add hash-based and raw value logging to trace why recent calls list shows two different phone numbers per entry. Raw values are visible when Logz.io anonymization is disabled, hashes work with it enabled. * feat: add support a blurred appbar surface (#971) * feat: add BlurredSurface widget and apply blur effect to all app bars Extract common ClipRect+BackdropFilter pattern into reusable BlurredSurface widget. Apply consistent blur background with extendBodyBehindAppBar to all screens using MainAppBar or AppBar. Remove isComplexBackground conditional logic. * refactor: standardize MainAppBar field order across screens * feat: apply blurred appbar surface to ConversationsScreen * fix: remove unused theme/theme.dart imports * fix: add top padding to prevent content overlap with blurred appbar Screens using extendBodyBehindAppBar lacked compensating padding, causing body content to be hidden behind the blurred AppBar. Added appropriate top padding to recents, favorites, about, contacts, and CDR screens consistent with the existing settings screen approach. * feat(screenshots): make IgnorePointer configurable in ScreenshotApp (#972) Add ignorePointer parameter (default true) to allow enabling interaction for specific screenshots that require it. * feat: add screenshot mocks and docs for all features (#973) * feat: add screenshot mocks for all features Add ~20 new screenshot definitions covering all user-visible screens: - Core flows: contact, chat conversation, SMS conversation, system notifications, recent CDRs, number CDRs, call log - Settings: network, language, diagnostic, caller ID, presence, theme mode, voicemail - Login: switch screen - Utility: log records console, contacts agreement, teardown, permissions, user agreement Includes shared mock data, mock blocs/cubits/repositories, screenshot widgets, router entries, and integration test registrations. * docs: add screenshots package documentation Add docs for screenshots architecture, mock reference, data reference, and step-by-step guide for adding new screenshots. Update README with links to the new documentation. * fix: address review feedback for screenshot mocks and docs - Add provider as direct dependency in screenshots/pubspec.yaml and remove ignore: depend_on_referenced_packages from 7 files - Fix mock type/factory mismatches in mocks.md documentation - Fix broken code formatting in mocks.md and adding_screenshots.md - Remove shadowed ChatTypingCubit/SmsTypingCubit BlocProviders from conversation screenshot wrappers - Guard registerFallbackValue with static flag to prevent duplicate registration - Fix duplicated phrase in README.md Firebase Hosting section - Document both flutter test and flutter drive workflows in overview.md * feat: update keystores path to new applications subdirectory (#974) * feat: add text style background decoration support (#975) * feat: add backgroundBorderRadius and backgroundPadding to TextStyleConfig * feat: add greetingTextStyle to LoginModeSelectPageConfig * feat: add ExtendedText widget with background decoration support * feat: add greetingTextStyle to login.modeSelect docs * feat: add tests for ExtendedText widget * feat: update theme template configs with missing components and fixes (#976) - Fix calStatuses typo to callStatuses in both widget configs - Add missing widget sections as examples: button, group, bar, input, text, confirmDialog - Fix non-existent dark SVG asset references to use existing logos - Remove unsupported fontFeatures from page text styles - Clean up dark page config duplicate keys in login.switchPage - Add greetingTextStyle for dark login modeSelect visibility - Update dark widget decoration gradient config * refactor: replace deprecated GradientColorsConfig with PageBackground (#977) * refactor: replace deprecated GradientColorsConfig with PageBackground Remove GradientColorsConfig, DecorationConfig, GradientsStyleFactory, and Gradients theme extension. Migrate login and call screens to use PageBackground via ThemedScaffold. * refactor: enable gradient backgrounds for login and call page configs Activate background for login modeSelect and add gradient background to dialing section using colors from the removed GradientColorsConfig. * refactor: adjust dark login gradient to contrast with buttons Change bottom gradient color from #000000 to #0A1929 to avoid blending with neutralOnDark button surface color. * refactor: reverse dark login gradient direction Start with dark color on top, lighter blue on bottom to keep contrast with buttons at the bottom of the screen. * refactor: rename misleading onSurface variable to surfaceColor * feat: appBar blurred surface config and theme updates (#979) * feat: extract appBar background color and blurred surface to config pipeline Add configurable appBarBackgroundColor and appBarBlurredSurface (color, sigmaX, sigmaY) to the theme config→style pipeline across all 7 main screens, replacing hardcoded values. * feat: update page configuration docs with appBar fields Add Common page fields section documenting appBarBackgroundColor and appBarBlurredSurface, update Keypad page example. * feat: remove appBarBackgroundColor from config pipeline AppBar backgroundColor is already handled by MainAppBar fallback chain (explicit → appBarTheme → canvasColor.withAlpha). Remove redundant appBarBackgroundColor from BasePageConfig, all page configs, screen styles, factories, and screens to avoid confusion. * feat: add BlurredSurface.fromStyle factory and simplify screen usage Replace inline BlurredSurface construction with fromStyle() factory that returns null when no config is present (no blur applied) or a configured BlurredSurface with resolved sigma defaults of 10. * feat: enable appBar blur and activate widget config sections - Add appBarBlurredSurface to all 7 page sections (dark/light) - Activate previously ignored widget config sections (_bar, _button, _group, _input, _text -> remove underscore prefix) - Set appBar backgroundColor/surfaceTintColor to transparent for blur - Fix tab indicator label contrast and remove divider line * feat: update light theme appBarBlurredSurface color to soft blue-white * feat: set dark status bar icons for light theme appBar * feat: fix appBarBlurredSurface sigma defaults and add tests - Make sigmaX/sigmaY nullable in BlurredSurfaceConfig so omitted fields resolve to 10 via BlurredSurface.fromStyle instead of staying 0 - Regenerate Freezed/JSON for BlurredSurfaceConfig - Restore blur on RecentCdrs and SystemNotifications screens by passing sigmaX: 10, sigmaY: 10 explicitly to const BlurredSurface() - Explicit Colors.transparent fallback in BlurredSurface Container child - Update docs to reflect null defaults with 10 resolved at widget layer - Add BlurredSurface.fromStyle widget tests (4 cases) * fix: system back button blocked in EmbeddedRequestErrorDialog (#935) - Remove canPop: false from EmbeddedRequestErrorDialog — default is true, and blocking pop prevented the system back button from working in Settings → Terms & Conditions when the page failed to load (WT-890). - Add onPopInvokedWithResult to call onBack when system back dismisses the dialog in Mode B (pushed route), ensuring _errorDialogShown is reset. - Reset _errorDialogShown in _handleEmbeddedErrorState when error is cleared, so repeated errors after system-back dismissal are shown again. Co-authored-by: Dmytro Serdun <d.serdun@webtrit.com> * fix: prevent keepalive write-after-close and harden transaction lifecycle (#870) * fix: prevent keepalive write-after-close and add regression test * fix: clean up transaction on send failure in _executeTransaction Move _addMessage inside the try-catch block so that if writing to a closed socket throws WebtritSignalingBadStateException, the transaction is properly removed from the _transactions map instead of leaking. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * test: add transaction cleanup and keepalive timer tests Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: add comment for closeCode guard in keepalive loop Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address Copilot review comments on keepalive integration tests - Fix compile error: replace called(greaterThan(0)) with called(1) - Fix lint warning: await streamController.close() in tearDown - Fix false-positive test: move closeCode mock before flushMicrotasks and add verify closeCode called(2) to catch timer-restart regression --------- Co-authored-by: Dmytro Serdun <d.serdun@webtrit.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * fix: fixed app is locked in Bluetooth call profile after first call (#982) * fix: fixed app is locked in Bluetooth call profile after first call Fixed situation when user returns to YouTube or music playback, the audio remains degraded, like during a call. This happens only on Android. * fix: improve comment for _onLastCallEnded to cover iOS and Android audio routing --------- Co-authored-by: Dmytro Serdun <d.serdun@webtrit.com> * feat: add melos v7 integration (#980) * feat: add melos integration with codegen, test, format, and dependency scripts * feat: fix melos scripts syntax for v7 and rename format to fmt * feat: configure melos v7 with workspace and scripts in pubspec.yaml - Add workspace: list to pubspec.yaml for Dart pub workspace / Melos v7 package discovery - Add melos: section to pubspec.yaml with scripts for codegen, tests, formatting, dependencies - Add resolution: workspace to all sub-package pubspec.yaml files - Fix ssl_certificates publish_to typo (none; → 'none') - Update screenshots SDK constraint to ^3.8.0 - Remove melos.yaml (ignored by Melos v7 — config lives in pubspec.yaml) * feat: include root package in melos workspace via useRootAsPackage * fix: replace non-ASCII em dashes with hyphens in pubspec.yaml comments * feat: migrate makefile targets to melos scripts and mark deprecated * fix: replace __ with _ in screenshots to fix unnecessary_underscores lint * fix: rename melos script from run to start to avoid CLI conflict The melos v7 CLI command `melos run <script>` conflicted with the script named `run`. When running `melos run fmt`, melos treated `run` as the script name and `fmt` as an argument, causing flutter to look for a file named `fmt` instead of formatting code. Renaming the script to `start` (and `run:ios` to `start:ios`) resolves the ambiguity. Use `melos run start` to launch the app. * feat: add melos smoke tests and update docs with melos commands - Add tool/scripts/melos_smoke_test.sh for smoke testing safe scripts - Add smoke:test melos script to pubspec.yaml - Rename start to start:android for clarity - Replace Makefile references with melos commands in docs/build.md - Rewrite docs/make_file.md as full melos commands reference - Update README.md to link to Melos Commands * fix: address Copilot review comments - Switch get/upgrade/outdated melos scripts from exec to run to correctly resolve from workspace root (not per-package) - Align screenshots flutter constraint with workspace root (^3.41.2) to fix sdk/flutter constraint inconsistency (Dart 3.8 requires Flutter 3.41+) * fix: reverse date divider in chat (#983) Placed date divider before bunch of messages. Also changed displaying date in this divider. Added displaying options "Today", "Yesterday", day of week, like "Monday", and date in E, d MMM format, like "Tue, 6 Jan" , depending on comparison between date of message and today's date. * feat: claude code setup (#981) * feat: replace .rules with CLAUDE.md for Claude Code * feat: configure .claude with hooks and allowed commands * feat: add melos usage rules and update common commands * feat: reduce CLAUDE.md duplication and soften melos usage rules * feat: add webtrit_callkeep docs and package-level CLAUDE.md files * feat: document call architecture, flows, isolates, and key patterns in CLAUDE.md * feat: restructure AI agent memory files (AGENTS.md + CLAUDE.md split) - Add root AGENTS.md (<100 lines): universal instructions for any AI tool - Slim root CLAUDE.md (<50 lines): @imports + Claude-specific gotchas only - Extract CallBloc architecture to docs/call_architecture.md - Add AGENTS.md for all 10 packages (shared content, any agent) - Remove package CLAUDE.md where content fully moved to AGENTS.md - Keep package CLAUDE.md only where Claude-specific gotchas exist (data, ssl_certificates) - Add CLAUDE.local.md to .gitignore * feat: address Copilot review comments in PR #981 - Fix md_formatter.py docstring: was 'prettier', now correctly says 'markdownlint-cli2 --fix' - Expand settings.json deny list to cover keystores (.jks, .keystore, .p12) and signing keys (.pem, .key, .p8) - Fix AGENTS.md import groups: two '2.' entries corrected to sequential 1–6 numbering - Fix packages/data/CLAUDE.md migration steps: misnumbered list corrected to 1–5 - Add working directory note to packages/data/AGENTS.md commands section - Fix _web_socket_channel/AGENTS.md: 'pinned to' → 'constrained to' for caret constraint * feat: remove webtrit_phone_keystores from deny list (dir is outside project scope) * feat: configure gitignore, formatter, and analysis for generated files (#985) * feat: configure gitignore, formatter, and analysis for generated files - Add **/build/ and **/.claude/ to .gitignore - Update lefthook pre-commit to skip *.g.dart / *.freezed.dart / *.gr.dart - Replace melos fmt/fmt:check with find-based scripts that exclude generated files * feat: update melos v7 integration across features, DAOs, and screenshots * feat: allow feat/ as valid branch prefix alongside feature/ (#987) * feat: allow feat/ as valid branch prefix alongside feature/ Update branch-name-check.sh and git-lint.yml to accept both feat/ and feature/ prefixes. Add CONTRIBUTING.md documenting branch naming rules and commit conventions. * docs: fix table alignment in CONTRIBUTING.md * docs: consolidate git conventions into CONTRIBUTING.md as single source of truth - Remove .rules.md (referenced non-existent .rules/ directory) - Update .github/copilot-instructions.md to reference CONTRIBUTING.md and AGENTS.md - Add feat/ to branch pattern in copilot-instructions.md - Fix pre-commit hook description in docs/development.md (dart format, not flutter analyze) - Add CONTRIBUTING.md reference in docs/development.md - Add docs/development.md pointer in CONTRIBUTING.md hooks section * feat: favorites remote syncable (#984) * refactor: cdr ui improvements, disconnect reason translations (#989) * fix: use firstWhere instead of first in getAllContacts test to avoid ordering assumption (#991) * fix: change contact_phones UNIQUE constraint to (number, label, contact_id) (#992) * feat: change contact_phones UNIQUE constraint to (number, label, contact_id) * feat: update ContactPhonesDao to use (number, label) pairs for stale-row deletion * feat: persist per-label phone rows in ContactsLocalDataSource for DID-only accounts * feat: add tests for DID-only contact phone handling and schema migration v20 * docs: add inline comment explaining grouping and merge logic in displayPhones * refactor: rename lambda param p to phone in deleteOtherContactPhonesOfContactId call * style: apply dart format to changed files * docs: add DartDoc to deleteOtherContactPhonesOfContactId * docs: add inline comments to deleteOtherContactPhonesOfContactId body * style: replace non-ASCII arrow with hyphen in comment * fix: use canonical label in displayPhones to prevent merged label from reaching favorites * fix: recreate contact_phones via new table to preserve favorites FK reference * test: add favorites FK integrity assertion for migration v20 * fix: resolve canonical ContactPhone by id before passing to favorites * docs: document merged label display and canonical label favorites behavior * refactor: replace ContactPhone display with flat fields in ContactPhoneDisplayEntry - Replace synthetic ContactPhone display field with displayLabel (String) and displayFavorite (bool) - only the two fields that actually differ from the canonical phone - Rename canonical field to phone for clarity - Update displayPhones convenience getter to reconstruct ContactPhone from phone + flat display fields - Update ContactPhoneTileAdapter to accept only primitives and closures, removing all model object dependencies - Update ContactScreen loop to use displayPhoneEntries with closures capturing entry.phone directly - Add widget tests for ContactPhoneTileAdapter covering all enable flags, callback wiring, transfer logic, and popup menu entries - Add direct tests for displayPhoneEntries covering displayLabel, displayFavorite, and phone field contracts * feat: melos exported env support (#993) * fix: log records file stability improvements (#998) * fix: guard File.delete() with exists() check in shareLogRecords Prevents PathNotFoundException on Android 15 when the OS clears the app cache directory under background memory pressure while the native share sheet is open, causing the finally block to attempt deleting a file that no longer exists (Crashlytics issue 25d22f4de9016c556b46cfacea29a20b). * fix: delegate dispose Future in LogRecordsFileRepositoryImpl Without awaiting or returning the Future from RotatingFileAppender.dispose(), the file handle could remain unclosed after the caller awaited repository disposal. Use direct return to delegate the Future without an async wrapper. * fix: replace existsSync retry with async exists() in _getAllLogFilesWithRetry existsSync() reads from the OS filesystem cache and can return false immediately after forceFlush() even though the file is on disk. Switching to async File.exists() forces a fresh stat() call that bypasses the cache. Also reduces max wait from 10s (5x2s) to 1s (10x100ms). * fix: guard forceFlush() with try/catch in readAllLogs An I/O error during forceFlush() would propagate and abort readAllLogs entirely, returning no logs to the caller. Catching the error allows reading whatever files are already on disk. * fix: guard forceFlush() with try/catch in cleanLogs An I/O error during forceFlush() would abort cleanLogs entirely, leaving stale log files on disk. Catching the error allows deletion to proceed on whatever files are already present. * fix: replace retry loop with async file discovery in log records - Replace _getAllLogFilesWithRetry (10×100ms polling loop) with _getAllLogFilesAsync — single async pass using await file.exists() per rotation slot, bypassing OS filesystem cache without any delay - Extend file discovery range to 0..keepRotateCount inclusive so rotated files (e.g. app_logs.log.1) are found even when base file is absent immediately after rotation - Fix cleanLogs to use await _getAllLogFilesAsync() instead of synchronous getAllLogFiles() which relied on existsSync() - Fix readAllLogs iteration order: remove files.reversed so newer file (rotation 0) is read before older rotated file (rotation 1) - Remove redundant file.exists() guard inside readAllLogs loop since _getAllLogFilesAsync already guarantees file presence - Add full test coverage: LogRecordsMemoryRepositoryImpl, ReadableRotatingFileAppender (readAllLogs + cleanLogs), LogRecordsFileRepositoryImpl — 26 tests total * fix: update outdated comment in readAllLogs test * feat: add Claude Code PostToolUse hooks (dart formatter + newline enforcer) (#995) * fix: call dropped when user taps before app fully initializes (#997) * fix: wait for signaling and registration before failing outgoing call In __onCallPerformEventStarted, the early registration guard ran before the signaling wait, causing calls to drop immediately when the user tapped call before the socket initialized. Move the guard after the signaling wait and extend the wait predicate to also require registration status to be known (isHandshakeEstablished && isSignalingEstablished). * fix: hold outgoing call as pending until routing state is available When the user taps call before CallRoutingCubit has initialized (app just launched, user info not yet fetched), wait for the first non-null routing state instead of immediately failing. The call proceeds automatically once routing state becomes available. If the cubit is disposed while waiting, the call is silently dropped. * fix: remove unused notification import from CallController * test: add CallController.createCall unit tests Cover immediate dispatch, pending wait, cubit disposal, and CallUndefinedLineNotification scenarios. * fix: remove unused optional params in _FakeCallRoutingState * fix: remove unnecessary call_controller.dart import in test * fix: address Copilot review — unawaited createCall and fast-fail on signaling failure - Make createCall void by delegating to private _createCallAsync via unawaited, avoiding unawaited_futures lint at all call sites - Add isFailure condition to signaling firstWhere predicate so outgoing calls fail immediately on signaling failure instead of waiting full timeout * fix: update call_controller tests to use void createCall Replace await controller.createCall(...) with call + await Future.delayed(Duration.zero) to pump microtasks after createCall became void * refactor: extract signaling wait predicate into named variables * fix: use currentState instead of state for registration check after signaling wait * fix: replace non-ASCII em dash with semicolon in comment * refactor: remove redundant signalingConnected/registrationKnown vars, use CallState getters directly * fix: await all addTrack calls before createOffer to prevent empty offer * refactor: add TODO to provide CallController as singleton via RepositoryProvider * fix: log warning when callRoutingCubit closes before routing state arrives * refactor: add comment explaining callRoutingState await logic * fix: add timeout to routing state wait and show NoInternetConnectionNotification on expiry * refactor: extract _waitForRoutingState helper to clean up routing state await * fix: catch unexpected errors from _createCallAsync and add timeout notification test * fix: await reportNewIncomingCall in background FCM handler (#1001) Without await the Pigeon IPC Future was fire-and-forget — the background isolate was destroyed before the call reached the Kotlin side, so PhoneConnectionService.startIncomingCall() was never invoked and the incoming call UI never appeared. * feat: provide CallController as singleton via RepositoryProvider in MainShell (#1000) * feat: provide CallController as singleton via RepositoryProvider in MainShell Register CallController once in MainShell widget tree after CallBloc, CallRoutingCubit and NotificationsBloc are available. All seven call sites now obtain the shared instance via late final field initializer (context.read<CallController>()) instead of constructing a new object per StatefulWidget. * feat: replace RepositoryProvider with CallControllerScope InheritedWidget RepositoryProvider is semantically for the data layer; CallController is a UI-tier coordinator. Introduce CallControllerScope (InheritedWidget) following the PresenceViewParams pattern already in the codebase. All seven consumers now resolve the controller via CallControllerScope.of(context). * fix: prevent stale CallController on MainShell rebuild Store CallController in _MainShellState via ??= so it is created once regardless of how many times build() reruns. Switch CallControllerScope.of to getElementForInheritedWidgetOfExactType to avoid registering a rebuild dependency that would never fire (updateShouldNotify is always false). * feat: add doc comment for _callController field in _MainShellState * fix: feature access mapper logic (#1007) * fix: correct icon and text case assertions in integration tests (#1010) * fix: correct icon and text case assertions in integration tests * revert: remove accidentally committed logger from call_bloc.dart * feat: add integration tests for webtrit_signaling keepalive and disconnect (#1009) * feat: add integration tests for webtrit_signaling keepalive and disconnect Add mock-based and live integration tests covering: - Keepalive timeout (WebtritSignalingKeepaliveTransactionTimeoutException) - Normal keepalive cycles (echo → timer restarts) - Graceful disconnect (onDisconnect callback) - Stream error (onError callback) - Execute after disconnect (WebtritSignalingDisconnectedException) - Request transaction timeout (non-keepalive variant) - Live tests against a real server (skipped when credentials not set) - Network simulation via pure-Dart WebSocket proxy with pause/resume to verify keepalive timeout fires on real packet drop * feat: replace WebSocket proxy with raw TCP proxy in live signaling test Drop packets at the TCP byte level instead of WebSocket frame level for more realistic network simulation. The proxy rewrites the HTTP Host header before forwarding so the server accepts the upgrade request. * feat: extract TcpProxy into reusable _tcp_proxy package Move the raw TCP proxy from the signaling test into a standalone internal package packages/_tcp_proxy so it can be used as a dev dependency in both webtrit_signaling tests and app-level tests. * fix: address Copilot review comments on signaling tests and tcp proxy - Fix expectLater() to pass Future directly instead of closure - Make live test client nullable to prevent LateInitializationError in tearDown - Use local signalingClient variable in each live test for clarity - Replace localhost with 127.0.0.1 to avoid IPv6-first resolution issues - Remove unconditional TLS certificate bypass in live test HTTP client - Discard bytes while paused in _relayWithHostRewrite to prevent unbounded buffer growth - Add 64 KB header size guard with socket teardown on overflow - Add AGENTS.md, README.md, analysis_options.yaml to _tcp_proxy package * fix: add lints dev_dependency to _tcp_proxy so analysis_options.yaml resolves * fix: replace magic timing numbers with constants in integration tests * docs: add DartDoc to _findCrlfCrlf explaining HTTP header boundary detection * fix(signaling): reconnect silently on server force-close (code 4441) (#1012) When the server sends disconnect code 4441 (controllerForceAttachClose) it means two signaling sessions from the same account were open at the same time — a race that can happen when a background push isolate is still connected as the main engine comes to the foreground and reconnects signaling. Previously this code path set lastSignalingDisconnectCode, which triggered CallStatus.connectIssue and showed a "Connection issue" snackbar to the user even though the situation was transient and self-healing. Fix: on 4441, emit lastSignalingDisconnectCode=null (no connectIssue UI), skip the notification, and reconnect with the fast 1s delay instead of the default 3s. A warning log is emitted so the race remains visible in logs for debugging. * refactor: decouple AppLogger from LogzioLoggingService via RemoteLoggingService abstraction (#1011) * refactor: decouple AppLogger from LogzioLoggingService via RemoteLoggingService abstraction * refactor: move remoteMinLevel extraction to init to avoid cast in applyConfig * refactor: add minLevel to RemoteLoggingService and remove cast from AppLogger * refactor: reorder LogzioLoggingService fields to match RemoteLoggingService interface order * refactor: remove labelsProvider from AppLogger state, pass labels explicitly * refactor: rename regenerateRemoteLabels to updateRemoteLabels and dispose before reinitialize * refactor: replace labels map with lazy callback in AppLogger to encapsulate labels retrieval * fix: attach remote appender before applyConfig so early logs are forwarded to remote * feat: enable and disable log anonymization with env (#1004) * feat: enable and disable log anonymization with env Added functionality to disable and enable log anonymization via environment variable * refactor: simplify AnonymizationType to none/full enum with bool flag Replace the mutable list-based anonymization approach with a single AnonymizationType enum (none/full), removing the race condition risk and simplifying the API surface. * docs: document intentional anonymization scope in AppLogger * fix: add @override annotation to setAnonymizationEnabled in LogzioLoggingService --------- Co-authored-by: Dmytro Serdun <d.serdun@webtrit.com> * fix: api healthcheck path (#1014) * fix: use shared static logger in WebtritSignalingClient (#1013) * fix: use shared static logger in WebtritSignalingClient with instance id prefix * fix: restore custom logger support via inner constructor parameter * fix: simplify logger to use fixed name without instance counter * fix: remove custom logger parameter from inner constructor * fix: signaling errors (#1016) * fix: snackbar duration * fix: new persist option usage * fix: reconnect message repeating * fix: reconnect on keepalive timeout * feat: user info cache (#1015) * feat: main impl * refactor: call to actions fix * fix: call to actions provider * docs: getAndListed explain * fix: integration tests rescue (#1025) * fix: migrate to 4.5 * fix: various fixes * fix: remove log file from standart test * fix: no audio if lone codec (#1028) * fix: WebRTC signaling state guards — ICE restart, renegotiation, and glare (WT-986) (#1003) * refactor: extract RenegotiationHandler with stable-state and concurrency guards - Extract renegotiation logic from CallBloc into a standalone RenegotiationHandler class - Add two stable-state guards: pre-offer check and TOCTOU guard after createOffer - Add _isHandling/_pendingRetry flags to serialize concurrent onRenegotiationNeeded firings - Catch WebtritSignalingErrorException for server-side error logging without swallowing - Catch plain String errors (flutter_webrtc native) separately from Dart exceptions - Add unit tests covering stable-state skip, concurrency serialization, and error paths - Document server-mediated vs P2P topology constraints and Perfect Negotiation limitation * fix: add signalingState guards and Perfect Negotiation rollback to call flow ICE restart handler: - Skip setLocalDescription when signalingState != stable to prevent native crash - Log warning instead of silently skipping Renegotiation / accepted handler: - Guard setRemoteDescription(answer) against wrong state after glare resolution - Log transceivers after setRemoteDescription for SDP debugging - Catch String errors from setRemoteDescription to prevent unhandled exception escalation Updating handler (Perfect Negotiation rollback): - Pre-check signalingState for glare: if have-local-offer, roll back local offer before setRemoteDescription - Catch String errors containing have-local-offer as fallback for stale flutter_webrtc signalingState cache - Roll back and retry setRemoteDescription on confirmed glare Renderer and state: - Always refresh srcObject in RTCStreamView.didUpdateWidget to handle renegotiation-replaced tracks - Fix remoteVideo getter to use logical OR (stream tracks || video flag) instead of short-circuit Add call_state_test.dart covering ActiveCall equality and remoteVideo edge cases * refactor: replace on String catch with typed RTC exceptions via RtcJsepErrorParser * fix: guard RTCVideoRenderer srcObject assignment until initialize() completes (#1027) * fix: guard RTCVideoRenderer srcObject assignment until initialize() completes Prevents a crash where didUpdateWidget set srcObject before the renderer was initialized, causing 'Call initialize before setting the stream'. The _initialized flag ensures srcObject is only set after initialize() resolves. Also always refreshes srcObject (not just on stream identity change) to handle track replacement during renegotiation. * fix: use setState and guard build before renderer initialization Wrap _initialized and srcObject assignment in setState so the flag change is properly synchronized with Flutter's build cycle. Guard build() to avoid passing an uninitialized renderer to RTCVideoView — the placeholderBuilder (or empty SizedBox) is shown until initialize() completes. * fix: update ExternalContactsSyncBloc tests to mock getAndListen instead of getLocalInfo (#1029) * feat: handle 'voicemail_not_configured' error (#1005) * feat: handle 'voicemail_not_configured' error Added handling 'voicemail_not_configured' error from voicemail api * fix: properly handle voicemail_not_configured and endpoint_not_supported errors across all layers - WebtritApiClient: skip SEVERE log for expected VoicemailNotConfiguredException / EndpointNotSupportedException - VoicemailRepositoryImpl: add _featureSupported flag to skip API calls once feature is known unavailable; add _fetchingCompleter.future.ignore() to prevent unhandled future errors; suppress stack trace in warning log for expected exceptions; expose isFeatureSupported getter - VoicemailRepository: add isFeatureSupported to abstract interface; EmptyVoicemailRepository returns false - VoicemailCubit: check isFeatureSupported on init to immediately emit featureNotSupported without an API call - SettingsBloc: catch expected exceptions from unawaited fetchVoicemails() to prevent runZonedGuarded propagation - PollingService / ConnectivityLifecycleService: remove stack trace from WARNING logs in generic catch blocks * fix: always allow navigation to voicemail screen regardless of feature support Previously the settings tile was disabled with reduced opacity when voicemail was not configured, which was confusing. Now the tile is always tappable and the voicemail screen shows a placeholder explaining the reason. * fix: move VoicemailCubit provider from SettingsScreenPage to VoicemailScreenPage VoicemailCubit was provided at the settings level but consumed in a separate route, causing ProviderNotFoundException on navigation. Moving it to VoicemailScreenPage makes the provider scope match the consumer. * fix: add missing call feature import to VoicemailScreenPage * fix: remove unused stack trace variables from catch blocks in polling and connectivity services * fix: address Copilot review comments - VoicemailCubit: guard watchVoicemails subscription against overwriting featureNotSupported state - VoicemailNotConfiguredException: pass token and error fields to super; remove commented-out placeholder - WebtritApiClient: log xRequestId (never null) instead of requestId parameter; pass token/error to VoicemailNotConfiguredException - SettingsTile: add assert that opacity is in [0.0, 1.0] --------- Co-authored-by: Dmytro Serdun <d.serdun@webtrit.com> * feat: add toJson() to all Event subclasses and fix NotifyEvent constructor inconsistency (#1030) * fix: suppress transient network error SnackBar in RegisterStatusCubit (#1031) Auto-triggered fetches (startup and connectivity restore) now use _fetchStatusSilently(), which skips handleError for SocketException, TimeoutException, and TlsException. The cubit retries automatically on the next connectivity change, so surfacing these transient failures to the user is misleading. User-initiated fetchStatus() retains the original behaviour and still calls handleError for all errors. * fix: add 10s timeout to reportNewIncomingCall in background message handler (WT-1061) (#1032) Telecom on affected devices can become overloaded with phantom PhoneAccount registrations, causing reportNewIncomingCall() to hang indefinitely. This keeps FlutterFirebaseMessagingBackgroundService alive and blocks subsequent cold starts. Adding a 10s timeout bounds the handler lifetime and logs a warning when Telecom is slow to respond. * fix: add 5s timeout to getInitialMessage() to prevent splash freeze (WT-1061) (#1033) * refactor: remove tryUse from AppDatabaseScope, migrate callers to useOrNull (#1034) * fix: eliminate write-write SQLite contention via shared DriftIsolate server (WT-1061) (#1035) * fix: eliminate write-write SQLite contention via shared DriftIsolate server (WT-1061) Spawns a single dedicated DriftIsolate server in the main isolate bootstrap and registers its SendPort in IsolateNameServer under a fixed key. Background isolates (FCM handler, WorkManager) now connect to the same server via IsolateDatabase.connectOrCreate(), which looks up the port and creates a client connection — falling back to a direct NativeDatabase connection when the main app is not running (cold push with no foreground app). All writes are serialized through the single server isolate, making write-write SQLITE_BUSY (code=5) between concurrent isolates impossible. Changes: - app_database: add createAppDatabaseNative() for synchronous NativeDatabase creation inside the server isolate (no createInBackground needed) - IsolateDatabase: add spawnServer(), connectOrCreate(), kDbPortName - AppDatabaseScope.use(): connect via connectOrCreate() instead of create() - bootstrap(): spawn DriftIsolate server, register DriftIsolate in InstanceRegistry - AppDatabaseLifecycleHolder: connect to DriftIsolate, shutdown server on dispose * fix: address Copilot review — robust spawnServer error handling and stale port cleanup (WT-1061) * test: add integration tests for IsolateDatabase stale port handling (WT-1061) * refactor: introduce SignalingModule stream abstraction (phase 1) (#1024) * refactor: introduce SignalingModule stream abstraction (phase 1) Replace SignalingManager callback-based API with SignalingModule — a sealed-event broadcast stream that owns the WebtritSignalingClient lifecycle without any BLoC, CallState, or UI dependency. Key changes: - Add SignalingModule with fire-and-forget connect(), disconnect(), dispose() and a sealed SignalingModuleEvent hierarchy (Connecting, Connected, ConnectionFailed, Disconnecting, Disconnected, HandshakeReceived, ProtocolEvent) - Add isRepeated deduplication on ConnectionFailed to suppress repeated identical error notifications - Map disconnect codes to recommendedReconnectDelay: 4441 → Duration.zero, protocolError → null, all others → 3 s - Migrate CallBloc from direct WebtritSignalingClient callbacks to SignalingModule stream subscription; new _SignalingClientEvent variants: connecting, connected, failed, disconnecting, disconnected - Migrate IsolateManager (Push + Foreground) to SignalingModule, replacing SignalingManager; add connectivity monitoring and pending request queue inside IsolateManager - Construct SignalingModule in main_shell.dart and inject into CallBloc - Delete SignalingManager and remove its export from common.dart - Add 31 unit and integration tests for SignalingModule * fix(test): update ExternalContactsSyncBloc tests for getAndListen API The BLoC was updated to call userRepository.getAndListen() instead of getLocalInfo(), but the mocks were never updated. Fix the setUp mock and correct the RefreshFailure test to use load() failure (which is the actual trigger for that state) rather than userRepository failure. * fix(test): rename local function to avoid leading underscore lint warning * docs: translate signaling architecture doc to English * docs: remove phase 1 requirements planning doc from repo * refactor: remove coreUrl/tenantId/token/trustedCertificates from CallBloc These four fields were passed through CallBloc only to construct SignalingModule internally. Now that SignalingModule is constructed externally and injected via the constructor, the fields are dead code. Remove them and the corresponding import from ssl_certificates. * fix: address Copilot review comments on SignalingModule/IsolateManager - Guard delayed reconnect callbacks with signalingClient == null check to avoid tearing down a healthy connection that connected during the delay - Populate _incomingCallEvents from handshake and protocol events so _findIncomingEventLog returns real caller data instead of null - Use disconnect() instead of dispose() in handleLifecycleStatus so the module remains reusable when the app returns to the foreground - Fix post-dispose connect() test to actually subscribe to the event stream and assert Connecting/Connected events are absent after dispose * feat: replay session events to late subscribers in SignalingModule Adds a per-subscriber replay buffer so that consumers created after connect() (e.g. CallBloc constructed after SignalingModule already connected and received a handshake) do not miss any events from the current session. - events getter now returns a single-subscription stream that first replays all events buffered since the last connect() call, then pipes live events from the broadcast controller - connect() clears the buffer so late subscribers see only the current session, not stale events from previous reconnect cycles - dispose() also clears the buffer on teardown - Uses sync: true on the intermediate StreamController to avoid an extra async ho…
SERDUN
added a commit
that referenced
this pull request
May 22, 2026
* refactor: sync localizely keys and update plural logic (#921) * refactor(app): implement safe teardown sequence for user logout (#922) * 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: Implement dynamic styling for LoginSwitch segmented button (#925) * 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 * refactor: convert CallPopupMenuButton to StatelessWidget and disable click effects (#923) * refactor: replace custom tab buttons with standard TabBar and add unread 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 (#926) * 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 * fix(contacts): prevent race condition in LocalContactsSyncBloc during teardown (#927) * refactor: re-engineer actionpad styling to semantic roles (#928) * 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 * fix(call): increase monitor interval to optimize log quota (#929) * 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. * feat: enhance RemoteConfigService with snapshots and stream support (#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 * feat: dynamic RTP traffic monitoring interval and configuration synchronization (#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. * feat: generalize emergency reboot logic and refine diagnostic reporting (#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> * refactor: add controller to HistoryAutocompleteField for manual history 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 * feat: migrate system notifications and SIP presence to supported features (#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. * fix: collect outbound rtp stats (#937) * fix: description for Media encoding configs (#938) Fixed description for Media encoding configs in Media settings. Full Flex option was removed, other options were renamed. * fix(call): preserve widget context during auto-compact transition (#939) Refactor CallActiveScaffold to use AnimatedOpacity and IgnorePointer instead of conditional widget removal. This ensures that CallActions remains in the widget tree, preventing callback failures (e.g., onAudioDeviceChanged) when the UI compacts while a popup is open. * fix: fixed localization issues in network settings (#936) Fixed issues with localization in network settings. Also added radio buttons and checkbox for 'Incoming Call Type' and 'SMS Fallback' accordingly * chore: actualize l10n code generation (#940) * chore: actualize configurations (#942) * chore: implement modular rules system (#943) * chore: implement modular rules system and copilot instructions (#946) * chore: implement modular rules system and copilot instructions * chore: add makefile helpers for copilot branch management * feat: add theme switching settings (#951) * chore: implement modular rules system and copilot instructions * feat(settings): expose theme mode switching in settings --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> * feat: remote cli settings (#944) * feat: support metadata for missed call display name in isolate (#953) * feat: support metadata for missed call display name in isolate - Introduce getDisplayNameForMissedCall in IsolateManager to resolve caller names. - Update PushNotificationIsolateManager to utilize CallkeepIncomingCallMetadata for missed call notifications. - Pass metadata through onPushNotificationSyncCallback and onSignalingSyncCallback. - Ensure LocalPushRepository returns the future from the notification plugin. * docs: add documentation for IsolateManager properties * refactor: improve missed call display name logic and logging * feat(call): override _onNoActiveLines in PushNotificationIsolateManager for missed call handling (#959) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: SERDUN <26858237+SERDUN@users.noreply.github.com> * chore: ai agent pipeline refinement (#954) * chore: ignore agent files * docs: clarify commit message convention * chore: enforce lowercase commit descriptions and update rules docs * docs: refine AI agent execution pipeline and commit rules * fix: user id migration (#960) * refactor: replace SupportedMonitorConfig with SupportedLoggingConfig (#957) * refactor: replace SupportedMonitorConfig with SupportedLoggingConfig * refactor(logging): decouple AppLogger from RemoteConfigSnapshot - make kLogLevelKey private in FeatureOverridesFactory - add remoteLoggingEnabled to FeatureOverrides and LoggingConfig - rewrite AppLogger.init() to accept LoggingConfig instead of RemoteConfigSnapshot - add watchFeatureAccess() to observe loggingConfig changes via FeatureAccess stream - add LoggingMapper.mapFromOverridesOnly() for isolate/background contexts - update bootstrap.dart and services_isolate.dart call sites Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor(logging): replace direct FeatureAccess stream with applyConfig in AppLogger - remove watchFeatureAccess() and StreamSubscription from AppLogger - add applyConfig(LoggingConfig) as the single public update point - call applyConfig in App.didChangeDependencies() via existing FeatureAccess watch - remove redundant feature_access.dart import from app_logger.dart Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor(logging): narrow AppLogger.applyConfig to accept Level directly - applyConfig now takes Level instead of LoggingConfig - caller extracts loggingConfig.logLevel before passing to AppLogger Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor(logging): remove LoggingConfig dependency from AppLogger - init() now accepts Level and bool remoteLoggingEnabled as primitives - remove models.dart import from app_logger.dart - extract primitives from LoggingConfig at each call site Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor(logging): remove EnvironmentConfig dependency from AppLogger - init() now accepts logzioLogLevel and pre-built List<RemoteLoggingService> - store logzioLogLevel as field for use in applyConfig - move _buildRemoteLoggingServices to bootstrap.dart and services_isolate.dart - remove environment_config.dart import from app_logger.dart Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor(logging): simplify AppLogger by replacing List<RemoteLoggingService> with LogzioLoggingService? - add LogzioLoggingService.fromEnvironment() factory to encapsulate EnvironmentConfig logic - AppLogger.init() now accepts LogzioLoggingService? instead of List<RemoteLoggingService> - remove _buildRemoteLoggingServices helpers from bootstrap.dart and services_isolate.dart Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor(logging): move logzioLogLevel resolution into LogzioLoggingService.fromEnvironment - fromEnvironment() now reads REMOTE_LOGZIO_LOG_LEVEL internally, no longer takes minLevel param - AppLogger.init() drops logzioLogLevel parameter, uses _logzioService?.minLevel in applyConfig - remove environment_config.dart import from services_isolate.dart Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(logging): add unit tests for LoggingMapper and FeatureAccessStreamFactory logging fields Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(call): compare monitorCheckInterval directly to avoid spurious CallBloc updates Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * chore: update Flutter to 3.41.2 and suppress experimental_member_use warnings (#962) * chore: update Flutter to 3.41.2 and suppress experimental_member_use warnings Both LockCachingAudioSource (just_audio) and TableMigration (drift) are the only available APIs for their respective tasks with no stable alternatives. Added ignore directives with explanatory comments. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: bump minimum Flutter SDK constraint to 3.41.2 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * chore: add claude code team settings (#965) * chore: add Claude Code team settings and documentation Add team-level `.claude/settings.json` with deny rules for keystores, signing keys, and environment files. Add `.claude/settings.local.json` to `.gitignore` and document settings levels in `docs/development.md`. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: add safe tool allow rules to team settings Allow flutter test, analyze, dart format, dart fix, pub get, and gen-l10n commands in team settings for all developers. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * fix: remove unused import in messaging_shell.dart (#966) Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * fix(settings): add optimistic toggle with spinner for register status switch (#967) Replaces the blocking register status toggle with an optimistic update pattern — the switch flips immediately, shows a spinner, disables during the API call, and reverts on failure. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * fix: skip static payloads sanitizing if no rtpmap (#968) * fix: media preset translation (#969) * fix: fix splash config generation and add android_12 image support (#865) - Escape # in Makefile color variables to prevent shell comment issues - Remove redundant shell parameter expansion, use Make-only expansion - Add ANDROID_12_SPLASH_IMAGE variable for separate Android 12 splash - Use single quotes in echo to avoid shell interpretation problems Co-authored-by: Dmytro Serdun <d.serdun@webtrit.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * chore: add diagnostic logging for recents number mismatch (#970) Add hash-based and raw value logging to trace why recent calls list shows two different phone numbers per entry. Raw values are visible when Logz.io anonymization is disabled, hashes work with it enabled. * feat: add support a blurred appbar surface (#971) * feat: add BlurredSurface widget and apply blur effect to all app bars Extract common ClipRect+BackdropFilter pattern into reusable BlurredSurface widget. Apply consistent blur background with extendBodyBehindAppBar to all screens using MainAppBar or AppBar. Remove isComplexBackground conditional logic. * refactor: standardize MainAppBar field order across screens * feat: apply blurred appbar surface to ConversationsScreen * fix: remove unused theme/theme.dart imports * fix: add top padding to prevent content overlap with blurred appbar Screens using extendBodyBehindAppBar lacked compensating padding, causing body content to be hidden behind the blurred AppBar. Added appropriate top padding to recents, favorites, about, contacts, and CDR screens consistent with the existing settings screen approach. * feat(screenshots): make IgnorePointer configurable in ScreenshotApp (#972) Add ignorePointer parameter (default true) to allow enabling interaction for specific screenshots that require it. * feat: add screenshot mocks and docs for all features (#973) * feat: add screenshot mocks for all features Add ~20 new screenshot definitions covering all user-visible screens: - Core flows: contact, chat conversation, SMS conversation, system notifications, recent CDRs, number CDRs, call log - Settings: network, language, diagnostic, caller ID, presence, theme mode, voicemail - Login: switch screen - Utility: log records console, contacts agreement, teardown, permissions, user agreement Includes shared mock data, mock blocs/cubits/repositories, screenshot widgets, router entries, and integration test registrations. * docs: add screenshots package documentation Add docs for screenshots architecture, mock reference, data reference, and step-by-step guide for adding new screenshots. Update README with links to the new documentation. * fix: address review feedback for screenshot mocks and docs - Add provider as direct dependency in screenshots/pubspec.yaml and remove ignore: depend_on_referenced_packages from 7 files - Fix mock type/factory mismatches in mocks.md documentation - Fix broken code formatting in mocks.md and adding_screenshots.md - Remove shadowed ChatTypingCubit/SmsTypingCubit BlocProviders from conversation screenshot wrappers - Guard registerFallbackValue with static flag to prevent duplicate registration - Fix duplicated phrase in README.md Firebase Hosting section - Document both flutter test and flutter drive workflows in overview.md * feat: update keystores path to new applications subdirectory (#974) * feat: add text style background decoration support (#975) * feat: add backgroundBorderRadius and backgroundPadding to TextStyleConfig * feat: add greetingTextStyle to LoginModeSelectPageConfig * feat: add ExtendedText widget with background decoration support * feat: add greetingTextStyle to login.modeSelect docs * feat: add tests for ExtendedText widget * feat: update theme template configs with missing components and fixes (#976) - Fix calStatuses typo to callStatuses in both widget configs - Add missing widget sections as examples: button, group, bar, input, text, confirmDialog - Fix non-existent dark SVG asset references to use existing logos - Remove unsupported fontFeatures from page text styles - Clean up dark page config duplicate keys in login.switchPage - Add greetingTextStyle for dark login modeSelect visibility - Update dark widget decoration gradient config * refactor: replace deprecated GradientColorsConfig with PageBackground (#977) * refactor: replace deprecated GradientColorsConfig with PageBackground Remove GradientColorsConfig, DecorationConfig, GradientsStyleFactory, and Gradients theme extension. Migrate login and call screens to use PageBackground via ThemedScaffold. * refactor: enable gradient backgrounds for login and call page configs Activate background for login modeSelect and add gradient background to dialing section using colors from the removed GradientColorsConfig. * refactor: adjust dark login gradient to contrast with buttons Change bottom gradient color from #000000 to #0A1929 to avoid blending with neutralOnDark button surface color. * refactor: reverse dark login gradient direction Start with dark color on top, lighter blue on bottom to keep contrast with buttons at the bottom of the screen. * refactor: rename misleading onSurface variable to surfaceColor * feat: appBar blurred surface config and theme updates (#979) * feat: extract appBar background color and blurred surface to config pipeline Add configurable appBarBackgroundColor and appBarBlurredSurface (color, sigmaX, sigmaY) to the theme config→style pipeline across all 7 main screens, replacing hardcoded values. * feat: update page configuration docs with appBar fields Add Common page fields section documenting appBarBackgroundColor and appBarBlurredSurface, update Keypad page example. * feat: remove appBarBackgroundColor from config pipeline AppBar backgroundColor is already handled by MainAppBar fallback chain (explicit → appBarTheme → canvasColor.withAlpha). Remove redundant appBarBackgroundColor from BasePageConfig, all page configs, screen styles, factories, and screens to avoid confusion. * feat: add BlurredSurface.fromStyle factory and simplify screen usage Replace inline BlurredSurface construction with fromStyle() factory that returns null when no config is present (no blur applied) or a configured BlurredSurface with resolved sigma defaults of 10. * feat: enable appBar blur and activate widget config sections - Add appBarBlurredSurface to all 7 page sections (dark/light) - Activate previously ignored widget config sections (_bar, _button, _group, _input, _text -> remove underscore prefix) - Set appBar backgroundColor/surfaceTintColor to transparent for blur - Fix tab indicator label contrast and remove divider line * feat: update light theme appBarBlurredSurface color to soft blue-white * feat: set dark status bar icons for light theme appBar * feat: fix appBarBlurredSurface sigma defaults and add tests - Make sigmaX/sigmaY nullable in BlurredSurfaceConfig so omitted fields resolve to 10 via BlurredSurface.fromStyle instead of staying 0 - Regenerate Freezed/JSON for BlurredSurfaceConfig - Restore blur on RecentCdrs and SystemNotifications screens by passing sigmaX: 10, sigmaY: 10 explicitly to const BlurredSurface() - Explicit Colors.transparent fallback in BlurredSurface Container child - Update docs to reflect null defaults with 10 resolved at widget layer - Add BlurredSurface.fromStyle widget tests (4 cases) * fix: system back button blocked in EmbeddedRequestErrorDialog (#935) - Remove canPop: false from EmbeddedRequestErrorDialog — default is true, and blocking pop prevented the system back button from working in Settings → Terms & Conditions when the page failed to load (WT-890). - Add onPopInvokedWithResult to call onBack when system back dismisses the dialog in Mode B (pushed route), ensuring _errorDialogShown is reset. - Reset _errorDialogShown in _handleEmbeddedErrorState when error is cleared, so repeated errors after system-back dismissal are shown again. Co-authored-by: Dmytro Serdun <d.serdun@webtrit.com> * fix: prevent keepalive write-after-close and harden transaction lifecycle (#870) * fix: prevent keepalive write-after-close and add regression test * fix: clean up transaction on send failure in _executeTransaction Move _addMessage inside the try-catch block so that if writing to a closed socket throws WebtritSignalingBadStateException, the transaction is properly removed from the _transactions map instead of leaking. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * test: add transaction cleanup and keepalive timer tests Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: add comment for closeCode guard in keepalive loop Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address Copilot review comments on keepalive integration tests - Fix compile error: replace called(greaterThan(0)) with called(1) - Fix lint warning: await streamController.close() in tearDown - Fix false-positive test: move closeCode mock before flushMicrotasks and add verify closeCode called(2) to catch timer-restart regression --------- Co-authored-by: Dmytro Serdun <d.serdun@webtrit.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * fix: fixed app is locked in Bluetooth call profile after first call (#982) * fix: fixed app is locked in Bluetooth call profile after first call Fixed situation when user returns to YouTube or music playback, the audio remains degraded, like during a call. This happens only on Android. * fix: improve comment for _onLastCallEnded to cover iOS and Android audio routing --------- Co-authored-by: Dmytro Serdun <d.serdun@webtrit.com> * feat: add melos v7 integration (#980) * feat: add melos integration with codegen, test, format, and dependency scripts * feat: fix melos scripts syntax for v7 and rename format to fmt * feat: configure melos v7 with workspace and scripts in pubspec.yaml - Add workspace: list to pubspec.yaml for Dart pub workspace / Melos v7 package discovery - Add melos: section to pubspec.yaml with scripts for codegen, tests, formatting, dependencies - Add resolution: workspace to all sub-package pubspec.yaml files - Fix ssl_certificates publish_to typo (none; → 'none') - Update screenshots SDK constraint to ^3.8.0 - Remove melos.yaml (ignored by Melos v7 — config lives in pubspec.yaml) * feat: include root package in melos workspace via useRootAsPackage * fix: replace non-ASCII em dashes with hyphens in pubspec.yaml comments * feat: migrate makefile targets to melos scripts and mark deprecated * fix: replace __ with _ in screenshots to fix unnecessary_underscores lint * fix: rename melos script from run to start to avoid CLI conflict The melos v7 CLI command `melos run <script>` conflicted with the script named `run`. When running `melos run fmt`, melos treated `run` as the script name and `fmt` as an argument, causing flutter to look for a file named `fmt` instead of formatting code. Renaming the script to `start` (and `run:ios` to `start:ios`) resolves the ambiguity. Use `melos run start` to launch the app. * feat: add melos smoke tests and update docs with melos commands - Add tool/scripts/melos_smoke_test.sh for smoke testing safe scripts - Add smoke:test melos script to pubspec.yaml - Rename start to start:android for clarity - Replace Makefile references with melos commands in docs/build.md - Rewrite docs/make_file.md as full melos commands reference - Update README.md to link to Melos Commands * fix: address Copilot review comments - Switch get/upgrade/outdated melos scripts from exec to run to correctly resolve from workspace root (not per-package) - Align screenshots flutter constraint with workspace root (^3.41.2) to fix sdk/flutter constraint inconsistency (Dart 3.8 requires Flutter 3.41+) * fix: reverse date divider in chat (#983) Placed date divider before bunch of messages. Also changed displaying date in this divider. Added displaying options "Today", "Yesterday", day of week, like "Monday", and date in E, d MMM format, like "Tue, 6 Jan" , depending on comparison between date of message and today's date. * feat: claude code setup (#981) * feat: replace .rules with CLAUDE.md for Claude Code * feat: configure .claude with hooks and allowed commands * feat: add melos usage rules and update common commands * feat: reduce CLAUDE.md duplication and soften melos usage rules * feat: add webtrit_callkeep docs and package-level CLAUDE.md files * feat: document call architecture, flows, isolates, and key patterns in CLAUDE.md * feat: restructure AI agent memory files (AGENTS.md + CLAUDE.md split) - Add root AGENTS.md (<100 lines): universal instructions for any AI tool - Slim root CLAUDE.md (<50 lines): @imports + Claude-specific gotchas only - Extract CallBloc architecture to docs/call_architecture.md - Add AGENTS.md for all 10 packages (shared content, any agent) - Remove package CLAUDE.md where content fully moved to AGENTS.md - Keep package CLAUDE.md only where Claude-specific gotchas exist (data, ssl_certificates) - Add CLAUDE.local.md to .gitignore * feat: address Copilot review comments in PR #981 - Fix md_formatter.py docstring: was 'prettier', now correctly says 'markdownlint-cli2 --fix' - Expand settings.json deny list to cover keystores (.jks, .keystore, .p12) and signing keys (.pem, .key, .p8) - Fix AGENTS.md import groups: two '2.' entries corrected to sequential 1–6 numbering - Fix packages/data/CLAUDE.md migration steps: misnumbered list corrected to 1–5 - Add working directory note to packages/data/AGENTS.md commands section - Fix _web_socket_channel/AGENTS.md: 'pinned to' → 'constrained to' for caret constraint * feat: remove webtrit_phone_keystores from deny list (dir is outside project scope) * feat: configure gitignore, formatter, and analysis for generated files (#985) * feat: configure gitignore, formatter, and analysis for generated files - Add **/build/ and **/.claude/ to .gitignore - Update lefthook pre-commit to skip *.g.dart / *.freezed.dart / *.gr.dart - Replace melos fmt/fmt:check with find-based scripts that exclude generated files * feat: update melos v7 integration across features, DAOs, and screenshots * feat: allow feat/ as valid branch prefix alongside feature/ (#987) * feat: allow feat/ as valid branch prefix alongside feature/ Update branch-name-check.sh and git-lint.yml to accept both feat/ and feature/ prefixes. Add CONTRIBUTING.md documenting branch naming rules and commit conventions. * docs: fix table alignment in CONTRIBUTING.md * docs: consolidate git conventions into CONTRIBUTING.md as single source of truth - Remove .rules.md (referenced non-existent .rules/ directory) - Update .github/copilot-instructions.md to reference CONTRIBUTING.md and AGENTS.md - Add feat/ to branch pattern in copilot-instructions.md - Fix pre-commit hook description in docs/development.md (dart format, not flutter analyze) - Add CONTRIBUTING.md reference in docs/development.md - Add docs/development.md pointer in CONTRIBUTING.md hooks section * feat: favorites remote syncable (#984) * refactor: cdr ui improvements, disconnect reason translations (#989) * fix: use firstWhere instead of first in getAllContacts test to avoid ordering assumption (#991) * fix: change contact_phones UNIQUE constraint to (number, label, contact_id) (#992) * feat: change contact_phones UNIQUE constraint to (number, label, contact_id) * feat: update ContactPhonesDao to use (number, label) pairs for stale-row deletion * feat: persist per-label phone rows in ContactsLocalDataSource for DID-only accounts * feat: add tests for DID-only contact phone handling and schema migration v20 * docs: add inline comment explaining grouping and merge logic in displayPhones * refactor: rename lambda param p to phone in deleteOtherContactPhonesOfContactId call * style: apply dart format to changed files * docs: add DartDoc to deleteOtherContactPhonesOfContactId * docs: add inline comments to deleteOtherContactPhonesOfContactId body * style: replace non-ASCII arrow with hyphen in comment * fix: use canonical label in displayPhones to prevent merged label from reaching favorites * fix: recreate contact_phones via new table to preserve favorites FK reference * test: add favorites FK integrity assertion for migration v20 * fix: resolve canonical ContactPhone by id before passing to favorites * docs: document merged label display and canonical label favorites behavior * refactor: replace ContactPhone display with flat fields in ContactPhoneDisplayEntry - Replace synthetic ContactPhone display field with displayLabel (String) and displayFavorite (bool) - only the two fields that actually differ from the canonical phone - Rename canonical field to phone for clarity - Update displayPhones convenience getter to reconstruct ContactPhone from phone + flat display fields - Update ContactPhoneTileAdapter to accept only primitives and closures, removing all model object dependencies - Update ContactScreen loop to use displayPhoneEntries with closures capturing entry.phone directly - Add widget tests for ContactPhoneTileAdapter covering all enable flags, callback wiring, transfer logic, and popup menu entries - Add direct tests for displayPhoneEntries covering displayLabel, displayFavorite, and phone field contracts * feat: melos exported env support (#993) * fix: log records file stability improvements (#998) * fix: guard File.delete() with exists() check in shareLogRecords Prevents PathNotFoundException on Android 15 when the OS clears the app cache directory under background memory pressure while the native share sheet is open, causing the finally block to attempt deleting a file that no longer exists (Crashlytics issue 25d22f4de9016c556b46cfacea29a20b). * fix: delegate dispose Future in LogRecordsFileRepositoryImpl Without awaiting or returning the Future from RotatingFileAppender.dispose(), the file handle could remain unclosed after the caller awaited repository disposal. Use direct return to delegate the Future without an async wrapper. * fix: replace existsSync retry with async exists() in _getAllLogFilesWithRetry existsSync() reads from the OS filesystem cache and can return false immediately after forceFlush() even though the file is on disk. Switching to async File.exists() forces a fresh stat() call that bypasses the cache. Also reduces max wait from 10s (5x2s) to 1s (10x100ms). * fix: guard forceFlush() with try/catch in readAllLogs An I/O error during forceFlush() would propagate and abort readAllLogs entirely, returning no logs to the caller. Catching the error allows reading whatever files are already on disk. * fix: guard forceFlush() with try/catch in cleanLogs An I/O error during forceFlush() would abort cleanLogs entirely, leaving stale log files on disk. Catching the error allows deletion to proceed on whatever files are already present. * fix: replace retry loop with async file discovery in log records - Replace _getAllLogFilesWithRetry (10×100ms polling loop) with _getAllLogFilesAsync — single async pass using await file.exists() per rotation slot, bypassing OS filesystem cache without any delay - Extend file discovery range to 0..keepRotateCount inclusive so rotated files (e.g. app_logs.log.1) are found even when base file is absent immediately after rotation - Fix cleanLogs to use await _getAllLogFilesAsync() instead of synchronous getAllLogFiles() which relied on existsSync() - Fix readAllLogs iteration order: remove files.reversed so newer file (rotation 0) is read before older rotated file (rotation 1) - Remove redundant file.exists() guard inside readAllLogs loop since _getAllLogFilesAsync already guarantees file presence - Add full test coverage: LogRecordsMemoryRepositoryImpl, ReadableRotatingFileAppender (readAllLogs + cleanLogs), LogRecordsFileRepositoryImpl — 26 tests total * fix: update outdated comment in readAllLogs test * feat: add Claude Code PostToolUse hooks (dart formatter + newline enforcer) (#995) * fix: call dropped when user taps before app fully initializes (#997) * fix: wait for signaling and registration before failing outgoing call In __onCallPerformEventStarted, the early registration guard ran before the signaling wait, causing calls to drop immediately when the user tapped call before the socket initialized. Move the guard after the signaling wait and extend the wait predicate to also require registration status to be known (isHandshakeEstablished && isSignalingEstablished). * fix: hold outgoing call as pending until routing state is available When the user taps call before CallRoutingCubit has initialized (app just launched, user info not yet fetched), wait for the first non-null routing state instead of immediately failing. The call proceeds automatically once routing state becomes available. If the cubit is disposed while waiting, the call is silently dropped. * fix: remove unused notification import from CallController * test: add CallController.createCall unit tests Cover immediate dispatch, pending wait, cubit disposal, and CallUndefinedLineNotification scenarios. * fix: remove unused optional params in _FakeCallRoutingState * fix: remove unnecessary call_controller.dart import in test * fix: address Copilot review — unawaited createCall and fast-fail on signaling failure - Make createCall void by delegating to private _createCallAsync via unawaited, avoiding unawaited_futures lint at all call sites - Add isFailure condition to signaling firstWhere predicate so outgoing calls fail immediately on signaling failure instead of waiting full timeout * fix: update call_controller tests to use void createCall Replace await controller.createCall(...) with call + await Future.delayed(Duration.zero) to pump microtasks after createCall became void * refactor: extract signaling wait predicate into named variables * fix: use currentState instead of state for registration check after signaling wait * fix: replace non-ASCII em dash with semicolon in comment * refactor: remove redundant signalingConnected/registrationKnown vars, use CallState getters directly * fix: await all addTrack calls before createOffer to prevent empty offer * refactor: add TODO to provide CallController as singleton via RepositoryProvider * fix: log warning when callRoutingCubit closes before routing state arrives * refactor: add comment explaining callRoutingState await logic * fix: add timeout to routing state wait and show NoInternetConnectionNotification on expiry * refactor: extract _waitForRoutingState helper to clean up routing state await * fix: catch unexpected errors from _createCallAsync and add timeout notification test * fix: await reportNewIncomingCall in background FCM handler (#1001) Without await the Pigeon IPC Future was fire-and-forget — the background isolate was destroyed before the call reached the Kotlin side, so PhoneConnectionService.startIncomingCall() was never invoked and the incoming call UI never appeared. * feat: provide CallController as singleton via RepositoryProvider in MainShell (#1000) * feat: provide CallController as singleton via RepositoryProvider in MainShell Register CallController once in MainShell widget tree after CallBloc, CallRoutingCubit and NotificationsBloc are available. All seven call sites now obtain the shared instance via late final field initializer (context.read<CallController>()) instead of constructing a new object per StatefulWidget. * feat: replace RepositoryProvider with CallControllerScope InheritedWidget RepositoryProvider is semantically for the data layer; CallController is a UI-tier coordinator. Introduce CallControllerScope (InheritedWidget) following the PresenceViewParams pattern already in the codebase. All seven consumers now resolve the controller via CallControllerScope.of(context). * fix: prevent stale CallController on MainShell rebuild Store CallController in _MainShellState via ??= so it is created once regardless of how many times build() reruns. Switch CallControllerScope.of to getElementForInheritedWidgetOfExactType to avoid registering a rebuild dependency that would never fire (updateShouldNotify is always false). * feat: add doc comment for _callController field in _MainShellState * fix: feature access mapper logic (#1007) * fix: correct icon and text case assertions in integration tests (#1010) * fix: correct icon and text case assertions in integration tests * revert: remove accidentally committed logger from call_bloc.dart * feat: add integration tests for webtrit_signaling keepalive and disconnect (#1009) * feat: add integration tests for webtrit_signaling keepalive and disconnect Add mock-based and live integration tests covering: - Keepalive timeout (WebtritSignalingKeepaliveTransactionTimeoutException) - Normal keepalive cycles (echo → timer restarts) - Graceful disconnect (onDisconnect callback) - Stream error (onError callback) - Execute after disconnect (WebtritSignalingDisconnectedException) - Request transaction timeout (non-keepalive variant) - Live tests against a real server (skipped when credentials not set) - Network simulation via pure-Dart WebSocket proxy with pause/resume to verify keepalive timeout fires on real packet drop * feat: replace WebSocket proxy with raw TCP proxy in live signaling test Drop packets at the TCP byte level instead of WebSocket frame level for more realistic network simulation. The proxy rewrites the HTTP Host header before forwarding so the server accepts the upgrade request. * feat: extract TcpProxy into reusable _tcp_proxy package Move the raw TCP proxy from the signaling test into a standalone internal package packages/_tcp_proxy so it can be used as a dev dependency in both webtrit_signaling tests and app-level tests. * fix: address Copilot review comments on signaling tests and tcp proxy - Fix expectLater() to pass Future directly instead of closure - Make live test client nullable to prevent LateInitializationError in tearDown - Use local signalingClient variable in each live test for clarity - Replace localhost with 127.0.0.1 to avoid IPv6-first resolution issues - Remove unconditional TLS certificate bypass in live test HTTP client - Discard bytes while paused in _relayWithHostRewrite to prevent unbounded buffer growth - Add 64 KB header size guard with socket teardown on overflow - Add AGENTS.md, README.md, analysis_options.yaml to _tcp_proxy package * fix: add lints dev_dependency to _tcp_proxy so analysis_options.yaml resolves * fix: replace magic timing numbers with constants in integration tests * docs: add DartDoc to _findCrlfCrlf explaining HTTP header boundary detection * fix(signaling): reconnect silently on server force-close (code 4441) (#1012) When the server sends disconnect code 4441 (controllerForceAttachClose) it means two signaling sessions from the same account were open at the same time — a race that can happen when a background push isolate is still connected as the main engine comes to the foreground and reconnects signaling. Previously this code path set lastSignalingDisconnectCode, which triggered CallStatus.connectIssue and showed a "Connection issue" snackbar to the user even though the situation was transient and self-healing. Fix: on 4441, emit lastSignalingDisconnectCode=null (no connectIssue UI), skip the notification, and reconnect with the fast 1s delay instead of the default 3s. A warning log is emitted so the race remains visible in logs for debugging. * refactor: decouple AppLogger from LogzioLoggingService via RemoteLoggingService abstraction (#1011) * refactor: decouple AppLogger from LogzioLoggingService via RemoteLoggingService abstraction * refactor: move remoteMinLevel extraction to init to avoid cast in applyConfig * refactor: add minLevel to RemoteLoggingService and remove cast from AppLogger * refactor: reorder LogzioLoggingService fields to match RemoteLoggingService interface order * refactor: remove labelsProvider from AppLogger state, pass labels explicitly * refactor: rename regenerateRemoteLabels to updateRemoteLabels and dispose before reinitialize * refactor: replace labels map with lazy callback in AppLogger to encapsulate labels retrieval * fix: attach remote appender before applyConfig so early logs are forwarded to remote * feat: enable and disable log anonymization with env (#1004) * feat: enable and disable log anonymization with env Added functionality to disable and enable log anonymization via environment variable * refactor: simplify AnonymizationType to none/full enum with bool flag Replace the mutable list-based anonymization approach with a single AnonymizationType enum (none/full), removing the race condition risk and simplifying the API surface. * docs: document intentional anonymization scope in AppLogger * fix: add @override annotation to setAnonymizationEnabled in LogzioLoggingService --------- Co-authored-by: Dmytro Serdun <d.serdun@webtrit.com> * fix: api healthcheck path (#1014) * fix: use shared static logger in WebtritSignalingClient (#1013) * fix: use shared static logger in WebtritSignalingClient with instance id prefix * fix: restore custom logger support via inner constructor parameter * fix: simplify logger to use fixed name without instance counter * fix: remove custom logger parameter from inner constructor * fix: signaling errors (#1016) * fix: snackbar duration * fix: new persist option usage * fix: reconnect message repeating * fix: reconnect on keepalive timeout * feat: user info cache (#1015) * feat: main impl * refactor: call to actions fix * fix: call to actions provider * docs: getAndListed explain * fix: integration tests rescue (#1025) * fix: migrate to 4.5 * fix: various fixes * fix: remove log file from standart test * fix: no audio if lone codec (#1028) * fix: WebRTC signaling state guards — ICE restart, renegotiation, and glare (WT-986) (#1003) * refactor: extract RenegotiationHandler with stable-state and concurrency guards - Extract renegotiation logic from CallBloc into a standalone RenegotiationHandler class - Add two stable-state guards: pre-offer check and TOCTOU guard after createOffer - Add _isHandling/_pendingRetry flags to serialize concurrent onRenegotiationNeeded firings - Catch WebtritSignalingErrorException for server-side error logging without swallowing - Catch plain String errors (flutter_webrtc native) separately from Dart exceptions - Add unit tests covering stable-state skip, concurrency serialization, and error paths - Document server-mediated vs P2P topology constraints and Perfect Negotiation limitation * fix: add signalingState guards and Perfect Negotiation rollback to call flow ICE restart handler: - Skip setLocalDescription when signalingState != stable to prevent native crash - Log warning instead of silently skipping Renegotiation / accepted handler: - Guard setRemoteDescription(answer) against wrong state after glare resolution - Log transceivers after setRemoteDescription for SDP debugging - Catch String errors from setRemoteDescription to prevent unhandled exception escalation Updating handler (Perfect Negotiation rollback): - Pre-check signalingState for glare: if have-local-offer, roll back local offer before setRemoteDescription - Catch String errors containing have-local-offer as fallback for stale flutter_webrtc signalingState cache - Roll back and retry setRemoteDescription on confirmed glare Renderer and state: - Always refresh srcObject in RTCStreamView.didUpdateWidget to handle renegotiation-replaced tracks - Fix remoteVideo getter to use logical OR (stream tracks || video flag) instead of short-circuit Add call_state_test.dart covering ActiveCall equality and remoteVideo edge cases * refactor: replace on String catch with typed RTC exceptions via RtcJsepErrorParser * fix: guard RTCVideoRenderer srcObject assignment until initialize() completes (#1027) * fix: guard RTCVideoRenderer srcObject assignment until initialize() completes Prevents a crash where didUpdateWidget set srcObject before the renderer was initialized, causing 'Call initialize before setting the stream'. The _initialized flag ensures srcObject is only set after initialize() resolves. Also always refreshes srcObject (not just on stream identity change) to handle track replacement during renegotiation. * fix: use setState and guard build before renderer initialization Wrap _initialized and srcObject assignment in setState so the flag change is properly synchronized with Flutter's build cycle. Guard build() to avoid passing an uninitialized renderer to RTCVideoView — the placeholderBuilder (or empty SizedBox) is shown until initialize() completes. * fix: update ExternalContactsSyncBloc tests to mock getAndListen instead of getLocalInfo (#1029) * feat: handle 'voicemail_not_configured' error (#1005) * feat: handle 'voicemail_not_configured' error Added handling 'voicemail_not_configured' error from voicemail api * fix: properly handle voicemail_not_configured and endpoint_not_supported errors across all layers - WebtritApiClient: skip SEVERE log for expected VoicemailNotConfiguredException / EndpointNotSupportedException - VoicemailRepositoryImpl: add _featureSupported flag to skip API calls once feature is known unavailable; add _fetchingCompleter.future.ignore() to prevent unhandled future errors; suppress stack trace in warning log for expected exceptions; expose isFeatureSupported getter - VoicemailRepository: add isFeatureSupported to abstract interface; EmptyVoicemailRepository returns false - VoicemailCubit: check isFeatureSupported on init to immediately emit featureNotSupported without an API call - SettingsBloc: catch expected exceptions from unawaited fetchVoicemails() to prevent runZonedGuarded propagation - PollingService / ConnectivityLifecycleService: remove stack trace from WARNING logs in generic catch blocks * fix: always allow navigation to voicemail screen regardless of feature support Previously the settings tile was disabled with reduced opacity when voicemail was not configured, which was confusing. Now the tile is always tappable and the voicemail screen shows a placeholder explaining the reason. * fix: move VoicemailCubit provider from SettingsScreenPage to VoicemailScreenPage VoicemailCubit was provided at the settings level but consumed in a separate route, causing ProviderNotFoundException on navigation. Moving it to VoicemailScreenPage makes the provider scope match the consumer. * fix: add missing call feature import to VoicemailScreenPage * fix: remove unused stack trace variables from catch blocks in polling and connectivity services * fix: address Copilot review comments - VoicemailCubit: guard watchVoicemails subscription against overwriting featureNotSupported state - VoicemailNotConfiguredException: pass token and error fields to super; remove commented-out placeholder - WebtritApiClient: log xRequestId (never null) instead of requestId parameter; pass token/error to VoicemailNotConfiguredException - SettingsTile: add assert that opacity is in [0.0, 1.0] --------- Co-authored-by: Dmytro Serdun <d.serdun@webtrit.com> * feat: add toJson() to all Event subclasses and fix NotifyEvent constructor inconsistency (#1030) * fix: suppress transient network error SnackBar in RegisterStatusCubit (#1031) Auto-triggered fetches (startup and connectivity restore) now use _fetchStatusSilently(), which skips handleError for SocketException, TimeoutException, and TlsException. The cubit retries automatically on the next connectivity change, so surfacing these transient failures to the user is misleading. User-initiated fetchStatus() retains the original behaviour and still calls handleError for all errors. * fix: add 10s timeout to reportNewIncomingCall in background message handler (WT-1061) (#1032) Telecom on affected devices can become overloaded with phantom PhoneAccount registrations, causing reportNewIncomingCall() to hang indefinitely. This keeps FlutterFirebaseMessagingBackgroundService alive and blocks subsequent cold starts. Adding a 10s timeout bounds the handler lifetime and logs a warning when Telecom is slow to respond. * fix: add 5s timeout to getInitialMessage() to prevent splash freeze (WT-1061) (#1033) * refactor: remove tryUse from AppDatabaseScope, migrate callers to useOrNull (#1034) * fix: eliminate write-write SQLite contention via shared DriftIsolate server (WT-1061) (#1035) * fix: eliminate write-write SQLite contention via shared DriftIsolate server (WT-1061) Spawns a single dedicated DriftIsolate server in the main isolate bootstrap and registers its SendPort in IsolateNameServer under a fixed key. Background isolates (FCM handler, WorkManager) now connect to the same server via IsolateDatabase.connectOrCreate(), which looks up the port and creates a client connection — falling back to a direct NativeDatabase connection when the main app is not running (cold push with no foreground app). All writes are serialized through the single server isolate, making write-write SQLITE_BUSY (code=5) between concurrent isolates impossible. Changes: - app_database: add createAppDatabaseNative() for synchronous NativeDatabase creation inside the server isolate (no createInBackground needed) - IsolateDatabase: add spawnServer(), connectOrCreate(), kDbPortName - AppDatabaseScope.use(): connect via connectOrCreate() instead of create() - bootstrap(): spawn DriftIsolate server, register DriftIsolate in InstanceRegistry - AppDatabaseLifecycleHolder: connect to DriftIsolate, shutdown server on dispose * fix: address Copilot review — robust spawnServer error handling and stale port cleanup (WT-1061) * test: add integration tests for IsolateDatabase stale port handling (WT-1061) * refactor: introduce SignalingModule stream abstraction (phase 1) (#1024) * refactor: introduce SignalingModule stream abstraction (phase 1) Replace SignalingManager callback-based API with SignalingModule — a sealed-event broadcast stream that owns the WebtritSignalingClient lifecycle without any BLoC, CallState, or UI dependency. Key changes: - Add SignalingModule with fire-and-forget connect(), disconnect(), dispose() and a sealed SignalingModuleEvent hierarchy (Connecting, Connected, ConnectionFailed, Disconnecting, Disconnected, HandshakeReceived, ProtocolEvent) - Add isRepeated deduplication on ConnectionFailed to suppress repeated identical error notifications - Map disconnect codes to recommendedReconnectDelay: 4441 → Duration.zero, protocolError → null, all others → 3 s - Migrate CallBloc from direct WebtritSignalingClient callbacks to SignalingModule stream subscription; new _SignalingClientEvent variants: connecting, connected, failed, disconnecting, disconnected - Migrate IsolateManager (Push + Foreground) to SignalingModule, replacing SignalingManager; add connectivity monitoring and pending request queue inside IsolateManager - Construct SignalingModule in main_shell.dart and inject into CallBloc - Delete SignalingManager and remove its export from common.dart - Add 31 unit and integration tests for SignalingModule * fix(test): update ExternalContactsSyncBloc tests for getAndListen API The BLoC was updated to call userRepository.getAndListen() instead of getLocalInfo(), but the mocks were never updated. Fix the setUp mock and correct the RefreshFailure test to use load() failure (which is the actual trigger for that state) rather than userRepository failure. * fix(test): rename local function to avoid leading underscore lint warning * docs: translate signaling architecture doc to English * docs: remove phase 1 requirements planning doc from repo * refactor: remove coreUrl/tenantId/token/trustedCertificates from CallBloc These four fields were passed through CallBloc only to construct SignalingModule internally. Now that SignalingModule is constructed externally and injected via the constructor, the fields are dead code. Remove them and the corresponding import from ssl_certificates. * fix: address Copilot review comments on SignalingModule/IsolateManager - Guard delayed reconnect callbacks with signalingClient == null check to avoid tearing down a healthy connection that connected during the delay - Populate _incomingCallEvents from handshake and protocol events so _findIncomingEventLog returns real caller data instead of null - Use disconnect() instead of dispose() in handleLifecycleStatus so the module remains reusable when the app returns to the foreground - Fix post-dispose connect() test to actually subscribe to the event stream and assert Connecting/Connected events are absent after dispose * feat: replay session events to late subscribers in SignalingModule Adds a per-subscriber replay buffer so that consumers created after connect() (e.g. CallBloc constructed after SignalingModule already connected and received a handshake) do not miss any events from the current session. - events getter now returns a single-subscription stream that first replays all events buffered since the last connect() call, then pipes live events from the broadcast controller - connect() clears the buffer so late subscribers see only the current session, not stale events from previous reconnect cycles - dispose() also clears the buffer on teardown - Uses sync: true on the intermediate StreamController to avoid an extra async hop and keep delivery ordering consistent with callers that await module operations - Adds two integration tests covering the late-subscriber replay and the buffer-clear-on-reconnect behaviours * feat: connect SignalingModule early in initState to reduce call setup latency SignalingModule is now created and connected in _MainShellState.initState(), running the WebSocket handshake in parallel while the widget tree and CallBloc are being built. When CallBloc is eventually created it subscribes to the replay stream and receives all buffered session events without missing anything. _MainShellState.dispose() owns the module lifecycle; CallBloc.close() still calls dispose() on the module (idempotent, safe). * docs: update signaling architecture doc with layer descriptions and diagrams * fix: add concurrency lock to _connectAsync to prevent parallel connects * fix: await disconnect ack in dispose() to prevent SignalingDisconnected drop on race * fix: suppress reconnect hint on intentional disconnect to prevent spurious reconnect * fix: remove SignalingModule.dispose() from CallBloc.close() — ownership belongs to MainShellState * fix: snapshot buffer before live subscribe to prevent replay duplicates in events getter * fix: store reconnect Timer in IsolateManager so it can be cancelled on close() * fix: forward recommendedReconnectDelay from SignalingDisconnected to _scheduleReconnect in CallBloc * fix: suppress _onDisconnect after _onError to prevent double reconnect scheduling * fix: exclude SignalingProtocolEvent from session buffer to prevent unbounded growth * fix: replace force-unwrap of session.coreUrl/token with null-safe logout in initState * fix: remove performEndCall early return so pre-handshake declines are queued * fix: close liveController on subscription cancel to prevent StreamController leak * fix: use _networkNone state instead of stale results snapshot in connectivity timer closure * docs: fix _scheduleReconnectIfNeeded → _scheduleReconnect in signaling architecture doc * test: replace Future.delayed(Duration.zero) with pumpEventQueue() in signaling module tests * fix: make _controller sync:true to eliminate async-dispatch event duplication * docs: clarify disconnect() docstring — SignalingDisconnected is callback-driven, not synchronous * fix: wrap _signalingModule.disconnect() in unawaited() in _disconnectInitiated * fix: remove unused shouldReconnect variable from __onSignalingClientEventDisconnected * test: add 8 tests to reach 100% SignalingModule coverage - concurrent connect() dropped while factory in-flight (_connecting guard) - intentional disconnect() emits SignalingDisconnected with null delay - disconnect() passes goingAway code to the underlying client - _onError suppresses subsequent _onDisconnect (_errorHandled flag) - SignalingProtocolEvent excluded from replay buffer - cancelled subscription receives no further events - dispose() awaits disconnect ack before closing the stream - _onHandshake/_onEvent are no-ops after dispose() * test: add scenario-driven SignalingModule tests from CallBloc usage analysis Covers scenarios observed in CallBloc's signaling subscription: internet dropped mid-session: - _onError after handshake → ConnectionFailed not Disconnected, signalingClient cleared - Unexpected socket close (null code) → Disconnected with kSignalingClientReconnectDelay - ConnectionFailed buffered so late subscribers reconstruct last-known failure handshake not completed: - Disconnect before handshake → no HandshakeReceived in buffer, Disconnected with delay - Late subscriber after no-handshake disconnect → gets Connecting+Connected+Disconnected only - Error before handshake → ConnectionFailed buffered, no HandshakeReceived - Reconnect after no-handshake failure delivers fresh session events late subscriber mid-session: - Factory still pending → gets Connecting from buffer, Connected arrives live - After full connect+handshake → all three lifecycle events replayed, no protocol events disconnect() robustness: - client.disconnect() throws → dispose() still completes without hanging - Second disconnect() with no active client is a silent no-op * fix: restore callkeep_signaling_status_converter.dart lost during rebase * fix: remove unused fields in _ThrowingDisconnectClient test helper * fix: use normalClosure (1000) instead of goingAway (1001) when client disconnects WebSocket * test: update disconnect test to expect normalClosure (1000) instead of goingAway (1001) * fix: address Copilot review comments in SignalingModule and IsolateManager - Fix doc comment on events getter: protocol events are not replayed, only lifecycle/handshake events - Guard connect() buffer clear behind _connecting check to avoid clearing on redundant calls - Remove stale comment in _onDisconnect that contradicted the !_disposed guard - Treat empty connectivity result as offline in _monitorConnectivity (results.isEmpty || any(none)) - Treat empty connectivity result as offline in performAnswerCall (isNotEmpty && !contains(none)) * fix: address post-review issues in SignalingModule and IsolateManager - isolate_manager: fix connectivityNoneCounter reset — error now fires exactly once at maxConnectivityNoneRepeats; subsequent none-events are silently ignored until connectivity is restored and counter resets to 0 - main_shell: split SignalingModule construction into valid/invalid-creds branches to remove ?? '' fallbacks and make intent explicit - signaling_module: document sync:true reentrancy assumption, single-use constraint on events getter, and _errorHandled ordering invariant * revert: restore main_shell.dart SignalingModule construction with ?? '' fallback The split-branch approach still created a module with empty strings in the null-creds case — identical in behaviour to the original. Reverted to the original form which is honest about the fallback until a proper nullable refactor is done. * fix: remove dead null-guard in MainShellState.initState for SignalingModule The router guard (onMainShellRouteGuardNavigation) redirects to login when state.status != authenticated, so coreUrl and token are always non-null when MainShell is mounted. Replace ?? '' fallbacks and the unreachable null-branch with direct ! unwraps. * docs: sync signaling_architecture_target.md and call_architecture.md with current code - signaling_architecture_target: add timer cancellation to IsolateManager and CallBloc _scheduleReconnect snippets (Future.delayed → Timer with cancel) - signaling_architecture_target: add missing SignalingDisconnecting case to CallBloc subscription snippet - call_architecture: update ownership — CallBloc owns SignalingModule, not WebtritSignalingClient directly * fix: replace non-ASCII characters with ASCII equivalents in Dart sources Replace em dash (U+2014) with '-' and right arrow (U+2192) with '->' in comments and test descriptions across signaling_module.dart, isolate_manager.dart, signaling_module_test.dart, and call_bloc.dart. * fix: incorrect styling of status bar on app start (#1006) Fixed status bar rendering with incorrect styling on initial app launch when theme is light * feat: show pr…
SERDUN
added a commit
that referenced
this pull request
May 22, 2026
* feat: generalize emergency reboot logic and refine diagnostic reporting (#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>
* refactor: add controller to HistoryAutocompleteField for manual history 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
* feat: migrate system notifications and SIP presence to supported features (#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.
* fix: collect outbound rtp stats (#937)
* fix: description for Media encoding configs (#938)
Fixed description for Media encoding configs in Media settings. Full Flex option was removed, other options were renamed.
* fix(call): preserve widget context during auto-compact transition (#939)
Refactor CallActiveScaffold to use AnimatedOpacity and IgnorePointer
instead of conditional widget removal. This ensures that CallActions
remains in the widget tree, preventing callback failures (e.g.,
onAudioDeviceChanged) when the UI compacts while a popup is open.
* fix: fixed localization issues in network settings (#936)
Fixed issues with localization in network settings. Also added radio buttons and checkbox for 'Incoming Call Type' and 'SMS Fallback' accordingly
* chore: actualize l10n code generation (#940)
* chore: actualize configurations (#942)
* chore: implement modular rules system (#943)
* chore: implement modular rules system and copilot instructions (#946)
* chore: implement modular rules system and copilot instructions
* chore: add makefile helpers for copilot branch management
* feat: add theme switching settings (#951)
* chore: implement modular rules system and copilot instructions
* feat(settings): expose theme mode switching in settings
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
* feat: remote cli settings (#944)
* feat: support metadata for missed call display name in isolate (#953)
* feat: support metadata for missed call display name in isolate
- Introduce getDisplayNameForMissedCall in IsolateManager to resolve caller names.
- Update PushNotificationIsolateManager to utilize CallkeepIncomingCallMetadata for missed call notifications.
- Pass metadata through onPushNotificationSyncCallback and onSignalingSyncCallback.
- Ensure LocalPushRepository returns the future from the notification plugin.
* docs: add documentation for IsolateManager properties
* refactor: improve missed call display name logic and logging
* feat(call): override _onNoActiveLines in PushNotificationIsolateManager for missed call handling (#959)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: SERDUN <26858237+SERDUN@users.noreply.github.com>
* chore: ai agent pipeline refinement (#954)
* chore: ignore agent files
* docs: clarify commit message convention
* chore: enforce lowercase commit descriptions and update rules docs
* docs: refine AI agent execution pipeline and commit rules
* fix: user id migration (#960)
* refactor: replace SupportedMonitorConfig with SupportedLoggingConfig (#957)
* refactor: replace SupportedMonitorConfig with SupportedLoggingConfig
* refactor(logging): decouple AppLogger from RemoteConfigSnapshot
- make kLogLevelKey private in FeatureOverridesFactory
- add remoteLoggingEnabled to FeatureOverrides and LoggingConfig
- rewrite AppLogger.init() to accept LoggingConfig instead of RemoteConfigSnapshot
- add watchFeatureAccess() to observe loggingConfig changes via FeatureAccess stream
- add LoggingMapper.mapFromOverridesOnly() for isolate/background contexts
- update bootstrap.dart and services_isolate.dart call sites
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(logging): replace direct FeatureAccess stream with applyConfig in AppLogger
- remove watchFeatureAccess() and StreamSubscription from AppLogger
- add applyConfig(LoggingConfig) as the single public update point
- call applyConfig in App.didChangeDependencies() via existing FeatureAccess watch
- remove redundant feature_access.dart import from app_logger.dart
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(logging): narrow AppLogger.applyConfig to accept Level directly
- applyConfig now takes Level instead of LoggingConfig
- caller extracts loggingConfig.logLevel before passing to AppLogger
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(logging): remove LoggingConfig dependency from AppLogger
- init() now accepts Level and bool remoteLoggingEnabled as primitives
- remove models.dart import from app_logger.dart
- extract primitives from LoggingConfig at each call site
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(logging): remove EnvironmentConfig dependency from AppLogger
- init() now accepts logzioLogLevel and pre-built List<RemoteLoggingService>
- store logzioLogLevel as field for use in applyConfig
- move _buildRemoteLoggingServices to bootstrap.dart and services_isolate.dart
- remove environment_config.dart import from app_logger.dart
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(logging): simplify AppLogger by replacing List<RemoteLoggingService> with LogzioLoggingService?
- add LogzioLoggingService.fromEnvironment() factory to encapsulate EnvironmentConfig logic
- AppLogger.init() now accepts LogzioLoggingService? instead of List<RemoteLoggingService>
- remove _buildRemoteLoggingServices helpers from bootstrap.dart and services_isolate.dart
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(logging): move logzioLogLevel resolution into LogzioLoggingService.fromEnvironment
- fromEnvironment() now reads REMOTE_LOGZIO_LOG_LEVEL internally, no longer takes minLevel param
- AppLogger.init() drops logzioLogLevel parameter, uses _logzioService?.minLevel in applyConfig
- remove environment_config.dart import from services_isolate.dart
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(logging): add unit tests for LoggingMapper and FeatureAccessStreamFactory logging fields
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(call): compare monitorCheckInterval directly to avoid spurious CallBloc updates
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore: update Flutter to 3.41.2 and suppress experimental_member_use warnings (#962)
* chore: update Flutter to 3.41.2 and suppress experimental_member_use warnings
Both LockCachingAudioSource (just_audio) and TableMigration (drift) are
the only available APIs for their respective tasks with no stable
alternatives. Added ignore directives with explanatory comments.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: bump minimum Flutter SDK constraint to 3.41.2
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* chore: add claude code team settings (#965)
* chore: add Claude Code team settings and documentation
Add team-level `.claude/settings.json` with deny rules for keystores,
signing keys, and environment files. Add `.claude/settings.local.json`
to `.gitignore` and document settings levels in `docs/development.md`.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: add safe tool allow rules to team settings
Allow flutter test, analyze, dart format, dart fix, pub get, and
gen-l10n commands in team settings for all developers.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix: remove unused import in messaging_shell.dart (#966)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix(settings): add optimistic toggle with spinner for register status switch (#967)
Replaces the blocking register status toggle with an optimistic update
pattern — the switch flips immediately, shows a spinner, disables during
the API call, and reverts on failure.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix: skip static payloads sanitizing if no rtpmap (#968)
* fix: media preset translation (#969)
* fix: fix splash config generation and add android_12 image support (#865)
- Escape # in Makefile color variables to prevent shell comment issues
- Remove redundant shell parameter expansion, use Make-only expansion
- Add ANDROID_12_SPLASH_IMAGE variable for separate Android 12 splash
- Use single quotes in echo to avoid shell interpretation problems
Co-authored-by: Dmytro Serdun <d.serdun@webtrit.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* chore: add diagnostic logging for recents number mismatch (#970)
Add hash-based and raw value logging to trace why recent calls list
shows two different phone numbers per entry. Raw values are visible
when Logz.io anonymization is disabled, hashes work with it enabled.
* feat: add support a blurred appbar surface (#971)
* feat: add BlurredSurface widget and apply blur effect to all app bars
Extract common ClipRect+BackdropFilter pattern into reusable BlurredSurface widget.
Apply consistent blur background with extendBodyBehindAppBar to all screens
using MainAppBar or AppBar. Remove isComplexBackground conditional logic.
* refactor: standardize MainAppBar field order across screens
* feat: apply blurred appbar surface to ConversationsScreen
* fix: remove unused theme/theme.dart imports
* fix: add top padding to prevent content overlap with blurred appbar
Screens using extendBodyBehindAppBar lacked compensating padding,
causing body content to be hidden behind the blurred AppBar. Added
appropriate top padding to recents, favorites, about, contacts, and
CDR screens consistent with the existing settings screen approach.
* feat(screenshots): make IgnorePointer configurable in ScreenshotApp (#972)
Add ignorePointer parameter (default true) to allow enabling interaction
for specific screenshots that require it.
* feat: add screenshot mocks and docs for all features (#973)
* feat: add screenshot mocks for all features
Add ~20 new screenshot definitions covering all user-visible screens:
- Core flows: contact, chat conversation, SMS conversation, system
notifications, recent CDRs, number CDRs, call log
- Settings: network, language, diagnostic, caller ID, presence,
theme mode, voicemail
- Login: switch screen
- Utility: log records console, contacts agreement, teardown,
permissions, user agreement
Includes shared mock data, mock blocs/cubits/repositories,
screenshot widgets, router entries, and integration test registrations.
* docs: add screenshots package documentation
Add docs for screenshots architecture, mock reference, data reference,
and step-by-step guide for adding new screenshots. Update README with
links to the new documentation.
* fix: address review feedback for screenshot mocks and docs
- Add provider as direct dependency in screenshots/pubspec.yaml
and remove ignore: depend_on_referenced_packages from 7 files
- Fix mock type/factory mismatches in mocks.md documentation
- Fix broken code formatting in mocks.md and adding_screenshots.md
- Remove shadowed ChatTypingCubit/SmsTypingCubit BlocProviders
from conversation screenshot wrappers
- Guard registerFallbackValue with static flag to prevent
duplicate registration
- Fix duplicated phrase in README.md Firebase Hosting section
- Document both flutter test and flutter drive workflows in
overview.md
* feat: update keystores path to new applications subdirectory (#974)
* feat: add text style background decoration support (#975)
* feat: add backgroundBorderRadius and backgroundPadding to TextStyleConfig
* feat: add greetingTextStyle to LoginModeSelectPageConfig
* feat: add ExtendedText widget with background decoration support
* feat: add greetingTextStyle to login.modeSelect docs
* feat: add tests for ExtendedText widget
* feat: update theme template configs with missing components and fixes (#976)
- Fix calStatuses typo to callStatuses in both widget configs
- Add missing widget sections as examples: button, group, bar, input, text, confirmDialog
- Fix non-existent dark SVG asset references to use existing logos
- Remove unsupported fontFeatures from page text styles
- Clean up dark page config duplicate keys in login.switchPage
- Add greetingTextStyle for dark login modeSelect visibility
- Update dark widget decoration gradient config
* refactor: replace deprecated GradientColorsConfig with PageBackground (#977)
* refactor: replace deprecated GradientColorsConfig with PageBackground
Remove GradientColorsConfig, DecorationConfig, GradientsStyleFactory,
and Gradients theme extension. Migrate login and call screens to use
PageBackground via ThemedScaffold.
* refactor: enable gradient backgrounds for login and call page configs
Activate background for login modeSelect and add gradient background
to dialing section using colors from the removed GradientColorsConfig.
* refactor: adjust dark login gradient to contrast with buttons
Change bottom gradient color from #000000 to #0A1929 to avoid
blending with neutralOnDark button surface color.
* refactor: reverse dark login gradient direction
Start with dark color on top, lighter blue on bottom to keep
contrast with buttons at the bottom of the screen.
* refactor: rename misleading onSurface variable to surfaceColor
* feat: appBar blurred surface config and theme updates (#979)
* feat: extract appBar background color and blurred surface to config pipeline
Add configurable appBarBackgroundColor and appBarBlurredSurface (color, sigmaX, sigmaY)
to the theme config→style pipeline across all 7 main screens, replacing hardcoded values.
* feat: update page configuration docs with appBar fields
Add Common page fields section documenting appBarBackgroundColor
and appBarBlurredSurface, update Keypad page example.
* feat: remove appBarBackgroundColor from config pipeline
AppBar backgroundColor is already handled by MainAppBar fallback chain
(explicit → appBarTheme → canvasColor.withAlpha). Remove redundant
appBarBackgroundColor from BasePageConfig, all page configs, screen styles,
factories, and screens to avoid confusion.
* feat: add BlurredSurface.fromStyle factory and simplify screen usage
Replace inline BlurredSurface construction with fromStyle() factory
that returns null when no config is present (no blur applied) or a
configured BlurredSurface with resolved sigma defaults of 10.
* feat: enable appBar blur and activate widget config sections
- Add appBarBlurredSurface to all 7 page sections (dark/light)
- Activate previously ignored widget config sections (_bar, _button,
_group, _input, _text -> remove underscore prefix)
- Set appBar backgroundColor/surfaceTintColor to transparent for blur
- Fix tab indicator label contrast and remove divider line
* feat: update light theme appBarBlurredSurface color to soft blue-white
* feat: set dark status bar icons for light theme appBar
* feat: fix appBarBlurredSurface sigma defaults and add tests
- Make sigmaX/sigmaY nullable in BlurredSurfaceConfig so omitted fields
resolve to 10 via BlurredSurface.fromStyle instead of staying 0
- Regenerate Freezed/JSON for BlurredSurfaceConfig
- Restore blur on RecentCdrs and SystemNotifications screens by passing
sigmaX: 10, sigmaY: 10 explicitly to const BlurredSurface()
- Explicit Colors.transparent fallback in BlurredSurface Container child
- Update docs to reflect null defaults with 10 resolved at widget layer
- Add BlurredSurface.fromStyle widget tests (4 cases)
* fix: system back button blocked in EmbeddedRequestErrorDialog (#935)
- Remove canPop: false from EmbeddedRequestErrorDialog — default is true,
and blocking pop prevented the system back button from working in
Settings → Terms & Conditions when the page failed to load (WT-890).
- Add onPopInvokedWithResult to call onBack when system back dismisses
the dialog in Mode B (pushed route), ensuring _errorDialogShown is reset.
- Reset _errorDialogShown in _handleEmbeddedErrorState when error is
cleared, so repeated errors after system-back dismissal are shown again.
Co-authored-by: Dmytro Serdun <d.serdun@webtrit.com>
* fix: prevent keepalive write-after-close and harden transaction lifecycle (#870)
* fix: prevent keepalive write-after-close and add regression test
* fix: clean up transaction on send failure in _executeTransaction
Move _addMessage inside the try-catch block so that if writing to a
closed socket throws WebtritSignalingBadStateException, the transaction
is properly removed from the _transactions map instead of leaking.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test: add transaction cleanup and keepalive timer tests
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: add comment for closeCode guard in keepalive loop
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address Copilot review comments on keepalive integration tests
- Fix compile error: replace called(greaterThan(0)) with called(1)
- Fix lint warning: await streamController.close() in tearDown
- Fix false-positive test: move closeCode mock before flushMicrotasks
and add verify closeCode called(2) to catch timer-restart regression
---------
Co-authored-by: Dmytro Serdun <d.serdun@webtrit.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix: fixed app is locked in Bluetooth call profile after first call (#982)
* fix: fixed app is locked in Bluetooth call profile after first call
Fixed situation when user returns to YouTube or music playback, the audio remains degraded, like during a call. This happens only on Android.
* fix: improve comment for _onLastCallEnded to cover iOS and Android audio routing
---------
Co-authored-by: Dmytro Serdun <d.serdun@webtrit.com>
* feat: add melos v7 integration (#980)
* feat: add melos integration with codegen, test, format, and dependency scripts
* feat: fix melos scripts syntax for v7 and rename format to fmt
* feat: configure melos v7 with workspace and scripts in pubspec.yaml
- Add workspace: list to pubspec.yaml for Dart pub workspace / Melos v7 package discovery
- Add melos: section to pubspec.yaml with scripts for codegen, tests, formatting, dependencies
- Add resolution: workspace to all sub-package pubspec.yaml files
- Fix ssl_certificates publish_to typo (none; → 'none')
- Update screenshots SDK constraint to ^3.8.0
- Remove melos.yaml (ignored by Melos v7 — config lives in pubspec.yaml)
* feat: include root package in melos workspace via useRootAsPackage
* fix: replace non-ASCII em dashes with hyphens in pubspec.yaml comments
* feat: migrate makefile targets to melos scripts and mark deprecated
* fix: replace __ with _ in screenshots to fix unnecessary_underscores lint
* fix: rename melos script from run to start to avoid CLI conflict
The melos v7 CLI command `melos run <script>` conflicted with the script
named `run`. When running `melos run fmt`, melos treated `run` as the
script name and `fmt` as an argument, causing flutter to look for a file
named `fmt` instead of formatting code.
Renaming the script to `start` (and `run:ios` to `start:ios`) resolves
the ambiguity. Use `melos run start` to launch the app.
* feat: add melos smoke tests and update docs with melos commands
- Add tool/scripts/melos_smoke_test.sh for smoke testing safe scripts
- Add smoke:test melos script to pubspec.yaml
- Rename start to start:android for clarity
- Replace Makefile references with melos commands in docs/build.md
- Rewrite docs/make_file.md as full melos commands reference
- Update README.md to link to Melos Commands
* fix: address Copilot review comments
- Switch get/upgrade/outdated melos scripts from exec to run
to correctly resolve from workspace root (not per-package)
- Align screenshots flutter constraint with workspace root (^3.41.2)
to fix sdk/flutter constraint inconsistency (Dart 3.8 requires Flutter 3.41+)
* fix: reverse date divider in chat (#983)
Placed date divider before bunch of messages. Also changed displaying date in this divider. Added displaying options "Today", "Yesterday", day of week, like "Monday", and date in E, d MMM format, like "Tue, 6 Jan" , depending on comparison between date of message and today's date.
* feat: claude code setup (#981)
* feat: replace .rules with CLAUDE.md for Claude Code
* feat: configure .claude with hooks and allowed commands
* feat: add melos usage rules and update common commands
* feat: reduce CLAUDE.md duplication and soften melos usage rules
* feat: add webtrit_callkeep docs and package-level CLAUDE.md files
* feat: document call architecture, flows, isolates, and key patterns in CLAUDE.md
* feat: restructure AI agent memory files (AGENTS.md + CLAUDE.md split)
- Add root AGENTS.md (<100 lines): universal instructions for any AI tool
- Slim root CLAUDE.md (<50 lines): @imports + Claude-specific gotchas only
- Extract CallBloc architecture to docs/call_architecture.md
- Add AGENTS.md for all 10 packages (shared content, any agent)
- Remove package CLAUDE.md where content fully moved to AGENTS.md
- Keep package CLAUDE.md only where Claude-specific gotchas exist (data, ssl_certificates)
- Add CLAUDE.local.md to .gitignore
* feat: address Copilot review comments in PR #981
- Fix md_formatter.py docstring: was 'prettier', now correctly says 'markdownlint-cli2 --fix'
- Expand settings.json deny list to cover keystores (.jks, .keystore, .p12) and signing keys (.pem, .key, .p8)
- Fix AGENTS.md import groups: two '2.' entries corrected to sequential 1–6 numbering
- Fix packages/data/CLAUDE.md migration steps: misnumbered list corrected to 1–5
- Add working directory note to packages/data/AGENTS.md commands section
- Fix _web_socket_channel/AGENTS.md: 'pinned to' → 'constrained to' for caret constraint
* feat: remove webtrit_phone_keystores from deny list (dir is outside project scope)
* feat: configure gitignore, formatter, and analysis for generated files (#985)
* feat: configure gitignore, formatter, and analysis for generated files
- Add **/build/ and **/.claude/ to .gitignore
- Update lefthook pre-commit to skip *.g.dart / *.freezed.dart / *.gr.dart
- Replace melos fmt/fmt:check with find-based scripts that exclude generated files
* feat: update melos v7 integration across features, DAOs, and screenshots
* feat: allow feat/ as valid branch prefix alongside feature/ (#987)
* feat: allow feat/ as valid branch prefix alongside feature/
Update branch-name-check.sh and git-lint.yml to accept both feat/ and
feature/ prefixes. Add CONTRIBUTING.md documenting branch naming rules
and commit conventions.
* docs: fix table alignment in CONTRIBUTING.md
* docs: consolidate git conventions into CONTRIBUTING.md as single source of truth
- Remove .rules.md (referenced non-existent .rules/ directory)
- Update .github/copilot-instructions.md to reference CONTRIBUTING.md and AGENTS.md
- Add feat/ to branch pattern in copilot-instructions.md
- Fix pre-commit hook description in docs/development.md (dart format, not flutter analyze)
- Add CONTRIBUTING.md reference in docs/development.md
- Add docs/development.md pointer in CONTRIBUTING.md hooks section
* feat: favorites remote syncable (#984)
* refactor: cdr ui improvements, disconnect reason translations (#989)
* fix: use firstWhere instead of first in getAllContacts test to avoid ordering assumption (#991)
* fix: change contact_phones UNIQUE constraint to (number, label, contact_id) (#992)
* feat: change contact_phones UNIQUE constraint to (number, label, contact_id)
* feat: update ContactPhonesDao to use (number, label) pairs for stale-row deletion
* feat: persist per-label phone rows in ContactsLocalDataSource for DID-only accounts
* feat: add tests for DID-only contact phone handling and schema migration v20
* docs: add inline comment explaining grouping and merge logic in displayPhones
* refactor: rename lambda param p to phone in deleteOtherContactPhonesOfContactId call
* style: apply dart format to changed files
* docs: add DartDoc to deleteOtherContactPhonesOfContactId
* docs: add inline comments to deleteOtherContactPhonesOfContactId body
* style: replace non-ASCII arrow with hyphen in comment
* fix: use canonical label in displayPhones to prevent merged label from reaching favorites
* fix: recreate contact_phones via new table to preserve favorites FK reference
* test: add favorites FK integrity assertion for migration v20
* fix: resolve canonical ContactPhone by id before passing to favorites
* docs: document merged label display and canonical label favorites behavior
* refactor: replace ContactPhone display with flat fields in ContactPhoneDisplayEntry
- Replace synthetic ContactPhone display field with displayLabel (String)
and displayFavorite (bool) - only the two fields that actually differ
from the canonical phone
- Rename canonical field to phone for clarity
- Update displayPhones convenience getter to reconstruct ContactPhone
from phone + flat display fields
- Update ContactPhoneTileAdapter to accept only primitives and closures,
removing all model object dependencies
- Update ContactScreen loop to use displayPhoneEntries with closures
capturing entry.phone directly
- Add widget tests for ContactPhoneTileAdapter covering all enable flags,
callback wiring, transfer logic, and popup menu entries
- Add direct tests for displayPhoneEntries covering displayLabel,
displayFavorite, and phone field contracts
* feat: melos exported env support (#993)
* fix: log records file stability improvements (#998)
* fix: guard File.delete() with exists() check in shareLogRecords
Prevents PathNotFoundException on Android 15 when the OS clears the
app cache directory under background memory pressure while the native
share sheet is open, causing the finally block to attempt deleting a
file that no longer exists (Crashlytics issue 25d22f4de9016c556b46cfacea29a20b).
* fix: delegate dispose Future in LogRecordsFileRepositoryImpl
Without awaiting or returning the Future from RotatingFileAppender.dispose(),
the file handle could remain unclosed after the caller awaited repository
disposal. Use direct return to delegate the Future without an async wrapper.
* fix: replace existsSync retry with async exists() in _getAllLogFilesWithRetry
existsSync() reads from the OS filesystem cache and can return false
immediately after forceFlush() even though the file is on disk.
Switching to async File.exists() forces a fresh stat() call that
bypasses the cache. Also reduces max wait from 10s (5x2s) to 1s (10x100ms).
* fix: guard forceFlush() with try/catch in readAllLogs
An I/O error during forceFlush() would propagate and abort readAllLogs
entirely, returning no logs to the caller. Catching the error allows
reading whatever files are already on disk.
* fix: guard forceFlush() with try/catch in cleanLogs
An I/O error during forceFlush() would abort cleanLogs entirely,
leaving stale log files on disk. Catching the error allows deletion
to proceed on whatever files are already present.
* fix: replace retry loop with async file discovery in log records
- Replace _getAllLogFilesWithRetry (10×100ms polling loop) with
_getAllLogFilesAsync — single async pass using await file.exists()
per rotation slot, bypassing OS filesystem cache without any delay
- Extend file discovery range to 0..keepRotateCount inclusive so
rotated files (e.g. app_logs.log.1) are found even when base file
is absent immediately after rotation
- Fix cleanLogs to use await _getAllLogFilesAsync() instead of
synchronous getAllLogFiles() which relied on existsSync()
- Fix readAllLogs iteration order: remove files.reversed so newer
file (rotation 0) is read before older rotated file (rotation 1)
- Remove redundant file.exists() guard inside readAllLogs loop since
_getAllLogFilesAsync already guarantees file presence
- Add full test coverage: LogRecordsMemoryRepositoryImpl,
ReadableRotatingFileAppender (readAllLogs + cleanLogs),
LogRecordsFileRepositoryImpl — 26 tests total
* fix: update outdated comment in readAllLogs test
* feat: add Claude Code PostToolUse hooks (dart formatter + newline enforcer) (#995)
* fix: call dropped when user taps before app fully initializes (#997)
* fix: wait for signaling and registration before failing outgoing call
In __onCallPerformEventStarted, the early registration guard ran before
the signaling wait, causing calls to drop immediately when the user tapped
call before the socket initialized. Move the guard after the signaling wait
and extend the wait predicate to also require registration status to be
known (isHandshakeEstablished && isSignalingEstablished).
* fix: hold outgoing call as pending until routing state is available
When the user taps call before CallRoutingCubit has initialized (app just
launched, user info not yet fetched), wait for the first non-null routing
state instead of immediately failing. The call proceeds automatically once
routing state becomes available. If the cubit is disposed while waiting,
the call is silently dropped.
* fix: remove unused notification import from CallController
* test: add CallController.createCall unit tests
Cover immediate dispatch, pending wait, cubit disposal, and
CallUndefinedLineNotification scenarios.
* fix: remove unused optional params in _FakeCallRoutingState
* fix: remove unnecessary call_controller.dart import in test
* fix: address Copilot review — unawaited createCall and fast-fail on signaling failure
- Make createCall void by delegating to private _createCallAsync via unawaited, avoiding unawaited_futures lint at all call sites
- Add isFailure condition to signaling firstWhere predicate so outgoing calls fail immediately on signaling failure instead of waiting full timeout
* fix: update call_controller tests to use void createCall
Replace await controller.createCall(...) with call + await Future.delayed(Duration.zero)
to pump microtasks after createCall became void
* refactor: extract signaling wait predicate into named variables
* fix: use currentState instead of state for registration check after signaling wait
* fix: replace non-ASCII em dash with semicolon in comment
* refactor: remove redundant signalingConnected/registrationKnown vars, use CallState getters directly
* fix: await all addTrack calls before createOffer to prevent empty offer
* refactor: add TODO to provide CallController as singleton via RepositoryProvider
* fix: log warning when callRoutingCubit closes before routing state arrives
* refactor: add comment explaining callRoutingState await logic
* fix: add timeout to routing state wait and show NoInternetConnectionNotification on expiry
* refactor: extract _waitForRoutingState helper to clean up routing state await
* fix: catch unexpected errors from _createCallAsync and add timeout notification test
* fix: await reportNewIncomingCall in background FCM handler (#1001)
Without await the Pigeon IPC Future was fire-and-forget — the background
isolate was destroyed before the call reached the Kotlin side, so
PhoneConnectionService.startIncomingCall() was never invoked and the
incoming call UI never appeared.
* feat: provide CallController as singleton via RepositoryProvider in MainShell (#1000)
* feat: provide CallController as singleton via RepositoryProvider in MainShell
Register CallController once in MainShell widget tree after CallBloc,
CallRoutingCubit and NotificationsBloc are available. All seven call
sites now obtain the shared instance via late final field initializer
(context.read<CallController>()) instead of constructing a new object
per StatefulWidget.
* feat: replace RepositoryProvider with CallControllerScope InheritedWidget
RepositoryProvider is semantically for the data layer; CallController
is a UI-tier coordinator. Introduce CallControllerScope (InheritedWidget)
following the PresenceViewParams pattern already in the codebase.
All seven consumers now resolve the controller via CallControllerScope.of(context).
* fix: prevent stale CallController on MainShell rebuild
Store CallController in _MainShellState via ??= so it is created once
regardless of how many times build() reruns. Switch CallControllerScope.of
to getElementForInheritedWidgetOfExactType to avoid registering a rebuild
dependency that would never fire (updateShouldNotify is always false).
* feat: add doc comment for _callController field in _MainShellState
* fix: feature access mapper logic (#1007)
* fix: correct icon and text case assertions in integration tests (#1010)
* fix: correct icon and text case assertions in integration tests
* revert: remove accidentally committed logger from call_bloc.dart
* feat: add integration tests for webtrit_signaling keepalive and disconnect (#1009)
* feat: add integration tests for webtrit_signaling keepalive and disconnect
Add mock-based and live integration tests covering:
- Keepalive timeout (WebtritSignalingKeepaliveTransactionTimeoutException)
- Normal keepalive cycles (echo → timer restarts)
- Graceful disconnect (onDisconnect callback)
- Stream error (onError callback)
- Execute after disconnect (WebtritSignalingDisconnectedException)
- Request transaction timeout (non-keepalive variant)
- Live tests against a real server (skipped when credentials not set)
- Network simulation via pure-Dart WebSocket proxy with pause/resume
to verify keepalive timeout fires on real packet drop
* feat: replace WebSocket proxy with raw TCP proxy in live signaling test
Drop packets at the TCP byte level instead of WebSocket frame level
for more realistic network simulation. The proxy rewrites the HTTP
Host header before forwarding so the server accepts the upgrade request.
* feat: extract TcpProxy into reusable _tcp_proxy package
Move the raw TCP proxy from the signaling test into a standalone
internal package packages/_tcp_proxy so it can be used as a
dev dependency in both webtrit_signaling tests and app-level tests.
* fix: address Copilot review comments on signaling tests and tcp proxy
- Fix expectLater() to pass Future directly instead of closure
- Make live test client nullable to prevent LateInitializationError in tearDown
- Use local signalingClient variable in each live test for clarity
- Replace localhost with 127.0.0.1 to avoid IPv6-first resolution issues
- Remove unconditional TLS certificate bypass in live test HTTP client
- Discard bytes while paused in _relayWithHostRewrite to prevent unbounded buffer growth
- Add 64 KB header size guard with socket teardown on overflow
- Add AGENTS.md, README.md, analysis_options.yaml to _tcp_proxy package
* fix: add lints dev_dependency to _tcp_proxy so analysis_options.yaml resolves
* fix: replace magic timing numbers with constants in integration tests
* docs: add DartDoc to _findCrlfCrlf explaining HTTP header boundary detection
* fix(signaling): reconnect silently on server force-close (code 4441) (#1012)
When the server sends disconnect code 4441 (controllerForceAttachClose)
it means two signaling sessions from the same account were open at the
same time — a race that can happen when a background push isolate is
still connected as the main engine comes to the foreground and
reconnects signaling.
Previously this code path set lastSignalingDisconnectCode, which
triggered CallStatus.connectIssue and showed a "Connection issue"
snackbar to the user even though the situation was transient and
self-healing.
Fix: on 4441, emit lastSignalingDisconnectCode=null (no connectIssue
UI), skip the notification, and reconnect with the fast 1s delay
instead of the default 3s. A warning log is emitted so the race remains
visible in logs for debugging.
* refactor: decouple AppLogger from LogzioLoggingService via RemoteLoggingService abstraction (#1011)
* refactor: decouple AppLogger from LogzioLoggingService via RemoteLoggingService abstraction
* refactor: move remoteMinLevel extraction to init to avoid cast in applyConfig
* refactor: add minLevel to RemoteLoggingService and remove cast from AppLogger
* refactor: reorder LogzioLoggingService fields to match RemoteLoggingService interface order
* refactor: remove labelsProvider from AppLogger state, pass labels explicitly
* refactor: rename regenerateRemoteLabels to updateRemoteLabels and dispose before reinitialize
* refactor: replace labels map with lazy callback in AppLogger to encapsulate labels retrieval
* fix: attach remote appender before applyConfig so early logs are forwarded to remote
* feat: enable and disable log anonymization with env (#1004)
* feat: enable and disable log anonymization with env
Added functionality to disable and enable log anonymization via environment variable
* refactor: simplify AnonymizationType to none/full enum with bool flag
Replace the mutable list-based anonymization approach with a single
AnonymizationType enum (none/full), removing the race condition risk
and simplifying the API surface.
* docs: document intentional anonymization scope in AppLogger
* fix: add @override annotation to setAnonymizationEnabled in LogzioLoggingService
---------
Co-authored-by: Dmytro Serdun <d.serdun@webtrit.com>
* fix: api healthcheck path (#1014)
* fix: use shared static logger in WebtritSignalingClient (#1013)
* fix: use shared static logger in WebtritSignalingClient with instance id prefix
* fix: restore custom logger support via inner constructor parameter
* fix: simplify logger to use fixed name without instance counter
* fix: remove custom logger parameter from inner constructor
* fix: signaling errors (#1016)
* fix: snackbar duration
* fix: new persist option usage
* fix: reconnect message repeating
* fix: reconnect on keepalive timeout
* feat: user info cache (#1015)
* feat: main impl
* refactor: call to actions fix
* fix: call to actions provider
* docs: getAndListed explain
* fix: integration tests rescue (#1025)
* fix: migrate to 4.5
* fix: various fixes
* fix: remove log file from standart test
* fix: no audio if lone codec (#1028)
* fix: WebRTC signaling state guards — ICE restart, renegotiation, and glare (WT-986) (#1003)
* refactor: extract RenegotiationHandler with stable-state and concurrency guards
- Extract renegotiation logic from CallBloc into a standalone RenegotiationHandler class
- Add two stable-state guards: pre-offer check and TOCTOU guard after createOffer
- Add _isHandling/_pendingRetry flags to serialize concurrent onRenegotiationNeeded firings
- Catch WebtritSignalingErrorException for server-side error logging without swallowing
- Catch plain String errors (flutter_webrtc native) separately from Dart exceptions
- Add unit tests covering stable-state skip, concurrency serialization, and error paths
- Document server-mediated vs P2P topology constraints and Perfect Negotiation limitation
* fix: add signalingState guards and Perfect Negotiation rollback to call flow
ICE restart handler:
- Skip setLocalDescription when signalingState != stable to prevent native crash
- Log warning instead of silently skipping
Renegotiation / accepted handler:
- Guard setRemoteDescription(answer) against wrong state after glare resolution
- Log transceivers after setRemoteDescription for SDP debugging
- Catch String errors from setRemoteDescription to prevent unhandled exception escalation
Updating handler (Perfect Negotiation rollback):
- Pre-check signalingState for glare: if have-local-offer, roll back local offer before setRemoteDescription
- Catch String errors containing have-local-offer as fallback for stale flutter_webrtc signalingState cache
- Roll back and retry setRemoteDescription on confirmed glare
Renderer and state:
- Always refresh srcObject in RTCStreamView.didUpdateWidget to handle renegotiation-replaced tracks
- Fix remoteVideo getter to use logical OR (stream tracks || video flag) instead of short-circuit
Add call_state_test.dart covering ActiveCall equality and remoteVideo edge cases
* refactor: replace on String catch with typed RTC exceptions via RtcJsepErrorParser
* fix: guard RTCVideoRenderer srcObject assignment until initialize() completes (#1027)
* fix: guard RTCVideoRenderer srcObject assignment until initialize() completes
Prevents a crash where didUpdateWidget set srcObject before the renderer
was initialized, causing 'Call initialize before setting the stream'.
The _initialized flag ensures srcObject is only set after initialize()
resolves. Also always refreshes srcObject (not just on stream identity
change) to handle track replacement during renegotiation.
* fix: use setState and guard build before renderer initialization
Wrap _initialized and srcObject assignment in setState so the flag change
is properly synchronized with Flutter's build cycle. Guard build() to
avoid passing an uninitialized renderer to RTCVideoView — the
placeholderBuilder (or empty SizedBox) is shown until initialize()
completes.
* fix: update ExternalContactsSyncBloc tests to mock getAndListen instead of getLocalInfo (#1029)
* feat: handle 'voicemail_not_configured' error (#1005)
* feat: handle 'voicemail_not_configured' error
Added handling 'voicemail_not_configured' error from voicemail api
* fix: properly handle voicemail_not_configured and endpoint_not_supported errors across all layers
- WebtritApiClient: skip SEVERE log for expected VoicemailNotConfiguredException / EndpointNotSupportedException
- VoicemailRepositoryImpl: add _featureSupported flag to skip API calls once feature is known unavailable; add _fetchingCompleter.future.ignore() to prevent unhandled future errors; suppress stack trace in warning log for expected exceptions; expose isFeatureSupported getter
- VoicemailRepository: add isFeatureSupported to abstract interface; EmptyVoicemailRepository returns false
- VoicemailCubit: check isFeatureSupported on init to immediately emit featureNotSupported without an API call
- SettingsBloc: catch expected exceptions from unawaited fetchVoicemails() to prevent runZonedGuarded propagation
- PollingService / ConnectivityLifecycleService: remove stack trace from WARNING logs in generic catch blocks
* fix: always allow navigation to voicemail screen regardless of feature support
Previously the settings tile was disabled with reduced opacity when voicemail
was not configured, which was confusing. Now the tile is always tappable and
the voicemail screen shows a placeholder explaining the reason.
* fix: move VoicemailCubit provider from SettingsScreenPage to VoicemailScreenPage
VoicemailCubit was provided at the settings level but consumed in a separate
route, causing ProviderNotFoundException on navigation. Moving it to
VoicemailScreenPage makes the provider scope match the consumer.
* fix: add missing call feature import to VoicemailScreenPage
* fix: remove unused stack trace variables from catch blocks in polling and connectivity services
* fix: address Copilot review comments
- VoicemailCubit: guard watchVoicemails subscription against overwriting featureNotSupported state
- VoicemailNotConfiguredException: pass token and error fields to super; remove commented-out placeholder
- WebtritApiClient: log xRequestId (never null) instead of requestId parameter; pass token/error to VoicemailNotConfiguredException
- SettingsTile: add assert that opacity is in [0.0, 1.0]
---------
Co-authored-by: Dmytro Serdun <d.serdun@webtrit.com>
* feat: add toJson() to all Event subclasses and fix NotifyEvent constructor inconsistency (#1030)
* fix: suppress transient network error SnackBar in RegisterStatusCubit (#1031)
Auto-triggered fetches (startup and connectivity restore) now use
_fetchStatusSilently(), which skips handleError for SocketException,
TimeoutException, and TlsException. The cubit retries automatically
on the next connectivity change, so surfacing these transient failures
to the user is misleading. User-initiated fetchStatus() retains the
original behaviour and still calls handleError for all errors.
* fix: add 10s timeout to reportNewIncomingCall in background message handler (WT-1061) (#1032)
Telecom on affected devices can become overloaded with phantom PhoneAccount
registrations, causing reportNewIncomingCall() to hang indefinitely. This
keeps FlutterFirebaseMessagingBackgroundService alive and blocks subsequent
cold starts. Adding a 10s timeout bounds the handler lifetime and logs a
warning when Telecom is slow to respond.
* fix: add 5s timeout to getInitialMessage() to prevent splash freeze (WT-1061) (#1033)
* refactor: remove tryUse from AppDatabaseScope, migrate callers to useOrNull (#1034)
* fix: eliminate write-write SQLite contention via shared DriftIsolate server (WT-1061) (#1035)
* fix: eliminate write-write SQLite contention via shared DriftIsolate server (WT-1061)
Spawns a single dedicated DriftIsolate server in the main isolate bootstrap
and registers its SendPort in IsolateNameServer under a fixed key.
Background isolates (FCM handler, WorkManager) now connect to the same server
via IsolateDatabase.connectOrCreate(), which looks up the port and creates a
client connection — falling back to a direct NativeDatabase connection when the
main app is not running (cold push with no foreground app).
All writes are serialized through the single server isolate, making
write-write SQLITE_BUSY (code=5) between concurrent isolates impossible.
Changes:
- app_database: add createAppDatabaseNative() for synchronous NativeDatabase
creation inside the server isolate (no createInBackground needed)
- IsolateDatabase: add spawnServer(), connectOrCreate(), kDbPortName
- AppDatabaseScope.use(): connect via connectOrCreate() instead of create()
- bootstrap(): spawn DriftIsolate server, register DriftIsolate in InstanceRegistry
- AppDatabaseLifecycleHolder: connect to DriftIsolate, shutdown server on dispose
* fix: address Copilot review — robust spawnServer error handling and stale port cleanup (WT-1061)
* test: add integration tests for IsolateDatabase stale port handling (WT-1061)
* refactor: introduce SignalingModule stream abstraction (phase 1) (#1024)
* refactor: introduce SignalingModule stream abstraction (phase 1)
Replace SignalingManager callback-based API with SignalingModule — a
sealed-event broadcast stream that owns the WebtritSignalingClient
lifecycle without any BLoC, CallState, or UI dependency.
Key changes:
- Add SignalingModule with fire-and-forget connect(), disconnect(),
dispose() and a sealed SignalingModuleEvent hierarchy
(Connecting, Connected, ConnectionFailed, Disconnecting,
Disconnected, HandshakeReceived, ProtocolEvent)
- Add isRepeated deduplication on ConnectionFailed to suppress
repeated identical error notifications
- Map disconnect codes to recommendedReconnectDelay:
4441 → Duration.zero, protocolError → null, all others → 3 s
- Migrate CallBloc from direct WebtritSignalingClient callbacks to
SignalingModule stream subscription; new _SignalingClientEvent
variants: connecting, connected, failed, disconnecting, disconnected
- Migrate IsolateManager (Push + Foreground) to SignalingModule,
replacing SignalingManager; add connectivity monitoring and pending
request queue inside IsolateManager
- Construct SignalingModule in main_shell.dart and inject into CallBloc
- Delete SignalingManager and remove its export from common.dart
- Add 31 unit and integration tests for SignalingModule
* fix(test): update ExternalContactsSyncBloc tests for getAndListen API
The BLoC was updated to call userRepository.getAndListen() instead of
getLocalInfo(), but the mocks were never updated. Fix the setUp mock
and correct the RefreshFailure test to use load() failure (which is
the actual trigger for that state) rather than userRepository failure.
* fix(test): rename local function to avoid leading underscore lint warning
* docs: translate signaling architecture doc to English
* docs: remove phase 1 requirements planning doc from repo
* refactor: remove coreUrl/tenantId/token/trustedCertificates from CallBloc
These four fields were passed through CallBloc only to construct
SignalingModule internally. Now that SignalingModule is constructed
externally and injected via the constructor, the fields are dead code.
Remove them and the corresponding import from ssl_certificates.
* fix: address Copilot review comments on SignalingModule/IsolateManager
- Guard delayed reconnect callbacks with signalingClient == null check to
avoid tearing down a healthy connection that connected during the delay
- Populate _incomingCallEvents from handshake and protocol events so
_findIncomingEventLog returns real caller data instead of null
- Use disconnect() instead of dispose() in handleLifecycleStatus so the
module remains reusable when the app returns to the foreground
- Fix post-dispose connect() test to actually subscribe to the event stream
and assert Connecting/Connected events are absent after dispose
* feat: replay session events to late subscribers in SignalingModule
Adds a per-subscriber replay buffer so that consumers created after
connect() (e.g. CallBloc constructed after SignalingModule already
connected and received a handshake) do not miss any events from the
current session.
- events getter now returns a single-subscription stream that first
replays all events buffered since the last connect() call, then
pipes live events from the broadcast controller
- connect() clears the buffer so late subscribers see only the
current session, not stale events from previous reconnect cycles
- dispose() also clears the buffer on teardown
- Uses sync: true on the intermediate StreamController to avoid an
extra async hop and keep delivery ordering consistent with callers
that await module operations
- Adds two integration tests covering the late-subscriber replay
and the buffer-clear-on-reconnect behaviours
* feat: connect SignalingModule early in initState to reduce call setup latency
SignalingModule is now created and connected in _MainShellState.initState(),
running the WebSocket handshake in parallel while the widget tree and
CallBloc are being built. When CallBloc is eventually created it subscribes
to the replay stream and receives all buffered session events without missing
anything.
_MainShellState.dispose() owns the module lifecycle; CallBloc.close()
still calls dispose() on the module (idempotent, safe).
* docs: update signaling architecture doc with layer descriptions and diagrams
* fix: add concurrency lock to _connectAsync to prevent parallel connects
* fix: await disconnect ack in dispose() to prevent SignalingDisconnected drop on race
* fix: suppress reconnect hint on intentional disconnect to prevent spurious reconnect
* fix: remove SignalingModule.dispose() from CallBloc.close() — ownership belongs to MainShellState
* fix: snapshot buffer before live subscribe to prevent replay duplicates in events getter
* fix: store reconnect Timer in IsolateManager so it can be cancelled on close()
* fix: forward recommendedReconnectDelay from SignalingDisconnected to _scheduleReconnect in CallBloc
* fix: suppress _onDisconnect after _onError to prevent double reconnect scheduling
* fix: exclude SignalingProtocolEvent from session buffer to prevent unbounded growth
* fix: replace force-unwrap of session.coreUrl/token with null-safe logout in initState
* fix: remove performEndCall early return so pre-handshake declines are queued
* fix: close liveController on subscription cancel to prevent StreamController leak
* fix: use _networkNone state instead of stale results snapshot in connectivity timer closure
* docs: fix _scheduleReconnectIfNeeded → _scheduleReconnect in signaling architecture doc
* test: replace Future.delayed(Duration.zero) with pumpEventQueue() in signaling module tests
* fix: make _controller sync:true to eliminate async-dispatch event duplication
* docs: clarify disconnect() docstring — SignalingDisconnected is callback-driven, not synchronous
* fix: wrap _signalingModule.disconnect() in unawaited() in _disconnectInitiated
* fix: remove unused shouldReconnect variable from __onSignalingClientEventDisconnected
* test: add 8 tests to reach 100% SignalingModule coverage
- concurrent connect() dropped while factory in-flight (_connecting guard)
- intentional disconnect() emits SignalingDisconnected with null delay
- disconnect() passes goingAway code to the underlying client
- _onError suppresses subsequent _onDisconnect (_errorHandled flag)
- SignalingProtocolEvent excluded from replay buffer
- cancelled subscription receives no further events
- dispose() awaits disconnect ack before closing the stream
- _onHandshake/_onEvent are no-ops after dispose()
* test: add scenario-driven SignalingModule tests from CallBloc usage analysis
Covers scenarios observed in CallBloc's signaling subscription:
internet dropped mid-session:
- _onError after handshake → ConnectionFailed not Disconnected, signalingClient cleared
- Unexpected socket close (null code) → Disconnected with kSignalingClientReconnectDelay
- ConnectionFailed buffered so late subscribers reconstruct last-known failure
handshake not completed:
- Disconnect before handshake → no HandshakeReceived in buffer, Disconnected with delay
- Late subscriber after no-handshake disconnect → gets Connecting+Connected+Disconnected only
- Error before handshake → ConnectionFailed buffered, no HandshakeReceived
- Reconnect after no-handshake failure delivers fresh session events
late subscriber mid-session:
- Factory still pending → gets Connecting from buffer, Connected arrives live
- After full connect+handshake → all three lifecycle events replayed, no protocol events
disconnect() robustness:
- client.disconnect() throws → dispose() still completes without hanging
- Second disconnect() with no active client is a silent no-op
* fix: restore callkeep_signaling_status_converter.dart lost during rebase
* fix: remove unused fields in _ThrowingDisconnectClient test helper
* fix: use normalClosure (1000) instead of goingAway (1001) when client disconnects WebSocket
* test: update disconnect test to expect normalClosure (1000) instead of goingAway (1001)
* fix: address Copilot review comments in SignalingModule and IsolateManager
- Fix doc comment on events getter: protocol events are not replayed, only lifecycle/handshake events
- Guard connect() buffer clear behind _connecting check to avoid clearing on redundant calls
- Remove stale comment in _onDisconnect that contradicted the !_disposed guard
- Treat empty connectivity result as offline in _monitorConnectivity (results.isEmpty || any(none))
- Treat empty connectivity result as offline in performAnswerCall (isNotEmpty && !contains(none))
* fix: address post-review issues in SignalingModule and IsolateManager
- isolate_manager: fix connectivityNoneCounter reset — error now fires
exactly once at maxConnectivityNoneRepeats; subsequent none-events are
silently ignored until connectivity is restored and counter resets to 0
- main_shell: split SignalingModule construction into valid/invalid-creds
branches to remove ?? '' fallbacks and make intent explicit
- signaling_module: document sync:true reentrancy assumption, single-use
constraint on events getter, and _errorHandled ordering invariant
* revert: restore main_shell.dart SignalingModule construction with ?? '' fallback
The split-branch approach still created a module with empty strings in the
null-creds case — identical in behaviour to the original. Reverted to the
original form which is honest about the fallback until a proper nullable
refactor is done.
* fix: remove dead null-guard in MainShellState.initState for SignalingModule
The router guard (onMainShellRouteGuardNavigation) redirects to login
when state.status != authenticated, so coreUrl and token are always
non-null when MainShell is mounted. Replace ?? '' fallbacks and the
unreachable null-branch with direct ! unwraps.
* docs: sync signaling_architecture_target.md and call_architecture.md with current code
- signaling_architecture_target: add timer cancellation to IsolateManager
and CallBloc _scheduleReconnect snippets (Future.delayed → Timer with cancel)
- signaling_architecture_target: add missing SignalingDisconnecting case to
CallBloc subscription snippet
- call_architecture: update ownership — CallBloc owns SignalingModule, not
WebtritSignalingClient directly
* fix: replace non-ASCII characters with ASCII equivalents in Dart sources
Replace em dash (U+2014) with '-' and right arrow (U+2192) with '->'
in comments and test descriptions across signaling_module.dart,
isolate_manager.dart, signaling_module_test.dart, and call_bloc.dart.
* fix: incorrect styling of status bar on app start (#1006)
Fixed status bar rendering with incorrect styling on initial app launch when theme is light
* feat: show progress indicator while sharing logs (#1036)
* feat: show progress indicator while sharing logs
Co-Authored-By: Dmytro Serdun <serdun@webtrit.com>
* refactor: replace inline SizedBox+CircularProgressIndicator with SizedCircularProgressIndicator
---------
Co-authored-by: Dmytro Serdun <serdun@webtrit.com>
* fix: upgrade to video resets hold (#1038)
* fix: media settings parsing (#1039)
* fix: call drops after theme or lang change (#1041)
* fix: cannot make calls after blind transfer — skip hangup + reconnect safety net (WT-1214) (#1040)
* fix: trigger reconnect when starting outgoing call with no signaling (WT-1214)
After a blind transfer, the signaling WebSocket is closed with code 4610
and the disconnect is marked intentional by SignalingModule, so no
reconnect is scheduled. Subsequent outgoing call attempts enter
outgoingConnectingToSignaling and wait passively — neither signalingReady
nor signalingFailed ever fires, causing the call to fail on timeout.
Add _scheduleReconnect(Duration.zero) at the start of the waiting block
so that initiating an outgoing call always recovers the signaling
connection, regardless of whether the previous disconnect was intentional.
* fix: skip hangup after successful blind transfer to avoid 4610 disconnect (WT-1214)
When a blind transfer completes (NOTIFY SIP/2.0 200 OK +
subscription_state: terminated), the SIP dialog is already closed
server-side via REFER. Sending a hangup request on the freed dialog
causes the server to close the WebSocket with code 4610 ("call request
on wrong line error"), triggering an unintended signaling disconnect.
Check the call's transfer state: if it is Transfering(fromBlindTransfer:
true), skip the hangup request and clean up the peer connection locally
only. This removes the root cause of the 4610 disconnect that led to
signaling not being reconnected for subsequent outgoing calls.
* test: cover blind-transfer hangup skip and 4610 reconnect hint
Add tests for two fixes from WT-1214:
- Transfer — isBlindTransferCompleted detection (call_state_test.dart):
verifies the switch pattern that decides whether to skip the hangup
request after a blind transfer. Covers Transfering(fromBlindTransfer:
true/false), earlier transfer states, and null.
- SignalingModule — requestCallIdError (4610) reconnect hint
(signaling_module_test.dart): verifies that a non-intentional 4610
carries a non-null recommendedReconnectDelay (reconnect scheduled),
while an intentional disconnect() followed by a server 4610 emits
null (reconnect suppressed — the scenario that triggered WT-1214).
* refactor: rename isBlindTransferCompleted → isBlindTransferInTransferingState
Transfering state in the model means "server started to process the
transfer", not "transfer completed". Rename the local variable and update
the surrounding comments and test group names to match the actual Transfer
model semantics, avoiding misinterpretation of the hangup guard.
Addresses Copilot review comments on PR #1040.
* fix: call or transfer to myself handling (#1046)
* fix: hide video for held call (#1048)
* fix: hide video for held call
* fix: tap area
* fix: transfer to same recipient (#1049)
* refactor: extract SignalingModuleInterface; migrate CallBloc and IsolateManager; extract toLinesState (#1045)
* refactor: extract SignalingModuleInterface; decouple IsolateManager from SignalingModule
Add local SignalingModuleInterface abstract class to signaling_module.dart
with the contract needed by IsolateManager: events, isConnected, connect(),
disconnect(), execute(Request), dispose().
SignalingModule implements the interface, gaining isConnected and execute()
alongside the existing signalingClient getter (kept for backward compat).
IsolateManager field type changed from SignalingModule to SignalingModuleInterface.
All signalingClient null-checks replaced with isConnected; direct client.execute()
calls replaced with module.execute(). IsolateManager no longer depends on the
concrete class, making it ready for a plugin-backed implementation.
* fix: decouple NetworkCubit from WebtritSignalingService; fix push dedup race
NetworkCubit held a concrete WebtritSignalingService only to call
updateMode(). Replaced with a Future<void> Function(SignalingServiceMode)
callback so the cubit has no plugin dependency and is mock-testable.
Call site passes WebtritSignalingService().updateMode as a tear-off.
_onCallPushEventIncoming checked the incomingFromOffer guard before
awaiting contactNameResolver.resolveWithNumber(). The signaling path
could create an ActiveCall during that async gap, causing both paths
to emit separate entries for the same callId. Added a post-await guard
that checks for any existing ActiveCall with the same callId before emitting.
* fix: prevent premature call routing state emission before signaling handshake (#1044)
LinesState.blank() is emitted at app startup before the signaling handshake
arrives. combineLatest fires immediately with cached UserInfo + blank LinesState,
causing CallRoutingCubit to emit a non-null state with empty mainLines.
CallController then skips _waitForRoutingState() and fails with
"no idle lines available".
Fix: add LinesState.isBlank discriminator (guestLine == null is an unambiguous
pre-handshake marker — CallBloc always sets guestLine to non-null after any
handshake). Return null from _combineInfo when linesState.isBlank so the cubit
stays in its unready state and CallController waits correctly.
* fix: preserve LinesState.blank in onChange until signaling handshake arrives
The previous fix relied on guestLine == null as a pre-handshake discriminator,
but CallBloc.onChange always set guestLine = LineState.idle regardless of
linesCount, overwriting LinesState.blank() almost immediately after startup.
Root cause: onChange fired with linesCount = 0 on any early state change
(e.g. connecting status) and produced LinesState([], LineState.idle).
isBlank returned false, so _combineInfo emitted a non-null CallRoutingState
with empty mainLines, and hasIdleMainLine = false blocked the call.
Fix: when linesCount == 0 (handshake not yet received), onChange now stores
LinesState.blank() explicitly. Once the handshake sets linesCount > 0,
normal LinesState with non-null guestLine is produced as before.
* fix: remove unused models import from NetworkScreenPage
* fix: wire BackgroundSignalingBootstrapService into NetworkCubit callback
* refactor: migrate CallBloc to SignalingModuleInterface
Apply the same interface migration already done for IsolateManager:
- change _signalingModule field/param type from SignalingModule to SignalingModuleInterface
- replace signalingClient?.execute() with execute() from the interface
- replace signalingClient != null checks with isConnected
* fix: address Copilot review comments on signaling module interface
- Handle null execute() in _executePendingRequests: complete completer
with error and clean up instead of leaving request to time out silently
- Handle null execute() in _sendRequest: log warning and return early
instead of awaiting null when module disconnects after isConnected check
- Fix linesCount == 0 guard: use isHandshakeEstablished to distinguish…
SERDUN
added a commit
that referenced
this pull request
May 22, 2026
* fix: skip static payloads sanitizing if no rtpmap (#968)
* fix: media preset translation (#969)
* fix: fix splash config generation and add android_12 image support (#865)
- Escape # in Makefile color variables to prevent shell comment issues
- Remove redundant shell parameter expansion, use Make-only expansion
- Add ANDROID_12_SPLASH_IMAGE variable for separate Android 12 splash
- Use single quotes in echo to avoid shell interpretation problems
Co-authored-by: Dmytro Serdun <d.serdun@webtrit.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* chore: add diagnostic logging for recents number mismatch (#970)
Add hash-based and raw value logging to trace why recent calls list
shows two different phone numbers per entry. Raw values are visible
when Logz.io anonymization is disabled, hashes work with it enabled.
* feat: add support a blurred appbar surface (#971)
* feat: add BlurredSurface widget and apply blur effect to all app bars
Extract common ClipRect+BackdropFilter pattern into reusable BlurredSurface widget.
Apply consistent blur background with extendBodyBehindAppBar to all screens
using MainAppBar or AppBar. Remove isComplexBackground conditional logic.
* refactor: standardize MainAppBar field order across screens
* feat: apply blurred appbar surface to ConversationsScreen
* fix: remove unused theme/theme.dart imports
* fix: add top padding to prevent content overlap with blurred appbar
Screens using extendBodyBehindAppBar lacked compensating padding,
causing body content to be hidden behind the blurred AppBar. Added
appropriate top padding to recents, favorites, about, contacts, and
CDR screens consistent with the existing settings screen approach.
* feat(screenshots): make IgnorePointer configurable in ScreenshotApp (#972)
Add ignorePointer parameter (default true) to allow enabling interaction
for specific screenshots that require it.
* feat: add screenshot mocks and docs for all features (#973)
* feat: add screenshot mocks for all features
Add ~20 new screenshot definitions covering all user-visible screens:
- Core flows: contact, chat conversation, SMS conversation, system
notifications, recent CDRs, number CDRs, call log
- Settings: network, language, diagnostic, caller ID, presence,
theme mode, voicemail
- Login: switch screen
- Utility: log records console, contacts agreement, teardown,
permissions, user agreement
Includes shared mock data, mock blocs/cubits/repositories,
screenshot widgets, router entries, and integration test registrations.
* docs: add screenshots package documentation
Add docs for screenshots architecture, mock reference, data reference,
and step-by-step guide for adding new screenshots. Update README with
links to the new documentation.
* fix: address review feedback for screenshot mocks and docs
- Add provider as direct dependency in screenshots/pubspec.yaml
and remove ignore: depend_on_referenced_packages from 7 files
- Fix mock type/factory mismatches in mocks.md documentation
- Fix broken code formatting in mocks.md and adding_screenshots.md
- Remove shadowed ChatTypingCubit/SmsTypingCubit BlocProviders
from conversation screenshot wrappers
- Guard registerFallbackValue with static flag to prevent
duplicate registration
- Fix duplicated phrase in README.md Firebase Hosting section
- Document both flutter test and flutter drive workflows in
overview.md
* feat: update keystores path to new applications subdirectory (#974)
* feat: add text style background decoration support (#975)
* feat: add backgroundBorderRadius and backgroundPadding to TextStyleConfig
* feat: add greetingTextStyle to LoginModeSelectPageConfig
* feat: add ExtendedText widget with background decoration support
* feat: add greetingTextStyle to login.modeSelect docs
* feat: add tests for ExtendedText widget
* feat: update theme template configs with missing components and fixes (#976)
- Fix calStatuses typo to callStatuses in both widget configs
- Add missing widget sections as examples: button, group, bar, input, text, confirmDialog
- Fix non-existent dark SVG asset references to use existing logos
- Remove unsupported fontFeatures from page text styles
- Clean up dark page config duplicate keys in login.switchPage
- Add greetingTextStyle for dark login modeSelect visibility
- Update dark widget decoration gradient config
* refactor: replace deprecated GradientColorsConfig with PageBackground (#977)
* refactor: replace deprecated GradientColorsConfig with PageBackground
Remove GradientColorsConfig, DecorationConfig, GradientsStyleFactory,
and Gradients theme extension. Migrate login and call screens to use
PageBackground via ThemedScaffold.
* refactor: enable gradient backgrounds for login and call page configs
Activate background for login modeSelect and add gradient background
to dialing section using colors from the removed GradientColorsConfig.
* refactor: adjust dark login gradient to contrast with buttons
Change bottom gradient color from #000000 to #0A1929 to avoid
blending with neutralOnDark button surface color.
* refactor: reverse dark login gradient direction
Start with dark color on top, lighter blue on bottom to keep
contrast with buttons at the bottom of the screen.
* refactor: rename misleading onSurface variable to surfaceColor
* feat: appBar blurred surface config and theme updates (#979)
* feat: extract appBar background color and blurred surface to config pipeline
Add configurable appBarBackgroundColor and appBarBlurredSurface (color, sigmaX, sigmaY)
to the theme config→style pipeline across all 7 main screens, replacing hardcoded values.
* feat: update page configuration docs with appBar fields
Add Common page fields section documenting appBarBackgroundColor
and appBarBlurredSurface, update Keypad page example.
* feat: remove appBarBackgroundColor from config pipeline
AppBar backgroundColor is already handled by MainAppBar fallback chain
(explicit → appBarTheme → canvasColor.withAlpha). Remove redundant
appBarBackgroundColor from BasePageConfig, all page configs, screen styles,
factories, and screens to avoid confusion.
* feat: add BlurredSurface.fromStyle factory and simplify screen usage
Replace inline BlurredSurface construction with fromStyle() factory
that returns null when no config is present (no blur applied) or a
configured BlurredSurface with resolved sigma defaults of 10.
* feat: enable appBar blur and activate widget config sections
- Add appBarBlurredSurface to all 7 page sections (dark/light)
- Activate previously ignored widget config sections (_bar, _button,
_group, _input, _text -> remove underscore prefix)
- Set appBar backgroundColor/surfaceTintColor to transparent for blur
- Fix tab indicator label contrast and remove divider line
* feat: update light theme appBarBlurredSurface color to soft blue-white
* feat: set dark status bar icons for light theme appBar
* feat: fix appBarBlurredSurface sigma defaults and add tests
- Make sigmaX/sigmaY nullable in BlurredSurfaceConfig so omitted fields
resolve to 10 via BlurredSurface.fromStyle instead of staying 0
- Regenerate Freezed/JSON for BlurredSurfaceConfig
- Restore blur on RecentCdrs and SystemNotifications screens by passing
sigmaX: 10, sigmaY: 10 explicitly to const BlurredSurface()
- Explicit Colors.transparent fallback in BlurredSurface Container child
- Update docs to reflect null defaults with 10 resolved at widget layer
- Add BlurredSurface.fromStyle widget tests (4 cases)
* fix: system back button blocked in EmbeddedRequestErrorDialog (#935)
- Remove canPop: false from EmbeddedRequestErrorDialog — default is true,
and blocking pop prevented the system back button from working in
Settings → Terms & Conditions when the page failed to load (WT-890).
- Add onPopInvokedWithResult to call onBack when system back dismisses
the dialog in Mode B (pushed route), ensuring _errorDialogShown is reset.
- Reset _errorDialogShown in _handleEmbeddedErrorState when error is
cleared, so repeated errors after system-back dismissal are shown again.
Co-authored-by: Dmytro Serdun <d.serdun@webtrit.com>
* fix: prevent keepalive write-after-close and harden transaction lifecycle (#870)
* fix: prevent keepalive write-after-close and add regression test
* fix: clean up transaction on send failure in _executeTransaction
Move _addMessage inside the try-catch block so that if writing to a
closed socket throws WebtritSignalingBadStateException, the transaction
is properly removed from the _transactions map instead of leaking.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test: add transaction cleanup and keepalive timer tests
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: add comment for closeCode guard in keepalive loop
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address Copilot review comments on keepalive integration tests
- Fix compile error: replace called(greaterThan(0)) with called(1)
- Fix lint warning: await streamController.close() in tearDown
- Fix false-positive test: move closeCode mock before flushMicrotasks
and add verify closeCode called(2) to catch timer-restart regression
---------
Co-authored-by: Dmytro Serdun <d.serdun@webtrit.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix: fixed app is locked in Bluetooth call profile after first call (#982)
* fix: fixed app is locked in Bluetooth call profile after first call
Fixed situation when user returns to YouTube or music playback, the audio remains degraded, like during a call. This happens only on Android.
* fix: improve comment for _onLastCallEnded to cover iOS and Android audio routing
---------
Co-authored-by: Dmytro Serdun <d.serdun@webtrit.com>
* feat: add melos v7 integration (#980)
* feat: add melos integration with codegen, test, format, and dependency scripts
* feat: fix melos scripts syntax for v7 and rename format to fmt
* feat: configure melos v7 with workspace and scripts in pubspec.yaml
- Add workspace: list to pubspec.yaml for Dart pub workspace / Melos v7 package discovery
- Add melos: section to pubspec.yaml with scripts for codegen, tests, formatting, dependencies
- Add resolution: workspace to all sub-package pubspec.yaml files
- Fix ssl_certificates publish_to typo (none; → 'none')
- Update screenshots SDK constraint to ^3.8.0
- Remove melos.yaml (ignored by Melos v7 — config lives in pubspec.yaml)
* feat: include root package in melos workspace via useRootAsPackage
* fix: replace non-ASCII em dashes with hyphens in pubspec.yaml comments
* feat: migrate makefile targets to melos scripts and mark deprecated
* fix: replace __ with _ in screenshots to fix unnecessary_underscores lint
* fix: rename melos script from run to start to avoid CLI conflict
The melos v7 CLI command `melos run <script>` conflicted with the script
named `run`. When running `melos run fmt`, melos treated `run` as the
script name and `fmt` as an argument, causing flutter to look for a file
named `fmt` instead of formatting code.
Renaming the script to `start` (and `run:ios` to `start:ios`) resolves
the ambiguity. Use `melos run start` to launch the app.
* feat: add melos smoke tests and update docs with melos commands
- Add tool/scripts/melos_smoke_test.sh for smoke testing safe scripts
- Add smoke:test melos script to pubspec.yaml
- Rename start to start:android for clarity
- Replace Makefile references with melos commands in docs/build.md
- Rewrite docs/make_file.md as full melos commands reference
- Update README.md to link to Melos Commands
* fix: address Copilot review comments
- Switch get/upgrade/outdated melos scripts from exec to run
to correctly resolve from workspace root (not per-package)
- Align screenshots flutter constraint with workspace root (^3.41.2)
to fix sdk/flutter constraint inconsistency (Dart 3.8 requires Flutter 3.41+)
* fix: reverse date divider in chat (#983)
Placed date divider before bunch of messages. Also changed displaying date in this divider. Added displaying options "Today", "Yesterday", day of week, like "Monday", and date in E, d MMM format, like "Tue, 6 Jan" , depending on comparison between date of message and today's date.
* feat: claude code setup (#981)
* feat: replace .rules with CLAUDE.md for Claude Code
* feat: configure .claude with hooks and allowed commands
* feat: add melos usage rules and update common commands
* feat: reduce CLAUDE.md duplication and soften melos usage rules
* feat: add webtrit_callkeep docs and package-level CLAUDE.md files
* feat: document call architecture, flows, isolates, and key patterns in CLAUDE.md
* feat: restructure AI agent memory files (AGENTS.md + CLAUDE.md split)
- Add root AGENTS.md (<100 lines): universal instructions for any AI tool
- Slim root CLAUDE.md (<50 lines): @imports + Claude-specific gotchas only
- Extract CallBloc architecture to docs/call_architecture.md
- Add AGENTS.md for all 10 packages (shared content, any agent)
- Remove package CLAUDE.md where content fully moved to AGENTS.md
- Keep package CLAUDE.md only where Claude-specific gotchas exist (data, ssl_certificates)
- Add CLAUDE.local.md to .gitignore
* feat: address Copilot review comments in PR #981
- Fix md_formatter.py docstring: was 'prettier', now correctly says 'markdownlint-cli2 --fix'
- Expand settings.json deny list to cover keystores (.jks, .keystore, .p12) and signing keys (.pem, .key, .p8)
- Fix AGENTS.md import groups: two '2.' entries corrected to sequential 1–6 numbering
- Fix packages/data/CLAUDE.md migration steps: misnumbered list corrected to 1–5
- Add working directory note to packages/data/AGENTS.md commands section
- Fix _web_socket_channel/AGENTS.md: 'pinned to' → 'constrained to' for caret constraint
* feat: remove webtrit_phone_keystores from deny list (dir is outside project scope)
* feat: configure gitignore, formatter, and analysis for generated files (#985)
* feat: configure gitignore, formatter, and analysis for generated files
- Add **/build/ and **/.claude/ to .gitignore
- Update lefthook pre-commit to skip *.g.dart / *.freezed.dart / *.gr.dart
- Replace melos fmt/fmt:check with find-based scripts that exclude generated files
* feat: update melos v7 integration across features, DAOs, and screenshots
* feat: allow feat/ as valid branch prefix alongside feature/ (#987)
* feat: allow feat/ as valid branch prefix alongside feature/
Update branch-name-check.sh and git-lint.yml to accept both feat/ and
feature/ prefixes. Add CONTRIBUTING.md documenting branch naming rules
and commit conventions.
* docs: fix table alignment in CONTRIBUTING.md
* docs: consolidate git conventions into CONTRIBUTING.md as single source of truth
- Remove .rules.md (referenced non-existent .rules/ directory)
- Update .github/copilot-instructions.md to reference CONTRIBUTING.md and AGENTS.md
- Add feat/ to branch pattern in copilot-instructions.md
- Fix pre-commit hook description in docs/development.md (dart format, not flutter analyze)
- Add CONTRIBUTING.md reference in docs/development.md
- Add docs/development.md pointer in CONTRIBUTING.md hooks section
* feat: favorites remote syncable (#984)
* refactor: cdr ui improvements, disconnect reason translations (#989)
* fix: use firstWhere instead of first in getAllContacts test to avoid ordering assumption (#991)
* fix: change contact_phones UNIQUE constraint to (number, label, contact_id) (#992)
* feat: change contact_phones UNIQUE constraint to (number, label, contact_id)
* feat: update ContactPhonesDao to use (number, label) pairs for stale-row deletion
* feat: persist per-label phone rows in ContactsLocalDataSource for DID-only accounts
* feat: add tests for DID-only contact phone handling and schema migration v20
* docs: add inline comment explaining grouping and merge logic in displayPhones
* refactor: rename lambda param p to phone in deleteOtherContactPhonesOfContactId call
* style: apply dart format to changed files
* docs: add DartDoc to deleteOtherContactPhonesOfContactId
* docs: add inline comments to deleteOtherContactPhonesOfContactId body
* style: replace non-ASCII arrow with hyphen in comment
* fix: use canonical label in displayPhones to prevent merged label from reaching favorites
* fix: recreate contact_phones via new table to preserve favorites FK reference
* test: add favorites FK integrity assertion for migration v20
* fix: resolve canonical ContactPhone by id before passing to favorites
* docs: document merged label display and canonical label favorites behavior
* refactor: replace ContactPhone display with flat fields in ContactPhoneDisplayEntry
- Replace synthetic ContactPhone display field with displayLabel (String)
and displayFavorite (bool) - only the two fields that actually differ
from the canonical phone
- Rename canonical field to phone for clarity
- Update displayPhones convenience getter to reconstruct ContactPhone
from phone + flat display fields
- Update ContactPhoneTileAdapter to accept only primitives and closures,
removing all model object dependencies
- Update ContactScreen loop to use displayPhoneEntries with closures
capturing entry.phone directly
- Add widget tests for ContactPhoneTileAdapter covering all enable flags,
callback wiring, transfer logic, and popup menu entries
- Add direct tests for displayPhoneEntries covering displayLabel,
displayFavorite, and phone field contracts
* feat: melos exported env support (#993)
* fix: log records file stability improvements (#998)
* fix: guard File.delete() with exists() check in shareLogRecords
Prevents PathNotFoundException on Android 15 when the OS clears the
app cache directory under background memory pressure while the native
share sheet is open, causing the finally block to attempt deleting a
file that no longer exists (Crashlytics issue 25d22f4de9016c556b46cfacea29a20b).
* fix: delegate dispose Future in LogRecordsFileRepositoryImpl
Without awaiting or returning the Future from RotatingFileAppender.dispose(),
the file handle could remain unclosed after the caller awaited repository
disposal. Use direct return to delegate the Future without an async wrapper.
* fix: replace existsSync retry with async exists() in _getAllLogFilesWithRetry
existsSync() reads from the OS filesystem cache and can return false
immediately after forceFlush() even though the file is on disk.
Switching to async File.exists() forces a fresh stat() call that
bypasses the cache. Also reduces max wait from 10s (5x2s) to 1s (10x100ms).
* fix: guard forceFlush() with try/catch in readAllLogs
An I/O error during forceFlush() would propagate and abort readAllLogs
entirely, returning no logs to the caller. Catching the error allows
reading whatever files are already on disk.
* fix: guard forceFlush() with try/catch in cleanLogs
An I/O error during forceFlush() would abort cleanLogs entirely,
leaving stale log files on disk. Catching the error allows deletion
to proceed on whatever files are already present.
* fix: replace retry loop with async file discovery in log records
- Replace _getAllLogFilesWithRetry (10×100ms polling loop) with
_getAllLogFilesAsync — single async pass using await file.exists()
per rotation slot, bypassing OS filesystem cache without any delay
- Extend file discovery range to 0..keepRotateCount inclusive so
rotated files (e.g. app_logs.log.1) are found even when base file
is absent immediately after rotation
- Fix cleanLogs to use await _getAllLogFilesAsync() instead of
synchronous getAllLogFiles() which relied on existsSync()
- Fix readAllLogs iteration order: remove files.reversed so newer
file (rotation 0) is read before older rotated file (rotation 1)
- Remove redundant file.exists() guard inside readAllLogs loop since
_getAllLogFilesAsync already guarantees file presence
- Add full test coverage: LogRecordsMemoryRepositoryImpl,
ReadableRotatingFileAppender (readAllLogs + cleanLogs),
LogRecordsFileRepositoryImpl — 26 tests total
* fix: update outdated comment in readAllLogs test
* feat: add Claude Code PostToolUse hooks (dart formatter + newline enforcer) (#995)
* fix: call dropped when user taps before app fully initializes (#997)
* fix: wait for signaling and registration before failing outgoing call
In __onCallPerformEventStarted, the early registration guard ran before
the signaling wait, causing calls to drop immediately when the user tapped
call before the socket initialized. Move the guard after the signaling wait
and extend the wait predicate to also require registration status to be
known (isHandshakeEstablished && isSignalingEstablished).
* fix: hold outgoing call as pending until routing state is available
When the user taps call before CallRoutingCubit has initialized (app just
launched, user info not yet fetched), wait for the first non-null routing
state instead of immediately failing. The call proceeds automatically once
routing state becomes available. If the cubit is disposed while waiting,
the call is silently dropped.
* fix: remove unused notification import from CallController
* test: add CallController.createCall unit tests
Cover immediate dispatch, pending wait, cubit disposal, and
CallUndefinedLineNotification scenarios.
* fix: remove unused optional params in _FakeCallRoutingState
* fix: remove unnecessary call_controller.dart import in test
* fix: address Copilot review — unawaited createCall and fast-fail on signaling failure
- Make createCall void by delegating to private _createCallAsync via unawaited, avoiding unawaited_futures lint at all call sites
- Add isFailure condition to signaling firstWhere predicate so outgoing calls fail immediately on signaling failure instead of waiting full timeout
* fix: update call_controller tests to use void createCall
Replace await controller.createCall(...) with call + await Future.delayed(Duration.zero)
to pump microtasks after createCall became void
* refactor: extract signaling wait predicate into named variables
* fix: use currentState instead of state for registration check after signaling wait
* fix: replace non-ASCII em dash with semicolon in comment
* refactor: remove redundant signalingConnected/registrationKnown vars, use CallState getters directly
* fix: await all addTrack calls before createOffer to prevent empty offer
* refactor: add TODO to provide CallController as singleton via RepositoryProvider
* fix: log warning when callRoutingCubit closes before routing state arrives
* refactor: add comment explaining callRoutingState await logic
* fix: add timeout to routing state wait and show NoInternetConnectionNotification on expiry
* refactor: extract _waitForRoutingState helper to clean up routing state await
* fix: catch unexpected errors from _createCallAsync and add timeout notification test
* fix: await reportNewIncomingCall in background FCM handler (#1001)
Without await the Pigeon IPC Future was fire-and-forget — the background
isolate was destroyed before the call reached the Kotlin side, so
PhoneConnectionService.startIncomingCall() was never invoked and the
incoming call UI never appeared.
* feat: provide CallController as singleton via RepositoryProvider in MainShell (#1000)
* feat: provide CallController as singleton via RepositoryProvider in MainShell
Register CallController once in MainShell widget tree after CallBloc,
CallRoutingCubit and NotificationsBloc are available. All seven call
sites now obtain the shared instance via late final field initializer
(context.read<CallController>()) instead of constructing a new object
per StatefulWidget.
* feat: replace RepositoryProvider with CallControllerScope InheritedWidget
RepositoryProvider is semantically for the data layer; CallController
is a UI-tier coordinator. Introduce CallControllerScope (InheritedWidget)
following the PresenceViewParams pattern already in the codebase.
All seven consumers now resolve the controller via CallControllerScope.of(context).
* fix: prevent stale CallController on MainShell rebuild
Store CallController in _MainShellState via ??= so it is created once
regardless of how many times build() reruns. Switch CallControllerScope.of
to getElementForInheritedWidgetOfExactType to avoid registering a rebuild
dependency that would never fire (updateShouldNotify is always false).
* feat: add doc comment for _callController field in _MainShellState
* fix: feature access mapper logic (#1007)
* fix: correct icon and text case assertions in integration tests (#1010)
* fix: correct icon and text case assertions in integration tests
* revert: remove accidentally committed logger from call_bloc.dart
* feat: add integration tests for webtrit_signaling keepalive and disconnect (#1009)
* feat: add integration tests for webtrit_signaling keepalive and disconnect
Add mock-based and live integration tests covering:
- Keepalive timeout (WebtritSignalingKeepaliveTransactionTimeoutException)
- Normal keepalive cycles (echo → timer restarts)
- Graceful disconnect (onDisconnect callback)
- Stream error (onError callback)
- Execute after disconnect (WebtritSignalingDisconnectedException)
- Request transaction timeout (non-keepalive variant)
- Live tests against a real server (skipped when credentials not set)
- Network simulation via pure-Dart WebSocket proxy with pause/resume
to verify keepalive timeout fires on real packet drop
* feat: replace WebSocket proxy with raw TCP proxy in live signaling test
Drop packets at the TCP byte level instead of WebSocket frame level
for more realistic network simulation. The proxy rewrites the HTTP
Host header before forwarding so the server accepts the upgrade request.
* feat: extract TcpProxy into reusable _tcp_proxy package
Move the raw TCP proxy from the signaling test into a standalone
internal package packages/_tcp_proxy so it can be used as a
dev dependency in both webtrit_signaling tests and app-level tests.
* fix: address Copilot review comments on signaling tests and tcp proxy
- Fix expectLater() to pass Future directly instead of closure
- Make live test client nullable to prevent LateInitializationError in tearDown
- Use local signalingClient variable in each live test for clarity
- Replace localhost with 127.0.0.1 to avoid IPv6-first resolution issues
- Remove unconditional TLS certificate bypass in live test HTTP client
- Discard bytes while paused in _relayWithHostRewrite to prevent unbounded buffer growth
- Add 64 KB header size guard with socket teardown on overflow
- Add AGENTS.md, README.md, analysis_options.yaml to _tcp_proxy package
* fix: add lints dev_dependency to _tcp_proxy so analysis_options.yaml resolves
* fix: replace magic timing numbers with constants in integration tests
* docs: add DartDoc to _findCrlfCrlf explaining HTTP header boundary detection
* fix(signaling): reconnect silently on server force-close (code 4441) (#1012)
When the server sends disconnect code 4441 (controllerForceAttachClose)
it means two signaling sessions from the same account were open at the
same time — a race that can happen when a background push isolate is
still connected as the main engine comes to the foreground and
reconnects signaling.
Previously this code path set lastSignalingDisconnectCode, which
triggered CallStatus.connectIssue and showed a "Connection issue"
snackbar to the user even though the situation was transient and
self-healing.
Fix: on 4441, emit lastSignalingDisconnectCode=null (no connectIssue
UI), skip the notification, and reconnect with the fast 1s delay
instead of the default 3s. A warning log is emitted so the race remains
visible in logs for debugging.
* refactor: decouple AppLogger from LogzioLoggingService via RemoteLoggingService abstraction (#1011)
* refactor: decouple AppLogger from LogzioLoggingService via RemoteLoggingService abstraction
* refactor: move remoteMinLevel extraction to init to avoid cast in applyConfig
* refactor: add minLevel to RemoteLoggingService and remove cast from AppLogger
* refactor: reorder LogzioLoggingService fields to match RemoteLoggingService interface order
* refactor: remove labelsProvider from AppLogger state, pass labels explicitly
* refactor: rename regenerateRemoteLabels to updateRemoteLabels and dispose before reinitialize
* refactor: replace labels map with lazy callback in AppLogger to encapsulate labels retrieval
* fix: attach remote appender before applyConfig so early logs are forwarded to remote
* feat: enable and disable log anonymization with env (#1004)
* feat: enable and disable log anonymization with env
Added functionality to disable and enable log anonymization via environment variable
* refactor: simplify AnonymizationType to none/full enum with bool flag
Replace the mutable list-based anonymization approach with a single
AnonymizationType enum (none/full), removing the race condition risk
and simplifying the API surface.
* docs: document intentional anonymization scope in AppLogger
* fix: add @override annotation to setAnonymizationEnabled in LogzioLoggingService
---------
Co-authored-by: Dmytro Serdun <d.serdun@webtrit.com>
* fix: api healthcheck path (#1014)
* fix: use shared static logger in WebtritSignalingClient (#1013)
* fix: use shared static logger in WebtritSignalingClient with instance id prefix
* fix: restore custom logger support via inner constructor parameter
* fix: simplify logger to use fixed name without instance counter
* fix: remove custom logger parameter from inner constructor
* fix: signaling errors (#1016)
* fix: snackbar duration
* fix: new persist option usage
* fix: reconnect message repeating
* fix: reconnect on keepalive timeout
* feat: user info cache (#1015)
* feat: main impl
* refactor: call to actions fix
* fix: call to actions provider
* docs: getAndListed explain
* fix: integration tests rescue (#1025)
* fix: migrate to 4.5
* fix: various fixes
* fix: remove log file from standart test
* fix: no audio if lone codec (#1028)
* fix: WebRTC signaling state guards — ICE restart, renegotiation, and glare (WT-986) (#1003)
* refactor: extract RenegotiationHandler with stable-state and concurrency guards
- Extract renegotiation logic from CallBloc into a standalone RenegotiationHandler class
- Add two stable-state guards: pre-offer check and TOCTOU guard after createOffer
- Add _isHandling/_pendingRetry flags to serialize concurrent onRenegotiationNeeded firings
- Catch WebtritSignalingErrorException for server-side error logging without swallowing
- Catch plain String errors (flutter_webrtc native) separately from Dart exceptions
- Add unit tests covering stable-state skip, concurrency serialization, and error paths
- Document server-mediated vs P2P topology constraints and Perfect Negotiation limitation
* fix: add signalingState guards and Perfect Negotiation rollback to call flow
ICE restart handler:
- Skip setLocalDescription when signalingState != stable to prevent native crash
- Log warning instead of silently skipping
Renegotiation / accepted handler:
- Guard setRemoteDescription(answer) against wrong state after glare resolution
- Log transceivers after setRemoteDescription for SDP debugging
- Catch String errors from setRemoteDescription to prevent unhandled exception escalation
Updating handler (Perfect Negotiation rollback):
- Pre-check signalingState for glare: if have-local-offer, roll back local offer before setRemoteDescription
- Catch String errors containing have-local-offer as fallback for stale flutter_webrtc signalingState cache
- Roll back and retry setRemoteDescription on confirmed glare
Renderer and state:
- Always refresh srcObject in RTCStreamView.didUpdateWidget to handle renegotiation-replaced tracks
- Fix remoteVideo getter to use logical OR (stream tracks || video flag) instead of short-circuit
Add call_state_test.dart covering ActiveCall equality and remoteVideo edge cases
* refactor: replace on String catch with typed RTC exceptions via RtcJsepErrorParser
* fix: guard RTCVideoRenderer srcObject assignment until initialize() completes (#1027)
* fix: guard RTCVideoRenderer srcObject assignment until initialize() completes
Prevents a crash where didUpdateWidget set srcObject before the renderer
was initialized, causing 'Call initialize before setting the stream'.
The _initialized flag ensures srcObject is only set after initialize()
resolves. Also always refreshes srcObject (not just on stream identity
change) to handle track replacement during renegotiation.
* fix: use setState and guard build before renderer initialization
Wrap _initialized and srcObject assignment in setState so the flag change
is properly synchronized with Flutter's build cycle. Guard build() to
avoid passing an uninitialized renderer to RTCVideoView — the
placeholderBuilder (or empty SizedBox) is shown until initialize()
completes.
* fix: update ExternalContactsSyncBloc tests to mock getAndListen instead of getLocalInfo (#1029)
* feat: handle 'voicemail_not_configured' error (#1005)
* feat: handle 'voicemail_not_configured' error
Added handling 'voicemail_not_configured' error from voicemail api
* fix: properly handle voicemail_not_configured and endpoint_not_supported errors across all layers
- WebtritApiClient: skip SEVERE log for expected VoicemailNotConfiguredException / EndpointNotSupportedException
- VoicemailRepositoryImpl: add _featureSupported flag to skip API calls once feature is known unavailable; add _fetchingCompleter.future.ignore() to prevent unhandled future errors; suppress stack trace in warning log for expected exceptions; expose isFeatureSupported getter
- VoicemailRepository: add isFeatureSupported to abstract interface; EmptyVoicemailRepository returns false
- VoicemailCubit: check isFeatureSupported on init to immediately emit featureNotSupported without an API call
- SettingsBloc: catch expected exceptions from unawaited fetchVoicemails() to prevent runZonedGuarded propagation
- PollingService / ConnectivityLifecycleService: remove stack trace from WARNING logs in generic catch blocks
* fix: always allow navigation to voicemail screen regardless of feature support
Previously the settings tile was disabled with reduced opacity when voicemail
was not configured, which was confusing. Now the tile is always tappable and
the voicemail screen shows a placeholder explaining the reason.
* fix: move VoicemailCubit provider from SettingsScreenPage to VoicemailScreenPage
VoicemailCubit was provided at the settings level but consumed in a separate
route, causing ProviderNotFoundException on navigation. Moving it to
VoicemailScreenPage makes the provider scope match the consumer.
* fix: add missing call feature import to VoicemailScreenPage
* fix: remove unused stack trace variables from catch blocks in polling and connectivity services
* fix: address Copilot review comments
- VoicemailCubit: guard watchVoicemails subscription against overwriting featureNotSupported state
- VoicemailNotConfiguredException: pass token and error fields to super; remove commented-out placeholder
- WebtritApiClient: log xRequestId (never null) instead of requestId parameter; pass token/error to VoicemailNotConfiguredException
- SettingsTile: add assert that opacity is in [0.0, 1.0]
---------
Co-authored-by: Dmytro Serdun <d.serdun@webtrit.com>
* feat: add toJson() to all Event subclasses and fix NotifyEvent constructor inconsistency (#1030)
* fix: suppress transient network error SnackBar in RegisterStatusCubit (#1031)
Auto-triggered fetches (startup and connectivity restore) now use
_fetchStatusSilently(), which skips handleError for SocketException,
TimeoutException, and TlsException. The cubit retries automatically
on the next connectivity change, so surfacing these transient failures
to the user is misleading. User-initiated fetchStatus() retains the
original behaviour and still calls handleError for all errors.
* fix: add 10s timeout to reportNewIncomingCall in background message handler (WT-1061) (#1032)
Telecom on affected devices can become overloaded with phantom PhoneAccount
registrations, causing reportNewIncomingCall() to hang indefinitely. This
keeps FlutterFirebaseMessagingBackgroundService alive and blocks subsequent
cold starts. Adding a 10s timeout bounds the handler lifetime and logs a
warning when Telecom is slow to respond.
* fix: add 5s timeout to getInitialMessage() to prevent splash freeze (WT-1061) (#1033)
* refactor: remove tryUse from AppDatabaseScope, migrate callers to useOrNull (#1034)
* fix: eliminate write-write SQLite contention via shared DriftIsolate server (WT-1061) (#1035)
* fix: eliminate write-write SQLite contention via shared DriftIsolate server (WT-1061)
Spawns a single dedicated DriftIsolate server in the main isolate bootstrap
and registers its SendPort in IsolateNameServer under a fixed key.
Background isolates (FCM handler, WorkManager) now connect to the same server
via IsolateDatabase.connectOrCreate(), which looks up the port and creates a
client connection — falling back to a direct NativeDatabase connection when the
main app is not running (cold push with no foreground app).
All writes are serialized through the single server isolate, making
write-write SQLITE_BUSY (code=5) between concurrent isolates impossible.
Changes:
- app_database: add createAppDatabaseNative() for synchronous NativeDatabase
creation inside the server isolate (no createInBackground needed)
- IsolateDatabase: add spawnServer(), connectOrCreate(), kDbPortName
- AppDatabaseScope.use(): connect via connectOrCreate() instead of create()
- bootstrap(): spawn DriftIsolate server, register DriftIsolate in InstanceRegistry
- AppDatabaseLifecycleHolder: connect to DriftIsolate, shutdown server on dispose
* fix: address Copilot review — robust spawnServer error handling and stale port cleanup (WT-1061)
* test: add integration tests for IsolateDatabase stale port handling (WT-1061)
* refactor: introduce SignalingModule stream abstraction (phase 1) (#1024)
* refactor: introduce SignalingModule stream abstraction (phase 1)
Replace SignalingManager callback-based API with SignalingModule — a
sealed-event broadcast stream that owns the WebtritSignalingClient
lifecycle without any BLoC, CallState, or UI dependency.
Key changes:
- Add SignalingModule with fire-and-forget connect(), disconnect(),
dispose() and a sealed SignalingModuleEvent hierarchy
(Connecting, Connected, ConnectionFailed, Disconnecting,
Disconnected, HandshakeReceived, ProtocolEvent)
- Add isRepeated deduplication on ConnectionFailed to suppress
repeated identical error notifications
- Map disconnect codes to recommendedReconnectDelay:
4441 → Duration.zero, protocolError → null, all others → 3 s
- Migrate CallBloc from direct WebtritSignalingClient callbacks to
SignalingModule stream subscription; new _SignalingClientEvent
variants: connecting, connected, failed, disconnecting, disconnected
- Migrate IsolateManager (Push + Foreground) to SignalingModule,
replacing SignalingManager; add connectivity monitoring and pending
request queue inside IsolateManager
- Construct SignalingModule in main_shell.dart and inject into CallBloc
- Delete SignalingManager and remove its export from common.dart
- Add 31 unit and integration tests for SignalingModule
* fix(test): update ExternalContactsSyncBloc tests for getAndListen API
The BLoC was updated to call userRepository.getAndListen() instead of
getLocalInfo(), but the mocks were never updated. Fix the setUp mock
and correct the RefreshFailure test to use load() failure (which is
the actual trigger for that state) rather than userRepository failure.
* fix(test): rename local function to avoid leading underscore lint warning
* docs: translate signaling architecture doc to English
* docs: remove phase 1 requirements planning doc from repo
* refactor: remove coreUrl/tenantId/token/trustedCertificates from CallBloc
These four fields were passed through CallBloc only to construct
SignalingModule internally. Now that SignalingModule is constructed
externally and injected via the constructor, the fields are dead code.
Remove them and the corresponding import from ssl_certificates.
* fix: address Copilot review comments on SignalingModule/IsolateManager
- Guard delayed reconnect callbacks with signalingClient == null check to
avoid tearing down a healthy connection that connected during the delay
- Populate _incomingCallEvents from handshake and protocol events so
_findIncomingEventLog returns real caller data instead of null
- Use disconnect() instead of dispose() in handleLifecycleStatus so the
module remains reusable when the app returns to the foreground
- Fix post-dispose connect() test to actually subscribe to the event stream
and assert Connecting/Connected events are absent after dispose
* feat: replay session events to late subscribers in SignalingModule
Adds a per-subscriber replay buffer so that consumers created after
connect() (e.g. CallBloc constructed after SignalingModule already
connected and received a handshake) do not miss any events from the
current session.
- events getter now returns a single-subscription stream that first
replays all events buffered since the last connect() call, then
pipes live events from the broadcast controller
- connect() clears the buffer so late subscribers see only the
current session, not stale events from previous reconnect cycles
- dispose() also clears the buffer on teardown
- Uses sync: true on the intermediate StreamController to avoid an
extra async hop and keep delivery ordering consistent with callers
that await module operations
- Adds two integration tests covering the late-subscriber replay
and the buffer-clear-on-reconnect behaviours
* feat: connect SignalingModule early in initState to reduce call setup latency
SignalingModule is now created and connected in _MainShellState.initState(),
running the WebSocket handshake in parallel while the widget tree and
CallBloc are being built. When CallBloc is eventually created it subscribes
to the replay stream and receives all buffered session events without missing
anything.
_MainShellState.dispose() owns the module lifecycle; CallBloc.close()
still calls dispose() on the module (idempotent, safe).
* docs: update signaling architecture doc with layer descriptions and diagrams
* fix: add concurrency lock to _connectAsync to prevent parallel connects
* fix: await disconnect ack in dispose() to prevent SignalingDisconnected drop on race
* fix: suppress reconnect hint on intentional disconnect to prevent spurious reconnect
* fix: remove SignalingModule.dispose() from CallBloc.close() — ownership belongs to MainShellState
* fix: snapshot buffer before live subscribe to prevent replay duplicates in events getter
* fix: store reconnect Timer in IsolateManager so it can be cancelled on close()
* fix: forward recommendedReconnectDelay from SignalingDisconnected to _scheduleReconnect in CallBloc
* fix: suppress _onDisconnect after _onError to prevent double reconnect scheduling
* fix: exclude SignalingProtocolEvent from session buffer to prevent unbounded growth
* fix: replace force-unwrap of session.coreUrl/token with null-safe logout in initState
* fix: remove performEndCall early return so pre-handshake declines are queued
* fix: close liveController on subscription cancel to prevent StreamController leak
* fix: use _networkNone state instead of stale results snapshot in connectivity timer closure
* docs: fix _scheduleReconnectIfNeeded → _scheduleReconnect in signaling architecture doc
* test: replace Future.delayed(Duration.zero) with pumpEventQueue() in signaling module tests
* fix: make _controller sync:true to eliminate async-dispatch event duplication
* docs: clarify disconnect() docstring — SignalingDisconnected is callback-driven, not synchronous
* fix: wrap _signalingModule.disconnect() in unawaited() in _disconnectInitiated
* fix: remove unused shouldReconnect variable from __onSignalingClientEventDisconnected
* test: add 8 tests to reach 100% SignalingModule coverage
- concurrent connect() dropped while factory in-flight (_connecting guard)
- intentional disconnect() emits SignalingDisconnected with null delay
- disconnect() passes goingAway code to the underlying client
- _onError suppresses subsequent _onDisconnect (_errorHandled flag)
- SignalingProtocolEvent excluded from replay buffer
- cancelled subscription receives no further events
- dispose() awaits disconnect ack before closing the stream
- _onHandshake/_onEvent are no-ops after dispose()
* test: add scenario-driven SignalingModule tests from CallBloc usage analysis
Covers scenarios observed in CallBloc's signaling subscription:
internet dropped mid-session:
- _onError after handshake → ConnectionFailed not Disconnected, signalingClient cleared
- Unexpected socket close (null code) → Disconnected with kSignalingClientReconnectDelay
- ConnectionFailed buffered so late subscribers reconstruct last-known failure
handshake not completed:
- Disconnect before handshake → no HandshakeReceived in buffer, Disconnected with delay
- Late subscriber after no-handshake disconnect → gets Connecting+Connected+Disconnected only
- Error before handshake → ConnectionFailed buffered, no HandshakeReceived
- Reconnect after no-handshake failure delivers fresh session events
late subscriber mid-session:
- Factory still pending → gets Connecting from buffer, Connected arrives live
- After full connect+handshake → all three lifecycle events replayed, no protocol events
disconnect() robustness:
- client.disconnect() throws → dispose() still completes without hanging
- Second disconnect() with no active client is a silent no-op
* fix: restore callkeep_signaling_status_converter.dart lost during rebase
* fix: remove unused fields in _ThrowingDisconnectClient test helper
* fix: use normalClosure (1000) instead of goingAway (1001) when client disconnects WebSocket
* test: update disconnect test to expect normalClosure (1000) instead of goingAway (1001)
* fix: address Copilot review comments in SignalingModule and IsolateManager
- Fix doc comment on events getter: protocol events are not replayed, only lifecycle/handshake events
- Guard connect() buffer clear behind _connecting check to avoid clearing on redundant calls
- Remove stale comment in _onDisconnect that contradicted the !_disposed guard
- Treat empty connectivity result as offline in _monitorConnectivity (results.isEmpty || any(none))
- Treat empty connectivity result as offline in performAnswerCall (isNotEmpty && !contains(none))
* fix: address post-review issues in SignalingModule and IsolateManager
- isolate_manager: fix connectivityNoneCounter reset — error now fires
exactly once at maxConnectivityNoneRepeats; subsequent none-events are
silently ignored until connectivity is restored and counter resets to 0
- main_shell: split SignalingModule construction into valid/invalid-creds
branches to remove ?? '' fallbacks and make intent explicit
- signaling_module: document sync:true reentrancy assumption, single-use
constraint on events getter, and _errorHandled ordering invariant
* revert: restore main_shell.dart SignalingModule construction with ?? '' fallback
The split-branch approach still created a module with empty strings in the
null-creds case — identical in behaviour to the original. Reverted to the
original form which is honest about the fallback until a proper nullable
refactor is done.
* fix: remove dead null-guard in MainShellState.initState for SignalingModule
The router guard (onMainShellRouteGuardNavigation) redirects to login
when state.status != authenticated, so coreUrl and token are always
non-null when MainShell is mounted. Replace ?? '' fallbacks and the
unreachable null-branch with direct ! unwraps.
* docs: sync signaling_architecture_target.md and call_architecture.md with current code
- signaling_architecture_target: add timer cancellation to IsolateManager
and CallBloc _scheduleReconnect snippets (Future.delayed → Timer with cancel)
- signaling_architecture_target: add missing SignalingDisconnecting case to
CallBloc subscription snippet
- call_architecture: update ownership — CallBloc owns SignalingModule, not
WebtritSignalingClient directly
* fix: replace non-ASCII characters with ASCII equivalents in Dart sources
Replace em dash (U+2014) with '-' and right arrow (U+2192) with '->'
in comments and test descriptions across signaling_module.dart,
isolate_manager.dart, signaling_module_test.dart, and call_bloc.dart.
* fix: incorrect styling of status bar on app start (#1006)
Fixed status bar rendering with incorrect styling on initial app launch when theme is light
* feat: show progress indicator while sharing logs (#1036)
* feat: show progress indicator while sharing logs
Co-Authored-By: Dmytro Serdun <serdun@webtrit.com>
* refactor: replace inline SizedBox+CircularProgressIndicator with SizedCircularProgressIndicator
---------
Co-authored-by: Dmytro Serdun <serdun@webtrit.com>
* fix: upgrade to video resets hold (#1038)
* fix: media settings parsing (#1039)
* fix: call drops after theme or lang change (#1041)
* fix: cannot make calls after blind transfer — skip hangup + reconnect safety net (WT-1214) (#1040)
* fix: trigger reconnect when starting outgoing call with no signaling (WT-1214)
After a blind transfer, the signaling WebSocket is closed with code 4610
and the disconnect is marked intentional by SignalingModule, so no
reconnect is scheduled. Subsequent outgoing call attempts enter
outgoingConnectingToSignaling and wait passively — neither signalingReady
nor signalingFailed ever fires, causing the call to fail on timeout.
Add _scheduleReconnect(Duration.zero) at the start of the waiting block
so that initiating an outgoing call always recovers the signaling
connection, regardless of whether the previous disconnect was intentional.
* fix: skip hangup after successful blind transfer to avoid 4610 disconnect (WT-1214)
When a blind transfer completes (NOTIFY SIP/2.0 200 OK +
subscription_state: terminated), the SIP dialog is already closed
server-side via REFER. Sending a hangup request on the freed dialog
causes the server to close the WebSocket with code 4610 ("call request
on wrong line error"), triggering an unintended signaling disconnect.
Check the call's transfer state: if it is Transfering(fromBlindTransfer:
true), skip the hangup request and clean up the peer connection locally
only. This removes the root cause of the 4610 disconnect that led to
signaling not being reconnected for subsequent outgoing calls.
* test: cover blind-transfer hangup skip and 4610 reconnect hint
Add tests for two fixes from WT-1214:
- Transfer — isBlindTransferCompleted detection (call_state_test.dart):
verifies the switch pattern that decides whether to skip the hangup
request after a blind transfer. Covers Transfering(fromBlindTransfer:
true/false), earlier transfer states, and null.
- SignalingModule — requestCallIdError (4610) reconnect hint
(signaling_module_test.dart): verifies that a non-intentional 4610
carries a non-null recommendedReconnectDelay (reconnect scheduled),
while an intentional disconnect() followed by a server 4610 emits
null (reconnect suppressed — the scenario that triggered WT-1214).
* refactor: rename isBlindTransferCompleted → isBlindTransferInTransferingState
Transfering state in the model means "server started to process the
transfer", not "transfer completed". Rename the local variable and update
the surrounding comments and test group names to match the actual Transfer
model semantics, avoiding misinterpretation of the hangup guard.
Addresses Copilot review comments on PR #1040.
* fix: call or transfer to myself handling (#1046)
* fix: hide video for held call (#1048)
* fix: hide video for held call
* fix: tap area
* fix: transfer to same recipient (#1049)
* refactor: extract SignalingModuleInterface; migrate CallBloc and IsolateManager; extract toLinesState (#1045)
* refactor: extract SignalingModuleInterface; decouple IsolateManager from SignalingModule
Add local SignalingModuleInterface abstract class to signaling_module.dart
with the contract needed by IsolateManager: events, isConnected, connect(),
disconnect(), execute(Request), dispose().
SignalingModule implements the interface, gaining isConnected and execute()
alongside the existing signalingClient getter (kept for backward compat).
IsolateManager field type changed from SignalingModule to SignalingModuleInterface.
All signalingClient null-checks replaced with isConnected; direct client.execute()
calls replaced with module.execute(). IsolateManager no longer depends on the
concrete class, making it ready for a plugin-backed implementation.
* fix: decouple NetworkCubit from WebtritSignalingService; fix push dedup race
NetworkCubit held a concrete WebtritSignalingService only to call
updateMode(). Replaced with a Future<void> Function(SignalingServiceMode)
callback so the cubit has no plugin dependency and is mock-testable.
Call site passes WebtritSignalingService().updateMode as a tear-off.
_onCallPushEventIncoming checked the incomingFromOffer guard before
awaiting contactNameResolver.resolveWithNumber(). The signaling path
could create an ActiveCall during that async gap, causing both paths
to emit separate entries for the same callId. Added a post-await guard
that checks for any existing ActiveCall with the same callId before emitting.
* fix: prevent premature call routing state emission before signaling handshake (#1044)
LinesState.blank() is emitted at app startup before the signaling handshake
arrives. combineLatest fires immediately with cached UserInfo + blank LinesState,
causing CallRoutingCubit to emit a non-null state with empty mainLines.
CallController then skips _waitForRoutingState() and fails with
"no idle lines available".
Fix: add LinesState.isBlank discriminator (guestLine == null is an unambiguous
pre-handshake marker — CallBloc always sets guestLine to non-null after any
handshake). Return null from _combineInfo when linesState.isBlank so the cubit
stays in its unready state and CallController waits correctly.
* fix: preserve LinesState.blank in onChange until signaling handshake arrives
The previous fix relied on guestLine == null as a pre-handshake discriminator,
but CallBloc.onChange always set guestLine = LineState.idle regardless of
linesCount, overwriting LinesState.blank() almost immediately after startup.
Root cause: onChange fired with linesCount = 0 on any early state change
(e.g. connecting status) and produced LinesState([], LineState.idle).
isBlank returned false, so _combineInfo emitted a non-null CallRoutingState
with empty mainLines, and hasIdleMainLine = false blocked the call.
Fix: when linesCount == 0 (handshake not yet received), onChange now stores
LinesState.blank() explicitly. Once the handshake sets linesCount > 0,
normal LinesState with non-null guestLine is produced as before.
* fix: remove unused models import from NetworkScreenPage
* fix: wire BackgroundSignalingBootstrapService into NetworkCubit callback
* refactor: migrate CallBloc to SignalingModuleInterface
Apply the same interface migration already done for IsolateManager:
- change _signalingModule field/param type from SignalingModule to SignalingModuleInterface
- replace signalingClient?.execute() with execute() from the interface
- replace signalingClient != null checks with isConnected
* fix: address Copilot review comments on signaling module interface
- Handle null execute() in _executePendingRequests: complete completer
with error and clean up instead of leaving request to time out silently
- Handle null execute() in _sendRequest: log warning and return early
instead of awaiting null when module disconnects after isConnected check
- Fix linesCount == 0 guard: use isHandshakeEstablished to distinguish
pre-handshake blank state from valid post-handshake 0-lines state
- Update LinesState.isBlank doc comment to remove inaccurate claim that
guestLine == null is an unambiguous pre-handshake marker
* refactor: extract toLinesState() from CallBloc.onChange into CallState
Pure deterministic logic moved to CallState.toLinesState() so it can be
tested without standing up CallBloc. onChange becomes a single line.
Tests cover: pre-handshake blank, 0-lines post-handshake with guest line,
main line idle/inUse combinations, guest line inUse alongside main calls.
* fix: rename _kRegistered to kRegistered (lint: no_leading_underscores_for_local)
* refactor: rename SignalingModuleInterface → SignalingModule, impl → SignalingModuleIsolateImpl
Drop the `Interface` postfix from the abstract contract and suffix the
concrete WebSocket implementation with `IsolateImpl` to clarify its role.
Add doc comments to every member of the SignalingModule interface.
Files touched:
- lib/features/call/services/signaling_module.dart
- lib/features/call/services/isolate_manager.dart
- lib/features/call/bloc/call_bloc.dart
- lib/app/router/main_shell.dart
* fix: update signaling_module_test to use SignalingModuleIsolateImpl concrete type
* fix: use SignalingModule.execute instead of signalingClient getter (#1050)
signalingClient is not part of the SignalingModule interface — only
SignalingModuleIsolateImpl exposes it. Replace with execute() which is
defined on the interface and returns null when not connected.
* fix: skip decline when push-registered call receives signaling line (#1051)
* fix: skip decline when push-registered call receives signaling line (WT-1091)
When an incoming call arrives via FCM push, it is registered with
line _kUndefinedLine (-1) as a placeholder. When the signaling
WebSocket subsequently delivers the same call with a real line (e.g. 0),
the guard that detects 'call to myself' incorrectly fires because
-1 != 0, causing a DECLINE to be sent on the wrong line and the call
to be dropped with server error 4610.
Fix: exclude _kUndefinedLine from the line-mismatch check so that
push-placeholder calls are updated with the real line rather than
declined.
* test: add push → signaling line handoff tests for WT-1091 fix
* feat: media settings ptime warning (#1053)
* fix: call glare (#1052)
* fix: call glare
* fix: commented code
* feat: call interaction guard if any updating (#1056)
* fix: stop ringback sound on forced call termination (#1055)
__onResetStateEventCompleteCall is the path taken when signaling disconnects
unexpectedly (network loss). Unlike other termination paths it was not calling
_stopRingbackSound(), leaving the ringback tone playing after the call UI disappeared.
* fix: sanitize keypad input before initiating a call (WT-1026) (#1057)
* fix: sanitize keypad input before initiating a call (WT-1026)
Apply PhoneParser.normalize() in _popNumber() so that Unicode lookalike
digits and stray formatting characters are stripped before the number
reaches CallController/CallBloc.
* fix: normalize keypad input at entry point via TextInputFormatter (WT-1026)
Replace the call-time normalize approach with a TextInputFormatter so
pasted or typed Unicode lookalike characters are sanitized immediately
in the TextField, keeping the displayed text and the dialed number
consistent.
* refactor: move PhoneNormalizingFormatter to keypad/utils (WT-1026)
Extract formatter into its own file under features/keypad/utils/ with a
barrel export, following the existing package structure convention.
* fix: strip non-dialable characters from keypad input (WT-1026)
After Unicode normalization, remove any character outside [0-9*#+] so
pasting arbitrary text (e.g. *"{) leaves only valid phone number chars
in the field.
* fix: address Copilot review comments on PhoneNormalizingFormatter (WT-1026)
- Cache _nonDialableChars as static final to avoid per-keystroke allocation
- Translate both baseOffset and extentOffset (preserving affinity/isDirectional)
instead of always returning a collapsed selection
- Extract sanitize() static helper and reuse it in _popNumber() as a
belt-and-suspenders guard for programmatic controller updates
* test: add unit tests for PhoneNormalizingFormatter (WT-1026)
Covers sanitize() and formatEditUpdate() — including Unicode normalization,
non-dialable character stripping, cursor translation, and selection range
preservation.
* refactor: extract renegotiation support (#1058)
* fix: prevent null crash in InkWell on hardware keyboard event during navigation (WT-1012) (#1060)
* fix: prevent null crash in InkWell on hardware keyboard event during navigation (WT-1012)
Remove debug focusColor/hoverColor from CdrTile InkWell to stop it
subscribing to _HighlightModeManager, and add FocusScope.unfocus()
before all fullscreenDialog navigations to cover ListTile-based widgets.
* fix: use FocusManager and mounted guard in openNotificationsScreen (WT-1012)
Replace FocusScope.of(context).unfocus() with
FocusManager.instance.primaryFocus?.unfocus() to avoid depending on
a potentially unmounted context, and add a mounted guard before
navigating, since the callback can fire from a stream after disposal.
* fix: voicemail audio cache collision on Android (WT-1016) (#1061)
* fix: resolve voicemail audio cache collision on Android (WT-1016)
All voicemails shared the same cache file path because the last URL
path segment is always 'attachment'. Concurrent LockCachingAudioSource
instances raced to rename the same .part file, causing a crash.
- Add cacheKey param to AudioView; VoicemailTile passes voicemail.id
- _getCacheFile falls back to joined URI segments when cacheKey is absent
- Ensure media_cache directory exists before screen initialises
* refactor: ensure media_cache dir exists in AppPath.init
Move Directory.create to AppPath.init so the path is ready to use
by the time any feature accesses mediaCacheBasePath, consistent with
how getApplicationDocumentsDirectory / getTemporaryDirectory work.
* refactor: move media_cache dir creation into AudioView._getCacheFile
AppPath is platform-agnostic and must not import dart:io (web support).
Directory is now created in _getCacheFile, co-located with the code
that writes the cache file and already guarded by the Platform.isIOS check.
* fix: address Copilot review comments on AudioView cache handling
- Move Directory.create to _initialize() as async IO, avoiding
synchronous filesystem call in _getCacheFile on every retry
- Guard empty rawKey with early return null so just_audio handles
caching when URI has no path segments
- Sanitize cacheKey against path separators to prevent path traversal
from server-provided voicemail IDs
* refactor: renegotiation handlers per call (#1065)
* fix: fall back to audio-only when camera permission is denied on incoming video call (WT-1049) (#1062)
* fix: fall back to audio-only when camera permission is denied on incoming video call (WT-1049)
When answering an incoming video call without camera permission, the app
no longer drops the call. Instead, it retries media acquisition with
video disabled and answers audio-only. If the microphone itself is
unavailable the error still propagates as before.
* refactor: check camera permission before requesting media instead of relying on error fallback (WT-1049)
Added `isVideoAvailable()` to the `UserMediaBuilder` contract so the BLoC
can explicitly query camera permission status before deciding whether to
request video. Replaces the previous error-based fallback approach with a
proactive permission check.
* refactor: inject camera permission check as callback into UserMediaBuilder (WT-1049)
Replaced the direct `permission_handler` dependency in `user_media_builder.dart`
with an optional `isCameraPermissionGranted` callback. `DefaultUserMediaBuilder`
remains free of plugin dependencies; the check is wired at construction time in
`main_shell.dart` via `AppPermissions.isPermissionGranted(Permission.camera)`.
* refactor: move permission-aware video resolution into UserMediaBuilder.build() (WT-1049)
`DefaultUserMediaBuilder.build()` now resolves the effective video flag
internally via `_isCameraAvailable()` before calling `getUserMedia`, so
callers pass their intent (`video: offer.hasVideo`) and the builder handles
the permission check transparently.
`call_bloc.dart` derives the resulting video state from the actual stream
tracks (`localStream.getVideoTracks().isNotEmpty`) instead of pre-computing
it, removing all permission logic from the BLoC layer.
* fix: scope audio fallback to incoming answer only via allowAudioFallback flag (WT-1049)
The unconditional permission-check fallback inside `build()` was silently
downgrading video for all callers, including the camera-enable action which
relies on `UserMediaError` to show a notification and skip the `video: true`
state update.
Introduced `allowAudioFallback: bool = false` on `build()`. Default behaviour
(throw on failure) is preserved for existing callers; `__onCallPerformEventAnswered`
opts in with `allowAudioFallback: true` to get the permission-aware fallback.
* test: add unit tests for DefaultUserMediaBuilder permission-aware fallback (WT-1049)
* fix: retry getUserMedia audio-only when video acquisition fails with allowAudioFallback (WT-1049)
* fix: stop voicemail polling after server responds with unsup…
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixed bug when return button does not work on Terms & Conditions screen in settings. Problem occurs when can not load web page.