diff --git a/.github/dependabot.yml b/.github/dependabot.yml index a640f740b2..7a351526bb 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -25,7 +25,6 @@ updates: # All packages grouped into a single configuration using multi-directory support - package-ecosystem: "pub" directories: - - "/sample_app" - "/packages/stream_chat" - "/packages/stream_chat_flutter_core" - "/packages/stream_chat_flutter" diff --git a/.github/workflows/distribute_external.yml b/.github/workflows/distribute_external.yml index dde97d8fdd..69b3615a4b 100644 --- a/.github/workflows/distribute_external.yml +++ b/.github/workflows/distribute_external.yml @@ -71,7 +71,6 @@ jobs: with: flutter-version: ${{ env.FLUTTER_VERSION }} channel: stable - cache: true cache-key: flutter-:os:-:channel:-:version:-:arch:-:hash:-${{ hashFiles('**/pubspec.lock') }} - name: Setup Ruby @@ -109,7 +108,6 @@ jobs: with: flutter-version: ${{ env.FLUTTER_VERSION }} channel: stable - cache: true cache-key: flutter-:os:-:channel:-:version:-:arch:-:hash:-${{ hashFiles('**/pubspec.lock') }} - name: Setup Ruby diff --git a/.github/workflows/distribute_internal.yml b/.github/workflows/distribute_internal.yml index 19969d27cc..ce986729e3 100644 --- a/.github/workflows/distribute_internal.yml +++ b/.github/workflows/distribute_internal.yml @@ -75,7 +75,6 @@ jobs: with: flutter-version: ${{ env.FLUTTER_VERSION }} channel: stable - cache: true cache-key: flutter-:os:-:channel:-:version:-:arch:-:hash:-${{ hashFiles('**/pubspec.lock') }} - name: "Install Tools" @@ -119,7 +118,6 @@ jobs: with: flutter-version: ${{ env.FLUTTER_VERSION }} channel: stable - cache: true cache-key: flutter-:os:-:channel:-:version:-:arch:-:hash:-${{ hashFiles('**/pubspec.lock') }} - name: "Install Tools" @@ -144,3 +142,46 @@ jobs: MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} APPSTORE_API_KEY: ${{ secrets.APPSTORE_API_KEY }} run: bundle exec fastlane distribute_to_firebase + + # TODO: Remove once feat/design-refresh is merged to master + ios_testflight: + needs: determine_platforms + if: ${{ needs.determine_platforms.outputs.run_ios == 'true' }} + runs-on: macos-15 # Requires xcode 15 or later + timeout-minutes: 30 + steps: + - name: Connect Bot + uses: webfactory/ssh-agent@v0.9.1 + with: + ssh-private-key: ${{ secrets.BOT_SSH_PRIVATE_KEY }} + + - name: "Git Checkout" + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: "Install Flutter" + uses: subosito/flutter-action@v2 + with: + flutter-version: ${{ env.FLUTTER_VERSION }} + channel: stable + cache-key: flutter-:os:-:channel:-:version:-:arch:-:hash:-${{ hashFiles('**/pubspec.lock') }} + + - name: "Install Tools" + run: flutter pub global activate melos + + - name: "Bootstrap Workspace" + run: melos bootstrap + + - name: Setup Ruby + uses: ruby/setup-ruby@v1 + with: + bundler-cache: true + working-directory: sample_app/ios + + - name: Distribute to TestFlight Internal + working-directory: sample_app/ios + env: + MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} + APPSTORE_API_KEY: ${{ secrets.APPSTORE_API_KEY }} + run: bundle exec fastlane distribute_to_testflight_internal diff --git a/.github/workflows/legacy_version_analyze.yml b/.github/workflows/legacy_version_analyze.yml index 7705c53b8b..443dcc7560 100644 --- a/.github/workflows/legacy_version_analyze.yml +++ b/.github/workflows/legacy_version_analyze.yml @@ -3,7 +3,7 @@ name: legacy_version_analyze env: # Note: The versions below should be manually updated after a new stable # version comes out. - flutter_version: "3.27.4" + flutter_version: "3.38.1" on: push: @@ -44,7 +44,6 @@ jobs: with: flutter-version: ${{ env.flutter_version }} channel: stable - cache: true cache-key: flutter-:os:-:channel:-:version:-:arch:-:hash:-${{ hashFiles('**/pubspec.lock') }} - name: 📊 Analyze and test packages diff --git a/.github/workflows/stream_flutter_workflow.yml b/.github/workflows/stream_flutter_workflow.yml index 5debb88dba..0cd8433abb 100644 --- a/.github/workflows/stream_flutter_workflow.yml +++ b/.github/workflows/stream_flutter_workflow.yml @@ -38,7 +38,6 @@ jobs: with: flutter-version: ${{ env.flutter_version }} channel: stable - cache: true cache-key: flutter-:os:-:channel:-:version:-:arch:-:hash:-${{ hashFiles('**/pubspec.lock') }} - name: "Install Tools" run: | @@ -67,7 +66,6 @@ jobs: with: flutter-version: ${{ env.flutter_version }} channel: stable - cache: true cache-key: flutter-:os:-:channel:-:version:-:arch:-:hash:-${{ hashFiles('**/pubspec.lock') }} - name: "Install Tools" run: | @@ -94,7 +92,6 @@ jobs: with: flutter-version: ${{ env.flutter_version }} channel: stable - cache: true cache-key: flutter-:os:-:channel:-:version:-:arch:-:hash:-${{ hashFiles('**/pubspec.lock') }} # This step is needed due to https://github.com/actions/runner-images/issues/11279 - name: Install SQLite3 @@ -168,7 +165,6 @@ jobs: with: flutter-version: ${{ env.flutter_version }} channel: stable - cache: true cache-key: flutter-:os:-:channel:-:version:-:arch:-:hash:-${{ hashFiles('**/pubspec.lock') }} - name: "Install Tools" run: flutter pub global activate melos diff --git a/.github/workflows/update_goldens.yml b/.github/workflows/update_goldens.yml index af6a6cff61..c81affdc59 100644 --- a/.github/workflows/update_goldens.yml +++ b/.github/workflows/update_goldens.yml @@ -16,7 +16,6 @@ jobs: with: flutter-version: "3.x" channel: stable - cache: true cache-key: flutter-:os:-:channel:-:version:-:arch:-:hash:-${{ hashFiles('**/pubspec.lock') }} - name: 📦 Install Tools diff --git a/.gitignore b/.gitignore index 981d572c14..06a0c25816 100644 --- a/.gitignore +++ b/.gitignore @@ -66,6 +66,7 @@ GeneratedPluginRegistrant.* **/ios/**/xcuserdata **/ios/.generated/ **/ios/Flutter/App.framework +**/ios/Flutter/ephemeral **/ios/Flutter/Flutter.framework **/ios/Flutter/Flutter.podspec **/ios/Flutter/Generated.xcconfig diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000000..7d2892bd2b --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,148 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Overview + +This is a Dart/Flutter monorepo for Stream Chat's official Flutter SDK, managed with [Melos](https://pub.dev/packages/melos). All packages live under `packages/`. + +## Common Commands + +### Setup +```bash +melos bootstrap # Fetch and link all dependencies (equivalent to pub get for all packages) +``` + +### Testing +```bash +melos run test:all # Run all Dart & Flutter tests +melos run test:dart # Run Dart-only tests +melos run test:flutter # Run Flutter tests +# Run tests in a specific package: +cd packages/stream_chat_flutter && flutter test +cd packages/stream_chat_flutter && flutter test test/src/path/to/test_file.dart +``` + +### Golden Tests +```bash +melos run update:goldens # Regenerate all golden image files +# In CI, CI goldens are used; locally, platform goldens are used (configured via alchemist) +``` + +### Linting & Formatting +```bash +melos run lint:all # Run analyze + format +melos run analyze # Run dart analyze --fatal-infos on all packages +melos run format # Check formatting (page width: 120) +``` + +### Code Generation +```bash +melos run generate:all # Run build_runner for all packages +melos run generate:flutter # Run build_runner for Flutter packages only +melos run generate:dart # Run build_runner for Dart packages only +``` + +### Versioning +```bash +melos run version:update # Regenerate version.dart from pubspec.yaml (runs automatically after bootstrap) +``` + +## Package Architecture + +The SDK is layered — each package builds on top of the previous: + +``` +stream_chat # Pure Dart, no Flutter dependency + └── stream_chat_persistence # Local disk cache using Drift (optional) + └── stream_chat_flutter_core # Flutter business logic, no UI + └── stream_chat_flutter # Full UI component library + └── stream_chat_localizations # i18n for UI components +``` + +### `stream_chat` +Low-level Dart client. Key types: +- `StreamChatClient` — central API client, manages WebSocket, REST, and state +- `Channel` — represents a chat channel, has its own state and streaming APIs +- Models in `lib/src/core/models/`: `Message`, `User`, `Member`, `Reaction`, `Poll`, `Event`, `Attachment`, `Draft`, etc. +- Generated code (`.g.dart`, `.freezed.dart`) for JSON serialization/immutable models — do not edit manually + +### `stream_chat_flutter_core` +Business logic layer. Key types: +- `StreamChatCore` — root widget, manages app lifecycle, WebSocket reconnection, and connectivity +- `StreamChannel` — provides a `Channel` to the widget tree via `StreamChannel.of(context)` +- `PagedValueNotifier` — base class for all list controllers +- Controllers: `StreamChannelListController`, `StreamMessageListController` (via `MessageListCore`), `StreamUserListController`, `StreamMemberListController`, `StreamThreadListController`, `StreamDraftListController`, `StreamMessageReminderListController`, `StreamPollController` +- `BetterStreamBuilder` — efficient StreamBuilder that only rebuilds when data changes + +### `stream_chat_flutter` +Full UI package. Key architectural points: + +**Root widget hierarchy:** +`StreamChat` → `StreamChatTheme` → `StreamChatConfiguration` → `StreamChatCore` → app content + +**Theming:** `StreamChatThemeData` (accessed via `StreamChatTheme.of(context)`) contains per-component theme data objects. Components read their theme from context. `StreamChatConfigurationData` holds non-theme UI config. + +**Widget tree integration pattern:** +- `StreamChat.of(context)` — get the chat state (current user, client) +- `StreamChannel.of(context)` — get the current channel state +- `StreamChatTheme.of(context)` — get current theme data + +**Key UI components:** +- `StreamChannelListView` + `StreamChannelListTile` — channel list using `StreamChannelListController` +- `StreamMessageListView` — message list with floating date dividers, unread indicators, thread separators +- `StreamMessageInput` (legacy) / `StreamChatMessageComposer` (new design system) — message composition +- `StreamMessageWidget` — renders individual messages with attachments, reactions, threads +- Scroll views in `lib/src/scroll_view/` — generic paged scroll views for channels, threads, members, users, drafts, polls + +**New design system components** (`lib/src/components/`): +- `StreamUserAvatar`, `StreamChannelAvatar`, `StreamUserAvatarGroup` — avatar components; these are chat-domain wrappers around the base components in `stream_core_flutter` +- `StreamChatMessageComposer` — new composer using `MessageComposerFactory` for custom layouts + +**Golden tests:** Use `alchemist` package. Platform goldens used locally, CI goldens used in CI (detected via `CI`/`GITHUB_ACTIONS` env vars). Goldens stored alongside tests in `goldens/` subdirectories. + +### `stream_chat_localizations` +Provides `StreamChatLocalizations` — Flutter localizations delegate with translations for all UI strings. + +### `stream_chat_persistence` +Optional local persistence using Drift (SQLite). Implements `ChatPersistenceClient` from `stream_chat`. + +## Code Style + +- Line length: **120 characters** (configured in `analysis_options.yaml`) +- Imports: always use package imports (`always_use_package_imports`), not relative imports +- All public APIs **must** have doc comments (`public_member_api_docs`) +- Sort constructors first, unnamed constructors before named +- Prefer `const` constructors, `final` locals, single quotes +- Trailing commas: `preserve` (formatter setting) +- Generated files (`.g.dart`, `.freezed.dart`) are excluded from analysis + +## PR & Commit Conventions + +PR titles follow [Conventional Commits](https://www.conventionalcommits.org/): +- `fix(scope): description` — bug fix +- `feat(scope): description` — new feature +- `refactor(scope)!: description` — breaking change +- `chore(scope): description`, `docs:`, `test:`, etc. + +After modifying any package, update its `CHANGELOG.md`. + +## Figma Designs + +UI designs for this SDK are in the **Chat SDK Design system** Figma project. Use the Figma MCP server to look up designs when implementing or updating UI components. + +## `stream_core_flutter` (external sibling repo) + +Basic UI components that can be shared across Stream products live in the `stream_core_flutter` package in the **stream-core-flutter** repository (a sibling repo, not inside this monorepo). These components: +- Never depend on chat domain models (`Channel`, `Message`, `User`, etc.) +- Provide primitive building blocks: avatars, layout primitives, theming tokens, etc. + +Components in this repo can be wrappers around those base components, adding chat domain models and extra logic on top. For example, `StreamChannelAvatar` wraps the base `StreamAvatarGroup` from `stream_core_flutter` and adds channel-specific member resolution logic. + +`stream_core_flutter` types used here are re-exported via a `show` allowlist in `lib/stream_chat_flutter.dart`. When adding a new type from `stream_core_flutter`, add it to that allowlist. + +## Dependency Management + +Dependencies are centrally managed in `melos.yaml` under `command.bootstrap.dependencies`. Do **not** edit version constraints directly in individual `pubspec.yaml` files — update `melos.yaml` and run `melos bootstrap`. + +> Note: `stream_chat_flutter` uses a local path dependency to `stream_core_flutter` (pointing to the sibling repo) when making changes to both repos together. Use a git dependency when no local changes to `stream_core_flutter` are needed. diff --git a/analysis_options.yaml b/analysis_options.yaml index d5ac4a48f4..6e7d2449c4 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -4,6 +4,10 @@ analyzer: - packages/*/lib/scrollable_positioned_list/** - packages/*/lib/**/*.freezed.dart +formatter: + page_width: 120 + trailing_commas: preserve + linter: rules: # these rules are documented on and in the same order as @@ -19,7 +23,6 @@ linter: - control_flow_in_finally - empty_statements - hash_and_equals - - invariant_booleans - literal_only_boolean_expressions - no_adjacent_strings_in_list - no_duplicate_case_values @@ -40,7 +43,9 @@ linter: - avoid_null_checks_in_equality_operators - avoid_positional_boolean_parameters - avoid_private_typedef_functions - - avoid_redundant_argument_values + # Does not always make sense to remove them; it also makes it hard + # to notice future breaking changes. + # - avoid_redundant_argument_values - avoid_return_types_on_setters - avoid_returning_null_for_void - avoid_shadowing_type_parameters @@ -64,7 +69,6 @@ linter: - leading_newlines_in_multiline_strings - library_names - library_prefixes - - lines_longer_than_80_chars - missing_whitespace_between_adjacent_strings - non_constant_identifier_names - null_closures @@ -81,7 +85,6 @@ linter: - prefer_const_declarations - prefer_const_literals_to_create_immutables - prefer_contains - - prefer_equal_for_default_values - prefer_final_fields - prefer_final_in_for_each - prefer_final_locals diff --git a/melos.yaml b/melos.yaml index 6a54c65de2..7dc48f1206 100644 --- a/melos.yaml +++ b/melos.yaml @@ -1,4 +1,4 @@ -name: stream_chat_flutter +name: stream_chat_flutter_workspace repository: https://github.com/GetStream/stream-chat-flutter packages: @@ -18,9 +18,9 @@ command: bootstrap: # Dart and Flutter environment used in the project. environment: - sdk: ^3.6.2 + sdk: ^3.10.0 # We are not using carat '^' syntax here because flutter don't follow semantic versioning. - flutter: ">=3.27.4" + flutter: ">=3.38.1" # List of all the dependencies used in the project. dependencies: @@ -73,7 +73,7 @@ command: package_info_plus: ">=8.3.0 <10.0.0" path: ^1.8.3 path_provider: ^2.1.3 - photo_manager: ^3.2.0 + photo_manager: ^3.8.3 photo_view: ^0.15.0 provider: ^6.0.5 rate_limiter: ^1.0.0 @@ -91,6 +91,12 @@ command: stream_chat_persistence: ^10.0.0-beta.12 streaming_shared_preferences: ^2.0.0 svg_icon_widget: ^0.0.1 + # TODO: Replace with hosted version before merging PR + stream_core_flutter: + git: + url: https://github.com/GetStream/stream-core-flutter.git + ref: 8057a775c2ed764dbd5cbabd2dd60d3cd68d2f08 + path: packages/stream_core_flutter synchronized: ^3.1.0+1 thumblr: ^0.0.4 url_launcher: ^6.3.0 @@ -100,7 +106,7 @@ command: # List of all the dev_dependencies used in the project. dev_dependencies: - alchemist: ">=0.11.0 <0.14.0" + alchemist: ^0.13.0 build_runner: ^2.4.9 connectivity_plus_platform_interface: ^2.0.0 drift_dev: ^2.28.0 diff --git a/migrations/redesign/README.md b/migrations/redesign/README.md new file mode 100644 index 0000000000..d444374bcb --- /dev/null +++ b/migrations/redesign/README.md @@ -0,0 +1,132 @@ +# Stream Chat Flutter UI Redesign Migration Guide + +This folder contains migration guides for the redesigned UI components in Stream Chat Flutter SDK. + +## Overview + +The redesigned components aim to provide: +- Simplified and consistent APIs +- Better theme integration +- Improved developer experience +- Reduced boilerplate + +Each component migration guide contains specific details about the changes and how to migrate. + +## Theming + +The redesigned components use `StreamTheme` for theming. If no `StreamTheme` is provided, a default theme is automatically created based on `Theme.of(context).brightness` (light or dark mode). + +To customize the default theming, add `StreamTheme` as a theme extension to your `MaterialApp`: + +```dart +MaterialApp( + theme: ThemeData( + extensions: [ + StreamTheme( + brightness: Brightness.light, + colorScheme: StreamColorScheme.light().copyWith( + // Customize colors... + ), + avatarTheme: const StreamAvatarThemeData( + // Customize avatar defaults... + ), + ), + ], + ), + // ... +) +``` + +You can also use the convenience factories `StreamTheme.light()` or `StreamTheme.dark()` as a starting point. + + +## Component factories + +In the redesigned components we don't use builders in the constructors anymore, but have a centralized component factory. +The component factory contains product agnotic component builders, such as the button and the avatar, and also product specific component builders, such as the channel list item. +You can supply your component factory at any point in the widget tree, but you would usually wrap your full app around it. + +An example of a component factory with custom buttons and a custom channel list item: + +```dart +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return StreamComponentFactory( + builders: StreamComponentBuilders( + button: (context, props) => switch (props.type) { + StreamButtonType.solid => ElevatedButton( + onPressed: props.onTap, + child: Text(props.label ?? ''), + ), + StreamButtonType.outline => OutlinedButton(onPressed: props.onTap, child: Text(props.label ?? '')), + StreamButtonType.ghost => TextButton(onPressed: props.onTap, child: Text(props.label ?? '')), + }, + extensions: streamChatComponentBuilders( + channelListItem: (context, props) => StreamChannelListTile( + title: Text(props.channel.name ?? ''), + avatar: StreamChannelAvatar(channel: props.channel), + onTap: props.onTap, + onLongPress: props.onLongPress, + selected: props.selected, + ), + ), + ), + child: ... + ); + } +} +``` + +You should make the builder themselves as simple as possible by extracting this into separate widgets, such as this: + +```dart +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return StreamComponentFactory( + builders: StreamComponentBuilders( + button: (context, props) => MyCustomButton(props: props), + ), + child: ... + ); + } +} + +class MyCustomButton extends StatelessWidget { + const MyCustomButton({super.key, required this.props}); + + final StreamButtonProps props; + + @override + Widget build(BuildContext context) { + return switch (props.type) { + StreamButtonType.solid => ElevatedButton( + onPressed: props.onTap, + child: Text(props.label ?? ''), + ), + StreamButtonType.outline => OutlinedButton(onPressed: props.onTap, child: Text(props.label ?? '')), + StreamButtonType.ghost => TextButton(onPressed: props.onTap, child: Text(props.label ?? '')), + }; + } +} +``` + +## Components + +| Component | Migration Guide | +|-----------|-----------------| +| Stream Avatar | [stream_avatar.md](stream_avatar.md) | +| Channel List Item | [channel_list_item.md](channel_list_item.md) | +| Message Actions | [message_actions.md](message_actions.md) | +| Reaction Picker / Reactions | [reaction_picker.md](reaction_picker.md) | +| Image CDN & Thumbnails | [image_cdn.md](image_cdn.md) | +| Message Widget & Message List | [message_widget.md](message_widget.md) | + +## Need Help? + +If you encounter any issues during migration or have questions, please [open an issue](https://github.com/GetStream/stream-chat-flutter/issues) on GitHub. diff --git a/migrations/redesign/channel_list_item.md b/migrations/redesign/channel_list_item.md new file mode 100644 index 0000000000..30d236b624 --- /dev/null +++ b/migrations/redesign/channel_list_item.md @@ -0,0 +1,270 @@ +# Channel List Item Migration Guide + +This guide covers the migration for the redesigned channel list item components in Stream Chat Flutter SDK. + +--- + +## Table of Contents + +- [Quick Reference](#quick-reference) +- [StreamChannelListTile → StreamChannelListItem](#streamchannellisttile--streamchannellistitem) +- [Customizing Slots](#customizing-slots) +- [Low-level Presentational Component](#low-level-presentational-component) +- [Theme Migration](#theme-migration) +- [Migration Checklist](#migration-checklist) + +--- + +## Quick Reference + +| Old | New | +|-----|-----| +| `StreamChannelListTile` | `StreamChannelListItem` | +| Constructor props: `leading`, `title`, `subtitle`, `trailing` | `StreamChannelListItemProps` (via `StreamComponentFactory`) | +| `tileColor`, `visualDensity`, `contentPadding` | Removed — use `StreamChannelListItemThemeData` | +| `selectedTileColor` | Removed — use `StreamChannelListItemThemeData.backgroundColor` | +| `unreadIndicatorBuilder` | Removed | +| `StreamChannelPreviewThemeData` | `StreamChannelListItemThemeData` | +| `StreamChannelPreviewTheme.of(context)` | `StreamChannelListItemTheme.of(context)` | +| `StreamChatThemeData.channelPreviewTheme` | `StreamChatThemeData.channelListItemTheme` | + +--- + +## StreamChannelListTile → StreamChannelListItem + +The old `StreamChannelListTile` accepted all slot widgets directly in its constructor. The new `StreamChannelListItem` takes only the essential interaction properties. Slot customization is now done via `StreamChannelListItemProps` and the `StreamComponentFactory`. + +### Breaking Changes + +- `leading`, `title`, `subtitle`, `trailing` removed from constructor +- `tileColor` removed — use `StreamChannelListItemThemeData.backgroundColor` +- `visualDensity` removed +- `contentPadding` removed +- `selectedTileColor` removed +- `unreadIndicatorBuilder` removed +- `sendingIndicatorBuilder` removed from constructor — pass via `StreamChannelListItemProps` + +### Migration + +**Before:** +```dart +StreamChannelListTile( + channel: channel, + onTap: () => openChannel(channel), + onLongPress: () => showOptions(channel), + tileColor: Colors.white, + selectedTileColor: Colors.blue.shade50, + selected: isSelected, + contentPadding: const EdgeInsets.symmetric(horizontal: 16), + leading: StreamChannelAvatar(channel: channel), + title: StreamChannelName(channel: channel), + subtitle: ChannelListTileSubtitle(channel: channel), + trailing: ChannelLastMessageDate(channel: channel), +) +``` + +**After:** +```dart +StreamChannelListItem( + channel: channel, + onTap: () => openChannel(channel), + onLongPress: () => showOptions(channel), + selected: isSelected, +) +``` + +--- + +## Customizing Slots + +To customize the slot widgets (avatar, title, subtitle, timestamp), provide a custom builder via `StreamComponentFactory`: + +**Before:** +```dart +StreamChannelListTile( + channel: channel, + leading: MyCustomAvatar(channel: channel), + subtitle: MyCustomSubtitle(channel: channel), +) +``` + +**After:** +```dart +StreamComponentFactory( + builders: StreamComponentBuilders( + extensions: streamChatComponentBuilders( + channelListItem: (context, props) => StreamChannelListTile( + avatar: StreamChannelAvatar(channel: props.channel), + title: Text(props.channel.name ?? ''), + subtitle: Text(props.channel.lastMessageAt?.toString() ?? ''), + ), + ), + ), + child: ..., +) +``` + +--- + +## Low-level Presentational Component + +The new `StreamChannelListTile` is a low-level component that renders pre-resolved data without any channel-specific logic. Use this when you want to display a channel-shaped list item with fully controlled content (e.g., in a skeleton loader or a custom list): + +```dart +StreamChannelListTile( + avatar: StreamChannelAvatar(channel: channel), + title: Text('General'), + subtitle: Text('Last message preview'), + timestamp: Text('9:41 AM'), + unreadCount: 3, + isMuted: false, + onTap: () {}, +) +``` + +> **Note:** This widget does not subscribe to any streams — all values must be provided explicitly. + +--- + +## Theme Migration + +`StreamChannelPreviewThemeData` has been replaced by `StreamChannelListItemThemeData`. + +### Property Mapping + +| Old (`StreamChannelPreviewThemeData`) | New (`StreamChannelListItemThemeData`) | +|---------------------------------------|----------------------------------------| +| `titleStyle` | `titleStyle` | +| `subtitleStyle` | `subtitleStyle` | +| `lastMessageAtStyle` | `timestampStyle` | +| `avatarTheme` | Removed — use `StreamAvatarThemeData` directly | +| `unreadCounterColor` | Removed — use `StreamBadgeNotificationThemeData` | +| `indicatorIconSize` | Removed | +| `lastMessageAtFormatter` | Removed from theme — pass to `ChannelLastMessageDate(formatter: ...)` | + +### New Properties + +| Property | Type | Description | +|----------|------|-------------| +| `backgroundColor` | `WidgetStateProperty?` | Background color resolved per state (default, hover, pressed, selected) | +| `borderColor` | `Color?` | Bottom border color | +| `muteIconPosition` | `MuteIconPosition?` | Whether the mute icon appears in `title` or `subtitle` row | + +### Global Theme Migration + +**Before:** +```dart +StreamChatTheme( + data: StreamChatThemeData( + channelPreviewTheme: StreamChannelPreviewThemeData( + titleStyle: TextStyle(fontWeight: FontWeight.bold), + subtitleStyle: TextStyle(color: Colors.grey), + lastMessageAtStyle: TextStyle(fontSize: 12), + unreadCounterColor: Colors.red, + ), + ), + child: ..., +) +``` + +**After:** +```dart +StreamChatTheme( + data: StreamChatThemeData( + channelListItemTheme: StreamChannelListItemThemeData( + titleStyle: TextStyle(fontWeight: FontWeight.bold), + subtitleStyle: TextStyle(color: Colors.grey), + timestampStyle: TextStyle(fontSize: 12), + // unreadCounterColor → customize via StreamBadgeNotificationThemeData + ), + ), + child: ..., +) +``` + +### Subtree Theme Override + +**Before:** +```dart +StreamChannelPreviewTheme( + data: StreamChannelPreviewThemeData( + titleStyle: TextStyle(color: Colors.blue), + ), + child: StreamChannelListView(...), +) +``` + +**After:** +```dart +StreamChannelListItemTheme( + data: StreamChannelListItemThemeData( + titleStyle: TextStyle(color: Colors.blue), + ), + child: StreamChannelListView(...), +) +``` + +### Background and Selected Color + +**Before:** +```dart +StreamChannelListTile( + channel: channel, + tileColor: Colors.white, + selectedTileColor: Colors.blue.shade50, + selected: isSelected, +) +``` + +**After:** +```dart +StreamChannelListItemTheme( + data: StreamChannelListItemThemeData( + backgroundColor: WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.selected)) return Colors.blue.shade50; + return Colors.white; + }), + ), + child: StreamChannelListItem( + channel: channel, + selected: isSelected, + ), +) +``` + +### Custom Timestamp Formatter + +**Before:** +```dart +StreamChannelPreviewThemeData( + lastMessageAtFormatter: (context, date) { + return Jiffy.parseFromDateTime(date).format('d MMMM'); + }, +) +``` + +**After:** +```dart +// Pass directly to the widget — no longer a theme property +ChannelLastMessageDate( + channel: channel, + formatter: (context, date) { + return Jiffy.parseFromDateTime(date).format('d MMMM'); + }, +) +``` + +--- + +## Migration Checklist + +- [ ] Replace `StreamChannelListTile` with `StreamChannelListItem` +- [ ] Remove `tileColor`, `visualDensity`, `contentPadding`, `selectedTileColor`, `unreadIndicatorBuilder` parameters +- [ ] Move slot customization (`leading`, `title`, `subtitle`, `trailing`) to `StreamComponentFactory` +- [ ] Replace `StreamChannelPreviewTheme` with `StreamChannelListItemTheme` +- [ ] Replace `StreamChatThemeData.channelPreviewTheme` with `StreamChatThemeData.channelListItemTheme` +- [ ] Rename `lastMessageAtStyle` to `timestampStyle` +- [ ] Move `lastMessageAtFormatter` from theme to `ChannelLastMessageDate(formatter: ...)` +- [ ] Replace `tileColor`/`selectedTileColor` with `StreamChannelListItemThemeData.backgroundColor` using `WidgetStateProperty` +- [ ] Replace `unreadCounterColor` with `StreamBadgeNotificationThemeData` +- [ ] Replace `avatarTheme` in `StreamChannelPreviewThemeData` with `StreamAvatarThemeData` on the avatar widget directly diff --git a/migrations/redesign/image_cdn.md b/migrations/redesign/image_cdn.md new file mode 100644 index 0000000000..bb341447a3 --- /dev/null +++ b/migrations/redesign/image_cdn.md @@ -0,0 +1,209 @@ +# Image CDN & Thumbnails Migration Guide + +This guide covers the migration for the redesigned image CDN handling and thumbnail resize parameters in Stream Chat Flutter SDK. + +--- + +## Table of Contents + +- [Quick Reference](#quick-reference) +- [StreamImageCDN](#streamimagecdn) +- [StreamImageAttachmentThumbnail](#streamimageattachmentthumbnail) +- [StreamMediaAttachmentThumbnail](#streammediaattachmentthumbnail) +- [StreamImageAttachment](#streamimageattachment) +- [Migration Checklist](#migration-checklist) + +--- + +## Quick Reference + +| Component | Key Changes | +|-----------|-------------| +| [**StreamImageCDN**](#streamimagecdn) | New class replacing `getResizedImageUrl` String extension (stable cache keys now via `StreamImageCDN.cacheKey()`) | +| [**StreamImageAttachmentThumbnail**](#streamimageattachmentthumbnail) | `thumbnailSize`, `thumbnailResizeType`, `thumbnailCropType` → single `resize` parameter | +| [**StreamMediaAttachmentThumbnail**](#streammediaattachmentthumbnail) | `thumbnailSize`, `thumbnailResizeType`, `thumbnailCropType` → single `resize` parameter | +| [**StreamImageAttachment**](#streamimageattachment) | `imageThumbnailSize`, `imageThumbnailResizeType`, `imageThumbnailCropType` → single `resize` parameter | + +--- + +## StreamImageCDN + +### What Changed: + +The `getResizedImageUrl` String extension has been replaced with a dedicated `StreamImageCDN` class. This class handles CDN URL resolution and stable cache key generation, preventing image reloads caused by expiring signed URL tokens. + +### Key Changes: + +- `getResizedImageUrl` String extension removed — use `StreamImageCDN.resolveUrl` instead +- New `StreamImageCDN.cacheKey` method generates stable cache keys that strip volatile signed URL tokens +- Raw `String` resize/crop type parameters replaced with `ResizeMode` and `CropMode` enums +- `StreamImageCDN` is injectable via `StreamChatConfigurationData` for custom CDN support + +### Migration: + +**Before:** +```dart +final resizedUrl = imageUrl.getResizedImageUrl( + width: 200, + height: 300, + resize: 'clip', + crop: 'center', +); +``` + +**After:** +```dart +const imageCDN = StreamImageCDN(); + +final resizedUrl = imageCDN.resolveUrl( + imageUrl, + resize: ImageResize(width: 200, height: 300), +); + +final cacheKey = imageCDN.cacheKey(resizedUrl); +``` + +### Custom CDN Support: + +Extend `StreamImageCDN` and inject it via configuration: + +```dart +class MyImageCDN extends StreamImageCDN { + @override + String cacheKey(String imageUrl) { + return Uri.parse(imageUrl).path; + } +} + +StreamChat( + client: client, + config: StreamChatConfigurationData( + imageCDN: MyImageCDN(), + ), + child: ..., +) +``` + +--- + +## StreamImageAttachmentThumbnail + +### Breaking Changes: + +- `thumbnailSize` parameter removed +- `thumbnailResizeType` parameter removed +- `thumbnailCropType` parameter removed +- New `resize` parameter (`ImageResize?`) replaces all three + +### Migration: + +**Before:** +```dart +StreamImageAttachmentThumbnail( + image: attachment, + thumbnailSize: const Size(200, 300), + thumbnailResizeType: 'clip', + thumbnailCropType: 'center', +) +``` + +**After:** +```dart +StreamImageAttachmentThumbnail( + image: attachment, + resize: ImageResize( + width: 200, + height: 300, + mode: ResizeMode.clip, + crop: CropMode.center, + ), +) +``` + +> **Note:** When `resize` is null, the size is auto-calculated from layout constraints and defaults to `ResizeMode.clip` and `CropMode.center`. + +--- + +## StreamMediaAttachmentThumbnail + +### Breaking Changes: + +- `thumbnailSize` parameter removed +- `thumbnailResizeType` parameter removed +- `thumbnailCropType` parameter removed +- New `resize` parameter (`ImageResize?`) replaces all three + +### Migration: + +**Before:** +```dart +StreamMediaAttachmentThumbnail( + media: attachment, + thumbnailSize: const Size(200, 300), + thumbnailResizeType: 'clip', + thumbnailCropType: 'center', +) +``` + +**After:** +```dart +StreamMediaAttachmentThumbnail( + media: attachment, + resize: ImageResize( + width: 200, + height: 300, + mode: ResizeMode.clip, + crop: CropMode.center, + ), +) +``` + +--- + +## StreamImageAttachment + +### Breaking Changes: + +- `imageThumbnailSize` parameter removed +- `imageThumbnailResizeType` parameter removed +- `imageThumbnailCropType` parameter removed +- New `resize` parameter (`ImageResize?`) replaces all three + +### Migration: + +**Before:** +```dart +StreamImageAttachment( + message: message, + image: attachment, + imageThumbnailSize: const Size(400, 600), + imageThumbnailResizeType: 'crop', + imageThumbnailCropType: 'center', +) +``` + +**After:** +```dart +StreamImageAttachment( + message: message, + image: attachment, + resize: ImageResize( + width: 400, + height: 600, + mode: ResizeMode.crop, + crop: CropMode.center, + ), +) +``` + +--- + +## Migration Checklist + +- [ ] Replace `getResizedImageUrl` String extension calls with `StreamImageCDN.resolveUrl` +- [ ] Use `StreamImageCDN.cacheKey` to generate stable cache keys for `CachedNetworkImage` +- [ ] Replace raw `String` resize/crop values (`'clip'`, `'crop'`, etc.) with `ResizeMode` and `CropMode` enums +- [ ] Update `StreamImageAttachmentThumbnail` to use `resize` parameter instead of `thumbnailSize`, `thumbnailResizeType`, `thumbnailCropType` +- [ ] Update `StreamMediaAttachmentThumbnail` to use `resize` parameter instead of `thumbnailSize`, `thumbnailResizeType`, `thumbnailCropType` +- [ ] Update `StreamImageAttachment` to use `resize` parameter instead of `imageThumbnailSize`, `imageThumbnailResizeType`, `imageThumbnailCropType` +- [ ] If using a custom CDN, extend `StreamImageCDN` and inject via `StreamChatConfigurationData` diff --git a/migrations/redesign/message_actions.md b/migrations/redesign/message_actions.md new file mode 100644 index 0000000000..c20bf77def --- /dev/null +++ b/migrations/redesign/message_actions.md @@ -0,0 +1,570 @@ +# Message Actions Migration Guide + +This guide covers the migration for the redesigned message action components in Stream Chat Flutter SDK. + +--- + +## Table of Contents + +- [Quick Reference](#quick-reference) +- [StreamMessageAction → StreamContextMenuAction](#streammessageaction--streamcontextmenuaction) +- [StreamMessageActionItem](#streammessageactionitem) +- [StreamMessageActionsModal](#streammessageactionsmodal) +- [StreamMessageReactionsModal](#streammessagereactionsmodal) +- [ModeratedMessageActionsModal](#moderatedmessageactionsmodal) +- [StreamMessageWidget.customActions → actionsBuilder](#streammessagewidgetcustomactions) +- [StreamMessageActionsBuilder](#streammessageactionsbuilder) +- [New Components](#new-components) +- [Migration Checklist](#migration-checklist) + +--- + +## Quick Reference + +| Symbol | Change | +|--------|--------| +| `StreamMessageAction` | **Removed** — replaced by `StreamContextMenuAction` | +| `StreamMessageActionItem` | **Removed** — rendering built into `StreamContextMenuAction` | +| `StreamMessageActionsModal.onActionTap` | **Removed** — use `onTap` per-action or await the dialog return value | +| `StreamMessageActionsModal.messageActions` | **Type changed**: `List` → `List` | +| `StreamMessageReactionsModal.onReactionPicked` | **Removed** — await the dialog return value (`SelectReaction`) | +| `ModeratedMessageActionsModal.onActionTap` | **Removed** — use `onTap` per-action or await the dialog return value | +| `ModeratedMessageActionsModal.messageActions` | **Type changed**: `List` → `List` | +| `StreamMessageWidget.customActions` | **Removed** — replaced by `actionsBuilder` (`MessageActionsBuilder?`) | +| `StreamMessageWidget.onCustomActionTap` | **Removed** — use `onTap` directly on each `StreamContextMenuAction` in `actionsBuilder` | +| `CustomMessageAction` | **Removed** — no longer needed; custom actions use `onTap` directly | +| `OnMessageActionTap` | **Removed** — no longer needed | +| `StreamMessageWidget.actionsBuilder` | **New** — `MessageActionsBuilder?` for the normal long-press menu | +| `StreamMessageActionsBuilder.buildActions` | **Changed**: return type `List`, `customActions` param **removed** | +| `StreamMessageActionsBuilder.buildBouncedErrorActions` | **Return type changed**: `List` → `List` | +| `MessageActionsBuilder` | **New typedef** — `List Function(BuildContext, List>)` | +| `StreamContextMenu` | **New** — exported from `stream_core_flutter` | +| `StreamContextMenuAction` | **New** — exported from `stream_core_flutter` | +| `StreamContextMenuSeparator` | **New** — exported from `stream_core_flutter` | + +> **Note:** `MessageAction` and all its built-in subclasses (`SelectReaction`, `CopyMessage`, `DeleteMessage`, etc.) are **unchanged**. `CustomMessageAction` (the escape-hatch subclass) has been **removed** — it was only needed for the old `onCustomActionTap` dispatch pattern. + +--- + +## StreamMessageAction → StreamContextMenuAction + +The `StreamMessageAction` data class has been removed. It was a pure data object that described how an action should look and which `MessageAction` it represents. It is replaced by `StreamContextMenuAction`, which is a self-rendering widget that carries a typed `value` and handles dispatch automatically. + +### Breaking Change + +`StreamMessageAction` no longer exists. Replace every usage with `StreamContextMenuAction`. + +### Tap dispatch behaviour + +`StreamContextMenuAction` has two complementary dispatch mechanisms: + +- **`value`** — when the action is tapped inside a popup route (dialog, bottom sheet, etc.) it calls `Navigator.pop(value)` first, then calls `onTap` if provided. The route is already closed when `onTap` runs. +- **`onTap`** — an optional `VoidCallback?`. When the action is used *inline* (outside any popup route) this is the only callback that fires. + +You can use `value`, `onTap`, or both together. + +### Migration + +**Before:** +```dart +StreamMessageAction( + action: QuotedReply(message: message), + leading: const StreamSvgIcon(icon: StreamSvgIcons.reply), + title: Text(context.translations.replyLabel), +) +``` + +**After (value-based — recommended for modals):** +```dart +StreamContextMenuAction( + value: QuotedReply(message: message), + leading: Icon(context.streamIcons.arrowShareLeft), + label: Text(context.translations.replyLabel), +) +// The caller receives QuotedReply via the Future returned by showStreamDialog. +``` + +**After (onTap-based — for inline usage or when you prefer callbacks):** +```dart +StreamContextMenuAction( + value: QuotedReply(message: message), + leading: Icon(context.streamIcons.arrowShareLeft), + label: Text(context.translations.replyLabel), + onTap: () => onReply(message), // called after the route is dismissed +) +``` + +### Property mapping + +| `StreamMessageAction` | `StreamContextMenuAction` | +|-----------------------|--------------------------| +| `action: T` | `value: T?` | +| `title: Widget?` | `label: Widget` (required) | +| `leading: Widget?` | `leading: Widget?` | +| `isDestructive: bool` | `isDestructive: bool` (or use `.destructive` constructor) | +| `iconColor: Color?` | Controlled via `StreamContextMenuActionTheme` | +| `titleTextColor: Color?` | Controlled via `StreamContextMenuActionTheme` | +| `titleTextStyle: TextStyle?` | Controlled via `StreamContextMenuActionTheme` | +| `backgroundColor: Color?` | Controlled via `StreamContextMenuActionTheme` | +| — | `onTap: VoidCallback?` (new) | +| — | `trailing: Widget?` (new) | +| — | `enabled: bool` (new) | + +> **Important:** +> - **`label` is now required** — `title: Widget?` was optional in `StreamMessageAction`; `label: Widget` is a required, non-nullable parameter in `StreamContextMenuAction`. Any call site that omitted `title` will fail to compile; you must supply a non-null `label` widget (typically a `Text`). +> - `onTap` signature changed from `void Function(MessageAction)` to `VoidCallback?` — capture data in a closure instead +> - Per-item colours and text styles are now unified via `StreamContextMenuActionTheme` rather than individual properties + +--- + +## StreamMessageActionItem + +The `StreamMessageActionItem` widget has been removed. `StreamContextMenuAction` is now a full self-rendering widget — no separate "item" wrapper is needed. + +### Breaking Change + +`StreamMessageActionItem` no longer exists. Remove all direct usages. + +### Migration + +**Before:** +```dart +StreamMessageActionItem( + action: StreamMessageAction( + action: CopyMessage(message: message), + leading: const StreamSvgIcon(icon: StreamSvgIcons.copy), + title: Text('Copy'), + ), + onTap: (action) => _handle(action), +) +``` + +**After:** +```dart +StreamContextMenuAction( + value: CopyMessage(message: message), + leading: Icon(context.streamIcons.copy), + label: Text('Copy'), + onTap: () => _handle(message), +) +``` + +--- + +## StreamMessageActionsModal + +### Breaking Changes + +- `onActionTap: OnMessageActionTap?` parameter **removed** — the modal no longer holds a top-level callback; use `onTap` on individual actions or await the dialog's return value +- `messageActions` parameter type changed from `List` to `List` + +### Migration + +**Before:** +```dart +StreamMessageActionsModal( + message: message, + messageWidget: messageWidget, + messageActions: [ + StreamMessageAction( + action: CopyMessage(message: message), + leading: const StreamSvgIcon(icon: StreamSvgIcons.copy), + title: Text(context.translations.copyMessageLabel), + ), + ], + onActionTap: (action) { + if (action is CopyMessage) _copyMessage(action.message); + }, +) +``` + +**After (onTap per-action):** +```dart +StreamMessageActionsModal( + message: message, + messageWidget: messageWidget, + messageActions: [ + StreamContextMenuAction( + value: CopyMessage(message: message), + leading: Icon(context.streamIcons.copy), + label: Text(context.translations.copyMessageLabel), + onTap: () => _copyMessage(message), // called after route dismissal + ), + ], +) +``` + +**After (await return value):** + +> `showStreamDialog` is a Stream-themed wrapper around `showGeneralDialog` — see [New Components](#showstreamdialogt) for details. + +```dart +final action = await showStreamDialog( + context: context, + builder: (_) => StreamMessageActionsModal( + message: message, + messageWidget: messageWidget, + messageActions: [ + StreamContextMenuAction( + value: CopyMessage(message: message), + leading: Icon(context.streamIcons.copy), + label: Text(context.translations.copyMessageLabel), + ), + ], + ), +); + +if (action is CopyMessage) _copyMessage(action.message); +``` + +> **Important:** +> - `onActionTap` on the modal is gone — move handling to `onTap` on each item or await the `Future` +> - Replace `StreamMessageAction` entries with `StreamContextMenuAction` + +--- + +## StreamMessageReactionsModal + +### Breaking Changes + +- `onReactionPicked: OnMessageActionTap?` parameter **removed** — the modal now pops the route with a `SelectReaction`; await the dialog return value to handle it + +### Migration + +**Before:** +```dart +StreamMessageReactionsModal( + message: message, + messageWidget: messageWidget, + onReactionPicked: (SelectReaction action) { + _addReaction(action.reaction); + }, +) +``` + +**After:** +```dart +final action = await showStreamDialog( + context: context, + builder: (_) => StreamMessageReactionsModal( + message: message, + messageWidget: messageWidget, + ), +); + +if (action is SelectReaction) { + _addReaction(action.reaction); +} +``` + +> **Important:** +> - The old `onReactionPicked` already received a `SelectReaction`, not a raw `Reaction` — the migration only changes *where* you handle it (caller vs callback) + +--- + +## ModeratedMessageActionsModal + +### Breaking Changes + +- `onActionTap: OnMessageActionTap?` parameter **removed** — move handling to `onTap` on each action or await the dialog return value +- `messageActions` parameter type changed from `List` to `List` + +### Migration + +**Before:** +```dart +ModeratedMessageActionsModal( + message: message, + messageActions: [ + StreamMessageAction( + action: ResendMessage(message: message), + title: Text(context.translations.sendAnywayLabel), + ), + StreamMessageAction( + action: EditMessage(message: message), + title: Text(context.translations.editMessageLabel), + ), + StreamMessageAction( + isDestructive: true, + action: HardDeleteMessage(message: message), + title: Text(context.translations.deleteMessageLabel), + ), + ], + onActionTap: (action) { + if (action is ResendMessage) _resend(action.message); + }, +) +``` + +**After (onTap per-action):** +```dart +ModeratedMessageActionsModal( + message: message, + messageActions: [ + StreamContextMenuAction( + value: ResendMessage(message: message), + label: Text(context.translations.sendAnywayLabel), + onTap: () => _resend(message), + ), + StreamContextMenuAction( + value: EditMessage(message: message), + label: Text(context.translations.editMessageLabel), + ), + StreamContextMenuAction.destructive( + value: HardDeleteMessage(message: message), + label: Text(context.translations.deleteMessageLabel), + ), + ], +) +``` + +**After (await return value):** +```dart +final action = await showStreamDialog( + context: context, + builder: (_) => ModeratedMessageActionsModal( + message: message, + messageActions: [ + StreamContextMenuAction( + value: ResendMessage(message: message), + label: Text(context.translations.sendAnywayLabel), + ), + // ... + ], + ), +); + +if (action is ResendMessage) _resend(action.message); +``` + +--- + +## StreamMessageWidget.customActions + +### Breaking Change + +`customActions: List` has been **removed**. It is replaced by `actionsBuilder`: + +```dart +typedef MessageActionsBuilder = + List Function( + BuildContext context, + List> defaultActions, + ); +``` + +`StreamMessageWidget.actionsBuilder` is declared as `MessageActionsBuilder?` (i.e. `MessageActionsBuilder?`), so `defaultActions` is typed as `List>` — each item's `.props.value` is a `MessageAction?`. + +The `defaultActions` list passed into the builder is already filtered by the widget's `show*` flags, so callers always start from a clean, ready-to-render baseline. + +`actionsBuilder` returns `List` — any widget can be mixed in alongside the default `StreamContextMenuAction` items (e.g. `StreamContextMenuSeparator`). + +### Migration + +**Before (append a custom action):** +```dart +StreamMessageWidget( + message: message, + messageTheme: messageTheme, + customActions: [ + StreamMessageAction( + action: CustomMessageAction( + message: message, + extraData: const {'type': 'favourite'}, + ), + leading: const Icon(Icons.star), + title: Text('Favourite'), + ), + ], + onCustomActionTap: (CustomMessageAction action) { + _favourite(action.message); + }, +) +``` + +**After:** +```dart +StreamMessageWidget( + message: message, + messageTheme: messageTheme, + actionsBuilder: (context, defaultActions) => [ + ...defaultActions, + StreamContextMenuAction( + leading: const Icon(Icons.star), + label: Text('Favourite'), + onTap: () => _favourite(message), + ), + ], +) +``` + +**After (remove an existing action and add a custom one):** +```dart +StreamMessageWidget( + message: message, + messageTheme: messageTheme, + actionsBuilder: (context, defaultActions) => [ + ...defaultActions.where((a) => a.props.value is! DeleteMessage), + StreamContextMenuSeparator(), + StreamContextMenuAction( + leading: const Icon(Icons.star), + label: Text('Favourite'), + onTap: () => _favourite(message), + ), + ], +) +``` + +> **Important:** +> - `onCustomActionTap` is **removed** — put dispatch logic directly in `onTap` on each action +> - `actionsBuilder` receives the defaults **already** filtered by `show*` flags (e.g. `showDeleteMessage`) +> - When `actionsBuilder` is not provided, the default list is wrapped in `StreamContextMenuAction.partitioned` automatically + +--- + +## StreamMessageActionsBuilder + +### Breaking Changes + +Both static methods now return `List` instead of `List`. Additionally, the `customActions` parameter of `buildActions` has been **removed** — appending custom actions is now handled by `StreamMessageWidget.actionsBuilder`. + +| Method / Parameter | Old type | New type | +|--------------------|----------|----------| +| `buildActions` return | `List` | `List` | +| `buildBouncedErrorActions` return | `List` | `List` | +| `buildActions(customActions:)` | `Iterable?` | **Removed** | + +### Migration + +**Before:** +```dart +final List actions = + StreamMessageActionsBuilder.buildActions( + context: context, + message: message, + channel: channel, + currentUser: currentUser, + customActions: myCustomStreamMessageActions, +); +``` + +**After:** +```dart +// buildActions no longer accepts customActions — add extras via actionsBuilder +final List> actions = + StreamMessageActionsBuilder.buildActions( + context: context, + message: message, + channel: channel, + currentUser: currentUser, +); +``` + +--- + +## New Components + +### showStreamDialog\ + +A top-level function from `package:stream_chat_flutter/stream_chat_flutter.dart` that replaces direct calls to `showDialog` when presenting Stream modals. It wraps `showGeneralDialog` and: + +- **Re-wraps `StreamChatTheme`** across the route boundary so the theme is available inside the dialog even when `useRootNavigator: true` +- **Applies a blur + scale transition** for a consistent Stream look +- **Returns `Future`** — the value passed to `Navigator.pop` inside the dialog, which is how `StreamMessageActionsModal`, `StreamMessageReactionsModal`, and `ModeratedMessageActionsModal` deliver the selected action back to the caller + +```dart +// Replace showDialog with showStreamDialog when presenting Stream modals: +final action = await showStreamDialog( + context: context, + builder: (_) => StreamMessageActionsModal(/* … */), +); +``` + +> **Note:** If you were calling `showDialog` directly and passing Stream modals to it, switch to `showStreamDialog` to ensure theming works correctly across the route boundary. + +### StreamContextMenuAction + +A self-contained menu action widget from `stream_core_flutter` that replaces `StreamMessageAction` + `StreamMessageActionItem`. It renders itself and supports two dispatch mechanisms: + +- **Inside a popup route** (dialog/bottom sheet): pops `value` via `Navigator.pop` first, then calls `onTap` if provided. +- **Inline** (outside any popup route): only `onTap` fires. + +```dart +// Standard action — value-based (recommended for modals) +StreamContextMenuAction( + value: CopyMessage(message: message), + leading: Icon(context.streamIcons.copy), + label: Text('Copy'), +) + +// With optional onTap callback (called after route dismissal) +StreamContextMenuAction( + value: CopyMessage(message: message), + leading: Icon(context.streamIcons.copy), + label: Text('Copy'), + onTap: () => _copyMessage(message), +) + +// Destructive action +StreamContextMenuAction.destructive( + value: DeleteMessage(message: message), + leading: Icon(context.streamIcons.trashBin), + label: Text('Delete'), +) +``` + +#### Helper methods for grouping + +```dart +// Insert a separator between every item +StreamContextMenuAction.separated(items: actions) + +// Insert separators between logical groups (provide groups as separate lists) +StreamContextMenuAction.sectioned(sections: [normalActions, destructiveActions]) + +// Automatically partition into normal / destructive groups with a separator between them +StreamContextMenuAction.partitioned(items: actions) +``` + +All three methods return `List` because they interleave `StreamContextMenuSeparator` widgets. + +### StreamContextMenu + +A themed container that wraps a list of `StreamContextMenuAction` and `StreamContextMenuSeparator` widgets. + +```dart +StreamContextMenu( + children: StreamContextMenuAction.partitioned(items: actions), +) +``` + +### StreamContextMenuSeparator + +A thin horizontal divider for use inside `StreamContextMenu`. + +```dart +StreamContextMenu( + children: [ + StreamContextMenuAction(value: reply, label: Text('Reply')), + const StreamContextMenuSeparator(), + StreamContextMenuAction.destructive(value: delete, label: Text('Delete')), + ], +) +``` + +--- + +## Migration Checklist + +- [ ] Replace all `StreamMessageAction(action: ..., title: ..., leading: ...)` with `StreamContextMenuAction(value: ..., label: ..., leading: ...)` +- [ ] Add a non-null `label` widget wherever `title` was previously omitted — `label` is now a required parameter and code that relied on a null/omitted `title` will not compile +- [ ] Update `onTap` callsites: old type was `void Function(MessageAction)`, new type is `VoidCallback?` — capture needed data in a closure +- [ ] Remove all `StreamMessageActionItem` usages +- [ ] Remove `onActionTap` from `StreamMessageActionsModal`; handle via per-action `onTap` or await the dialog return value +- [ ] Remove `onReactionPicked` from `StreamMessageReactionsModal`; await a `SelectReaction` return value +- [ ] Remove `onActionTap` from `ModeratedMessageActionsModal`; handle via per-action `onTap` or await the dialog return value +- [ ] Replace `StreamMessageWidget.customActions` with `actionsBuilder` +- [ ] Update `StreamMessageActionsBuilder.buildActions` call sites — return type is now `List` and `customActions` parameter no longer exists +- [ ] Update `StreamMessageActionsBuilder.buildBouncedErrorActions` call sites — return type is now `List` +- [ ] Replace `StreamSvgIcon` leading widgets in custom actions with `Icon(context.streamIcons.*)` +- [ ] Replace per-action color/style properties (`iconColor`, `titleTextColor`, etc.) with `StreamContextMenuActionTheme` diff --git a/migrations/redesign/message_widget.md b/migrations/redesign/message_widget.md new file mode 100644 index 0000000000..f85c65e47a --- /dev/null +++ b/migrations/redesign/message_widget.md @@ -0,0 +1,499 @@ +# Message Widget & Message List Migration Guide + +This guide covers migrating the message widget and message list view from the old design (`feat/design-refresh`) to the new redesigned API. + +--- + +## Table of Contents + +- [Quick Reference](#quick-reference) +- [Architecture Changes](#architecture-changes) +- [StreamMessageWidget](#streammessagewidget) + - [Removed Parameters](#removed-parameters) + - [New Parameters](#new-parameters) + - [Changed Signatures](#changed-signatures) +- [StreamMessageListView](#streammessagelistview) + - [Builder Signature Changes](#builder-signature-changes) + - [New List-Level Callbacks](#new-list-level-callbacks) + - [Removed: MessageDetails](#removed-messagedetails) +- [Custom Actions Migration](#custom-actions-migration) +- [Theme Migration](#theme-migration) +- [Swipeable Message Example](#swipeable-message-example) +- [Deleted Classes & Files](#deleted-classes--files) +- [Typedef Changes](#typedef-changes) +- [Migration Checklist](#migration-checklist) + +--- + +## Quick Reference + +| Old | New | +|-----|-----| +| `StreamMessageWidget` (50+ params) | `StreamMessageWidget` (thin shell) + `StreamMessageWidgetProps` | +| `MessageWidgetContent` | `DefaultStreamMessage` + `StreamMessageContent` | +| `BottomRow` | `StreamMessageFooter` | +| `StreamMessageText` (message_text.dart) | `StreamMessageText` (components/stream_message_text.dart) | +| `StreamDeletedMessage` | `StreamMessageDeleted` | +| `MessageCard` | `core.StreamMessageBubble` | +| `TextBubble` | `core.StreamMessageBubble` | +| `PinnedMessage` | `streamMessageHeader()` function | +| `QuotedMessage` | Inline in `StreamMessageContent` | +| `Username` | Inline in `StreamMessageFooter` | +| `SendingIndicatorBuilder` | `StreamMessageSendingStatus` | +| `ThreadReplyPainter` | `core.StreamMessageReplies` | +| `ThreadParticipants` | Inline in `core.StreamMessageReplies` | +| `UserAvatarTransform` | `StreamMessageLeading` | +| `DisplayWidget` enum | `StreamVisibility` (from theme) | +| `MessageBuilder` typedef | `StreamMessageWidgetBuilder` typedef | +| `ParentMessageBuilder` typedef | `StreamMessageWidgetBuilder` typedef | +| `OnQuotedMessageTap = void Function(String?)` | `void Function(Message quotedMessage)` | +| `StreamMessageWidget.customActions` | `StreamMessageWidgetProps.actionsBuilder` | +| `StreamMessageWidget.onCustomActionTap` | Use `onTap` per `StreamContextMenuAction` | +| `CustomMessageAction` | Removed — use `StreamContextMenuAction` with `onTap` | +| `StreamMessageWidget.copyWith()` | `StreamMessageWidgetProps.copyWith()` | + +--- + +## Architecture Changes + +The old design used a single monolithic `StreamMessageWidget` with 50+ parameters controlling every aspect of rendering. The new design splits responsibilities: + +- **`StreamMessageWidget`** — thin shell that resolves the `StreamComponentFactory` and delegates to the factory builder or `DefaultStreamMessage`. +- **`StreamMessageWidgetProps`** — plain data class holding all configuration. Supports `copyWith()`. +- **`DefaultStreamMessage`** — the default rendering implementation. Composes the sub-components below. +- **`StreamMessageContent`** — bubble, attachments, text, reactions, thread replies. +- **`StreamMessageFooter`** — username, timestamp, sending status, edited indicator. +- **`streamMessageHeader()`** — pinned, saved-for-later, show-in-channel annotations. +- **`StreamMessageLeading`** — author avatar. +- **`StreamMessageReactions`** — clustered reaction chips around the bubble. +- **`StreamMessageText`** — markdown-rendered message text. +- **`StreamMessageDeleted`** — deleted message placeholder. +- **`StreamMessageSendingStatus`** — delivery status icon. + +### Component Factory Pattern + +The new design adds a **component factory** layer for app-wide customization. The `messageBuilder` / `parentMessageBuilder` callbacks on `StreamMessageListView` are still supported for per-list customization. + +**App-wide customization via component factory:** +```dart +StreamChat( + client: client, + componentBuilders: StreamComponentBuilders( + extensions: streamChatComponentBuilders( + messageWidget: (context, props) { + return DefaultStreamMessage( + props: props.copyWith( + actionsBuilder: (context, defaultActions) { + return [...defaultActions, myCustomAction]; + }, + ), + ); + }, + ), + ), + child: ..., +) +``` + +**Per-list customization via `messageBuilder` (still supported):** +```dart +StreamMessageListView( + messageBuilder: (context, message, defaultProps) { + return StreamMessageWidget.fromProps(props: defaultProps); + }, +) +``` + +Both can be combined — the component factory applies first, then the per-list `messageBuilder` can further customize or wrap the result. + +--- + +## StreamMessageWidget + +### Removed Parameters + +These parameters have been removed entirely. See the **Migration Path** column for how to achieve the same result. + +#### Visibility Booleans + +| Old Parameter | Migration Path | +|---|---| +| `showReactions` | Controlled via `StreamMessageItemThemeData` visibility | +| `showDeleteMessage` | Controlled via channel permissions (`canDeleteOwnMessage`, `canDeleteAnyMessage`) | +| `showEditMessage` | Controlled via channel permissions (`canUpdateOwnMessage`, `canUpdateAnyMessage`) | +| `showReplyMessage` | Controlled via channel permissions (`canSendReply`) | +| `showThreadReplyMessage` | Controlled via channel permissions (`canSendReply`) | +| `showMarkUnreadMessage` | Shown automatically when applicable | +| `showResendMessage` | Shown automatically for failed messages | +| `showCopyMessage` | Shown automatically when message has text | +| `showFlagButton` | Controlled via channel permissions (`canFlagMessage`) | +| `showPinButton` | Controlled via channel permissions (`canPinMessage`) | +| `showPinHighlight` | Controlled via `StreamMessageItemThemeData` background color | +| `showReactionPicker` | Removed | +| `showUsername` | Controlled via `StreamMessageItemThemeData.footerVisibility` | +| `showTimestamp` | Controlled via `StreamMessageItemThemeData.footerVisibility` | +| `showEditedLabel` | Controlled via `StreamMessageItemThemeData.footerVisibility` | +| `showSendingIndicator` | Controlled via `StreamMessageItemThemeData.footerVisibility` | +| `showThreadReplyIndicator` | Shown automatically when `replyCount > 0` | +| `showInChannelIndicator` | Shown automatically via `streamMessageHeader()` | +| `showUserAvatar` (`DisplayWidget`) | Controlled via `StreamMessageItemThemeData.leadingVisibility` | + +#### Builder Callbacks + +| Old Parameter | Migration Path | +|---|---| +| `userAvatarBuilder` | Use component factory to replace `DefaultStreamMessage` | +| `textBuilder` | Use component factory to replace `StreamMessageContent` | +| `quotedMessageBuilder` | Use component factory to replace `StreamMessageContent` | +| `deletedMessageBuilder` | Use component factory to replace `StreamMessageContent` | +| `editMessageInputBuilder` | Removed; use `onEditMessageTap` callback instead | +| `bottomRowBuilderWithDefaultWidget` | Use component factory; `StreamMessageFooter` is the new equivalent | +| `reactionPickerBuilder` | Configured globally via `StreamChatConfigurationData.reactionIconResolver` | +| `reactionIndicatorBuilder` | Replaced by `StreamMessageReactions` component | + +#### Shape & Style + +| Old Parameter | Migration Path | +|---|---| +| `shape` | Controlled via `StreamMessageBubble` theming in `stream_core_flutter` | +| `borderSide` | Controlled via `StreamMessageBubble` theming | +| `borderRadiusGeometry` | Controlled via `StreamMessageBubble` theming | +| `attachmentShape` | Controlled via attachment builder theming | +| `textPadding` | Controlled via `StreamMessageBubble` content padding theming | +| `attachmentPadding` | Configured internally by `ParseAttachments` | +| `messageTheme` | Resolved from context via `StreamMessageItemTheme.of(context)` | + +#### Other Removed Parameters + +| Old Parameter | Migration Path | +|---|---| +| `reverse` | Determined by `StreamMessagePlacement` context (set by list view) | +| `translateUserAvatar` | Removed; avatar positioning is theme-driven | +| `onConfirmDeleteTap` | Handled internally by `StreamMessageActionsBuilder` | +| `onShowMessage` | Removed | +| `onReactionsHover` | Removed | +| `customActions` | Use `actionsBuilder` on `StreamMessageWidgetProps` | +| `onCustomActionTap` | Use `actionsBuilder` on `StreamMessageWidgetProps` | +| `onAttachmentTap` | Handle in custom attachment builders | +| `imageAttachmentThumbnailSize` | Configured in attachment builders | +| `imageAttachmentThumbnailResizeType` | Configured in attachment builders | +| `imageAttachmentThumbnailCropType` | Configured in attachment builders | +| `attachmentActionsModalBuilder` | Configured in attachment builders | +| `attachmentBuilders` | Moved to `StreamChatConfigurationData.attachmentBuilders` (still overridable per-message via `StreamMessageWidgetProps.attachmentBuilders`) | +| `copyWith()` on `StreamMessageWidget` | Use `StreamMessageWidgetProps.copyWith()` instead | + +### New Parameters + +| New Parameter | Description | +|---|---| +| `padding` | Outer padding around the message item (overrides theme) | +| `spacing` | Horizontal spacing between avatar and content (overrides theme) | +| `backgroundColor` | Background color for the message row (overrides theme) | +| `widthFactor` | Max content width as fraction of parent (default: `0.8`) | +| `onMessageLinkTap` | `void Function(Message, String)` — receives message and URL | +| `onUserMentionTap` | `void Function(User)` — receives the mentioned user | +| `onQuotedMessageTap` | `void Function(Message)` — receives the quoted message object | +| `onReactionsTap` | `void Function(Message)` — overrides default reaction detail sheet | +| `reactionSorting` | `Comparator` for reaction display order | +| `actionsBuilder` | `MessageActionsBuilder` for customizing the actions list | +| `onMessageActions` | Override the default long-press modal entirely | +| `onBouncedErrorMessageActions` | Override the bounced-error modal entirely | +| `onEditMessageTap` | Called when edit action is selected | + +### Changed Signatures + +| Callback | Old Signature | New Signature | +|---|---|---| +| Link tap | `void Function(String url)` | `void Function(Message message, String url)` | +| Mention tap | `void Function(User user)` | `void Function(User user)` (renamed: `onMentionTap` → `onUserMentionTap`) | +| Quoted message tap | `void Function(String? quotedMessageId)` | `void Function(Message quotedMessage)` | +| Thread tap | `void Function(Message message)` | `void Function(Message message)` (unchanged signature, renamed: `onThreadTap`) | +| Reply tap | `void Function(Message message)` | `void Function(Message message)` (new: `onReplyTap`) | + +--- + +## StreamMessageListView + +### Builder Signature Changes + +Both `messageBuilder` and `parentMessageBuilder` now use the same typedef: + +**Before:** +```dart +typedef MessageBuilder = Widget Function( + BuildContext context, + MessageDetails details, + List messages, + StreamMessageWidget defaultMessageWidget, +); + +typedef ParentMessageBuilder = Widget Function( + BuildContext context, + Message? parentMessage, + StreamMessageWidget defaultMessageWidget, +); +``` + +**After:** +```dart +typedef StreamMessageWidgetBuilder = Widget Function( + BuildContext context, + Message message, + StreamMessageWidgetProps defaultProps, +); +``` + +The old builders received a pre-built `StreamMessageWidget` that you could `copyWith`. The new builders receive `StreamMessageWidgetProps` — raw configuration data. Use `StreamMessageWidget.fromProps(props:)` to build the default widget through the component factory. + +**Before:** +```dart +StreamMessageListView( + messageBuilder: (context, details, messages, defaultWidget) { + return defaultWidget.copyWith(showReactions: false); + }, +) +``` + +**After:** +```dart +StreamMessageListView( + messageBuilder: (context, message, defaultProps) { + // Build default widget (goes through component factory) + return StreamMessageWidget.fromProps(props: defaultProps); + + // Or customize props before building + return StreamMessageWidget.fromProps( + props: defaultProps.copyWith( + actionsBuilder: (context, actions) => [...actions, myAction], + ), + ); + + // Or replace entirely + return MyCustomMessageWidget(message: message); + }, +) +``` + +> **Important:** The `messageBuilder` callback now receives a `BuildContext` that has `StreamMessagePlacement` in its ancestor chain. You can call `StreamMessagePlacement.alignmentDirectionalOf(context)` to determine message alignment. + +### New List-Level Callbacks + +These callbacks were previously only configurable per-message on `StreamMessageWidget`. They are now available at the list level and forwarded to all messages: + +| New Parameter | Type | +|---|---| +| `onEditMessageTap` | `void Function(Message)?` | +| `onReplyTap` | `void Function(Message)?` | +| `onUserAvatarTap` | `void Function(User)?` | +| `onReactionsTap` | `void Function(Message)?` | +| `onQuotedMessageTap` | `void Function(Message)?` | +| `onMessageLinkTap` | `void Function(Message, String)?` | +| `onUserMentionTap` | `void Function(User)?` | + +### Changed: `showUnreadCountOnScrollToBottom` Default + +```dart +// Old +showUnreadCountOnScrollToBottom: false + +// New +showUnreadCountOnScrollToBottom: true +``` + +### Removed: MessageDetails + +The old `messageBuilder` received `MessageDetails` which contained `userId`, `message`, `messages`, and `index`. The new builder receives just `Message` and `StreamMessageWidgetProps`. The user ID is accessible via `StreamChat.of(context).currentUser?.id`. Message alignment is provided by `StreamMessagePlacement.of(context)`. + +--- + +## Custom Actions Migration + +**Before (using `customActions` + `onCustomActionTap`):** +```dart +StreamMessageWidget( + message: message, + messageTheme: theme, + customActions: [ + StreamMessageAction( + leading: Icon(Icons.info), + title: Text('Info'), + onTap: (message) => showInfo(message), + ), + ], + onCustomActionTap: (action) { + // handle CustomMessageAction + }, +) +``` + +**After (using `actionsBuilder` via component factory):** +```dart +StreamChat( + client: client, + componentBuilders: StreamComponentBuilders( + extensions: streamChatComponentBuilders( + messageWidget: (context, props) { + return DefaultStreamMessage( + props: props.copyWith( + actionsBuilder: (context, defaultActions) { + return StreamContextMenuAction.partitioned( + items: [ + ...defaultActions, + StreamContextMenuAction( + leading: Icon(context.streamIcons.informationCircle), + label: Text('Info'), + onTap: () => showInfo(props.message), + ), + ], + ); + }, + ), + ); + }, + ), + ), + child: ..., +) +``` + +**After (removing a default action):** +```dart +actionsBuilder: (context, defaultActions) { + return StreamContextMenuAction.partitioned( + items: defaultActions.where( + (a) => a.props.value is! DeleteMessage, + ).toList(), + ); +}, +``` + +> **Important:** +> - `customActions` and `onCustomActionTap` are removed +> - `CustomMessageAction` class is removed — use `StreamContextMenuAction` with `onTap` +> - `actionsBuilder` receives defaults already filtered by channel permissions +> - Return `List` — you can mix `StreamContextMenuAction` and `StreamContextMenuSeparator` + +--- + +## Theme Migration + +**Before (explicit `messageTheme` parameter):** +```dart +StreamMessageWidget( + message: message, + messageTheme: isMyMessage + ? streamTheme.ownMessageTheme + : streamTheme.otherMessageTheme, +) +``` + +**After (theme resolved automatically from context):** +```dart +StreamMessageWidget(message: message) +``` + +`StreamMessageItemTheme` is provided by `StreamChatTheme` and resolved based on `StreamMessagePlacement` (alignment, stack position, etc.). + +### StreamMessageItemThemeData + +The old per-property visibility booleans are replaced by a structured visibility system: + +```dart +StreamMessageItemThemeData( + leadingVisibility: StreamMessageStyleVisibility( + incoming: StreamVisibility.visible, + outgoing: StreamVisibility.gone, + ), + headerVisibility: StreamMessageStyleVisibility(...), + footerVisibility: StreamMessageStyleVisibility(...), + + incoming: StreamMessageItemStyle( + padding: EdgeInsets.all(4), + backgroundColor: Colors.white, + ), + outgoing: StreamMessageItemStyle( + padding: EdgeInsets.all(4), + backgroundColor: Colors.blue.shade50, + ), +) +``` + +--- + +## Swipeable Message Example + +```dart +StreamMessageListView( + messageBuilder: (context, message, defaultProps) { + final defaultWidget = StreamMessageWidget.fromProps(props: defaultProps); + + if (message.isDeleted || message.state.isFailed) return defaultWidget; + + final alignment = StreamMessagePlacement.alignmentDirectionalOf(context); + final isEnd = alignment == AlignmentDirectional.centerEnd; + + return Swipeable( + key: ValueKey(message.id), + direction: isEnd ? SwipeDirection.endToStart : SwipeDirection.startToEnd, + swipeThreshold: 0.2, + onSwiped: (_) => onReply(message), + child: defaultWidget, + ); + }, +) +``` + +--- + +## Deleted Classes & Files + +| Old File | Old Class | Replacement | +|---|---|---| +| `message_widget_content.dart` | `MessageWidgetContent` | `DefaultStreamMessage` + `StreamMessageContent` | +| `message_widget_content_components.dart` | Various internal helpers | Merged into `components/` sub-widgets | +| `bottom_row.dart` | `BottomRow` | `StreamMessageFooter` | +| `message_text.dart` | `StreamMessageText` | `components/stream_message_text.dart` | +| `deleted_message.dart` | `StreamDeletedMessage` | `StreamMessageDeleted` | +| `message_card.dart` | `MessageCard` | `core.StreamMessageBubble` | +| `text_bubble.dart` | `TextBubble` | `core.StreamMessageBubble` | +| `pinned_message.dart` | `PinnedMessage` | `streamMessageHeader()` function | +| `quoted_message.dart` | `QuotedMessage` | Inline in `StreamMessageContent` | +| `thread_painter.dart` | `ThreadReplyPainter` | `core.StreamMessageReplies` | +| `thread_participants.dart` | `ThreadParticipants` | Inline in `core.StreamMessageReplies` | +| `user_avatar_transform.dart` | `UserAvatarTransform` | `StreamMessageLeading` | +| `username.dart` | `Username` | Inline in `StreamMessageFooter` | +| `sending_indicator_builder.dart` | `SendingIndicatorBuilder` | `StreamMessageSendingStatus` | + +--- + +## Typedef Changes + +| Old Typedef | New Typedef | +|---|---| +| `MessageBuilder = Widget Function(BuildContext, MessageDetails, List, StreamMessageWidget)` | `StreamMessageWidgetBuilder = Widget Function(BuildContext, Message, StreamMessageWidgetProps)` | +| `ParentMessageBuilder = Widget Function(BuildContext, Message?, StreamMessageWidget)` | `StreamMessageWidgetBuilder` (same as above) | +| `OnQuotedMessageTap = void Function(String?)` | Removed — use `void Function(Message)` directly | +| — | `MessageActionsBuilder = List Function(BuildContext, List>)` (new) | + +> **Note:** `MessageBuilder` and `ParentMessageBuilder` are removed from `typedefs.dart`. The new `StreamMessageWidgetBuilder` is defined in `message_list_view.dart` and exported via the barrel file. + +--- + +## Migration Checklist + +- [ ] Replace `StreamMessageWidget(message:, messageTheme:, ...)` with `StreamMessageWidget(message:)` — theme is now resolved from context +- [ ] Remove all `show*` boolean parameters — visibility is now controlled via `StreamMessageItemThemeData` and channel permissions +- [ ] Remove `customActions` and `onCustomActionTap` — use `actionsBuilder` via component factory or `StreamMessageWidgetProps.copyWith()` +- [ ] Remove all per-widget builder callbacks (`userAvatarBuilder`, `textBuilder`, `quotedMessageBuilder`, `deletedMessageBuilder`, `bottomRowBuilderWithDefaultWidget`, `reactionPickerBuilder`, `reactionIndicatorBuilder`) — use component factory instead +- [ ] Remove `shape`, `borderSide`, `borderRadiusGeometry`, `attachmentShape`, `textPadding`, `attachmentPadding` — controlled via `StreamMessageBubble` theming +- [ ] Remove `reverse` — determined by `StreamMessagePlacement` context +- [ ] Remove `translateUserAvatar` — avatar positioning is theme-driven +- [ ] Update `messageBuilder` / `parentMessageBuilder` callbacks to new `StreamMessageWidgetBuilder` signature +- [ ] Replace `MessageDetails` usage — use `StreamMessagePlacement.of(context)` for alignment, `StreamChat.of(context).currentUser` for user ID +- [ ] Update `onLinkTap` to `onMessageLinkTap` with new signature `void Function(Message, String)` +- [ ] Update `onMentionTap` to `onUserMentionTap` +- [ ] Update `onQuotedMessageTap` from `void Function(String?)` to `void Function(Message)` +- [ ] Replace `StreamDeletedMessage` with `StreamMessageDeleted` +- [ ] Replace `StreamMessageAction` with `StreamContextMenuAction` (see [message_actions.md](message_actions.md)) +- [ ] Replace `StreamSvgIcon(icon: StreamSvgIcons.*)` with `Icon(context.streamIcons.*)` +- [ ] Remove `StreamMessageWidget.copyWith()` usage — use `StreamMessageWidgetProps.copyWith()` instead diff --git a/migrations/redesign/reaction_picker.md b/migrations/redesign/reaction_picker.md new file mode 100644 index 0000000000..fe6c9c7764 --- /dev/null +++ b/migrations/redesign/reaction_picker.md @@ -0,0 +1,341 @@ +# Reaction Picker Migration Guide + +This guide covers the migration for the redesigned reaction picker and reaction indicator components in Stream Chat Flutter SDK. + +--- + +## Table of Contents + +- [Quick Reference](#quick-reference) +- [StreamChatConfigurationData](#streamchatconfigurationdata) +- [Removed Icon-List APIs](#removed-icon-list-apis) +- [ReactionIconResolver and DefaultReactionIconResolver](#reactioniconresolver-and-defaultreactioniconresolver) +- [StreamMessageReactionPicker](#streammessagereactionpicker-formerly-streamreactionpicker) +- [StreamReactionIndicator](#streamreactionindicator) +- [New Components](#new-components) +- [Migration Checklist](#migration-checklist) + +--- + +## Quick Reference + +| Symbol | Change | +|--------|--------| +| `StreamChatConfigurationData.reactionIcons` | **Removed** — replaced by `reactionIconResolver` | +| `StreamChatConfigurationData.reactionIconResolver` | **New** — optional (default: `DefaultReactionIconResolver()`). Replaces `reactionIcons` | +| `ReactionIconResolver` | **New** — abstract contract for mapping reaction type → widget/emoji | +| `DefaultReactionIconResolver` | **New** — ready-to-use default; extend to customize `defaultReactions`, `emojiCode`, or rendering hooks | +| `ReactionPickerIconList` / `ReactionIndicatorIconList` | **Removed** — list rendering now lives inside picker/indicator widgets | +| `ReactionPickerIcon` / `ReactionIndicatorIcon` | **Removed** — use resolver-based reaction mapping instead | +| `StreamReactionPicker` | **Renamed** to `StreamMessageReactionPicker` — reaction set from `config.reactionIconResolver.defaultReactions` only | +| `StreamReactionPickerTheme` / `StreamReactionPickerThemeData` | **New** (from `stream_core_flutter`) — theme-based visual customisation for the picker | +| `StreamReactionIndicator` | **Changed** — uses `config.reactionIconResolver.resolve(context, type)` only | +| `ReactionDetailSheet` | **New** — `ReactionDetailSheet.show()` for reaction details bottom sheet | + +> **Note:** If you were using default reactions only, behavior stays the same (`like`, `haha`, `love`, `wow`, `sad`). Migration is required only for custom reaction icon/type setups. + +--- + +## StreamChatConfigurationData + +### Breaking Changes: + +- `reactionIcons` **removed** — was a list of reaction type + builder pairs for picker/indicator +- `reactionIconResolver` **new** — optional; defaults to `DefaultReactionIconResolver()`. All reaction UI uses it + +### Migration + +**Before:** +```dart +StreamChat( + client: client, + streamChatConfigData: StreamChatConfigurationData( + reactionIcons: [ /* type + builder per reaction */ ], + ), + child: MyApp(), +) +``` + +**After:** +```dart +StreamChat( + client: client, + streamChatConfigData: StreamChatConfigurationData( + reactionIconResolver: const MyReactionIconResolver(), + ), + child: MyApp(), +) +``` + +Extend `DefaultReactionIconResolver` (see below), pass as `reactionIconResolver`. Omit to keep defaults. + +> **Important:** +> - Resolver replaces the old list: use `defaultReactions` + `resolve(context, type)` (which uses `emojiCode(type)`) + +--- + +## Removed Icon-List APIs + +### Breaking Changes: + +- `ReactionPickerIconList` and `ReactionIndicatorIconList` were removed +- `ReactionPickerIcon` and `ReactionIndicatorIcon` were removed +- Per-widget icon list injection moved to a single global resolver (`StreamChatConfigurationData.reactionIconResolver`) + +### Migration + +**Before:** +```dart +StreamChat( + client: client, + streamChatConfigData: StreamChatConfigurationData( + reactionIcons: [ + // old reaction icon entries + ], + ), + child: MyApp(), +) +``` + +**After:** +```dart +class MyReactionIconResolver extends DefaultReactionIconResolver { + const MyReactionIconResolver(); + + @override + Set get defaultReactions => const {'like', 'love', 'celebrate'}; + + @override + String? emojiCode(String type) { + if (type == 'celebrate') return '🎉'; + return super.emojiCode(type); + } +} + +StreamChat( + client: client, + streamChatConfigData: StreamChatConfigurationData( + reactionIconResolver: const MyReactionIconResolver(), + ), + child: MyApp(), +) +``` + +--- + +## ReactionIconResolver and DefaultReactionIconResolver + +Picker uses `defaultReactions`; picker and indicator call `resolve(context, type)` → uses `emojiCode(type)` then `buildEmojiReaction` or `buildFallbackReaction`. Extend `DefaultReactionIconResolver` and override only what you need. + +### Contract + +- **`defaultReactions`** — types in quick-pick bar. Every type here must be resolvable by `emojiCode(type)` (return non-null emoji) or fallback is shown. +- **`emojiCode(type)`** — return Unicode emoji (e.g. `'👍'`) or `null`. Used by `resolve`. +- **`supportedReactions`** — full resolver-supported type set. Keep this in sync with your resolver implementation. +- **`resolve(context, type)`** — widget for display. Default: `emojiCode(type)` → `buildEmojiReaction` else `buildFallbackReaction`. + +Override points on `DefaultReactionIconResolver`: `defaultReactions`, `emojiCode`, `buildEmojiReaction`, `buildFallbackReaction`, `supportedReactions`. + +### Migration (custom quick-pick set) + +Restrict `defaultReactions` to keys in `streamSupportedEmojis` so inherited `emojiCode` returns the emoji. + +**Before:** Custom list of reaction types on config or picker. + +**After:** +```dart +class MyReactionIconResolver extends DefaultReactionIconResolver { + const MyReactionIconResolver(); + static const _defaults = {'like', 'haha', 'love', 'wow', 'sad'}; + + @override + Set get defaultReactions => _defaults; +} +// StreamChatConfigurationData(reactionIconResolver: const MyReactionIconResolver(), ...) +``` + +> **Important:** If you add a type not in `streamSupportedEmojis`, override `emojiCode` to return the Unicode emoji for it (see next section). + +### Migration (custom types not in streamSupportedEmojis) + +Override `defaultReactions` (and/or `supportedReactions`) and `emojiCode` so every type has an emoji. + +**After:** +```dart +class MyReactionIconResolver extends DefaultReactionIconResolver { + const MyReactionIconResolver(); + static const _defaults = {'like', 'love', 'custom_celebration'}; + static const _supported = {'like', 'love', 'custom_celebration'}; + static const _customEmojis = {'custom_celebration': '🎉'}; + + @override + Set get defaultReactions => _defaults; + + @override + Set get supportedReactions => _supported; + + @override + String? emojiCode(String type) => _customEmojis[type] ?? streamSupportedEmojis[type]?.emoji; +} +``` + +### Migration (custom rendering, e.g. Twemoji) + +For type-based custom rendering (e.g. Twemoji assets keyed by reaction type), +override `resolve(context, type)` and branch by `type`. + +**After:** +```dart +class MyReactionIconResolver extends DefaultReactionIconResolver { + const MyReactionIconResolver(); + + @override + Widget resolve(BuildContext context, String type) { + switch (type) { + case 'love': + return MyTwemojiWidget(assetName: 'heart'); + case 'haha': + return MyTwemojiWidget(assetName: 'joy'); + default: + return super.resolve(context, type); + } + } +} +``` + +--- + +## StreamMessageReactionPicker (formerly StreamReactionPicker) + +### Breaking Changes: + +- **Renamed** from `StreamReactionPicker` to `StreamMessageReactionPicker` +- `StreamReactionPicker` now refers to the domain-agnostic core component from `stream_core_flutter` +- Picker icons are no longer configured with per-widget icon models +- Quick-pick entries now come from `config.reactionIconResolver.defaultReactions` +- Visual properties (`backgroundColor`, `padding`, `shape`) removed from the widget — use `StreamReactionPickerTheme` instead +- The core picker now uses a `StreamComponentFactory` pattern with `StreamReactionPickerProps` for full customization + +### Migration + +**Before:** +```dart +StreamReactionPicker( + message: message, +) +``` + +**After:** +```dart +StreamMessageReactionPicker( + message: message, + onReactionPicked: onReactionPicked, +) +``` + +Configure reactions globally via `reactionIconResolver`: + +```dart +StreamChat( + client: client, + streamChatConfigData: StreamChatConfigurationData( + reactionIconResolver: const MyReactionIconResolver(), + ), + child: MyApp(), +) +``` + +Customize visual appearance via theme: + +```dart +StreamReactionPickerTheme( + data: StreamReactionPickerThemeData( + backgroundColor: Colors.white, + elevation: 4, + spacing: 2, + shape: RoundedSuperellipseBorder( + borderRadius: BorderRadius.all(Radius.circular(24)), + ), + side: BorderSide(color: Colors.grey), + ), + child: // ... +) +``` + +--- + +## StreamReactionIndicator + +### Breaking Changes: + +- Indicator icons are resolved only through `config.reactionIconResolver.resolve(context, type)` +- Old icon-list based customization paths were removed + +### Migration + +**Before:** +```dart +StreamChat( + client: client, + streamChatConfigData: StreamChatConfigurationData( + reactionIcons: [ /* old icon list */ ], + ), + child: MyApp(), +) +``` + +**After:** +```dart +StreamChat( + client: client, + streamChatConfigData: StreamChatConfigurationData( + reactionIconResolver: const MyReactionIconResolver(), + ), + child: MyApp(), +) +``` + +Then keep indicator usage unchanged: + +```dart +StreamReactionIndicator( + message: message, + onTap: onTap, +) +``` + +Customize via `reactionIconResolver` in config. + +--- + +## New Components + +### ReactionDetailSheet + +Bottom sheet: reaction counts, filter chips per type, list of users. Returns `Future` (e.g. `SelectReaction`). + +```dart +final action = await ReactionDetailSheet.show( + context: context, + message: message, + initialReactionType: selectedType, // optional +); +if (action is SelectReaction) handleSelectReaction(action); +``` + +### ReactionIconResolver / DefaultReactionIconResolver + +Exported for `StreamChatConfigurationData`. See [ReactionIconResolver and DefaultReactionIconResolver](#reactioniconresolver-and-defaultreactioniconresolver). + +--- + +## Migration Checklist + +- [ ] Rename `StreamReactionPicker` → `StreamMessageReactionPicker` in your code +- [ ] Remove `reactionIcons` from `StreamChatConfigurationData` +- [ ] Remove `backgroundColor`, `padding`, `shape` props from picker usage — use `StreamReactionPickerTheme` instead +- [ ] Custom quick-pick: extend `DefaultReactionIconResolver`, override `defaultReactions` with types from `streamSupportedEmojis` (so `emojiCode` returns emoji); set `reactionIconResolver` +- [ ] Custom types not in `streamSupportedEmojis`: also override `emojiCode` to return Unicode emoji for each; optionally `supportedReactions` +- [ ] Custom rendering (e.g. Twemoji): extend `DefaultReactionIconResolver`, override `resolve(context, type)` and branch by type, set `reactionIconResolver` +- [ ] Remove old icon-list based customization and configure reactions via `reactionIconResolver` only +- [ ] Optionally use `ReactionDetailSheet.show()` diff --git a/migrations/redesign/stream_avatar.md b/migrations/redesign/stream_avatar.md new file mode 100644 index 0000000000..7205691ad5 --- /dev/null +++ b/migrations/redesign/stream_avatar.md @@ -0,0 +1,225 @@ +# Stream Avatar Components Migration Guide + +This guide covers the migration for the redesigned avatar components in Stream Chat Flutter SDK. + +--- + +## Table of Contents + +- [Quick Reference](#quick-reference) +- [StreamUserAvatar](#streamuseravatar) +- [StreamChannelAvatar](#streamchannelavatar) +- [StreamGroupAvatar](#streamgroupavatar) +- [StreamUserAvatarStack](#streamuseravatarstack) +- [Size Reference](#size-reference) +- [Migration Checklist](#migration-checklist) + +--- + +## Quick Reference + +| Component | Key Changes | +|-----------|-------------| +| [**StreamUserAvatar**](#streamuseravatar) | `constraints` → `size` enum, `showOnlineStatus` → `showOnlineIndicator`, `onTap` removed | +| [**StreamChannelAvatar**](#streamchannelavatar) | `constraints` → `size` enum, `onTap` and builder callbacks removed | +| [**StreamGroupAvatar**](#streamgroupavatar) | Renamed to `StreamUserAvatarGroup`, `members` → `users` | +| [**StreamUserAvatarStack**](#streamuseravatarstack) | New component for overlapping avatars | + +--- + +## StreamUserAvatar + +### Breaking Changes: + +- `constraints` parameter replaced with `size` enum (`StreamAvatarSize`) +- `showOnlineStatus` renamed to `showOnlineIndicator` +- `onTap` callback removed — wrap with `GestureDetector` or `InkWell` instead +- `borderRadius` parameter removed +- `selected`, `selectionColor`, `selectionThickness` parameters removed +- `onlineIndicatorAlignment` and `onlineIndicatorConstraints` removed + +### Migration: + +**Before:** +```dart +StreamUserAvatar( + user: user, + constraints: BoxConstraints.tight(const Size(40, 40)), + borderRadius: BorderRadius.circular(20), + showOnlineStatus: false, + onTap: (user) => print('Tapped ${user.name}'), +) +``` + +**After:** +```dart +GestureDetector( + onTap: () => print('Tapped ${user.name}'), + child: StreamUserAvatar( + size: StreamAvatarSize.lg, + user: user, + showOnlineIndicator: false, + ), +) +``` + +> **Important:** +> - Use `GestureDetector` or `InkWell` to handle tap events +> - Use `StreamAvatarSize` enum values (`.xs`, `.sm`, `.md`, `.lg`, `.xl`) instead of `BoxConstraints` +> - See [Size Reference](#size-reference) for mapping old constraints to new enum values + +--- + +## StreamChannelAvatar + +### Breaking Changes: + +- `constraints` parameter replaced with `size` enum (`StreamAvatarGroupSize`) +- `onTap` callback removed — wrap with `GestureDetector` or `InkWell` instead +- `borderRadius` parameter removed +- `selected`, `selectionColor`, `selectionThickness` parameters removed +- `ownSpaceAvatarBuilder`, `oneToOneAvatarBuilder`, `groupAvatarBuilder` callbacks removed + +### Migration: + +**Before:** +```dart +StreamChannelAvatar( + channel: channel, + constraints: BoxConstraints.tight(const Size(40, 40)), + onTap: () => print('Tapped channel'), + selected: isSelected, +) +``` + +**After:** +```dart +GestureDetector( + onTap: () => print('Tapped channel'), + child: StreamChannelAvatar( + size: StreamAvatarGroupSize.lg, + channel: channel, + ), +) +``` + +> **Important:** +> - Use `StreamAvatarGroupSize` enum values (`.lg`, `.xl`) instead of `BoxConstraints` +> - Custom avatar builders are no longer supported + +--- + +## StreamGroupAvatar + +### Breaking Changes: + +- Renamed from `StreamGroupAvatar` to `StreamUserAvatarGroup` +- `members` parameter replaced with `users` (`Iterable` instead of `List`) +- `constraints` parameter replaced with `size` enum (`StreamAvatarGroupSize`) +- `channel` parameter removed +- `onTap` callback removed — wrap with `GestureDetector` or `InkWell` instead +- `borderRadius` parameter removed +- `selected`, `selectionColor`, `selectionThickness` parameters removed + +### Migration: + +**Before:** +```dart +StreamGroupAvatar( + channel: channel, + members: otherMembers, + constraints: BoxConstraints.tight(const Size(40, 40)), + onTap: () => print('Tapped group'), +) +``` + +**After:** +```dart +GestureDetector( + onTap: () => print('Tapped group'), + child: StreamUserAvatarGroup( + size: StreamAvatarGroupSize.lg, + users: otherMembers.map((m) => m.user!), + ), +) +``` + +> **Important:** +> - Extract `User` objects from `Member` when migrating: `members.map((m) => m.user!)` +> - The component no longer requires a `channel` reference + +--- + +## StreamUserAvatarStack + +### Breaking Changes: + +- **New component** for displaying overlapping user avatars (e.g., thread participants) +- Replaces custom `Stack` + `Positioned` implementations + +### Parameters: + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `users` | `Iterable` | required | Users to display | +| `size` | `StreamAvatarStackSize?` | `.sm` | Size of avatars | +| `max` | `int` | `5` | Max avatars before overflow badge | +| `overlap` | `double` | `0.33` | Overlap fraction (0.0 - 1.0) | + +### Usage: + +```dart +StreamUserAvatarStack( + max: 3, + size: StreamAvatarStackSize.xs, + users: threadParticipants, +) +``` + +> **Important:** +> - Use this component instead of manually building overlapping avatar stacks +> - The `overlap` parameter controls how much each avatar overlaps the previous one + +--- + +## Size Reference + +### StreamAvatarSize + +| Old Constraints | New Size | Diameter | +|-----------------|----------|----------| +| `BoxConstraints.tight(Size(20, 20))` | `.xs` | 20px | +| `BoxConstraints.tight(Size(24, 24))` | `.sm` | 24px | +| `BoxConstraints.tight(Size(32, 32))` | `.md` | 32px | +| `BoxConstraints.tight(Size(40, 40))` | `.lg` | 40px | +| `BoxConstraints.tight(Size(64, 64))` | `.xl` | 64px | + +### StreamAvatarGroupSize + +| Old Constraints | New Size | Diameter | +|-----------------|----------|----------| +| `BoxConstraints.tight(Size(40, 40))` | `.lg` | 40px | +| `BoxConstraints.tight(Size(64, 64))` | `.xl` | 64px | + +### StreamAvatarStackSize + +| Old Constraints | New Size | Diameter | +|-----------------|----------|----------| +| `BoxConstraints.tight(Size(20, 20))` | `.xs` | 20px | +| `BoxConstraints.tight(Size(24, 24))` | `.sm` | 24px | + +> **Note:** +> If your old constraints don't match exactly, choose the closest available size. + +--- + +## Migration Checklist + +- [ ] Replace `StreamUserAvatar` `constraints` with `size` enum (`StreamAvatarSize`) +- [ ] Rename `showOnlineStatus` to `showOnlineIndicator` +- [ ] Move `onTap` callbacks to parent `GestureDetector` or `InkWell` widgets +- [ ] Replace `StreamGroupAvatar` with `StreamUserAvatarGroup` +- [ ] Change `members` parameter to `users` (extract `User` from `Member`) +- [ ] Replace `StreamChannelAvatar` `constraints` with `size` enum (`StreamAvatarGroupSize`) +- [ ] Remove `selected`, `selectionColor`, `selectionThickness` parameters +- [ ] Use `StreamUserAvatarStack` for overlapping avatar displays diff --git a/packages/stream_chat/example/lib/main.dart b/packages/stream_chat/example/lib/main.dart index 0744726424..f85f4d99b3 100644 --- a/packages/stream_chat/example/lib/main.dart +++ b/packages/stream_chat/example/lib/main.dart @@ -17,8 +17,7 @@ Future main() async { User( id: 'cool-shadow-7', name: 'Cool Shadow', - image: - 'https://getstream.io/random_png/?id=cool-shadow-7&name=Cool+shadow', + image: 'https://getstream.io/random_png/?id=cool-shadow-7&name=Cool+shadow', ), '''eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiY29vbC1zaGFkb3ctNyJ9.gkOlCRb1qgy4joHPaxFwPOdXcGvSPvp6QY0S4mpRkVo''', ); @@ -60,9 +59,9 @@ class StreamExample extends StatelessWidget { @override Widget build(BuildContext context) => MaterialApp( - title: 'Stream Chat Dart Example', - home: HomeScreen(channel: channel), - ); + title: 'Stream Chat Dart Example', + home: HomeScreen(channel: channel), + ); } /// Main screen of our application. The layout is comprised of an [AppBar] @@ -87,30 +86,31 @@ class HomeScreen extends StatelessWidget { body: SafeArea( child: StreamBuilder?>( stream: messages, - builder: ( - BuildContext context, - AsyncSnapshot?> snapshot, - ) { - if (snapshot.hasData && snapshot.data != null) { - return MessageView( - messages: snapshot.data!.reversed.toList(), - channel: channel, - ); - } else if (snapshot.hasError) { - return const Center( - child: Text( - 'There was an error loading messages. Please see logs.', - ), - ); - } - return const Center( - child: SizedBox( - width: 100, - height: 100, - child: CircularProgressIndicator(), - ), - ); - }, + builder: + ( + BuildContext context, + AsyncSnapshot?> snapshot, + ) { + if (snapshot.hasData && snapshot.data != null) { + return MessageView( + messages: snapshot.data!.reversed.toList(), + channel: channel, + ); + } else if (snapshot.hasError) { + return const Center( + child: Text( + 'There was an error loading messages. Please see logs.', + ), + ); + } + return const Center( + child: SizedBox( + width: 100, + height: 100, + child: CircularProgressIndicator(), + ), + ); + }, ), ), ); @@ -168,80 +168,80 @@ class _MessageViewState extends State { @override Widget build(BuildContext context) => Column( - children: [ - Expanded( - child: ListView.builder( - controller: _scrollController, - itemCount: _messages.length, - reverse: true, - itemBuilder: (BuildContext context, int index) { - final item = _messages[index]; - if (item.user?.id == widget.channel.client.uid) { - return Align( - alignment: Alignment.centerRight, - child: Padding( - padding: const EdgeInsets.all(8), - child: Text(item.text ?? ''), - ), - ); - } else { - return Align( - alignment: Alignment.centerLeft, - child: Padding( - padding: const EdgeInsets.all(8), - child: Text(item.text ?? ''), - ), - ); - } - }, - ), - ), - Padding( - padding: const EdgeInsets.all(8), - child: Row( - children: [ - Expanded( - child: TextField( - controller: _controller, - decoration: const InputDecoration( - hintText: 'Enter your message', - ), - ), + children: [ + Expanded( + child: ListView.builder( + controller: _scrollController, + itemCount: _messages.length, + reverse: true, + itemBuilder: (BuildContext context, int index) { + final item = _messages[index]; + if (item.user?.id == widget.channel.client.uid) { + return Align( + alignment: Alignment.centerRight, + child: Padding( + padding: const EdgeInsets.all(8), + child: Text(item.text ?? ''), + ), + ); + } else { + return Align( + alignment: Alignment.centerLeft, + child: Padding( + padding: const EdgeInsets.all(8), + child: Text(item.text ?? ''), + ), + ); + } + }, + ), + ), + Padding( + padding: const EdgeInsets.all(8), + child: Row( + children: [ + Expanded( + child: TextField( + controller: _controller, + decoration: const InputDecoration( + hintText: 'Enter your message', ), - Material( - type: MaterialType.circle, - color: Colors.blue, - clipBehavior: Clip.hardEdge, - child: InkWell( - onTap: () async { - // We can send a new message by calling `sendMessage` on - // the current channel. After sending a message, the - // TextField is cleared and the list view is scrolled - // to show the new item. - if (_controller.value.text.isNotEmpty) { - await widget.channel.sendMessage( - Message(text: _controller.value.text), - ); - _controller.clear(); - _updateList(); - } - }, - child: const Padding( - padding: EdgeInsets.all(8), - child: Center( - child: Icon( - Icons.send, - color: Colors.white, - ), - ), + ), + ), + Material( + type: MaterialType.circle, + color: Colors.blue, + clipBehavior: Clip.hardEdge, + child: InkWell( + onTap: () async { + // We can send a new message by calling `sendMessage` on + // the current channel. After sending a message, the + // TextField is cleared and the list view is scrolled + // to show the new item. + if (_controller.value.text.isNotEmpty) { + await widget.channel.sendMessage( + Message(text: _controller.value.text), + ); + _controller.clear(); + _updateList(); + } + }, + child: const Padding( + padding: EdgeInsets.all(8), + child: Center( + child: Icon( + Icons.send, + color: Colors.white, ), ), ), - ], + ), ), - ), - ], - ); + ], + ), + ), + ], + ); } /// Helper extension for quickly retrieving diff --git a/packages/stream_chat/example/pubspec.yaml b/packages/stream_chat/example/pubspec.yaml index 421f34dce5..f4690ca13f 100644 --- a/packages/stream_chat/example/pubspec.yaml +++ b/packages/stream_chat/example/pubspec.yaml @@ -17,8 +17,8 @@ version: 1.0.0+1 # 2. Add it to the melos.yaml file for future updates. environment: - sdk: ^3.6.2 - flutter: ">=3.27.4" + sdk: ^3.10.0 + flutter: ">=3.38.1" dependencies: cupertino_icons: ^1.0.3 diff --git a/packages/stream_chat/lib/src/client/channel.dart b/packages/stream_chat/lib/src/client/channel.dart index 1619d0d468..f63116877c 100644 --- a/packages/stream_chat/lib/src/client/channel.dart +++ b/packages/stream_chat/lib/src/client/channel.dart @@ -75,25 +75,25 @@ class Channel { String? name, String? image, Map? extraData, - }) : _cid = _id != null ? '$_type:$_id' : null, - _extraData = { - ...?extraData, - if (name != null) 'name': name, - if (image != null) 'image': image, - } { + }) : _cid = _id != null ? '$_type:$_id' : null, + _extraData = { + ...?extraData, + if (name != null) 'name': name, + if (image != null) 'image': image, + } { _client.logger.info('New Channel instance created, not yet initialized'); } /// Create a channel client instance from a [ChannelState] object. Channel.fromState(this._client, ChannelState channelState) - : assert( - channelState.channel != null, - 'No channel found inside channel state', - ), - _id = channelState.channel!.id, - _type = channelState.channel!.type, - _cid = channelState.channel!.cid, - _extraData = channelState.channel!.extraData { + : assert( + channelState.channel != null, + 'No channel found inside channel state', + ), + _id = channelState.channel!.id, + _type = channelState.channel!.type, + _cid = channelState.channel!.cid, + _extraData = channelState.channel!.extraData { _initState(channelState); // Initialize the state immediately. } @@ -144,16 +144,11 @@ class Channel { } /// Returns true if the channel is muted. - bool get isMuted => - _client.state.currentUser?.channelMutes - .any((element) => element.channel.cid == cid) == - true; + bool get isMuted => _client.state.currentUser?.channelMutes.any((element) => element.channel.cid == cid) == true; /// Returns true if the channel is muted, as a stream. Stream get isMutedStream => _client.state.currentUserStream - .map((event) => - event?.channelMutes.any((element) => element.channel.cid == cid) == - true) + .map((event) => event?.channelMutes.any((element) => element.channel.cid == cid) == true) .distinct(); /// True if the channel is a group. @@ -456,15 +451,12 @@ class Channel { } /// List of user permissions on this channel - List get ownCapabilities => - state?._channelState.channel?.ownCapabilities ?? []; + List get ownCapabilities => state?._channelState.channel?.ownCapabilities ?? []; /// List of user permissions on this channel Stream> get ownCapabilitiesStream { _checkInitialized(); - return state!.channelStateStream - .map((cs) => cs.channel?.ownCapabilities ?? []) - .distinct(); + return state!.channelStateStream.map((cs) => cs.channel?.ownCapabilities ?? []).distinct(); } /// Channel extra data as a stream. @@ -593,80 +585,85 @@ class Channel { } } - return Future.wait(attachments.map((it) { - client.logger.info('Uploading ${it.id} attachment...'); - - final throttledUpdateAttachment = updateAttachment.throttled( - const Duration(milliseconds: 500), - ); - - void onSendProgress(int sent, int total) { - throttledUpdateAttachment([ - it.copyWith( - uploadState: UploadState.inProgress(uploaded: sent, total: total), - ), - ]); - } + return Future.wait( + attachments.map((it) { + client.logger.info('Uploading ${it.id} attachment...'); - final isImage = it.type == AttachmentType.image; - final cancelToken = CancelToken(); - Future future; - if (isImage) { - future = sendImage( - it.file!, - onSendProgress: onSendProgress, - cancelToken: cancelToken, - extraData: it.extraData, + final throttledUpdateAttachment = updateAttachment.throttled( + const Duration(milliseconds: 500), ); - } else { - future = sendFile( - it.file!, - onSendProgress: onSendProgress, - cancelToken: cancelToken, - extraData: it.extraData, - ); - } - _cancelableAttachmentUploadRequest[it.id] = cancelToken; - return future.then((response) { - client.logger.info('Attachment ${it.id} uploaded successfully...'); - - // If the response is SendFileResponse, then we might also be getting - // thumbUrl in case of video. So we need to update the attachment with - // both the assetUrl and thumbUrl. - if (response is SendFileResponse) { - updateAttachment( + + void onSendProgress(int sent, int total) { + throttledUpdateAttachment([ it.copyWith( - assetUrl: response.file, - thumbUrl: response.thumbUrl, - uploadState: const UploadState.success(), + uploadState: UploadState.inProgress(uploaded: sent, total: total), ), + ]); + } + + final isImage = it.type == AttachmentType.image; + final cancelToken = CancelToken(); + Future future; + if (isImage) { + future = sendImage( + it.file!, + onSendProgress: onSendProgress, + cancelToken: cancelToken, + extraData: it.extraData, ); } else { - updateAttachment( - it.copyWith( - imageUrl: response.file, - uploadState: const UploadState.success(), - ), + future = sendFile( + it.file!, + onSendProgress: onSendProgress, + cancelToken: cancelToken, + extraData: it.extraData, ); } - }).catchError((e, stk) { - if (e is StreamChatNetworkError && e.isRequestCancelledError) { - client.logger.info('Attachment ${it.id} upload cancelled'); - - // remove attachment from message if cancelled. - updateAttachment(it, remove: true); - return; - } + _cancelableAttachmentUploadRequest[it.id] = cancelToken; + return future + .then((response) { + client.logger.info('Attachment ${it.id} uploaded successfully...'); + + // If the response is SendFileResponse, then we might also be getting + // thumbUrl in case of video. So we need to update the attachment with + // both the assetUrl and thumbUrl. + if (response is SendFileResponse) { + updateAttachment( + it.copyWith( + assetUrl: response.file, + thumbUrl: response.thumbUrl, + uploadState: const UploadState.success(), + ), + ); + } else { + updateAttachment( + it.copyWith( + imageUrl: response.file, + uploadState: const UploadState.success(), + ), + ); + } + }) + .catchError((e, stk) { + if (e is StreamChatNetworkError && e.isRequestCancelledError) { + client.logger.info('Attachment ${it.id} upload cancelled'); + + // remove attachment from message if cancelled. + updateAttachment(it, remove: true); + return; + } - client.logger.severe('error uploading the attachment', e, stk); - updateAttachment( - it.copyWith(uploadState: UploadState.failed(error: e.toString())), - ); - }).whenComplete(() { - throttledUpdateAttachment.cancel(); - _cancelableAttachmentUploadRequest.remove(it.id); - }); - })).whenComplete(() { + client.logger.severe('error uploading the attachment', e, stk); + updateAttachment( + it.copyWith(uploadState: UploadState.failed(error: e.toString())), + ); + }) + .whenComplete(() { + throttledUpdateAttachment.cancel(); + _cancelableAttachmentUploadRequest.remove(it.id); + }); + }), + ).whenComplete(() { if (message!.attachments.every((it) => it.uploadState.isSuccess)) { _messageAttachmentsUploadCompleter.remove(messageId)?.complete(message); } @@ -693,9 +690,7 @@ class Channel { // Cancelling previous completer in case it's called again in the process // Eg. Updating the message while the previous call is in progress. - _messageAttachmentsUploadCompleter - .remove(message.id) - ?.completeError(const StreamChatError('Message cancelled')); + _messageAttachmentsUploadCompleter.remove(message.id)?.completeError(const StreamChatError('Message cancelled')); final quotedMessage = state!.messages.firstWhereOrNull( (m) => m.id == message.quotedMessageId, @@ -719,8 +714,7 @@ class Channel { try { if (message.attachments.any((it) => !it.uploadState.isSuccess)) { final attachmentsUploadCompleter = Completer(); - _messageAttachmentsUploadCompleter[message.id] = - attachmentsUploadCompleter; + _messageAttachmentsUploadCompleter[message.id] = attachmentsUploadCompleter; _uploadAttachments( message.id, @@ -752,7 +746,9 @@ class Channel { ), ); - final sentMessage = response.message.syncWith(message).copyWith( + final sentMessage = response.message + .syncWith(message) + .copyWith( // Update the message state to sent. state: MessageState.sent, ); @@ -794,9 +790,7 @@ class Channel { // Cancelling previous completer in case it's called again in the process // Eg. Updating the message while the previous call is in progress. - _messageAttachmentsUploadCompleter - .remove(message.id) - ?.completeError(const StreamChatError('Message cancelled')); + _messageAttachmentsUploadCompleter.remove(message.id)?.completeError(const StreamChatError('Message cancelled')); // ignore: parameter_assignments message = message.copyWith( @@ -815,8 +809,7 @@ class Channel { try { if (message.attachments.any((it) => !it.uploadState.isSuccess)) { final attachmentsUploadCompleter = Completer(); - _messageAttachmentsUploadCompleter[message.id] = - attachmentsUploadCompleter; + _messageAttachmentsUploadCompleter[message.id] = attachmentsUploadCompleter; _uploadAttachments( message.id, @@ -837,7 +830,9 @@ class Channel { ), ); - final updateMessage = response.message.syncWith(message).copyWith( + final updateMessage = response.message + .syncWith(message) + .copyWith( // Update the message state to updated. state: MessageState.updated, ownReactions: message.ownReactions, @@ -880,9 +875,7 @@ class Channel { // Cancelling previous completer in case it's called again in the process // Eg. Updating the message while the previous call is in progress. - _messageAttachmentsUploadCompleter - .remove(message.id) - ?.completeError(const StreamChatError('Message cancelled')); + _messageAttachmentsUploadCompleter.remove(message.id)?.completeError(const StreamChatError('Message cancelled')); // ignore: parameter_assignments message = message.copyWith( @@ -904,7 +897,9 @@ class Channel { ), ); - final updatedMessage = response.message.syncWith(message).copyWith( + final updatedMessage = response.message + .syncWith(message) + .copyWith( // Update the message state to updated. state: MessageState.updated, ownReactions: message.ownReactions, @@ -1128,9 +1123,7 @@ class Channel { Object? /*num|DateTime*/ timeoutOrExpirationDate, }) { assert(() { - if (timeoutOrExpirationDate is! DateTime && - timeoutOrExpirationDate != null && - timeoutOrExpirationDate is! num) { + if (timeoutOrExpirationDate is! DateTime && timeoutOrExpirationDate != null && timeoutOrExpirationDate is! num) { throw ArgumentError('Invalid timeout or Expiration date'); } return true; @@ -1154,13 +1147,12 @@ class Channel { } /// Unpins provided message. - Future unpinMessage(Message message) => - partialUpdateMessage( - message, - set: { - 'pinned': false, - }, - ); + Future unpinMessage(Message message) => partialUpdateMessage( + message, + set: { + 'pinned': false, + }, + ); /// Creates or updates a new [draft] for this channel. Future createDraft( @@ -1605,8 +1597,7 @@ class Channel { /// ```dart /// channel.updateName('Updated channel name'); /// ``` - Future updateName(String name) => - updatePartial(set: {'name': name}); + Future updateName(String name) => updatePartial(set: {'name': name}); /// Update the channel's [image]. /// @@ -1623,8 +1614,7 @@ class Channel { /// ```dart /// channel.updateImage('https://getstream.io/new-image'); /// ``` - Future updateImage(String image) => - updatePartial(set: {'image': image}); + Future updateImage(String image) => updatePartial(set: {'image': image}); /// Update the channel custom data. This replaces all of the channel data /// with the given [channelData]. @@ -1929,11 +1919,10 @@ class Channel { Future getReactions( String messageId, { PaginationParams? pagination, - }) => - _client.getReactions( - messageId, - pagination: pagination, - ); + }) => _client.getReactions( + messageId, + pagination: pagination, + ); /// Retrieves a list of messages by given [messageIDs]. Future getMessagesById( @@ -1950,11 +1939,10 @@ class Channel { Future translateMessage( String messageId, String language, - ) => - _client.translateMessage( - messageId, - language, - ); + ) => _client.translateMessage( + messageId, + language, + ); /// Creates a new channel. Future create() => query(state: false); @@ -2061,15 +2049,14 @@ class Channel { Filter? filter, SortOrder? sort, PaginationParams? pagination, - }) => - _client.queryMembers( - type, - channelId: id, - filter: filter, - members: state?.members, - sort: sort, - pagination: pagination, - ); + }) => _client.queryMembers( + type, + channelId: id, + filter: filter, + members: state?.members, + sort: sort, + pagination: pagination, + ); /// Query channel banned users. Future queryBannedUsers({ @@ -2237,15 +2224,14 @@ class Channel { String? eventType2, String? eventType3, String? eventType4, - ]) => - _client - .on( - eventType, - eventType2, - eventType3, - eventType4, - ) - .where((e) => e.cid == cid); + ]) => _client + .on( + eventType, + eventType2, + eventType3, + eventType4, + ) + .where((e) => e.cid == cid); late final _keyStrokeHandler = KeyStrokeHandler( onStartTyping: startTyping, @@ -2277,10 +2263,12 @@ class Channel { if (!_canSendTypingEvents) return; client.logger.info('start typing'); - await sendEvent(Event( - type: EventType.typingStart, - parentId: parentId, - )); + await sendEvent( + Event( + type: EventType.typingStart, + parentId: parentId, + ), + ); } /// Sends the [EventType.typingStop] event. @@ -2288,10 +2276,12 @@ class Channel { if (!_canSendTypingEvents) return; client.logger.info('stop typing'); - await sendEvent(Event( - type: EventType.typingStop, - parentId: parentId, - )); + await sendEvent( + Event( + type: EventType.typingStop, + parentId: parentId, + ), + ); } /// Call this method to dispose the channel client. @@ -2411,10 +2401,13 @@ class ChannelClientState { _listenChannelPushPreferenceUpdated(); final persistenceClient = _client.chatPersistenceClient; - persistenceClient?.getChannelThreads(_channel.cid!).then((threads) { - // Load all the threads for the channel from the offline storage. - if (threads.isNotEmpty) _threads = threads; - }).then((_) => retryFailedMessages()); + persistenceClient + ?.getChannelThreads(_channel.cid!) + .then((threads) { + // Load all the threads for the channel from the offline storage. + if (threads.isNotEmpty) _threads = threads; + }) + .then((_) => retryFailedMessages()); } final Channel _channel; @@ -2423,35 +2416,34 @@ class ChannelClientState { void _checkExpiredAttachmentMessages(ChannelState channelState) async { final expiredAttachmentMessagesId = channelState.messages - ?.where((m) => - !_updatedMessagesIds.contains(m.id) && - m.attachments.isNotEmpty && - m.attachments.any((e) { - final url = e.imageUrl ?? e.assetUrl; - if (url == null || !url.contains('')) { - return false; - } - try { - final uri = Uri.parse(url); - if (!uri.host.endsWith('stream-io-cdn.com') || - uri.queryParameters['Expires'] == null) { + ?.where( + (m) => + !_updatedMessagesIds.contains(m.id) && + m.attachments.isNotEmpty && + m.attachments.any((e) { + final url = e.imageUrl ?? e.assetUrl; + if (url == null || !url.contains('')) { return false; } - final secondsFromEpoch = - int.parse(uri.queryParameters['Expires']!); - final expiration = DateTime.fromMillisecondsSinceEpoch( - secondsFromEpoch * 1000, - ); - return expiration.isBefore(DateTime.now()); - } catch (_) { - return false; - } - })) + try { + final uri = Uri.parse(url); + if (!uri.host.endsWith('stream-io-cdn.com') || uri.queryParameters['Expires'] == null) { + return false; + } + final secondsFromEpoch = int.parse(uri.queryParameters['Expires']!); + final expiration = DateTime.fromMillisecondsSinceEpoch( + secondsFromEpoch * 1000, + ); + return expiration.isBefore(DateTime.now()); + } catch (_) { + return false; + } + }), + ) .map((e) => e.id) .toList(); - if (expiredAttachmentMessagesId != null && - expiredAttachmentMessagesId.isNotEmpty) { + if (expiredAttachmentMessagesId != null && expiredAttachmentMessagesId.isNotEmpty) { await _channel.initialized; _updatedMessagesIds.addAll(expiredAttachmentMessagesId); _channel.getMessagesById(expiredAttachmentMessagesId); @@ -2459,141 +2451,156 @@ class ChannelClientState { } void _listenMemberAdded() { - _subscriptions.add(_channel.on(EventType.memberAdded).listen((Event e) { - final member = e.member!; - final existingMembers = channelState.members ?? []; + _subscriptions.add( + _channel.on(EventType.memberAdded).listen((Event e) { + final member = e.member!; + final existingMembers = channelState.members ?? []; - updateChannelState( - channelState.copyWith( - members: [...existingMembers, member], - ), - ); - })); + updateChannelState( + channelState.copyWith( + members: [...existingMembers, member], + ), + ); + }), + ); } void _listenMemberRemoved() { - _subscriptions.add(_channel.on(EventType.memberRemoved).listen((Event e) { - final user = e.user!; - final existingRead = channelState.read ?? []; - final existingMembers = channelState.members ?? []; - - updateChannelState( - channelState.copyWith( - read: [...existingRead.where((r) => r.user.id != user.id)], - members: [...existingMembers.where((m) => m.userId != user.id)], - ), - ); - })); + _subscriptions.add( + _channel.on(EventType.memberRemoved).listen((Event e) { + final user = e.user!; + final existingRead = channelState.read ?? []; + final existingMembers = channelState.members ?? []; + + updateChannelState( + channelState.copyWith( + read: [...existingRead.where((r) => r.user.id != user.id)], + members: [...existingMembers.where((m) => m.userId != user.id)], + ), + ); + }), + ); } void _listenMemberUpdated() { _subscriptions // Listen to events containing member users - ..add(_channel.on().listen( - (event) { - final user = event.user; - if (user == null) return; + ..add( + _channel.on().listen( + (event) { + final user = event.user; + if (user == null) return; - final existingMembers = [...?channelState.members]; - final existingMembership = channelState.membership; + final existingMembers = [...?channelState.members]; + final existingMembership = channelState.membership; - // Return if the user is not a existing member of the channel. - if (!existingMembers.any((m) => m.userId == user.id)) return; + // Return if the user is not a existing member of the channel. + if (!existingMembers.any((m) => m.userId == user.id)) return; - Member? maybeUpdateMemberUser(Member? existingMember) { - if (existingMember == null) return null; - if (existingMember.userId == user.id) { - return existingMember.copyWith(user: user); + Member? maybeUpdateMemberUser(Member? existingMember) { + if (existingMember == null) return null; + if (existingMember.userId == user.id) { + return existingMember.copyWith(user: user); + } + return existingMember; } - return existingMember; - } - - updateChannelState( - channelState.copyWith( - membership: maybeUpdateMemberUser(existingMembership), - members: [...existingMembers.map(maybeUpdateMemberUser).nonNulls], - ), - ); - }, - )) + updateChannelState( + channelState.copyWith( + membership: maybeUpdateMemberUser(existingMembership), + members: [...existingMembers.map(maybeUpdateMemberUser).nonNulls], + ), + ); + }, + ), + ) // Listen to member updated events. - ..add(_channel.on(EventType.memberUpdated).listen( - (Event e) { - final member = e.member!; - final existingMembers = channelState.members ?? []; - final existingMembership = channelState.membership; - - Member? maybeUpdateMember(Member? existingMember) { - if (existingMember == null) return null; - if (existingMember.userId == member.userId) return member; - return existingMember; - } + ..add( + _channel.on(EventType.memberUpdated).listen( + (Event e) { + final member = e.member!; + final existingMembers = channelState.members ?? []; + final existingMembership = channelState.membership; + + Member? maybeUpdateMember(Member? existingMember) { + if (existingMember == null) return null; + if (existingMember.userId == member.userId) return member; + return existingMember; + } - updateChannelState( - channelState.copyWith( - membership: maybeUpdateMember(existingMembership), - members: [...existingMembers.map(maybeUpdateMember).nonNulls], - ), - ); - }, - )); + updateChannelState( + channelState.copyWith( + membership: maybeUpdateMember(existingMembership), + members: [...existingMembers.map(maybeUpdateMember).nonNulls], + ), + ); + }, + ), + ); } void _listenChannelUpdated() { - _subscriptions.add(_channel.on(EventType.channelUpdated).listen((Event e) { - final channel = e.channel!; - updateChannelState(channelState.copyWith( - channel: channelState.channel?.merge(channel), - members: channel.members, - )); - })); + _subscriptions.add( + _channel.on(EventType.channelUpdated).listen((Event e) { + final channel = e.channel!; + updateChannelState( + channelState.copyWith( + channel: channelState.channel?.merge(channel), + members: channel.members, + ), + ); + }), + ); } void _listenChannelMessageCount() { - _subscriptions.add(_channel.on().listen( - (Event e) { - final messageCount = e.channelMessageCount; - if (messageCount == null) return; + _subscriptions.add( + _channel.on().listen( + (Event e) { + final messageCount = e.channelMessageCount; + if (messageCount == null) return; - updateChannelState( - channelState.copyWith( - channel: channelState.channel?.copyWith( - messageCount: messageCount, + updateChannelState( + channelState.copyWith( + channel: channelState.channel?.copyWith( + messageCount: messageCount, + ), ), - ), - ); - }, - )); + ); + }, + ), + ); } void _listenChannelTruncated() { - _subscriptions.add(_channel - .on(EventType.channelTruncated, EventType.notificationChannelTruncated) - .listen((event) async { - final channel = event.channel!; - await _client.chatPersistenceClient?.deleteMessageByCid(channel.cid); - truncate(); - if (event.message != null) { - updateMessage(event.message!); - } - })); + _subscriptions.add( + _channel.on(EventType.channelTruncated, EventType.notificationChannelTruncated).listen((event) async { + final channel = event.channel!; + await _client.chatPersistenceClient?.deleteMessageByCid(channel.cid); + truncate(); + if (event.message != null) { + updateMessage(event.message!); + } + }), + ); } void _listenMemberBanned() { - _subscriptions.add(_channel - .on(EventType.userBanned) - .where((it) => it.cid != null) // filters channel ban from app ban - .listen( - (event) async { - final user = event.user!; - final member = await _channel - .queryMembers(filter: Filter.equal('id', user.id)) - .then((it) => it.members.first); - - _updateMember(member); - }, - )); + _subscriptions.add( + _channel + .on(EventType.userBanned) + .where((it) => it.cid != null) // filters channel ban from app ban + .listen( + (event) async { + final user = event.user!; + final member = await _channel + .queryMembers(filter: Filter.equal('id', user.id)) + .then((it) => it.members.first); + + _updateMember(member); + }, + ), + ); } void _listenUserStartWatching() { @@ -2602,12 +2609,14 @@ class ChannelClientState { final watcher = event.user; if (watcher != null) { final existingWatchers = channelState.watchers; - updateChannelState(channelState.copyWith( - watchers: [ - watcher, - ...?existingWatchers?.where((user) => user.id != watcher.id), - ], - )); + updateChannelState( + channelState.copyWith( + watchers: [ + watcher, + ...?existingWatchers?.where((user) => user.id != watcher.id), + ], + ), + ); } }), ); @@ -2619,30 +2628,32 @@ class ChannelClientState { final watcher = event.user; if (watcher != null) { final existingWatchers = channelState.watchers; - updateChannelState(channelState.copyWith( - watchers: [ - ...?existingWatchers?.where((user) => user.id != watcher.id) - ], - )); + updateChannelState( + channelState.copyWith( + watchers: [...?existingWatchers?.where((user) => user.id != watcher.id)], + ), + ); } }), ); } void _listenMemberUnbanned() { - _subscriptions.add(_channel - .on(EventType.userUnbanned) - .where((it) => it.cid != null) // filters channel ban from app ban - .listen( - (event) async { - final user = event.user!; - final member = await _channel - .queryMembers(filter: Filter.equal('id', user.id)) - .then((it) => it.members.first); - - _updateMember(member); - }, - )); + _subscriptions.add( + _channel + .on(EventType.userUnbanned) + .where((it) => it.cid != null) // filters channel ban from app ban + .listen( + (event) async { + final user = event.user!; + final member = await _channel + .queryMembers(filter: Filter.equal('id', user.id)) + .then((it) => it.members.first); + + _updateMember(member); + }, + ), + ); } void _updateMember(Member member) { @@ -2710,184 +2721,194 @@ class ChannelClientState { } void _listenPollUpdated() { - _subscriptions.add(_channel.on(EventType.pollUpdated).listen((event) { - final eventPoll = event.poll; - if (eventPoll == null) return; + _subscriptions.add( + _channel.on(EventType.pollUpdated).listen((event) { + final eventPoll = event.poll; + if (eventPoll == null) return; - final pollMessage = _findPollMessage(eventPoll.id); - if (pollMessage == null) return; + final pollMessage = _findPollMessage(eventPoll.id); + if (pollMessage == null) return; - final oldPoll = pollMessage.poll; + final oldPoll = pollMessage.poll; - final latestAnswers = oldPoll?.latestAnswers ?? eventPoll.latestAnswers; - final ownVotesAndAnswers = - oldPoll?.ownVotesAndAnswers ?? eventPoll.ownVotesAndAnswers; + final latestAnswers = oldPoll?.latestAnswers ?? eventPoll.latestAnswers; + final ownVotesAndAnswers = oldPoll?.ownVotesAndAnswers ?? eventPoll.ownVotesAndAnswers; - final poll = eventPoll.copyWith( - latestAnswers: latestAnswers, - ownVotesAndAnswers: ownVotesAndAnswers, - ); + final poll = eventPoll.copyWith( + latestAnswers: latestAnswers, + ownVotesAndAnswers: ownVotesAndAnswers, + ); - final message = pollMessage.copyWith(poll: poll); - updateMessage(message); - })); + final message = pollMessage.copyWith(poll: poll); + updateMessage(message); + }), + ); } void _listenPollClosed() { - _subscriptions.add(_channel.on(EventType.pollClosed).listen((event) { - final eventPoll = event.poll; - if (eventPoll == null) return; + _subscriptions.add( + _channel.on(EventType.pollClosed).listen((event) { + final eventPoll = event.poll; + if (eventPoll == null) return; - final pollMessage = _findPollMessage(eventPoll.id); - if (pollMessage == null) return; + final pollMessage = _findPollMessage(eventPoll.id); + if (pollMessage == null) return; - final oldPoll = pollMessage.poll; - final poll = oldPoll?.copyWith(isClosed: true) ?? eventPoll; + final oldPoll = pollMessage.poll; + final poll = oldPoll?.copyWith(isClosed: true) ?? eventPoll; - final message = pollMessage.copyWith(poll: poll); - updateMessage(message); - })); + final message = pollMessage.copyWith(poll: poll); + updateMessage(message); + }), + ); } void _listenPollAnswerCasted() { - _subscriptions.add(_channel.on(EventType.pollAnswerCasted).listen((event) { - final (eventPoll, eventPollVote) = (event.poll, event.pollVote); - if (eventPoll == null || eventPollVote == null) return; - - final pollMessage = _findPollMessage(eventPoll.id); - if (pollMessage == null) return; + _subscriptions.add( + _channel.on(EventType.pollAnswerCasted).listen((event) { + final (eventPoll, eventPollVote) = (event.poll, event.pollVote); + if (eventPoll == null || eventPollVote == null) return; - final oldPoll = pollMessage.poll; + final pollMessage = _findPollMessage(eventPoll.id); + if (pollMessage == null) return; - final latestAnswers = { - for (final ans in oldPoll?.latestAnswers ?? []) ans.id: ans, - eventPollVote.id!: eventPollVote, - }; + final oldPoll = pollMessage.poll; - final currentUserId = _client.state.currentUser?.id; - final ownVotesAndAnswers = { - for (final vote in oldPoll?.ownVotesAndAnswers ?? []) vote.id: vote, - if (eventPollVote.userId == currentUserId) + final latestAnswers = { + for (final ans in oldPoll?.latestAnswers ?? []) ans.id: ans, eventPollVote.id!: eventPollVote, - }; + }; - final poll = eventPoll.copyWith( - latestAnswers: [...latestAnswers.values], - ownVotesAndAnswers: [...ownVotesAndAnswers.values], - ); + final currentUserId = _client.state.currentUser?.id; + final ownVotesAndAnswers = { + for (final vote in oldPoll?.ownVotesAndAnswers ?? []) vote.id: vote, + if (eventPollVote.userId == currentUserId) eventPollVote.id!: eventPollVote, + }; - final message = pollMessage.copyWith(poll: poll); - updateMessage(message); - })); + final poll = eventPoll.copyWith( + latestAnswers: [...latestAnswers.values], + ownVotesAndAnswers: [...ownVotesAndAnswers.values], + ); + + final message = pollMessage.copyWith(poll: poll); + updateMessage(message); + }), + ); } void _listenPollVoteCasted() { - _subscriptions.add(_channel.on(EventType.pollVoteCasted).listen((event) { - final (eventPoll, eventPollVote) = (event.poll, event.pollVote); - if (eventPoll == null || eventPollVote == null) return; + _subscriptions.add( + _channel.on(EventType.pollVoteCasted).listen((event) { + final (eventPoll, eventPollVote) = (event.poll, event.pollVote); + if (eventPoll == null || eventPollVote == null) return; - final pollMessage = _findPollMessage(eventPoll.id); - if (pollMessage == null) return; + final pollMessage = _findPollMessage(eventPoll.id); + if (pollMessage == null) return; - final oldPoll = pollMessage.poll; + final oldPoll = pollMessage.poll; - final latestAnswers = oldPoll?.latestAnswers ?? eventPoll.latestAnswers; - final currentUserId = _client.state.currentUser?.id; - final ownVotesAndAnswers = { - for (final vote in oldPoll?.ownVotesAndAnswers ?? []) vote.id: vote, - if (eventPollVote.userId == currentUserId) - eventPollVote.id!: eventPollVote, - }; + final latestAnswers = oldPoll?.latestAnswers ?? eventPoll.latestAnswers; + final currentUserId = _client.state.currentUser?.id; + final ownVotesAndAnswers = { + for (final vote in oldPoll?.ownVotesAndAnswers ?? []) vote.id: vote, + if (eventPollVote.userId == currentUserId) eventPollVote.id!: eventPollVote, + }; - final poll = eventPoll.copyWith( - latestAnswers: latestAnswers, - ownVotesAndAnswers: [...ownVotesAndAnswers.values], - ); + final poll = eventPoll.copyWith( + latestAnswers: latestAnswers, + ownVotesAndAnswers: [...ownVotesAndAnswers.values], + ); - final message = pollMessage.copyWith(poll: poll); - updateMessage(message); - })); + final message = pollMessage.copyWith(poll: poll); + updateMessage(message); + }), + ); } void _listenPollAnswerRemoved() { - _subscriptions.add(_channel.on(EventType.pollAnswerRemoved).listen((event) { - final (eventPoll, eventPollVote) = (event.poll, event.pollVote); - if (eventPoll == null || eventPollVote == null) return; + _subscriptions.add( + _channel.on(EventType.pollAnswerRemoved).listen((event) { + final (eventPoll, eventPollVote) = (event.poll, event.pollVote); + if (eventPoll == null || eventPollVote == null) return; - final pollMessage = _findPollMessage(eventPoll.id); - if (pollMessage == null) return; + final pollMessage = _findPollMessage(eventPoll.id); + if (pollMessage == null) return; - final oldPoll = pollMessage.poll; + final oldPoll = pollMessage.poll; - final latestAnswers = { - for (final ans in oldPoll?.latestAnswers ?? []) ans.id: ans, - }..remove(eventPollVote.id); + final latestAnswers = { + for (final ans in oldPoll?.latestAnswers ?? []) ans.id: ans, + }..remove(eventPollVote.id); - final ownVotesAndAnswers = { - for (final vote in oldPoll?.ownVotesAndAnswers ?? []) vote.id: vote, - }..remove(eventPollVote.id); + final ownVotesAndAnswers = { + for (final vote in oldPoll?.ownVotesAndAnswers ?? []) vote.id: vote, + }..remove(eventPollVote.id); - final poll = eventPoll.copyWith( - latestAnswers: [...latestAnswers.values], - ownVotesAndAnswers: [...ownVotesAndAnswers.values], - ); + final poll = eventPoll.copyWith( + latestAnswers: [...latestAnswers.values], + ownVotesAndAnswers: [...ownVotesAndAnswers.values], + ); - final message = pollMessage.copyWith(poll: poll); - updateMessage(message); - })); + final message = pollMessage.copyWith(poll: poll); + updateMessage(message); + }), + ); } void _listenPollVoteRemoved() { - _subscriptions.add(_channel.on(EventType.pollVoteRemoved).listen((event) { - final (eventPoll, eventPollVote) = (event.poll, event.pollVote); - if (eventPoll == null || eventPollVote == null) return; + _subscriptions.add( + _channel.on(EventType.pollVoteRemoved).listen((event) { + final (eventPoll, eventPollVote) = (event.poll, event.pollVote); + if (eventPoll == null || eventPollVote == null) return; - final pollMessage = _findPollMessage(eventPoll.id); - if (pollMessage == null) return; + final pollMessage = _findPollMessage(eventPoll.id); + if (pollMessage == null) return; - final oldPoll = pollMessage.poll; + final oldPoll = pollMessage.poll; - final latestAnswers = oldPoll?.latestAnswers ?? eventPoll.latestAnswers; - final ownVotesAndAnswers = { - for (final vote in oldPoll?.ownVotesAndAnswers ?? []) vote.id: vote, - }..remove(eventPollVote.id); + final latestAnswers = oldPoll?.latestAnswers ?? eventPoll.latestAnswers; + final ownVotesAndAnswers = { + for (final vote in oldPoll?.ownVotesAndAnswers ?? []) vote.id: vote, + }..remove(eventPollVote.id); - final poll = eventPoll.copyWith( - latestAnswers: latestAnswers, - ownVotesAndAnswers: [...ownVotesAndAnswers.values], - ); + final poll = eventPoll.copyWith( + latestAnswers: latestAnswers, + ownVotesAndAnswers: [...ownVotesAndAnswers.values], + ); - final message = pollMessage.copyWith(poll: poll); - updateMessage(message); - })); + final message = pollMessage.copyWith(poll: poll); + updateMessage(message); + }), + ); } void _listenPollVoteChanged() { - _subscriptions.add(_channel.on(EventType.pollVoteChanged).listen((event) { - final (eventPoll, eventPollVote) = (event.poll, event.pollVote); - if (eventPoll == null || eventPollVote == null) return; + _subscriptions.add( + _channel.on(EventType.pollVoteChanged).listen((event) { + final (eventPoll, eventPollVote) = (event.poll, event.pollVote); + if (eventPoll == null || eventPollVote == null) return; - final pollMessage = _findPollMessage(eventPoll.id); - if (pollMessage == null) return; + final pollMessage = _findPollMessage(eventPoll.id); + if (pollMessage == null) return; - final oldPoll = pollMessage.poll; + final oldPoll = pollMessage.poll; - final latestAnswers = oldPoll?.latestAnswers ?? eventPoll.latestAnswers; - final currentUserId = _client.state.currentUser?.id; - final ownVotesAndAnswers = { - for (final vote in oldPoll?.ownVotesAndAnswers ?? []) vote.id: vote, - if (eventPollVote.userId == currentUserId) - eventPollVote.id!: eventPollVote, - }; + final latestAnswers = oldPoll?.latestAnswers ?? eventPoll.latestAnswers; + final currentUserId = _client.state.currentUser?.id; + final ownVotesAndAnswers = { + for (final vote in oldPoll?.ownVotesAndAnswers ?? []) vote.id: vote, + if (eventPollVote.userId == currentUserId) eventPollVote.id!: eventPollVote, + }; - final poll = eventPoll.copyWith( - latestAnswers: latestAnswers, - ownVotesAndAnswers: [...ownVotesAndAnswers.values], - ); + final poll = eventPoll.copyWith( + latestAnswers: latestAnswers, + ownVotesAndAnswers: [...ownVotesAndAnswers.values], + ); - final message = pollMessage.copyWith(poll: poll); - updateMessage(message); - })); + final message = pollMessage.copyWith(poll: poll); + updateMessage(message); + }), + ); } void _listenDraftUpdated() { @@ -3046,8 +3067,9 @@ class ChannelClientState { final currentUserId = _channel.client.state.currentUser?.id; final currentMessage = switch (currentUserId) { - final userId? when userId == eventReaction.userId => - message.deleteMyReaction(reactionType: eventReaction.type), + final userId? when userId == eventReaction.userId => message.deleteMyReaction( + reactionType: eventReaction.type, + ), _ => message, }; @@ -3063,31 +3085,32 @@ class ChannelClientState { } void _listenReactionNew() { - _subscriptions.add(_channel.on(EventType.reactionNew).listen((event) { - final (eventReaction, eventMessage) = (event.reaction, event.message); - if (eventReaction == null || eventMessage == null) return; + _subscriptions.add( + _channel.on(EventType.reactionNew).listen((event) { + final (eventReaction, eventMessage) = (event.reaction, event.message); + if (eventReaction == null || eventMessage == null) return; - final messageId = eventMessage.id; - final parentId = eventMessage.parentId; + final messageId = eventMessage.id; + final parentId = eventMessage.parentId; - for (final message in [...messages, ...?threads[parentId]]) { - if (message.id == messageId) { - final currentUserId = _channel.client.state.currentUser?.id; + for (final message in [...messages, ...?threads[parentId]]) { + if (message.id == messageId) { + final currentUserId = _channel.client.state.currentUser?.id; - final currentMessage = switch (currentUserId) { - final userId? when userId == eventReaction.userId => - message.addMyReaction(eventReaction), - _ => message, - }; + final currentMessage = switch (currentUserId) { + final userId? when userId == eventReaction.userId => message.addMyReaction(eventReaction), + _ => message, + }; - return updateMessage( - eventMessage.copyWith( - ownReactions: currentMessage.ownReactions, - ), - ); + return updateMessage( + eventMessage.copyWith( + ownReactions: currentMessage.ownReactions, + ), + ); + } } - } - })); + }), + ); } void _listenReactionUpdated() { @@ -3122,39 +3145,45 @@ class ChannelClientState { } void _listenMessageUpdated() { - _subscriptions.add(_channel.on(EventType.messageUpdated).listen((event) { - final message = event.message; - if (message == null) return; + _subscriptions.add( + _channel.on(EventType.messageUpdated).listen((event) { + final message = event.message; + if (message == null) return; - return updateMessage(message); - })); + return updateMessage(message); + }), + ); } void _listenMessageDeleted() { - _subscriptions.add(_channel.on(EventType.messageDeleted).listen((event) { - final hardDelete = event.hardDelete ?? false; + _subscriptions.add( + _channel.on(EventType.messageDeleted).listen((event) { + final hardDelete = event.hardDelete ?? false; - final message = event.message!.copyWith( - // TODO: Remove once deletedForMe is properly enriched on the backend. - deletedForMe: event.deletedForMe, - ); + final message = event.message!.copyWith( + // TODO: Remove once deletedForMe is properly enriched on the backend. + deletedForMe: event.deletedForMe, + ); - return deleteMessage(message, hardDelete: hardDelete); - })); + return deleteMessage(message, hardDelete: hardDelete); + }), + ); } void _listenMessageNew() { - _subscriptions.add(_channel - .on( - EventType.messageNew, - EventType.notificationMessageNew, - ) - .listen((event) { - final message = event.message; - if (message == null) return; + _subscriptions.add( + _channel + .on( + EventType.messageNew, + EventType.notificationMessageNew, + ) + .listen((event) { + final message = event.message; + if (message == null) return; - return addNewMessage(message); - })); + return addNewMessage(message); + }), + ); } /// Adds a new message to the channel state and updates the unread count. @@ -3345,27 +3374,22 @@ class ChannelClientState { List get messages => _channelState.messages ?? []; /// Channel message list as a stream. - Stream> get messagesStream => channelStateStream - .map((cs) => cs.messages ?? []) - .distinct(const ListEquality().equals); + Stream> get messagesStream => + channelStateStream.map((cs) => cs.messages ?? []).distinct(const ListEquality().equals); /// Channel pinned message list. - List get pinnedMessages => - _channelState.pinnedMessages ?? []; + List get pinnedMessages => _channelState.pinnedMessages ?? []; /// Channel pinned message list as a stream. - Stream> get pinnedMessagesStream => channelStateStream - .map((cs) => cs.pinnedMessages ?? []) - .distinct(const ListEquality().equals); + Stream> get pinnedMessagesStream => + channelStateStream.map((cs) => cs.pinnedMessages ?? []).distinct(const ListEquality().equals); /// Channel pending message list. - List get pendingMessages => - _channelState.pendingMessages ?? []; + List get pendingMessages => _channelState.pendingMessages ?? []; /// Channel pending message list as a stream. - Stream> get pendingMessagesStream => channelStateStream - .map((cs) => cs.pendingMessages ?? []) - .distinct(const ListEquality().equals); + Stream> get pendingMessagesStream => + channelStateStream.map((cs) => cs.pendingMessages ?? []).distinct(const ListEquality().equals); /// Get channel last message. Message? get lastMessage => messages.lastOrNull; @@ -3376,38 +3400,32 @@ class ChannelClientState { } /// Channel members list. - List get members => (_channelState.members ?? []) - .map((e) => e.copyWith(user: _client.state.users[e.user!.id])) - .toList(); + List get members => + (_channelState.members ?? []).map((e) => e.copyWith(user: _client.state.users[e.user!.id])).toList(); /// Channel members list as a stream. - Stream> get membersStream => CombineLatestStream.combine2< - List?, Map, List>( + Stream> get membersStream => + CombineLatestStream.combine2?, Map, List>( channelStateStream.map((cs) => cs.members), _client.state.usersStream, - (members, users) => - [...?members?.map((e) => e!.copyWith(user: users[e.user!.id]))], + (members, users) => [...?members?.map((e) => e!.copyWith(user: users[e.user!.id]))], ).distinct(const ListEquality().equals); /// Channel watcher count. int? get watcherCount => _channelState.watcherCount; /// Channel watcher count as a stream. - Stream get watcherCountStream => - channelStateStream.map((cs) => cs.watcherCount); + Stream get watcherCountStream => channelStateStream.map((cs) => cs.watcherCount); /// Channel watchers list. - List get watchers => (_channelState.watchers ?? []) - .map((e) => _client.state.users[e.id] ?? e) - .toList(); + List get watchers => (_channelState.watchers ?? []).map((e) => _client.state.users[e.id] ?? e).toList(); /// Channel watchers list as a stream. - Stream> get watchersStream => CombineLatestStream.combine2< - List?, Map, List>( - channelStateStream.map((cs) => cs.watchers), - _client.state.usersStream, - (watchers, users) => [...?watchers?.map((e) => users[e.id] ?? e)], - ).distinct(const ListEquality().equals); + Stream> get watchersStream => CombineLatestStream.combine2?, Map, List>( + channelStateStream.map((cs) => cs.watchers), + _client.state.usersStream, + (watchers, users) => [...?watchers?.map((e) => users[e.id] ?? e)], + ).distinct(const ListEquality().equals); /// Channel active live locations. List get activeLiveLocations { @@ -3415,9 +3433,8 @@ class ChannelClientState { } /// Channel active live locations as a stream. - Stream> get activeLiveLocationsStream => channelStateStream - .map((cs) => cs.activeLiveLocations ?? []) - .distinct(const ListEquality().equals); + Stream> get activeLiveLocationsStream => + channelStateStream.map((cs) => cs.activeLiveLocations ?? []).distinct(const ListEquality().equals); /// Channel draft. Draft? get draft => _channelState.draft; @@ -3429,8 +3446,8 @@ class ChannelClientState { /// Channel member for the current user. Member? get currentUserMember => members.firstWhereOrNull( - (m) => m.user?.id == _client.state.currentUser?.id, - ); + (m) => m.user?.id == _client.state.currentUser?.id, + ); /// Channel role for the current user String? get currentUserChannelRole => currentUserMember?.channelRole; @@ -3554,8 +3571,7 @@ class ChannelClientState { ); } - int _sortByCreatedAt(Message a, Message b) => - a.createdAt.compareTo(b.createdAt); + int _sortByCreatedAt(Message a, Message b) => a.createdAt.compareTo(b.createdAt); /// The channel state related to this client. ChannelState get _channelState => _channelStateController.value; @@ -3639,13 +3655,11 @@ class ChannelClientState { /// /// This stream emits a new value whenever the draft associated with the /// specified thread is updated or removed. - Stream threadDraftStream(String parentId) => channelStateStream - .map((cs) => _getThreadDraft(parentId, cs.messages)) - .distinct(); + Stream threadDraftStream(String parentId) => + channelStateStream.map((cs) => _getThreadDraft(parentId, cs.messages)).distinct(); /// Channel related typing users stream. - Stream> get typingEventsStream => - _typingEventsController.stream; + Stream> get typingEventsStream => _typingEventsController.stream; /// Channel related typing users last value. Map get typingEvents => _typingEventsController.value; @@ -3694,8 +3708,7 @@ class ChannelClientState { (_) { final now = DateTime.now(); typingEvents.forEach((user, event) { - if (now.difference(event.createdAt).inSeconds > - incomingTypingStartEventTimeout) { + if (now.difference(event.createdAt).inSeconds > incomingTypingStartEventTimeout) { _client.handleEvent( Event( type: EventType.typingStop, @@ -3718,21 +3731,23 @@ class ChannelClientState { const Duration(seconds: 30), (_) { final now = DateTime.now(); - var expiredMessages = channelState.pinnedMessages - ?.where((m) => m.pinExpires?.isBefore(now) == true) - .toList(); + var expiredMessages = channelState.pinnedMessages?.where((m) => m.pinExpires?.isBefore(now) == true).toList(); if (expiredMessages != null && expiredMessages.isNotEmpty) { expiredMessages = expiredMessages - .map((m) => m.copyWith( - pinExpires: null, - pinned: false, - )) + .map( + (m) => m.copyWith( + pinExpires: null, + pinned: false, + ), + ) .toList(); - updateChannelState(_channelState.copyWith( - pinnedMessages: pinnedMessages.where(_pinIsValid).toList(), - messages: expiredMessages, - )); + updateChannelState( + _channelState.copyWith( + pinnedMessages: pinnedMessages.where(_pinIsValid).toList(), + messages: expiredMessages, + ), + ); } }, ); @@ -4140,16 +4155,18 @@ class ChannelClientState { if (toRemove.isEmpty) return existing; final toRemoveIds = toRemove.map((m) => m.id).toSet(); - final updatedMessages = existing.where((it) { - // Remove the message if it's in the toRemove list. - return !toRemoveIds.contains(it.id); - }).map((it) { - // Continue if the message doesn't quote any of the deleted messages. - if (!toRemoveIds.contains(it.quotedMessageId)) return it; - - // Setting it to null will remove the quoted message from the message. - return it.copyWith(quotedMessageId: null, quotedMessage: null); - }); + final updatedMessages = existing + .where((it) { + // Remove the message if it's in the toRemove list. + return !toRemoveIds.contains(it.id); + }) + .map((it) { + // Continue if the message doesn't quote any of the deleted messages. + if (!toRemoveIds.contains(it.quotedMessageId)) return it; + + // Setting it to null will remove the quoted message from the message. + return it.copyWith(quotedMessageId: null, quotedMessage: null); + }); return updatedMessages; } diff --git a/packages/stream_chat/lib/src/client/channel_delivery_reporter.dart b/packages/stream_chat/lib/src/client/channel_delivery_reporter.dart index 001f19625a..1342d397a3 100644 --- a/packages/stream_chat/lib/src/client/channel_delivery_reporter.dart +++ b/packages/stream_chat/lib/src/client/channel_delivery_reporter.dart @@ -10,9 +10,10 @@ import 'package:synchronized/synchronized.dart'; /// /// Each [MessageDeliveryInfo] represents an acknowledgment that the current /// user has received a message. -typedef MarkChannelsDelivered = Future Function( - Iterable deliveries, -); +typedef MarkChannelsDelivered = + Future Function( + Iterable deliveries, + ); /// Manages the delivery reporting for channel messages. /// @@ -31,8 +32,8 @@ class ChannelDeliveryReporter { Logger? logger, required this.onMarkChannelsDelivered, Duration throttleDuration = const Duration(seconds: 1), - }) : _logger = logger, - _markAsDeliveredThrottleDuration = throttleDuration; + }) : _logger = logger, + _markAsDeliveredThrottleDuration = throttleDuration; final Logger? _logger; final Duration _markAsDeliveredThrottleDuration; @@ -43,7 +44,7 @@ class ChannelDeliveryReporter { final MarkChannelsDelivered onMarkChannelsDelivered; final _deliveryCandidatesLock = Lock(); - final _deliveryCandidates = {}; + final _deliveryCandidates = {}; /// Submits [channels] for delivery reporting. /// diff --git a/packages/stream_chat/lib/src/client/client.dart b/packages/stream_chat/lib/src/client/client.dart index 6fb7aac5bf..4f24364bb3 100644 --- a/packages/stream_chat/lib/src/client/client.dart +++ b/packages/stream_chat/lib/src/client/client.dart @@ -86,12 +86,11 @@ class StreamChatClient { RetryPolicy? retryPolicy, String? baseURL, String? baseWsUrl, - Duration connectTimeout = const Duration(seconds: 6), - Duration receiveTimeout = const Duration(seconds: 6), + Duration connectTimeout = kDefaultConnectTimeout, + Duration receiveTimeout = kDefaultReceiveTimeout, StreamChatApi? chatApi, WebSocket? ws, - AttachmentFileUploaderProvider attachmentFileUploaderProvider = - StreamAttachmentFileUploader.new, + AttachmentFileUploaderProvider attachmentFileUploaderProvider = StreamAttachmentFileUploader.new, Iterable? chatApiInterceptors, HttpClientAdapter? httpClientAdapter, }) { @@ -103,7 +102,8 @@ class StreamChatClient { receiveTimeout: receiveTimeout, ); - _chatApi = chatApi ?? + _chatApi = + chatApi ?? StreamChatApi( apiKey, options: options, @@ -116,7 +116,8 @@ class StreamChatClient { httpClientAdapter: httpClientAdapter, ); - _ws = ws ?? + _ws = + ws ?? WebSocket( apiKey: apiKey, baseUrl: baseWsUrl ?? options.baseUrl, @@ -126,7 +127,8 @@ class StreamChatClient { logger: detachedLogger('🔌'), ); - _retryPolicy = retryPolicy ?? + _retryPolicy = + retryPolicy ?? RetryPolicy( shouldRetry: (_, __, error) { return error is StreamChatNetworkError && error.isRetriable; @@ -292,12 +294,11 @@ class StreamChatClient { User user, String token, { bool connectWebSocket = true, - }) => - _connectUser( - user, - token: Token.fromRawValue(token), - connectWebSocket: connectWebSocket, - ); + }) => _connectUser( + user, + token: Token.fromRawValue(token), + connectWebSocket: connectWebSocket, + ); /// Connects the current user using the [tokenProvider] to fetch the token. /// It returns a [Future] that resolves when the connection is setup. @@ -305,12 +306,11 @@ class StreamChatClient { User user, TokenProvider tokenProvider, { bool connectWebSocket = true, - }) => - _connectUser( - user, - provider: tokenProvider, - connectWebSocket: connectWebSocket, - ); + }) => _connectUser( + user, + provider: tokenProvider, + connectWebSocket: connectWebSocket, + ); /// Connects the current user with an anonymous id, this triggers a connection /// to the API. It returns a [Future] that resolves when the connection is @@ -523,10 +523,12 @@ class StreamChatClient { final isConnected = currStatus == ConnectionStatus.connected; // Notify the connection status change event - handleEvent(Event( - type: EventType.connectionChanged, - online: isConnected, - )); + handleEvent( + Event( + type: EventType.connectionChanged, + online: isConnected, + ), + ); final connectionRecovered = !wasConnected && isConnected; @@ -543,10 +545,12 @@ class StreamChatClient { if (persistenceEnabled) await sync(cids: cids); } - handleEvent(Event( - type: EventType.connectionRecovered, - online: true, - )); + handleEvent( + Event( + type: EventType.connectionRecovered, + online: true, + ), + ); } } @@ -559,11 +563,10 @@ class StreamChatClient { String? eventType4, ]) { if (eventType == null || eventType == EventType.any) return eventStream; - return eventStream.where((event) => - event.type == eventType || - event.type == eventType2 || - event.type == eventType3 || - event.type == eventType4); + return eventStream.where( + (event) => + event.type == eventType || event.type == eventType2 || event.type == eventType3 || event.type == eventType4, + ); } // Lock to make sure only one sync process is running at a time. @@ -676,26 +679,29 @@ class StreamChatClient { } try { - final newQueryChannelsFuture = queryChannelsOnline( - filter: filter, - sort: channelStateSort, - state: state, - watch: watch, - presence: presence, - memberLimit: memberLimit, - messageLimit: messageLimit, - paginationParams: paginationParams, - waitForConnect: waitForConnect, - ).timeout( - const Duration(seconds: 30), - onTimeout: () { - logger.warning('Online channel query timed out'); - throw TimeoutException('Channel query timed out'); - }, - ).whenComplete(() { - // Always clean up cache reference when done - _queryChannelsStreams.remove(hash); - }); + final newQueryChannelsFuture = + queryChannelsOnline( + filter: filter, + sort: channelStateSort, + state: state, + watch: watch, + presence: presence, + memberLimit: memberLimit, + messageLimit: messageLimit, + paginationParams: paginationParams, + waitForConnect: waitForConnect, + ) + .timeout( + const Duration(seconds: 30), + onTimeout: () { + logger.warning('Online channel query timed out'); + throw TimeoutException('Channel query timed out'); + }, + ) + .whenComplete(() { + // Always clean up cache reference when done + _queryChannelsStreams.remove(hash); + }); // Store the future in cache _queryChannelsStreams[hash] = newQueryChannelsFuture; @@ -762,10 +768,7 @@ class StreamChatClient { final channels = res.channels; - final users = channels - .expand((it) => it.members ?? []) - .map((it) => it.user) - .toList(growable: false); + final users = channels.expand((it) => it.members ?? []).map((it) => it.user).toList(growable: false); this.state.updateUsers(users); @@ -854,12 +857,11 @@ class StreamChatClient { required Filter filter, SortOrder? sort, PaginationParams? pagination, - }) => - _chatApi.moderation.queryBannedUsers( - filter: filter, - sort: sort, - pagination: pagination, - ); + }) => _chatApi.moderation.queryBannedUsers( + filter: filter, + sort: sort, + pagination: pagination, + ); /// A message search. Future search( @@ -868,14 +870,13 @@ class StreamChatClient { SortOrder? sort, PaginationParams? paginationParams, Filter? messageFilters, - }) => - _chatApi.general.searchMessages( - filter, - query: query, - sort: sort, - pagination: paginationParams, - messageFilters: messageFilters, - ); + }) => _chatApi.general.searchMessages( + filter, + query: query, + sort: sort, + pagination: paginationParams, + messageFilters: messageFilters, + ); /// Send a [file] to the [channelId] of type [channelType] Future sendFile( @@ -885,15 +886,14 @@ class StreamChatClient { ProgressCallback? onSendProgress, CancelToken? cancelToken, Map? extraData, - }) => - _chatApi.fileUploader.sendFile( - file, - channelId, - channelType, - onSendProgress: onSendProgress, - cancelToken: cancelToken, - extraData: extraData, - ); + }) => _chatApi.fileUploader.sendFile( + file, + channelId, + channelType, + onSendProgress: onSendProgress, + cancelToken: cancelToken, + extraData: extraData, + ); /// Send a [image] to the [channelId] of type [channelType] Future sendImage( @@ -903,15 +903,14 @@ class StreamChatClient { ProgressCallback? onSendProgress, CancelToken? cancelToken, Map? extraData, - }) => - _chatApi.fileUploader.sendImage( - image, - channelId, - channelType, - onSendProgress: onSendProgress, - cancelToken: cancelToken, - extraData: extraData, - ); + }) => _chatApi.fileUploader.sendImage( + image, + channelId, + channelType, + onSendProgress: onSendProgress, + cancelToken: cancelToken, + extraData: extraData, + ); /// Delete a file from this channel Future deleteFile( @@ -920,14 +919,13 @@ class StreamChatClient { String channelType, { CancelToken? cancelToken, Map? extraData, - }) => - _chatApi.fileUploader.deleteFile( - url, - channelId, - channelType, - cancelToken: cancelToken, - extraData: extraData, - ); + }) => _chatApi.fileUploader.deleteFile( + url, + channelId, + channelType, + cancelToken: cancelToken, + extraData: extraData, + ); /// Delete an image from this channel Future deleteImage( @@ -936,14 +934,13 @@ class StreamChatClient { String channelType, { CancelToken? cancelToken, Map? extraData, - }) => - _chatApi.fileUploader.deleteImage( - url, - channelId, - channelType, - cancelToken: cancelToken, - extraData: extraData, - ); + }) => _chatApi.fileUploader.deleteImage( + url, + channelId, + channelType, + cancelToken: cancelToken, + extraData: extraData, + ); /// Upload an image to the Stream CDN /// @@ -955,12 +952,11 @@ class StreamChatClient { AttachmentFile image, { ProgressCallback? onUploadProgress, CancelToken? cancelToken, - }) => - _chatApi.fileUploader.uploadImage( - image, - onSendProgress: onUploadProgress, - cancelToken: cancelToken, - ); + }) => _chatApi.fileUploader.uploadImage( + image, + onSendProgress: onUploadProgress, + cancelToken: cancelToken, + ); /// Upload a file to the Stream CDN /// @@ -972,12 +968,11 @@ class StreamChatClient { AttachmentFile file, { ProgressCallback? onUploadProgress, CancelToken? cancelToken, - }) => - _chatApi.fileUploader.uploadFile( - file, - onSendProgress: onUploadProgress, - cancelToken: cancelToken, - ); + }) => _chatApi.fileUploader.uploadFile( + file, + onSendProgress: onUploadProgress, + cancelToken: cancelToken, + ); /// Remove an image from the Stream CDN using its [url]. /// @@ -987,11 +982,10 @@ class StreamChatClient { Future removeImage( String url, { CancelToken? cancelToken, - }) => - _chatApi.fileUploader.removeImage( - url, - cancelToken: cancelToken, - ); + }) => _chatApi.fileUploader.removeImage( + url, + cancelToken: cancelToken, + ); /// Remove a file from the Stream CDN using its [url]. /// @@ -1001,11 +995,10 @@ class StreamChatClient { Future removeFile( String url, { CancelToken? cancelToken, - }) => - _chatApi.fileUploader.removeFile( - url, - cancelToken: cancelToken, - ); + }) => _chatApi.fileUploader.removeFile( + url, + cancelToken: cancelToken, + ); /// Replaces the [channelId] of type [ChannelType] data with [data]. /// @@ -1015,13 +1008,12 @@ class StreamChatClient { String channelType, Map data, { Message? message, - }) => - _chatApi.channel.updateChannel( - channelId, - channelType, - data, - message: message, - ); + }) => _chatApi.channel.updateChannel( + channelId, + channelType, + data, + message: message, + ); /// Partial update for the [channelId] of type [ChannelType]. Sets the /// data provided in [set], and removes the attributes given in [unset]. @@ -1032,32 +1024,29 @@ class StreamChatClient { String channelType, { Map? set, List? unset, - }) => - _chatApi.channel.updateChannelPartial( - channelId, - channelType, - set: set, - unset: unset, - ); + }) => _chatApi.channel.updateChannelPartial( + channelId, + channelType, + set: set, + unset: unset, + ); /// Add a device for Push Notifications. Future addDevice( String id, PushProvider pushProvider, { String? pushProviderName, - }) => - _chatApi.device.addDevice( - id, - pushProvider, - pushProviderName: pushProviderName, - ); + }) => _chatApi.device.addDevice( + id, + pushProvider, + pushProviderName: pushProviderName, + ); /// Gets a list of user devices. Future getDevices() => _chatApi.device.getDevices(); /// Remove a user's device. - Future removeDevice(String id) => - _chatApi.device.removeDevice(id); + Future removeDevice(String id) => _chatApi.device.removeDevice(id); /// Set push preferences for the current user. /// @@ -1158,13 +1147,12 @@ class StreamChatClient { String channelType, { String? channelId, Map? channelData, - }) => - queryChannel( - channelType, - channelId: channelId, - state: false, - channelData: channelData, - ); + }) => queryChannel( + channelType, + channelId: channelId, + state: false, + channelData: channelData, + ); /// watches the provided channel /// Creates first if not yet created @@ -1172,13 +1160,12 @@ class StreamChatClient { String channelType, { String? channelId, Map? channelData, - }) => - queryChannel( - channelType, - channelId: channelId, - watch: true, - channelData: channelData, - ); + }) => queryChannel( + channelType, + channelId: channelId, + watch: true, + channelData: channelData, + ); /// Query the API, get messages, members or other channel fields /// Creates the channel first if not yet created @@ -1192,18 +1179,17 @@ class StreamChatClient { PaginationParams? messagesPagination, PaginationParams? membersPagination, PaginationParams? watchersPagination, - }) => - _chatApi.channel.queryChannel( - channelType, - channelId: channelId, - channelData: channelData, - state: state, - watch: watch, - presence: presence, - messagesPagination: messagesPagination, - membersPagination: membersPagination, - watchersPagination: watchersPagination, - ); + }) => _chatApi.channel.queryChannel( + channelType, + channelId: channelId, + channelData: channelData, + state: state, + watch: watch, + presence: presence, + messagesPagination: messagesPagination, + membersPagination: membersPagination, + watchersPagination: watchersPagination, + ); /// Query channel members Future queryMembers( @@ -1213,15 +1199,14 @@ class StreamChatClient { List? members, SortOrder? sort, PaginationParams? pagination, - }) => - _chatApi.general.queryMembers( - channelType, - channelId: channelId, - filter: filter, - members: members, - sort: sort, - pagination: pagination, - ); + }) => _chatApi.general.queryMembers( + channelType, + channelId: channelId, + filter: filter, + members: members, + sort: sort, + pagination: pagination, + ); /// Hides the channel from [queryChannels] for the user /// until a message is added If [clearHistory] is set to true - all messages @@ -1230,32 +1215,29 @@ class StreamChatClient { String channelId, String channelType, { bool clearHistory = false, - }) => - _chatApi.channel.hideChannel( - channelId, - channelType, - clearHistory: clearHistory, - ); + }) => _chatApi.channel.hideChannel( + channelId, + channelType, + clearHistory: clearHistory, + ); /// Removes the hidden status for the channel Future showChannel( String channelId, String channelType, - ) => - _chatApi.channel.showChannel( - channelId, - channelType, - ); + ) => _chatApi.channel.showChannel( + channelId, + channelType, + ); /// Delete this channel. Messages are permanently removed. Future deleteChannel( String channelId, String channelType, - ) => - _chatApi.channel.deleteChannel( - channelId, - channelType, - ); + ) => _chatApi.channel.deleteChannel( + channelId, + channelType, + ); /// Removes all messages from the channel up to [truncatedAt] or now if /// [truncatedAt] is not provided. @@ -1267,52 +1249,47 @@ class StreamChatClient { Message? message, bool? skipPush, DateTime? truncatedAt, - }) => - _chatApi.channel.truncateChannel( - channelId, - channelType, - message: message, - skipPush: skipPush, - truncatedAt: truncatedAt, - ); + }) => _chatApi.channel.truncateChannel( + channelId, + channelType, + message: message, + skipPush: skipPush, + truncatedAt: truncatedAt, + ); /// Mutes the channel Future muteChannel( String channelCid, { Duration? expiration, - }) => - _chatApi.moderation.muteChannel( - channelCid, - expiration: expiration, - ); + }) => _chatApi.moderation.muteChannel( + channelCid, + expiration: expiration, + ); /// Unmutes the channel - Future unmuteChannel(String channelCid) => - _chatApi.moderation.unmuteChannel(channelCid); + Future unmuteChannel(String channelCid) => _chatApi.moderation.unmuteChannel(channelCid); /// Accept invitation to the channel Future acceptChannelInvite( String channelId, String channelType, { Message? message, - }) => - _chatApi.channel.acceptChannelInvite( - channelId, - channelType, - message: message, - ); + }) => _chatApi.channel.acceptChannelInvite( + channelId, + channelType, + message: message, + ); /// Reject invitation to the channel Future rejectChannelInvite( String channelId, String channelType, { Message? message, - }) => - _chatApi.channel.rejectChannelInvite( - channelId, - channelType, - message: message, - ); + }) => _chatApi.channel.rejectChannelInvite( + channelId, + channelType, + message: message, + ); /// Add members to the channel Future addChannelMembers( @@ -1322,15 +1299,14 @@ class StreamChatClient { Message? message, bool hideHistory = false, DateTime? hideHistoryBefore, - }) => - _chatApi.channel.addMembers( - channelId, - channelType, - memberIds, - message: message, - hideHistory: hideHistory, - hideHistoryBefore: hideHistoryBefore, - ); + }) => _chatApi.channel.addMembers( + channelId, + channelType, + memberIds, + message: message, + hideHistory: hideHistory, + hideHistoryBefore: hideHistoryBefore, + ); /// Remove members from the channel Future removeChannelMembers( @@ -1338,13 +1314,12 @@ class StreamChatClient { String channelType, List memberIds, { Message? message, - }) => - _chatApi.channel.removeMembers( - channelId, - channelType, - memberIds, - message: message, - ); + }) => _chatApi.channel.removeMembers( + channelId, + channelType, + memberIds, + message: message, + ); /// Invite members to the channel Future inviteChannelMembers( @@ -1352,23 +1327,21 @@ class StreamChatClient { String channelType, List memberIds, { Message? message, - }) => - _chatApi.channel.inviteChannelMembers( - channelId, - channelType, - memberIds, - message: message, - ); + }) => _chatApi.channel.inviteChannelMembers( + channelId, + channelType, + memberIds, + message: message, + ); /// Stop watching the channel Future stopChannelWatching( String channelId, String channelType, - ) => - _chatApi.channel.stopWatching( - channelId, - channelType, - ); + ) => _chatApi.channel.stopWatching( + channelId, + channelType, + ); /// Send action for a specific message of this channel Future sendAction( @@ -1376,13 +1349,12 @@ class StreamChatClient { String channelType, String messageId, Map formData, - ) => - _chatApi.message.sendAction( - channelId, - channelType, - messageId, - formData, - ); + ) => _chatApi.message.sendAction( + channelId, + channelType, + messageId, + formData, + ); /// Mark [channelId] of type [channelType] all messages as read /// Optionally provide a [messageId] if you want to mark a @@ -1391,12 +1363,11 @@ class StreamChatClient { String channelId, String channelType, { String? messageId, - }) => - _chatApi.channel.markRead( - channelId, - channelType, - messageId: messageId, - ); + }) => _chatApi.channel.markRead( + channelId, + channelType, + messageId: messageId, + ); /// Marks the [channelId] of type [channelType] as unread /// by a given [messageId]. @@ -1406,12 +1377,11 @@ class StreamChatClient { String channelId, String channelType, String messageId, - ) => - _chatApi.channel.markUnread( - channelId, - channelType, - messageId, - ); + ) => _chatApi.channel.markUnread( + channelId, + channelType, + messageId, + ); /// Marks the [channelId] of type [channelType] as unread /// by a given [timestamp]. @@ -1421,12 +1391,11 @@ class StreamChatClient { String channelId, String channelType, DateTime timestamp, - ) => - _chatApi.channel.markUnreadByTimestamp( - channelId, - channelType, - timestamp, - ); + ) => _chatApi.channel.markUnreadByTimestamp( + channelId, + channelType, + timestamp, + ); /// Mark the thread with [threadId] in the channel with [channelId] of type /// [channelType] as read. @@ -1434,12 +1403,11 @@ class StreamChatClient { String channelId, String channelType, String threadId, - ) => - _chatApi.channel.markThreadRead( - channelId, - channelType, - threadId, - ); + ) => _chatApi.channel.markThreadRead( + channelId, + channelType, + threadId, + ); /// Mark the thread with [threadId] in the channel with [channelId] of type /// [channelType] as unread. @@ -1447,24 +1415,20 @@ class StreamChatClient { String channelId, String channelType, String threadId, - ) => - _chatApi.channel.markThreadUnread( - channelId, - channelType, - threadId, - ); + ) => _chatApi.channel.markThreadUnread( + channelId, + channelType, + threadId, + ); /// Creates a new Poll - Future createPoll(Poll poll) => - _chatApi.polls.createPoll(poll); + Future createPoll(Poll poll) => _chatApi.polls.createPoll(poll); /// Retrieves a Poll by [pollId] - Future getPoll(String pollId) => - _chatApi.polls.getPoll(pollId); + Future getPoll(String pollId) => _chatApi.polls.getPoll(pollId); /// Updates a Poll - Future updatePoll(Poll poll) => - _chatApi.polls.updatePoll(poll); + Future updatePoll(Poll poll) => _chatApi.polls.updatePoll(poll); /// Partially updates a Poll by [pollId]. /// @@ -1474,50 +1438,46 @@ class StreamChatClient { String pollId, { Map? set, List? unset, - }) => - _chatApi.polls.partialUpdatePoll( - pollId, - set: set, - unset: unset, - ); + }) => _chatApi.polls.partialUpdatePoll( + pollId, + set: set, + unset: unset, + ); /// Deletes the Poll by [pollId]. - Future deletePoll(String pollId) => - _chatApi.polls.deletePoll(pollId); + Future deletePoll(String pollId) => _chatApi.polls.deletePoll(pollId); /// Marks the Poll [pollId] as closed. - Future closePoll(String pollId) => - partialUpdatePoll(pollId, set: { - 'is_closed': true, - }); + Future closePoll(String pollId) => partialUpdatePoll( + pollId, + set: { + 'is_closed': true, + }, + ); /// Creates a new Poll Option for the Poll [pollId]. Future createPollOption( String pollId, PollOption option, - ) => - _chatApi.polls.createPollOption(pollId, option); + ) => _chatApi.polls.createPollOption(pollId, option); /// Retrieves a Poll Option by [optionId] for the Poll [pollId]. Future getPollOption( String pollId, String optionId, - ) => - _chatApi.polls.getPollOption(pollId, optionId); + ) => _chatApi.polls.getPollOption(pollId, optionId); /// Updates a Poll Option for the Poll [pollId]. Future updatePollOption( String pollId, PollOption option, - ) => - _chatApi.polls.updatePollOption(pollId, option); + ) => _chatApi.polls.updatePollOption(pollId, option); /// Deletes a Poll Option by [optionId] for the Poll [pollId]. Future deletePollOption( String pollId, String optionId, - ) => - _chatApi.polls.deletePollOption(pollId, optionId); + ) => _chatApi.polls.deletePollOption(pollId, optionId); /// Cast a [vote] for the Poll [pollId]. Future castPollVote( @@ -1544,20 +1504,18 @@ class StreamChatClient { String messageId, String pollId, String voteId, - ) => - _chatApi.polls.removePollVote(messageId, pollId, voteId); + ) => _chatApi.polls.removePollVote(messageId, pollId, voteId); /// Queries Polls with the given [filter] and [sort] options. Future queryPolls({ Filter? filter, SortOrder? sort, PaginationParams pagination = const PaginationParams(), - }) => - _chatApi.polls.queryPolls( - filter: filter, - sort: sort, - pagination: pagination, - ); + }) => _chatApi.polls.queryPolls( + filter: filter, + sort: sort, + pagination: pagination, + ); /// Queries Poll Votes for the Poll [pollId] with the given [filter] /// and [sort] options. @@ -1566,20 +1524,18 @@ class StreamChatClient { Filter? filter, SortOrder? sort, PaginationParams pagination = const PaginationParams(), - }) => - _chatApi.polls.queryPollVotes( - pollId, - filter: filter, - sort: sort, - pagination: pagination, - ); + }) => _chatApi.polls.queryPollVotes( + pollId, + filter: filter, + sort: sort, + pagination: pagination, + ); /// Update or Create the given user object. Future updateUser(User user) => updateUsers([user]); /// Batch update a list of users - Future updateUsers(List users) => - _chatApi.user.updateUsers(users); + Future updateUsers(List users) => _chatApi.user.updateUsers(users); /// Partially update the given user with [id]. /// Use [set] to define values to be set. @@ -1600,48 +1556,43 @@ class StreamChatClient { /// Batch partial updates the [users]. Future partialUpdateUsers( List users, - ) => - _chatApi.user.partialUpdateUsers(users); + ) => _chatApi.user.partialUpdateUsers(users); /// Bans a user from all channels Future banUser( String targetUserId, [ Map options = const {}, - ]) => - _chatApi.moderation.banUser( - targetUserId, - options: options, - ); + ]) => _chatApi.moderation.banUser( + targetUserId, + options: options, + ); /// Remove global ban for a user Future unbanUser( String targetUserId, [ Map options = const {}, - ]) => - _chatApi.moderation.unbanUser( - targetUserId, - options: options, - ); + ]) => _chatApi.moderation.unbanUser( + targetUserId, + options: options, + ); /// Shadow bans a user Future shadowBan( String targetID, [ Map options = const {}, - ]) => - banUser(targetID, { - 'shadow': true, - ...options, - }); + ]) => banUser(targetID, { + 'shadow': true, + ...options, + }); /// Removes shadow ban from a user Future removeShadowBan( String targetID, [ Map options = const {}, - ]) => - unbanUser(targetID, { - 'shadow': true, - ...options, - }); + ]) => unbanUser(targetID, { + 'shadow': true, + ...options, + }); final _userBlockLock = Lock(); @@ -1711,38 +1662,34 @@ class StreamChatClient { // Emit an local event with the unread count information as a side effect // in order to update the current user state. - handleEvent(Event( - totalUnreadCount: response.totalUnreadCount, - unreadChannels: response.channels.length, - unreadThreads: response.threads.length, - )); + handleEvent( + Event( + totalUnreadCount: response.totalUnreadCount, + unreadChannels: response.channels.length, + unreadThreads: response.threads.length, + ), + ); return response; } /// Mutes a user - Future muteUser(String userId) => - _chatApi.moderation.muteUser(userId); + Future muteUser(String userId) => _chatApi.moderation.muteUser(userId); /// Unmutes a user - Future unmuteUser(String userId) => - _chatApi.moderation.unmuteUser(userId); + Future unmuteUser(String userId) => _chatApi.moderation.unmuteUser(userId); /// Flag a message - Future flagMessage(String messageId) => - _chatApi.moderation.flagMessage(messageId); + Future flagMessage(String messageId) => _chatApi.moderation.flagMessage(messageId); /// Unflag a message - Future unflagMessage(String messageId) => - _chatApi.moderation.unflagMessage(messageId); + Future unflagMessage(String messageId) => _chatApi.moderation.unflagMessage(messageId); /// Flag a user - Future flagUser(String userId) => - _chatApi.moderation.flagUser(userId); + Future flagUser(String userId) => _chatApi.moderation.flagUser(userId); /// Unflag a message - Future unflagUser(String userId) => - _chatApi.moderation.unflagUser(userId); + Future unflagUser(String userId) => _chatApi.moderation.unflagUser(userId); /// Mark all channels for this user as read Future markAllRead() => _chatApi.channel.markAllRead(); @@ -1775,12 +1722,11 @@ class StreamChatClient { String channelId, String channelType, Event event, - ) => - _chatApi.channel.sendEvent( - channelId, - channelType, - event, - ); + ) => _chatApi.channel.sendEvent( + channelId, + channelType, + event, + ); /// Send a [reactionType] for this [messageId] /// Set [enforceUnique] to true to remove the existing user reaction @@ -1789,23 +1735,21 @@ class StreamChatClient { Reaction reaction, { bool skipPush = false, bool enforceUnique = false, - }) => - _chatApi.message.sendReaction( - messageId, - reaction, - skipPush: skipPush, - enforceUnique: enforceUnique, - ); + }) => _chatApi.message.sendReaction( + messageId, + reaction, + skipPush: skipPush, + enforceUnique: enforceUnique, + ); /// Delete a [reactionType] from this [messageId] Future deleteReaction( String messageId, String reactionType, - ) => - _chatApi.message.deleteReaction( - messageId, - reactionType, - ); + ) => _chatApi.message.deleteReaction( + messageId, + reactionType, + ); /// Sends the message to the given channel Future sendMessage( @@ -1814,46 +1758,59 @@ class StreamChatClient { String channelType, { bool skipPush = false, bool skipEnrichUrl = false, - }) => - _chatApi.message.sendMessage( - channelId, - channelType, - message, - skipPush: skipPush, - skipEnrichUrl: skipEnrichUrl, - ); + }) => _chatApi.message.sendMessage( + channelId, + channelType, + message, + skipPush: skipPush, + skipEnrichUrl: skipEnrichUrl, + ); /// Lists all the message replies for the [parentId] Future getReplies( String parentId, { PaginationParams? options, - }) => - _chatApi.message.getReplies( - parentId, - options: options, - ); + }) => _chatApi.message.getReplies( + parentId, + options: options, + ); /// Get all the reactions for a [messageId] Future getReactions( String messageId, { PaginationParams? pagination, - }) => - _chatApi.message.getReactions( - messageId, - pagination: pagination, - ); + }) => _chatApi.message.getReactions( + messageId, + pagination: pagination, + ); + + /// Queries reactions for a [messageId] with optional [filter], [sort], + /// and [pagination]. + /// + /// Unlike [getReactions], this method supports filtering by reaction type, + /// user ID, or creation date, sorting, and cursor-based pagination. + Future queryReactions( + String messageId, { + Filter? filter, + SortOrder? sort, + PaginationParams? pagination, + }) => _chatApi.message.queryReactions( + messageId, + filter: filter, + sort: sort, + pagination: pagination, + ); /// Update the given message Future updateMessage( Message message, { bool skipPush = false, bool skipEnrichUrl = false, - }) => - _chatApi.message.updateMessage( - message, - skipPush: skipPush, - skipEnrichUrl: skipEnrichUrl, - ); + }) => _chatApi.message.updateMessage( + message, + skipPush: skipPush, + skipEnrichUrl: skipEnrichUrl, + ); /// Partially update the given [messageId] /// Use [set] to define values to be set @@ -1863,13 +1820,12 @@ class StreamChatClient { Map? set, List? unset, bool skipEnrichUrl = false, - }) => - _chatApi.message.partialUpdateMessage( - messageId, - set: set, - unset: unset, - skipEnrichUrl: skipEnrichUrl, - ); + }) => _chatApi.message.partialUpdateMessage( + messageId, + set: set, + unset: unset, + skipEnrichUrl: skipEnrichUrl, + ); /// Deletes the given message. /// @@ -1897,8 +1853,7 @@ class StreamChatClient { } /// Get a message by [messageId] - Future getMessage(String messageId) => - _chatApi.message.getMessage(messageId); + Future getMessage(String messageId) => _chatApi.message.getMessage(messageId); /// Retrieves a list of messages by [messageIDs] /// from the given [channelId] of type [channelType] @@ -1906,34 +1861,31 @@ class StreamChatClient { String channelId, String channelType, List messageIDs, - ) => - _chatApi.message.getMessagesById( - channelId, - channelType, - messageIDs, - ); + ) => _chatApi.message.getMessagesById( + channelId, + channelType, + messageIDs, + ); /// Translates the [messageId] in provided [language] Future translateMessage( String messageId, String language, - ) => - _chatApi.message.translateMessage( - messageId, - language, - ); + ) => _chatApi.message.translateMessage( + messageId, + language, + ); /// Creates a draft for the given [channelId] of type [channelType]. Future createDraft( DraftMessage draft, String channelId, String channelType, - ) => - _chatApi.message.createDraft( - channelId, - channelType, - draft, - ); + ) => _chatApi.message.createDraft( + channelId, + channelType, + draft, + ); /// Retrieves a draft for the given [channelId] of type [channelType]. /// @@ -1942,12 +1894,11 @@ class StreamChatClient { String channelId, String channelType, { String? parentId, - }) => - _chatApi.message.getDraft( - channelId, - channelType, - parentId: parentId, - ); + }) => _chatApi.message.getDraft( + channelId, + channelType, + parentId: parentId, + ); /// Deletes a draft for the given [channelId] of type [channelType]. /// @@ -1956,23 +1907,21 @@ class StreamChatClient { String channelId, String channelType, { String? parentId, - }) => - _chatApi.message.deleteDraft( - channelId, - channelType, - parentId: parentId, - ); + }) => _chatApi.message.deleteDraft( + channelId, + channelType, + parentId: parentId, + ); /// Queries drafts for the current user. Future queryDrafts({ Filter? filter, SortOrder? sort, PaginationParams? pagination, - }) => - _chatApi.message.queryDrafts( - sort: sort, - pagination: pagination, - ); + }) => _chatApi.message.queryDrafts( + sort: sort, + pagination: pagination, + ); /// Retrieves all the active live locations of the current user. Future getActiveLiveLocations() async { @@ -2024,22 +1973,20 @@ class StreamChatClient { String channelId, String channelType, int cooldown, - ) async => - _chatApi.channel.enableSlowdown( - channelId, - channelType, - cooldown, - ); + ) async => _chatApi.channel.enableSlowdown( + channelId, + channelType, + cooldown, + ); /// Disables slow mode Future disableSlowdown( String channelId, String channelType, - ) async => - _chatApi.channel.disableSlowdown( - channelId, - channelType, - ); + ) async => _chatApi.channel.disableSlowdown( + channelId, + channelType, + ); /// Pins provided message /// [timeoutOrExpirationDate] can either be a [DateTime] or a value in seconds @@ -2049,9 +1996,7 @@ class StreamChatClient { Object? /*num|DateTime*/ timeoutOrExpirationDate, }) { assert(() { - if (timeoutOrExpirationDate is! DateTime && - timeoutOrExpirationDate != null && - timeoutOrExpirationDate is! num) { + if (timeoutOrExpirationDate is! DateTime && timeoutOrExpirationDate != null && timeoutOrExpirationDate is! num) { throw ArgumentError('Invalid timeout or Expiration date'); } return true; @@ -2075,17 +2020,15 @@ class StreamChatClient { } /// Unpins provided message - Future unpinMessage(String messageId) => - partialUpdateMessage( - messageId, - set: { - 'pinned': false, - }, - ); + Future unpinMessage(String messageId) => partialUpdateMessage( + messageId, + set: { + 'pinned': false, + }, + ); /// Get OpenGraph data of the given [url]. - Future enrichUrl(String url) => - _chatApi.general.enrichUrl(url); + Future enrichUrl(String url) => _chatApi.general.enrichUrl(url); /// Queries threads with the given [options] and [pagination] params. /// @@ -2095,13 +2038,12 @@ class StreamChatClient { SortOrder? sort, ThreadOptions options = const ThreadOptions(), PaginationParams pagination = const PaginationParams(), - }) => - _chatApi.threads.queryThreads( - filter: filter, - sort: sort, - options: options, - pagination: pagination, - ); + }) => _chatApi.threads.queryThreads( + filter: filter, + sort: sort, + options: options, + pagination: pagination, + ); /// Retrieves a thread with the given [messageId]. /// @@ -2109,11 +2051,10 @@ class StreamChatClient { Future getThread( String messageId, { ThreadOptions options = const ThreadOptions(), - }) => - _chatApi.threads.getThread( - messageId, - options: options, - ); + }) => _chatApi.threads.getThread( + messageId, + options: options, + ); /// Partially updates the thread with the given [messageId]. /// @@ -2123,12 +2064,11 @@ class StreamChatClient { String messageId, { Map? set, List? unset, - }) => - _chatApi.threads.partialUpdateThread( - messageId, - set: set, - unset: unset, - ); + }) => _chatApi.threads.partialUpdateThread( + messageId, + set: set, + unset: unset, + ); /// Pins the channel for the current user. Future pinChannel({ @@ -2421,18 +2361,17 @@ class ClientState { _eventsSubscription?.add( _client .on( - EventType.memberRemoved, - EventType.notificationRemovedFromChannel, - ) + EventType.memberRemoved, + EventType.notificationRemovedFromChannel, + ) .listen((event) async { - final isCurrentUser = event.user!.id == currentUser!.id; - if (isCurrentUser && event.channel != null) { - final eventChannel = event.channel!; - await _client.chatPersistenceClient - ?.deleteChannels([eventChannel.cid]); - channels.remove(eventChannel.cid)?.dispose(); - } - }), + final isCurrentUser = event.user!.id == currentUser!.id; + if (isCurrentUser && event.channel != null) { + final eventChannel = event.channel!; + await _client.chatPersistenceClient?.deleteChannels([eventChannel.cid]); + channels.remove(eventChannel.cid)?.dispose(); + } + }), ); } @@ -2440,14 +2379,14 @@ class ClientState { _eventsSubscription?.add( _client .on( - EventType.channelDeleted, - EventType.notificationChannelDeleted, - ) + EventType.channelDeleted, + EventType.notificationChannelDeleted, + ) .listen((Event event) async { - final eventChannel = event.channel!; - await _client.chatPersistenceClient?.deleteChannels([eventChannel.cid]); - channels.remove(eventChannel.cid)?.dispose(); - }), + final eventChannel = event.channel!; + await _client.chatPersistenceClient?.deleteChannels([eventChannel.cid]); + channels.remove(eventChannel.cid)?.dispose(); + }), ); } @@ -2528,7 +2467,7 @@ class ClientState { final newActiveLiveLocations = [ ...activeLiveLocations.where( (it) => it.messageId != location.messageId, - ) + ), ]; activeLiveLocations = newActiveLiveLocations; diff --git a/packages/stream_chat/lib/src/client/key_stroke_handler.dart b/packages/stream_chat/lib/src/client/key_stroke_handler.dart index 52878182fd..501c1f944d 100644 --- a/packages/stream_chat/lib/src/client/key_stroke_handler.dart +++ b/packages/stream_chat/lib/src/client/key_stroke_handler.dart @@ -87,13 +87,15 @@ class KeyStrokeHandler { _cancelKeyStrokeTimer(); _keyStrokeTimer = Timer(Duration(seconds: startTypingEventTimeout), () { - _stopTyping(parentId).then((_) { - if (completer.isCompleted) return; - completer.complete(); - }).onError((error, stackTrace) { - if (completer.isCompleted) return; - completer.completeError(error!, stackTrace); - }); + _stopTyping(parentId) + .then((_) { + if (completer.isCompleted) return; + completer.complete(); + }) + .onError((error, stackTrace) { + if (completer.isCompleted) return; + completer.completeError(error!, stackTrace); + }); }); // If the user is typing too long, it should call [onStartTyping] again. diff --git a/packages/stream_chat/lib/src/client/retry_policy.dart b/packages/stream_chat/lib/src/client/retry_policy.dart index b5d0804368..4c7d249e84 100644 --- a/packages/stream_chat/lib/src/client/retry_policy.dart +++ b/packages/stream_chat/lib/src/client/retry_policy.dart @@ -51,5 +51,6 @@ class RetryPolicy { StreamChatClient client, int attempt, StreamChatError? error, - ) shouldRetry; + ) + shouldRetry; } diff --git a/packages/stream_chat/lib/src/client/retry_queue.dart b/packages/stream_chat/lib/src/client/retry_queue.dart index c1f8841406..feb6b623f6 100644 --- a/packages/stream_chat/lib/src/client/retry_queue.dart +++ b/packages/stream_chat/lib/src/client/retry_queue.dart @@ -31,12 +31,16 @@ class RetryQueue { final _messageQueue = HeapPriorityQueue(_byDate); void _listenConnectionRecovered() { - client.on(EventType.connectionRecovered).distinct().listen((event) { - if (event.online == true) { - logger?.info('Connection recovered, retrying failed messages'); - channel.state?.retryFailedMessages(); - } - }).addTo(_compositeSubscription); + client + .on(EventType.connectionRecovered) + .distinct() + .listen((event) { + if (event.online == true) { + logger?.info('Connection recovered, retrying failed messages'); + channel.state?.retryFailedMessages(); + } + }) + .addTo(_compositeSubscription); } /// Add a list of messages. diff --git a/packages/stream_chat/lib/src/core/api/attachment_file_uploader.dart b/packages/stream_chat/lib/src/core/api/attachment_file_uploader.dart index 09fc25a081..9575dfa55d 100644 --- a/packages/stream_chat/lib/src/core/api/attachment_file_uploader.dart +++ b/packages/stream_chat/lib/src/core/api/attachment_file_uploader.dart @@ -4,9 +4,10 @@ import 'package:stream_chat/src/core/http/stream_http_client.dart'; import 'package:stream_chat/src/core/models/attachment_file.dart'; /// Signature for a function which provides instance of [AttachmentFileUploader] -typedef AttachmentFileUploaderProvider = AttachmentFileUploader Function( - StreamHttpClient httpClient, -); +typedef AttachmentFileUploaderProvider = + AttachmentFileUploader Function( + StreamHttpClient httpClient, + ); /// Class responsible for uploading images and files to a given channel abstract class AttachmentFileUploader { diff --git a/packages/stream_chat/lib/src/core/api/channel_api.dart b/packages/stream_chat/lib/src/core/api/channel_api.dart index 77c2425a55..b2addba7d6 100644 --- a/packages/stream_chat/lib/src/core/api/channel_api.dart +++ b/packages/stream_chat/lib/src/core/api/channel_api.dart @@ -17,8 +17,7 @@ class ChannelApi { final StreamHttpClient _client; - String _getChannelUrl(String channelId, String channelType) => - '/channels/$channelType/$channelId'; + String _getChannelUrl(String channelId, String channelType) => '/channels/$channelType/$channelId'; /// Query the API, get messages, members or other channel fields Future queryChannel( @@ -100,8 +99,7 @@ class ChannelApi { _getChannelUrl(channelId, channelType), data: { 'data': data, - if (message != null) - 'message': message.copyWith(updatedAt: DateTime.now()), + if (message != null) 'message': message.copyWith(updatedAt: DateTime.now()), }, ); return UpdateChannelResponse.fromJson(response.data); diff --git a/packages/stream_chat/lib/src/core/api/device_api.dart b/packages/stream_chat/lib/src/core/api/device_api.dart index b450ddc18e..1d75994036 100644 --- a/packages/stream_chat/lib/src/core/api/device_api.dart +++ b/packages/stream_chat/lib/src/core/api/device_api.dart @@ -37,8 +37,7 @@ class DeviceApi { data: { 'id': deviceId, 'push_provider': pushProvider.name, - if (pushProviderName != null && pushProviderName.isNotEmpty) - 'push_provider_name': pushProviderName, + if (pushProviderName != null && pushProviderName.isNotEmpty) 'push_provider_name': pushProviderName, }, ); return EmptyResponse.fromJson(response.data); diff --git a/packages/stream_chat/lib/src/core/api/general_api.dart b/packages/stream_chat/lib/src/core/api/general_api.dart index c364674463..16b4bdb9f8 100644 --- a/packages/stream_chat/lib/src/core/api/general_api.dart +++ b/packages/stream_chat/lib/src/core/api/general_api.dart @@ -60,8 +60,7 @@ class GeneralApi { 'filter_conditions': filter, if (sort != null) 'sort': sort, if (query != null) 'query': query, - if (messageFilters != null) - 'message_filter_conditions': messageFilters, + if (messageFilters != null) 'message_filter_conditions': messageFilters, if (pagination != null) ...pagination.toJson(), }), }, @@ -85,10 +84,7 @@ class GeneralApi { 'payload': jsonEncode({ 'type': channelType, 'filter_conditions': filter ?? {}, - if (channelId != null) - 'id': channelId - else if (members != null) - 'members': members, + if (channelId != null) 'id': channelId else if (members != null) 'members': members, if (sort != null) 'sort': sort, if (pagination != null) ...pagination.toJson(), }), diff --git a/packages/stream_chat/lib/src/core/api/message_api.dart b/packages/stream_chat/lib/src/core/api/message_api.dart index 3dc132ea1c..5f3fe356cf 100644 --- a/packages/stream_chat/lib/src/core/api/message_api.dart +++ b/packages/stream_chat/lib/src/core/api/message_api.dart @@ -257,6 +257,28 @@ class MessageApi { return QueryReactionsResponse.fromJson(response.data); } + /// Queries reactions for a [messageId] with optional [filter], [sort], + /// and [pagination]. + /// + /// Unlike [getReactions], this method supports filtering by reaction type, + /// user ID, or creation date, sorting, and cursor-based pagination. + Future queryReactions( + String messageId, { + Filter? filter, + SortOrder? sort, + PaginationParams? pagination, + }) async { + final response = await _client.post( + '/messages/$messageId/reactions', + data: jsonEncode({ + if (filter != null) 'filter': filter.toJson(), + if (sort != null) 'sort': sort.map((e) => e.toJson()).toList(), + if (pagination != null) ...pagination.toJson(), + }), + ); + return QueryReactionsResponse.fromJson(response.data); + } + /// Translates the [messageId] in provided [language] Future translateMessage( String messageId, diff --git a/packages/stream_chat/lib/src/core/api/requests.dart b/packages/stream_chat/lib/src/core/api/requests.dart index 0b5a704608..c3953e3233 100644 --- a/packages/stream_chat/lib/src/core/api/requests.dart +++ b/packages/stream_chat/lib/src/core/api/requests.dart @@ -31,13 +31,12 @@ class PaginationParams extends Equatable { this.createdAtBefore, this.createdAtAround, }) : assert( - offset == null || offset == 0 || next == null, - 'Cannot specify non-zero `offset` with `next` parameter', - ); + offset == null || offset == 0 || next == null, + 'Cannot specify non-zero `offset` with `next` parameter', + ); /// Create a new instance from a json - factory PaginationParams.fromJson(Map json) => - _$PaginationParamsFromJson(json); + factory PaginationParams.fromJson(Map json) => _$PaginationParamsFromJson(json); /// The amount of items requested from the APIs. final int limit; @@ -108,41 +107,38 @@ class PaginationParams extends Equatable { DateTime? createdAtBeforeOrEqual, DateTime? createdAtBefore, DateTime? createdAtAround, - }) => - PaginationParams( - limit: limit ?? this.limit, - offset: offset ?? this.offset, - idAround: idAround ?? this.idAround, - next: next ?? this.next, - greaterThan: greaterThan ?? this.greaterThan, - greaterThanOrEqual: greaterThanOrEqual ?? this.greaterThanOrEqual, - lessThan: lessThan ?? this.lessThan, - lessThanOrEqual: lessThanOrEqual ?? this.lessThanOrEqual, - createdAtAfterOrEqual: - createdAtAfterOrEqual ?? this.createdAtAfterOrEqual, - createdAtAfter: createdAtAfter ?? this.createdAtAfter, - createdAtBeforeOrEqual: - createdAtBeforeOrEqual ?? this.createdAtBeforeOrEqual, - createdAtBefore: createdAtBefore ?? this.createdAtBefore, - createdAtAround: createdAtAround ?? this.createdAtAround, - ); + }) => PaginationParams( + limit: limit ?? this.limit, + offset: offset ?? this.offset, + idAround: idAround ?? this.idAround, + next: next ?? this.next, + greaterThan: greaterThan ?? this.greaterThan, + greaterThanOrEqual: greaterThanOrEqual ?? this.greaterThanOrEqual, + lessThan: lessThan ?? this.lessThan, + lessThanOrEqual: lessThanOrEqual ?? this.lessThanOrEqual, + createdAtAfterOrEqual: createdAtAfterOrEqual ?? this.createdAtAfterOrEqual, + createdAtAfter: createdAtAfter ?? this.createdAtAfter, + createdAtBeforeOrEqual: createdAtBeforeOrEqual ?? this.createdAtBeforeOrEqual, + createdAtBefore: createdAtBefore ?? this.createdAtBefore, + createdAtAround: createdAtAround ?? this.createdAtAround, + ); @override List get props => [ - limit, - offset, - next, - idAround, - greaterThan, - greaterThanOrEqual, - lessThan, - lessThanOrEqual, - createdAtAfterOrEqual, - createdAtAfter, - createdAtBeforeOrEqual, - createdAtBefore, - createdAtAround, - ]; + limit, + offset, + next, + idAround, + greaterThan, + greaterThanOrEqual, + lessThan, + lessThanOrEqual, + createdAtAfterOrEqual, + createdAtAfter, + createdAtBeforeOrEqual, + createdAtBefore, + createdAtAround, + ]; } /// Request model for the [client.partialUpdateUser] api call. diff --git a/packages/stream_chat/lib/src/core/api/requests.g.dart b/packages/stream_chat/lib/src/core/api/requests.g.dart index 7a77503534..b04bb02430 100644 --- a/packages/stream_chat/lib/src/core/api/requests.g.dart +++ b/packages/stream_chat/lib/src/core/api/requests.g.dart @@ -6,80 +6,62 @@ part of 'requests.dart'; // JsonSerializableGenerator // ************************************************************************** -PaginationParams _$PaginationParamsFromJson(Map json) => - PaginationParams( - limit: (json['limit'] as num?)?.toInt() ?? 10, - offset: (json['offset'] as num?)?.toInt(), - next: json['next'] as String?, - idAround: json['id_around'] as String?, - greaterThan: json['id_gt'] as String?, - greaterThanOrEqual: json['id_gte'] as String?, - lessThan: json['id_lt'] as String?, - lessThanOrEqual: json['id_lte'] as String?, - createdAtAfterOrEqual: json['created_at_after_or_equal'] == null - ? null - : DateTime.parse(json['created_at_after_or_equal'] as String), - createdAtAfter: json['created_at_after'] == null - ? null - : DateTime.parse(json['created_at_after'] as String), - createdAtBeforeOrEqual: json['created_at_before_or_equal'] == null - ? null - : DateTime.parse(json['created_at_before_or_equal'] as String), - createdAtBefore: json['created_at_before'] == null - ? null - : DateTime.parse(json['created_at_before'] as String), - createdAtAround: json['created_at_around'] == null - ? null - : DateTime.parse(json['created_at_around'] as String), - ); +PaginationParams _$PaginationParamsFromJson(Map json) => PaginationParams( + limit: (json['limit'] as num?)?.toInt() ?? 10, + offset: (json['offset'] as num?)?.toInt(), + next: json['next'] as String?, + idAround: json['id_around'] as String?, + greaterThan: json['id_gt'] as String?, + greaterThanOrEqual: json['id_gte'] as String?, + lessThan: json['id_lt'] as String?, + lessThanOrEqual: json['id_lte'] as String?, + createdAtAfterOrEqual: json['created_at_after_or_equal'] == null + ? null + : DateTime.parse(json['created_at_after_or_equal'] as String), + createdAtAfter: json['created_at_after'] == null ? null : DateTime.parse(json['created_at_after'] as String), + createdAtBeforeOrEqual: json['created_at_before_or_equal'] == null + ? null + : DateTime.parse(json['created_at_before_or_equal'] as String), + createdAtBefore: json['created_at_before'] == null ? null : DateTime.parse(json['created_at_before'] as String), + createdAtAround: json['created_at_around'] == null ? null : DateTime.parse(json['created_at_around'] as String), +); -Map _$PaginationParamsToJson(PaginationParams instance) => - { - 'limit': instance.limit, - if (instance.offset case final value?) 'offset': value, - if (instance.next case final value?) 'next': value, - if (instance.idAround case final value?) 'id_around': value, - if (instance.greaterThan case final value?) 'id_gt': value, - if (instance.greaterThanOrEqual case final value?) 'id_gte': value, - if (instance.lessThan case final value?) 'id_lt': value, - if (instance.lessThanOrEqual case final value?) 'id_lte': value, - if (instance.createdAtAfterOrEqual?.toIso8601String() case final value?) - 'created_at_after_or_equal': value, - if (instance.createdAtAfter?.toIso8601String() case final value?) - 'created_at_after': value, - if (instance.createdAtBeforeOrEqual?.toIso8601String() case final value?) - 'created_at_before_or_equal': value, - if (instance.createdAtBefore?.toIso8601String() case final value?) - 'created_at_before': value, - if (instance.createdAtAround?.toIso8601String() case final value?) - 'created_at_around': value, - }; +Map _$PaginationParamsToJson(PaginationParams instance) => { + 'limit': instance.limit, + if (instance.offset case final value?) 'offset': value, + if (instance.next case final value?) 'next': value, + if (instance.idAround case final value?) 'id_around': value, + if (instance.greaterThan case final value?) 'id_gt': value, + if (instance.greaterThanOrEqual case final value?) 'id_gte': value, + if (instance.lessThan case final value?) 'id_lt': value, + if (instance.lessThanOrEqual case final value?) 'id_lte': value, + if (instance.createdAtAfterOrEqual?.toIso8601String() case final value?) 'created_at_after_or_equal': value, + if (instance.createdAtAfter?.toIso8601String() case final value?) 'created_at_after': value, + if (instance.createdAtBeforeOrEqual?.toIso8601String() case final value?) 'created_at_before_or_equal': value, + if (instance.createdAtBefore?.toIso8601String() case final value?) 'created_at_before': value, + if (instance.createdAtAround?.toIso8601String() case final value?) 'created_at_around': value, +}; -Map _$PartialUpdateUserRequestToJson( - PartialUpdateUserRequest instance) => - { - 'stringify': instance.stringify, - 'hash_code': instance.hashCode, - 'id': instance.id, - 'set': instance.set, - 'unset': instance.unset, - 'props': instance.props, - }; +Map _$PartialUpdateUserRequestToJson(PartialUpdateUserRequest instance) => { + 'stringify': instance.stringify, + 'hash_code': instance.hashCode, + 'id': instance.id, + 'set': instance.set, + 'unset': instance.unset, + 'props': instance.props, +}; -Map _$ThreadOptionsToJson(ThreadOptions instance) => - { - 'stringify': instance.stringify, - 'hash_code': instance.hashCode, - 'watch': instance.watch, - 'reply_limit': instance.replyLimit, - 'participant_limit': instance.participantLimit, - 'member_limit': instance.memberLimit, - 'props': instance.props, - }; +Map _$ThreadOptionsToJson(ThreadOptions instance) => { + 'stringify': instance.stringify, + 'hash_code': instance.hashCode, + 'watch': instance.watch, + 'reply_limit': instance.replyLimit, + 'participant_limit': instance.participantLimit, + 'member_limit': instance.memberLimit, + 'props': instance.props, +}; -Map _$MemberUpdatePayloadToJson( - MemberUpdatePayload instance) => - { - if (instance.archived case final value?) 'archived': value, - if (instance.pinned case final value?) 'pinned': value, - }; +Map _$MemberUpdatePayloadToJson(MemberUpdatePayload instance) => { + if (instance.archived case final value?) 'archived': value, + if (instance.pinned case final value?) 'pinned': value, +}; diff --git a/packages/stream_chat/lib/src/core/api/responses.dart b/packages/stream_chat/lib/src/core/api/responses.dart index 2a2985d7c5..c5bd2e92ab 100644 --- a/packages/stream_chat/lib/src/core/api/responses.dart +++ b/packages/stream_chat/lib/src/core/api/responses.dart @@ -45,14 +45,14 @@ class ErrorResponse extends _BaseResponse { String? moreInfo; /// Create a new instance from a json - static ErrorResponse fromJson(Map json) => - _$ErrorResponseFromJson(json); + static ErrorResponse fromJson(Map json) => _$ErrorResponseFromJson(json); /// Serialize to json Map toJson() => _$ErrorResponseToJson(this); @override - String toString() => 'ErrorResponse(code: $code, ' + String toString() => + 'ErrorResponse(code: $code, ' 'message: $message, ' 'statusCode: $statusCode, ' 'moreInfo: $moreInfo)'; @@ -66,8 +66,7 @@ class SyncResponse extends _BaseResponse { late List events; /// Create a new instance from a json - static SyncResponse fromJson(Map json) => - _$SyncResponseFromJson(json); + static SyncResponse fromJson(Map json) => _$SyncResponseFromJson(json); } /// Model response for [StreamChatClient.queryChannels] api call @@ -78,16 +77,14 @@ class QueryChannelsResponse extends _BaseResponse { late List channels; /// Create a new instance from a json - static QueryChannelsResponse fromJson(Map json) => - _$QueryChannelsResponseFromJson(json); + static QueryChannelsResponse fromJson(Map json) => _$QueryChannelsResponseFromJson(json); } /// Model response for [StreamChatClient.queryChannels] api call @JsonSerializable(createToJson: false) class TranslateMessageResponse extends MessageResponse { /// Create a new instance from a json - static TranslateMessageResponse fromJson(Map json) => - _$TranslateMessageResponseFromJson(json); + static TranslateMessageResponse fromJson(Map json) => _$TranslateMessageResponseFromJson(json); } /// Model response for [StreamChatClient.queryChannels] api call @@ -98,8 +95,7 @@ class QueryMembersResponse extends _BaseResponse { late List members; /// Create a new instance from a json - static QueryMembersResponse fromJson(Map json) => - _$QueryMembersResponseFromJson(json); + static QueryMembersResponse fromJson(Map json) => _$QueryMembersResponseFromJson(json); } /// Model response for update member API calls, such as @@ -110,8 +106,7 @@ class PartialUpdateMemberResponse extends _BaseResponse { late Member channelMember; /// Create a new instance from a json - static PartialUpdateMemberResponse fromJson(Map json) => - _$PartialUpdateMemberResponseFromJson(json); + static PartialUpdateMemberResponse fromJson(Map json) => _$PartialUpdateMemberResponseFromJson(json); } /// Model response for [StreamChatClient.queryUsers] api call @@ -122,8 +117,7 @@ class QueryUsersResponse extends _BaseResponse { late List users; /// Create a new instance from a json - static QueryUsersResponse fromJson(Map json) => - _$QueryUsersResponseFromJson(json); + static QueryUsersResponse fromJson(Map json) => _$QueryUsersResponseFromJson(json); } /// Model response for [StreamChatClient.queryBannedUsers] api call @@ -134,20 +128,23 @@ class QueryBannedUsersResponse extends _BaseResponse { late List bans; /// Create a new instance from a json - static QueryBannedUsersResponse fromJson(Map json) => - _$QueryBannedUsersResponseFromJson(json); + static QueryBannedUsersResponse fromJson(Map json) => _$QueryBannedUsersResponseFromJson(json); } -/// Model response for [channel.getReactions] api call +/// Model response for [channel.getReactions] or [channel.queryReactions] api call @JsonSerializable(createToJson: false) class QueryReactionsResponse extends _BaseResponse { /// List of reactions returned by the query @JsonKey(defaultValue: []) late List reactions; + /// The cursor for the next page of results. + /// + /// Will be `null` if there are no more results. + late String? next; + /// Create a new instance from a json - static QueryReactionsResponse fromJson(Map json) => - _$QueryReactionsResponseFromJson(json); + static QueryReactionsResponse fromJson(Map json) => _$QueryReactionsResponseFromJson(json); } /// Model response for [Channel.getReplies] api call @@ -158,8 +155,7 @@ class QueryRepliesResponse extends _BaseResponse { late List messages; /// Create a new instance from a json - static QueryRepliesResponse fromJson(Map json) => - _$QueryRepliesResponseFromJson(json); + static QueryRepliesResponse fromJson(Map json) => _$QueryRepliesResponseFromJson(json); } /// Model response for [StreamChatClient.getDevices] api call @@ -170,8 +166,7 @@ class ListDevicesResponse extends _BaseResponse { late List devices; /// Create a new instance from a json - static ListDevicesResponse fromJson(Map json) => - _$ListDevicesResponseFromJson(json); + static ListDevicesResponse fromJson(Map json) => _$ListDevicesResponseFromJson(json); } /// Base Model response for [Channel.sendImage] and [Channel.sendFile] api call. @@ -181,8 +176,7 @@ class SendAttachmentResponse extends _BaseResponse { late String? file; /// Create a new instance from a json - static SendAttachmentResponse fromJson(Map json) => - _$SendAttachmentResponseFromJson(json); + static SendAttachmentResponse fromJson(Map json) => _$SendAttachmentResponseFromJson(json); } /// Model response for [Channel.sendFile] api call @@ -194,8 +188,7 @@ class SendFileResponse extends SendAttachmentResponse { String? thumbUrl; /// Create a new instance from a json - static SendFileResponse fromJson(Map json) => - _$SendFileResponseFromJson(json); + static SendFileResponse fromJson(Map json) => _$SendFileResponseFromJson(json); } /// Model response for [Channel.sendImage] api call @@ -214,8 +207,7 @@ class SendReactionResponse extends MessageResponse { late Reaction reaction; /// Create a new instance from a json - static SendReactionResponse fromJson(Map json) => - _$SendReactionResponseFromJson(json); + static SendReactionResponse fromJson(Map json) => _$SendReactionResponseFromJson(json); } /// Model response for [StreamChatClient.connectGuestUser] api call @@ -228,8 +220,7 @@ class ConnectGuestUserResponse extends _BaseResponse { late User user; /// Create a new instance from a json - static ConnectGuestUserResponse fromJson(Map json) => - _$ConnectGuestUserResponseFromJson(json); + static ConnectGuestUserResponse fromJson(Map json) => _$ConnectGuestUserResponseFromJson(json); } /// Model response for [StreamChatClient.updateUser] api call @@ -240,8 +231,7 @@ class UpdateUsersResponse extends _BaseResponse { late Map users; /// Create a new instance from a json - static UpdateUsersResponse fromJson(Map json) => - _$UpdateUsersResponseFromJson(json); + static UpdateUsersResponse fromJson(Map json) => _$UpdateUsersResponseFromJson(json); } /// Base Model response for message based api calls. @@ -254,16 +244,14 @@ class MessageResponse extends _BaseResponse { @JsonSerializable(createToJson: false) class UpdateMessageResponse extends MessageResponse { /// Create a new instance from a json - static UpdateMessageResponse fromJson(Map json) => - _$UpdateMessageResponseFromJson(json); + static UpdateMessageResponse fromJson(Map json) => _$UpdateMessageResponseFromJson(json); } /// Model response for [Channel.sendMessage] api call @JsonSerializable(createToJson: false) class SendMessageResponse extends MessageResponse { /// Create a new instance from a json - static SendMessageResponse fromJson(Map json) => - _$SendMessageResponseFromJson(json); + static SendMessageResponse fromJson(Map json) => _$SendMessageResponseFromJson(json); } /// Model response for [StreamChatClient.getMessage] api call @@ -297,8 +285,7 @@ class SearchMessagesResponse extends _BaseResponse { late String? previous; /// Create a new instance from a json - static SearchMessagesResponse fromJson(Map json) => - _$SearchMessagesResponseFromJson(json); + static SearchMessagesResponse fromJson(Map json) => _$SearchMessagesResponseFromJson(json); } /// Model response for [Channel.getMessagesById] api call @@ -309,8 +296,7 @@ class GetMessagesByIdResponse extends _BaseResponse { late List messages; /// Create a new instance from a json - static GetMessagesByIdResponse fromJson(Map json) => - _$GetMessagesByIdResponseFromJson(json); + static GetMessagesByIdResponse fromJson(Map json) => _$GetMessagesByIdResponseFromJson(json); } /// Model response for [Channel.update] api call @@ -326,8 +312,7 @@ class UpdateChannelResponse extends _BaseResponse { Message? message; /// Create a new instance from a json - static UpdateChannelResponse fromJson(Map json) => - _$UpdateChannelResponseFromJson(json); + static UpdateChannelResponse fromJson(Map json) => _$UpdateChannelResponseFromJson(json); } /// Model response for [Channel.updatePartial] api call @@ -358,8 +343,7 @@ class InviteMembersResponse extends _BaseResponse { Message? message; /// Create a new instance from a json - static InviteMembersResponse fromJson(Map json) => - _$InviteMembersResponseFromJson(json); + static InviteMembersResponse fromJson(Map json) => _$InviteMembersResponseFromJson(json); } /// Model response for [Channel.removeMembers] api call @@ -376,8 +360,7 @@ class RemoveMembersResponse extends _BaseResponse { Message? message; /// Create a new instance from a json - static RemoveMembersResponse fromJson(Map json) => - _$RemoveMembersResponseFromJson(json); + static RemoveMembersResponse fromJson(Map json) => _$RemoveMembersResponseFromJson(json); } /// Model response for [Channel.sendAction] api call @@ -387,8 +370,7 @@ class SendActionResponse extends _BaseResponse { Message? message; /// Create a new instance from a json - static SendActionResponse fromJson(Map json) => - _$SendActionResponseFromJson(json); + static SendActionResponse fromJson(Map json) => _$SendActionResponseFromJson(json); } /// Model response for [Channel.addMembers] api call @@ -405,8 +387,7 @@ class AddMembersResponse extends _BaseResponse { Message? message; /// Create a new instance from a json - static AddMembersResponse fromJson(Map json) => - _$AddMembersResponseFromJson(json); + static AddMembersResponse fromJson(Map json) => _$AddMembersResponseFromJson(json); } /// Model response for [Channel.acceptInvite] api call @@ -423,8 +404,7 @@ class AcceptInviteResponse extends _BaseResponse { Message? message; /// Create a new instance from a json - static AcceptInviteResponse fromJson(Map json) => - _$AcceptInviteResponseFromJson(json); + static AcceptInviteResponse fromJson(Map json) => _$AcceptInviteResponseFromJson(json); } /// Model response for [Channel.rejectInvite] api call @@ -441,16 +421,14 @@ class RejectInviteResponse extends _BaseResponse { Message? message; /// Create a new instance from a json - static RejectInviteResponse fromJson(Map json) => - _$RejectInviteResponseFromJson(json); + static RejectInviteResponse fromJson(Map json) => _$RejectInviteResponseFromJson(json); } /// Model response for empty responses @JsonSerializable(createToJson: false) class EmptyResponse extends _BaseResponse { /// Create a new instance from a json - static EmptyResponse fromJson(Map json) => - _$EmptyResponseFromJson(json); + static EmptyResponse fromJson(Map json) => _$EmptyResponseFromJson(json); } /// Model response for [Channel.query] api call @@ -476,8 +454,7 @@ class ChannelStateResponse extends _BaseResponse { late List read; /// Create a new instance from a json - static ChannelStateResponse fromJson(Map json) => - _$ChannelStateResponseFromJson(json); + static ChannelStateResponse fromJson(Map json) => _$ChannelStateResponseFromJson(json); } /// Model response for [Client.enrichUrl] api call. @@ -516,8 +493,7 @@ class OGAttachmentResponse extends _BaseResponse { String? type; /// Create a new instance from a [json]. - static OGAttachmentResponse fromJson(Map json) => - _$OGAttachmentResponseFromJson(json); + static OGAttachmentResponse fromJson(Map json) => _$OGAttachmentResponseFromJson(json); } /// Contains information about a [User] that was banned from a [Channel] or App. @@ -535,8 +511,7 @@ class UserBlockResponse extends _BaseResponse { late DateTime createdAt; /// Create a new instance from a json - static UserBlockResponse fromJson(Map json) => - _$UserBlockResponseFromJson(json); + static UserBlockResponse fromJson(Map json) => _$UserBlockResponseFromJson(json); } /// Model response for [StreamChatClient.queryBlockedUsers] api call @@ -547,8 +522,7 @@ class BlockedUsersResponse extends _BaseResponse { late List blocks; /// Create a new instance from a json - static BlockedUsersResponse fromJson(Map json) => - _$BlockedUsersResponseFromJson(json); + static BlockedUsersResponse fromJson(Map json) => _$BlockedUsersResponseFromJson(json); } /// Model response for [StreamChatClient.createPoll] api call @@ -558,8 +532,7 @@ class CreatePollResponse extends _BaseResponse { late Poll poll; /// Create a new instance from a json - static CreatePollResponse fromJson(Map json) => - _$CreatePollResponseFromJson(json); + static CreatePollResponse fromJson(Map json) => _$CreatePollResponseFromJson(json); } /// Model response for [StreamChatClient.getPoll] api call @@ -569,8 +542,7 @@ class GetPollResponse extends _BaseResponse { late Poll poll; /// Create a new instance from a json - static GetPollResponse fromJson(Map json) => - _$GetPollResponseFromJson(json); + static GetPollResponse fromJson(Map json) => _$GetPollResponseFromJson(json); } /// Model response for [StreamChatClient.updatePoll] api call @@ -580,8 +552,7 @@ class UpdatePollResponse extends _BaseResponse { late Poll poll; /// Create a new instance from a json - static UpdatePollResponse fromJson(Map json) => - _$UpdatePollResponseFromJson(json); + static UpdatePollResponse fromJson(Map json) => _$UpdatePollResponseFromJson(json); } /// Model response for [StreamChatClient.createPollOption] api call @@ -591,8 +562,7 @@ class CreatePollOptionResponse extends _BaseResponse { late PollOption pollOption; /// Create a new instance from a json - static CreatePollOptionResponse fromJson(Map json) => - _$CreatePollOptionResponseFromJson(json); + static CreatePollOptionResponse fromJson(Map json) => _$CreatePollOptionResponseFromJson(json); } /// Model response for [StreamChatClient.getPollOption] api call @@ -602,8 +572,7 @@ class GetPollOptionResponse extends _BaseResponse { late PollOption pollOption; /// Create a new instance from a json - static GetPollOptionResponse fromJson(Map json) => - _$GetPollOptionResponseFromJson(json); + static GetPollOptionResponse fromJson(Map json) => _$GetPollOptionResponseFromJson(json); } /// Model response for [StreamChatClient.updatePollOption] api call @@ -613,8 +582,7 @@ class UpdatePollOptionResponse extends _BaseResponse { late PollOption pollOption; /// Create a new instance from a json - static UpdatePollOptionResponse fromJson(Map json) => - _$UpdatePollOptionResponseFromJson(json); + static UpdatePollOptionResponse fromJson(Map json) => _$UpdatePollOptionResponseFromJson(json); } /// Model response for [StreamChatClient.castPollVote] api call @@ -624,8 +592,7 @@ class CastPollVoteResponse extends _BaseResponse { late PollVote vote; /// Create a new instance from a json - static CastPollVoteResponse fromJson(Map json) => - _$CastPollVoteResponseFromJson(json); + static CastPollVoteResponse fromJson(Map json) => _$CastPollVoteResponseFromJson(json); } /// Model response for [StreamChatClient.removePollVote] api call @@ -635,8 +602,7 @@ class RemovePollVoteResponse extends EmptyResponse { late PollVote vote; /// Create a new instance from a json - static RemovePollVoteResponse fromJson(Map json) => - _$RemovePollVoteResponseFromJson(json); + static RemovePollVoteResponse fromJson(Map json) => _$RemovePollVoteResponseFromJson(json); } /// Model response for [StreamChatClient.queryPolls] api call @@ -650,8 +616,7 @@ class QueryPollsResponse extends _BaseResponse { late String? next; /// Create a new instance from a json - static QueryPollsResponse fromJson(Map json) => - _$QueryPollsResponseFromJson(json); + static QueryPollsResponse fromJson(Map json) => _$QueryPollsResponseFromJson(json); } /// Model response for [StreamChatClient.queryPollVotes] api call @@ -665,8 +630,7 @@ class QueryPollVotesResponse extends _BaseResponse { late String? next; /// Create a new instance from a json - static QueryPollVotesResponse fromJson(Map json) => - _$QueryPollVotesResponseFromJson(json); + static QueryPollVotesResponse fromJson(Map json) => _$QueryPollVotesResponseFromJson(json); } /// Model response for [StreamChatClient.getThread] api call @@ -676,8 +640,7 @@ class GetThreadResponse extends _BaseResponse { late Thread thread; /// Create a new instance from a json - static GetThreadResponse fromJson(Map json) => - _$GetThreadResponseFromJson(json); + static GetThreadResponse fromJson(Map json) => _$GetThreadResponseFromJson(json); } /// Model response for [StreamChatClient.updateThread] api call @@ -687,8 +650,7 @@ class UpdateThreadResponse extends _BaseResponse { late Thread thread; /// Create a new instance from a json - static UpdateThreadResponse fromJson(Map json) => - _$UpdateThreadResponseFromJson(json); + static UpdateThreadResponse fromJson(Map json) => _$UpdateThreadResponseFromJson(json); } /// Model response for [StreamChatClient.queryThreads] api call @@ -702,8 +664,7 @@ class QueryThreadsResponse extends _BaseResponse { late String? next; /// Create a new instance from a json - static QueryThreadsResponse fromJson(Map json) => - _$QueryThreadsResponseFromJson(json); + static QueryThreadsResponse fromJson(Map json) => _$QueryThreadsResponseFromJson(json); } /// Base Model response for draft based api calls. @@ -716,16 +677,14 @@ class DraftResponse extends _BaseResponse { @JsonSerializable(createToJson: false) class CreateDraftResponse extends DraftResponse { /// Create a new instance from a json - static CreateDraftResponse fromJson(Map json) => - _$CreateDraftResponseFromJson(json); + static CreateDraftResponse fromJson(Map json) => _$CreateDraftResponseFromJson(json); } /// Model response for [StreamChatClient.getDraft] api call @JsonSerializable(createToJson: false) class GetDraftResponse extends DraftResponse { /// Create a new instance from a json - static GetDraftResponse fromJson(Map json) => - _$GetDraftResponseFromJson(json); + static GetDraftResponse fromJson(Map json) => _$GetDraftResponseFromJson(json); } /// Model response for [StreamChatClient.queryDrafts] api call @@ -739,8 +698,7 @@ class QueryDraftsResponse extends _BaseResponse { late String? next; /// Create a new instance from a json - static QueryDraftsResponse fromJson(Map json) => - _$QueryDraftsResponseFromJson(json); + static QueryDraftsResponse fromJson(Map json) => _$QueryDraftsResponseFromJson(json); } /// Base Model response for draft based api calls. @@ -753,16 +711,14 @@ class MessageReminderResponse extends _BaseResponse { @JsonSerializable(createToJson: false) class CreateReminderResponse extends MessageReminderResponse { /// Create a new instance from a json - static CreateReminderResponse fromJson(Map json) => - _$CreateReminderResponseFromJson(json); + static CreateReminderResponse fromJson(Map json) => _$CreateReminderResponseFromJson(json); } /// Model response for [StreamChatClient.updateReminder] api call @JsonSerializable(createToJson: false) class UpdateReminderResponse extends MessageReminderResponse { /// Create a new instance from a json - static UpdateReminderResponse fromJson(Map json) => - _$UpdateReminderResponseFromJson(json); + static UpdateReminderResponse fromJson(Map json) => _$UpdateReminderResponseFromJson(json); } /// Model response for [StreamChatClient.queryReminders] api call @@ -776,8 +732,7 @@ class QueryRemindersResponse extends _BaseResponse { late String? next; /// Create a new instance from a json - static QueryRemindersResponse fromJson(Map json) => - _$QueryRemindersResponseFromJson(json); + static QueryRemindersResponse fromJson(Map json) => _$QueryRemindersResponseFromJson(json); } /// Model response for [StreamChatClient.getUnreadCount] api call @@ -802,8 +757,7 @@ class GetUnreadCountResponse extends _BaseResponse { late List threads; /// Create a new instance from a json - static GetUnreadCountResponse fromJson(Map json) => - _$GetUnreadCountResponseFromJson(json); + static GetUnreadCountResponse fromJson(Map json) => _$GetUnreadCountResponseFromJson(json); } /// Model response for [StreamChatClient.setPushPreferences] api call diff --git a/packages/stream_chat/lib/src/core/api/responses.g.dart b/packages/stream_chat/lib/src/core/api/responses.g.dart index adfa731942..4c54864009 100644 --- a/packages/stream_chat/lib/src/core/api/responses.g.dart +++ b/packages/stream_chat/lib/src/core/api/responses.g.dart @@ -6,503 +6,418 @@ part of 'responses.dart'; // JsonSerializableGenerator // ************************************************************************** -ErrorResponse _$ErrorResponseFromJson(Map json) => - ErrorResponse() - ..duration = json['duration'] as String? - ..code = (json['code'] as num?)?.toInt() - ..message = json['message'] as String? - ..statusCode = (json['StatusCode'] as num?)?.toInt() - ..moreInfo = json['more_info'] as String?; - -Map _$ErrorResponseToJson(ErrorResponse instance) => - { - 'duration': instance.duration, - 'code': instance.code, - 'message': instance.message, - 'StatusCode': instance.statusCode, - 'more_info': instance.moreInfo, - }; +ErrorResponse _$ErrorResponseFromJson(Map json) => ErrorResponse() + ..duration = json['duration'] as String? + ..code = (json['code'] as num?)?.toInt() + ..message = json['message'] as String? + ..statusCode = (json['StatusCode'] as num?)?.toInt() + ..moreInfo = json['more_info'] as String?; + +Map _$ErrorResponseToJson(ErrorResponse instance) => { + 'duration': instance.duration, + 'code': instance.code, + 'message': instance.message, + 'StatusCode': instance.statusCode, + 'more_info': instance.moreInfo, +}; SyncResponse _$SyncResponseFromJson(Map json) => SyncResponse() ..duration = json['duration'] as String? - ..events = (json['events'] as List?) - ?.map((e) => Event.fromJson(e as Map)) - .toList() ?? - []; + ..events = (json['events'] as List?)?.map((e) => Event.fromJson(e as Map)).toList() ?? []; QueryChannelsResponse _$QueryChannelsResponseFromJson( - Map json) => - QueryChannelsResponse() - ..duration = json['duration'] as String? - ..channels = (json['channels'] as List?) - ?.map((e) => ChannelState.fromJson(e as Map)) - .toList() ?? - []; + Map json, +) => QueryChannelsResponse() + ..duration = json['duration'] as String? + ..channels = + (json['channels'] as List?)?.map((e) => ChannelState.fromJson(e as Map)).toList() ?? []; TranslateMessageResponse _$TranslateMessageResponseFromJson( - Map json) => - TranslateMessageResponse() - ..duration = json['duration'] as String? - ..message = Message.fromJson(json['message'] as Map); + Map json, +) => TranslateMessageResponse() + ..duration = json['duration'] as String? + ..message = Message.fromJson(json['message'] as Map); QueryMembersResponse _$QueryMembersResponseFromJson( - Map json) => - QueryMembersResponse() - ..duration = json['duration'] as String? - ..members = (json['members'] as List?) - ?.map((e) => Member.fromJson(e as Map)) - .toList() ?? - []; + Map json, +) => QueryMembersResponse() + ..duration = json['duration'] as String? + ..members = + (json['members'] as List?)?.map((e) => Member.fromJson(e as Map)).toList() ?? []; PartialUpdateMemberResponse _$PartialUpdateMemberResponseFromJson( - Map json) => - PartialUpdateMemberResponse() - ..duration = json['duration'] as String? - ..channelMember = - Member.fromJson(json['channel_member'] as Map); - -QueryUsersResponse _$QueryUsersResponseFromJson(Map json) => - QueryUsersResponse() - ..duration = json['duration'] as String? - ..users = (json['users'] as List?) - ?.map((e) => User.fromJson(e as Map)) - .toList() ?? - []; + Map json, +) => PartialUpdateMemberResponse() + ..duration = json['duration'] as String? + ..channelMember = Member.fromJson( + json['channel_member'] as Map, + ); + +QueryUsersResponse _$QueryUsersResponseFromJson(Map json) => QueryUsersResponse() + ..duration = json['duration'] as String? + ..users = (json['users'] as List?)?.map((e) => User.fromJson(e as Map)).toList() ?? []; QueryBannedUsersResponse _$QueryBannedUsersResponseFromJson( - Map json) => - QueryBannedUsersResponse() - ..duration = json['duration'] as String? - ..bans = (json['bans'] as List?) - ?.map((e) => BannedUser.fromJson(e as Map)) - .toList() ?? - []; + Map json, +) => QueryBannedUsersResponse() + ..duration = json['duration'] as String? + ..bans = (json['bans'] as List?)?.map((e) => BannedUser.fromJson(e as Map)).toList() ?? []; QueryReactionsResponse _$QueryReactionsResponseFromJson( - Map json) => - QueryReactionsResponse() - ..duration = json['duration'] as String? - ..reactions = (json['reactions'] as List?) - ?.map((e) => Reaction.fromJson(e as Map)) - .toList() ?? - []; + Map json, +) => QueryReactionsResponse() + ..duration = json['duration'] as String? + ..reactions = + (json['reactions'] as List?)?.map((e) => Reaction.fromJson(e as Map)).toList() ?? [] + ..next = json['next'] as String?; QueryRepliesResponse _$QueryRepliesResponseFromJson( - Map json) => - QueryRepliesResponse() - ..duration = json['duration'] as String? - ..messages = (json['messages'] as List?) - ?.map((e) => Message.fromJson(e as Map)) - .toList() ?? - []; - -ListDevicesResponse _$ListDevicesResponseFromJson(Map json) => - ListDevicesResponse() - ..duration = json['duration'] as String? - ..devices = (json['devices'] as List?) - ?.map((e) => Device.fromJson(e as Map)) - .toList() ?? - []; + Map json, +) => QueryRepliesResponse() + ..duration = json['duration'] as String? + ..messages = + (json['messages'] as List?)?.map((e) => Message.fromJson(e as Map)).toList() ?? []; + +ListDevicesResponse _$ListDevicesResponseFromJson(Map json) => ListDevicesResponse() + ..duration = json['duration'] as String? + ..devices = + (json['devices'] as List?)?.map((e) => Device.fromJson(e as Map)).toList() ?? []; SendAttachmentResponse _$SendAttachmentResponseFromJson( - Map json) => - SendAttachmentResponse() - ..duration = json['duration'] as String? - ..file = json['file'] as String?; + Map json, +) => SendAttachmentResponse() + ..duration = json['duration'] as String? + ..file = json['file'] as String?; -SendFileResponse _$SendFileResponseFromJson(Map json) => - SendFileResponse() - ..duration = json['duration'] as String? - ..file = json['file'] as String? - ..thumbUrl = json['thumb_url'] as String?; +SendFileResponse _$SendFileResponseFromJson(Map json) => SendFileResponse() + ..duration = json['duration'] as String? + ..file = json['file'] as String? + ..thumbUrl = json['thumb_url'] as String?; SendReactionResponse _$SendReactionResponseFromJson( - Map json) => - SendReactionResponse() - ..duration = json['duration'] as String? - ..message = Message.fromJson(json['message'] as Map) - ..reaction = Reaction.fromJson(json['reaction'] as Map); + Map json, +) => SendReactionResponse() + ..duration = json['duration'] as String? + ..message = Message.fromJson(json['message'] as Map) + ..reaction = Reaction.fromJson(json['reaction'] as Map); ConnectGuestUserResponse _$ConnectGuestUserResponseFromJson( - Map json) => - ConnectGuestUserResponse() - ..duration = json['duration'] as String? - ..accessToken = json['access_token'] as String - ..user = User.fromJson(json['user'] as Map); - -UpdateUsersResponse _$UpdateUsersResponseFromJson(Map json) => - UpdateUsersResponse() - ..duration = json['duration'] as String? - ..users = (json['users'] as Map?)?.map( - (k, e) => MapEntry(k, User.fromJson(e as Map)), - ) ?? - {}; + Map json, +) => ConnectGuestUserResponse() + ..duration = json['duration'] as String? + ..accessToken = json['access_token'] as String + ..user = User.fromJson(json['user'] as Map); + +UpdateUsersResponse _$UpdateUsersResponseFromJson(Map json) => UpdateUsersResponse() + ..duration = json['duration'] as String? + ..users = + (json['users'] as Map?)?.map( + (k, e) => MapEntry(k, User.fromJson(e as Map)), + ) ?? + {}; UpdateMessageResponse _$UpdateMessageResponseFromJson( - Map json) => - UpdateMessageResponse() - ..duration = json['duration'] as String? - ..message = Message.fromJson(json['message'] as Map); - -SendMessageResponse _$SendMessageResponseFromJson(Map json) => - SendMessageResponse() - ..duration = json['duration'] as String? - ..message = Message.fromJson(json['message'] as Map); - -GetMessageResponse _$GetMessageResponseFromJson(Map json) => - GetMessageResponse() - ..duration = json['duration'] as String? - ..message = Message.fromJson(json['message'] as Map) - ..channel = json['channel'] == null - ? null - : ChannelModel.fromJson(json['channel'] as Map); + Map json, +) => UpdateMessageResponse() + ..duration = json['duration'] as String? + ..message = Message.fromJson(json['message'] as Map); + +SendMessageResponse _$SendMessageResponseFromJson(Map json) => SendMessageResponse() + ..duration = json['duration'] as String? + ..message = Message.fromJson(json['message'] as Map); + +GetMessageResponse _$GetMessageResponseFromJson(Map json) => GetMessageResponse() + ..duration = json['duration'] as String? + ..message = Message.fromJson(json['message'] as Map) + ..channel = json['channel'] == null ? null : ChannelModel.fromJson(json['channel'] as Map); SearchMessagesResponse _$SearchMessagesResponseFromJson( - Map json) => - SearchMessagesResponse() - ..duration = json['duration'] as String? - ..results = (json['results'] as List?) - ?.map( - (e) => GetMessageResponse.fromJson(e as Map)) - .toList() ?? - [] - ..next = json['next'] as String? - ..previous = json['previous'] as String?; + Map json, +) => SearchMessagesResponse() + ..duration = json['duration'] as String? + ..results = + (json['results'] as List?) + ?.map( + (e) => GetMessageResponse.fromJson(e as Map), + ) + .toList() ?? + [] + ..next = json['next'] as String? + ..previous = json['previous'] as String?; GetMessagesByIdResponse _$GetMessagesByIdResponseFromJson( - Map json) => - GetMessagesByIdResponse() - ..duration = json['duration'] as String? - ..messages = (json['messages'] as List?) - ?.map((e) => Message.fromJson(e as Map)) - .toList() ?? - []; + Map json, +) => GetMessagesByIdResponse() + ..duration = json['duration'] as String? + ..messages = + (json['messages'] as List?)?.map((e) => Message.fromJson(e as Map)).toList() ?? []; UpdateChannelResponse _$UpdateChannelResponseFromJson( - Map json) => - UpdateChannelResponse() - ..duration = json['duration'] as String? - ..channel = ChannelModel.fromJson(json['channel'] as Map) - ..members = (json['members'] as List?) - ?.map((e) => Member.fromJson(e as Map)) - .toList() - ..message = json['message'] == null - ? null - : Message.fromJson(json['message'] as Map); + Map json, +) => UpdateChannelResponse() + ..duration = json['duration'] as String? + ..channel = ChannelModel.fromJson(json['channel'] as Map) + ..members = (json['members'] as List?)?.map((e) => Member.fromJson(e as Map)).toList() + ..message = json['message'] == null ? null : Message.fromJson(json['message'] as Map); PartialUpdateChannelResponse _$PartialUpdateChannelResponseFromJson( - Map json) => - PartialUpdateChannelResponse() - ..duration = json['duration'] as String? - ..channel = ChannelModel.fromJson(json['channel'] as Map) - ..members = (json['members'] as List?) - ?.map((e) => Member.fromJson(e as Map)) - .toList(); + Map json, +) => PartialUpdateChannelResponse() + ..duration = json['duration'] as String? + ..channel = ChannelModel.fromJson(json['channel'] as Map) + ..members = (json['members'] as List?)?.map((e) => Member.fromJson(e as Map)).toList(); InviteMembersResponse _$InviteMembersResponseFromJson( - Map json) => - InviteMembersResponse() - ..duration = json['duration'] as String? - ..channel = ChannelModel.fromJson(json['channel'] as Map) - ..members = (json['members'] as List?) - ?.map((e) => Member.fromJson(e as Map)) - .toList() ?? - [] - ..message = json['message'] == null - ? null - : Message.fromJson(json['message'] as Map); + Map json, +) => InviteMembersResponse() + ..duration = json['duration'] as String? + ..channel = ChannelModel.fromJson(json['channel'] as Map) + ..members = (json['members'] as List?)?.map((e) => Member.fromJson(e as Map)).toList() ?? [] + ..message = json['message'] == null ? null : Message.fromJson(json['message'] as Map); RemoveMembersResponse _$RemoveMembersResponseFromJson( - Map json) => - RemoveMembersResponse() - ..duration = json['duration'] as String? - ..channel = ChannelModel.fromJson(json['channel'] as Map) - ..members = (json['members'] as List?) - ?.map((e) => Member.fromJson(e as Map)) - .toList() ?? - [] - ..message = json['message'] == null - ? null - : Message.fromJson(json['message'] as Map); - -SendActionResponse _$SendActionResponseFromJson(Map json) => - SendActionResponse() - ..duration = json['duration'] as String? - ..message = json['message'] == null - ? null - : Message.fromJson(json['message'] as Map); - -AddMembersResponse _$AddMembersResponseFromJson(Map json) => - AddMembersResponse() - ..duration = json['duration'] as String? - ..channel = ChannelModel.fromJson(json['channel'] as Map) - ..members = (json['members'] as List?) - ?.map((e) => Member.fromJson(e as Map)) - .toList() ?? - [] - ..message = json['message'] == null - ? null - : Message.fromJson(json['message'] as Map); + Map json, +) => RemoveMembersResponse() + ..duration = json['duration'] as String? + ..channel = ChannelModel.fromJson(json['channel'] as Map) + ..members = (json['members'] as List?)?.map((e) => Member.fromJson(e as Map)).toList() ?? [] + ..message = json['message'] == null ? null : Message.fromJson(json['message'] as Map); + +SendActionResponse _$SendActionResponseFromJson(Map json) => SendActionResponse() + ..duration = json['duration'] as String? + ..message = json['message'] == null ? null : Message.fromJson(json['message'] as Map); + +AddMembersResponse _$AddMembersResponseFromJson(Map json) => AddMembersResponse() + ..duration = json['duration'] as String? + ..channel = ChannelModel.fromJson(json['channel'] as Map) + ..members = (json['members'] as List?)?.map((e) => Member.fromJson(e as Map)).toList() ?? [] + ..message = json['message'] == null ? null : Message.fromJson(json['message'] as Map); AcceptInviteResponse _$AcceptInviteResponseFromJson( - Map json) => - AcceptInviteResponse() - ..duration = json['duration'] as String? - ..channel = ChannelModel.fromJson(json['channel'] as Map) - ..members = (json['members'] as List?) - ?.map((e) => Member.fromJson(e as Map)) - .toList() ?? - [] - ..message = json['message'] == null - ? null - : Message.fromJson(json['message'] as Map); + Map json, +) => AcceptInviteResponse() + ..duration = json['duration'] as String? + ..channel = ChannelModel.fromJson(json['channel'] as Map) + ..members = (json['members'] as List?)?.map((e) => Member.fromJson(e as Map)).toList() ?? [] + ..message = json['message'] == null ? null : Message.fromJson(json['message'] as Map); RejectInviteResponse _$RejectInviteResponseFromJson( - Map json) => - RejectInviteResponse() - ..duration = json['duration'] as String? - ..channel = ChannelModel.fromJson(json['channel'] as Map) - ..members = (json['members'] as List?) - ?.map((e) => Member.fromJson(e as Map)) - .toList() ?? - [] - ..message = json['message'] == null - ? null - : Message.fromJson(json['message'] as Map); + Map json, +) => RejectInviteResponse() + ..duration = json['duration'] as String? + ..channel = ChannelModel.fromJson(json['channel'] as Map) + ..members = (json['members'] as List?)?.map((e) => Member.fromJson(e as Map)).toList() ?? [] + ..message = json['message'] == null ? null : Message.fromJson(json['message'] as Map); EmptyResponse _$EmptyResponseFromJson(Map json) => EmptyResponse()..duration = json['duration'] as String?; ChannelStateResponse _$ChannelStateResponseFromJson( - Map json) => - ChannelStateResponse() - ..duration = json['duration'] as String? - ..channel = ChannelModel.fromJson(json['channel'] as Map) - ..messages = (json['messages'] as List?) - ?.map((e) => Message.fromJson(e as Map)) - .toList() ?? - [] - ..members = (json['members'] as List?) - ?.map((e) => Member.fromJson(e as Map)) - .toList() ?? - [] - ..watcherCount = (json['watcher_count'] as num?)?.toInt() ?? 0 - ..read = (json['read'] as List?) - ?.map((e) => Read.fromJson(e as Map)) - .toList() ?? - []; + Map json, +) => ChannelStateResponse() + ..duration = json['duration'] as String? + ..channel = ChannelModel.fromJson(json['channel'] as Map) + ..messages = + (json['messages'] as List?)?.map((e) => Message.fromJson(e as Map)).toList() ?? [] + ..members = (json['members'] as List?)?.map((e) => Member.fromJson(e as Map)).toList() ?? [] + ..watcherCount = (json['watcher_count'] as num?)?.toInt() ?? 0 + ..read = (json['read'] as List?)?.map((e) => Read.fromJson(e as Map)).toList() ?? []; OGAttachmentResponse _$OGAttachmentResponseFromJson( - Map json) => - OGAttachmentResponse() - ..duration = json['duration'] as String? - ..ogScrapeUrl = json['og_scrape_url'] as String - ..assetUrl = json['asset_url'] as String? - ..authorLink = json['author_link'] as String? - ..authorName = json['author_name'] as String? - ..imageUrl = json['image_url'] as String? - ..text = json['text'] as String? - ..thumbUrl = json['thumb_url'] as String? - ..title = json['title'] as String? - ..titleLink = json['title_link'] as String? - ..type = json['type'] as String?; - -UserBlockResponse _$UserBlockResponseFromJson(Map json) => - UserBlockResponse() - ..duration = json['duration'] as String? - ..blockedByUserId = json['blocked_by_user_id'] as String? ?? '' - ..blockedUserId = json['blocked_user_id'] as String? ?? '' - ..createdAt = DateTime.parse(json['created_at'] as String); + Map json, +) => OGAttachmentResponse() + ..duration = json['duration'] as String? + ..ogScrapeUrl = json['og_scrape_url'] as String + ..assetUrl = json['asset_url'] as String? + ..authorLink = json['author_link'] as String? + ..authorName = json['author_name'] as String? + ..imageUrl = json['image_url'] as String? + ..text = json['text'] as String? + ..thumbUrl = json['thumb_url'] as String? + ..title = json['title'] as String? + ..titleLink = json['title_link'] as String? + ..type = json['type'] as String?; + +UserBlockResponse _$UserBlockResponseFromJson(Map json) => UserBlockResponse() + ..duration = json['duration'] as String? + ..blockedByUserId = json['blocked_by_user_id'] as String? ?? '' + ..blockedUserId = json['blocked_user_id'] as String? ?? '' + ..createdAt = DateTime.parse(json['created_at'] as String); BlockedUsersResponse _$BlockedUsersResponseFromJson( - Map json) => - BlockedUsersResponse() - ..duration = json['duration'] as String? - ..blocks = (json['blocks'] as List?) - ?.map((e) => UserBlock.fromJson(e as Map)) - .toList() ?? - []; - -CreatePollResponse _$CreatePollResponseFromJson(Map json) => - CreatePollResponse() - ..duration = json['duration'] as String? - ..poll = Poll.fromJson(json['poll'] as Map); - -GetPollResponse _$GetPollResponseFromJson(Map json) => - GetPollResponse() - ..duration = json['duration'] as String? - ..poll = Poll.fromJson(json['poll'] as Map); - -UpdatePollResponse _$UpdatePollResponseFromJson(Map json) => - UpdatePollResponse() - ..duration = json['duration'] as String? - ..poll = Poll.fromJson(json['poll'] as Map); + Map json, +) => BlockedUsersResponse() + ..duration = json['duration'] as String? + ..blocks = + (json['blocks'] as List?)?.map((e) => UserBlock.fromJson(e as Map)).toList() ?? []; + +CreatePollResponse _$CreatePollResponseFromJson(Map json) => CreatePollResponse() + ..duration = json['duration'] as String? + ..poll = Poll.fromJson(json['poll'] as Map); + +GetPollResponse _$GetPollResponseFromJson(Map json) => GetPollResponse() + ..duration = json['duration'] as String? + ..poll = Poll.fromJson(json['poll'] as Map); + +UpdatePollResponse _$UpdatePollResponseFromJson(Map json) => UpdatePollResponse() + ..duration = json['duration'] as String? + ..poll = Poll.fromJson(json['poll'] as Map); CreatePollOptionResponse _$CreatePollOptionResponseFromJson( - Map json) => - CreatePollOptionResponse() - ..duration = json['duration'] as String? - ..pollOption = - PollOption.fromJson(json['poll_option'] as Map); + Map json, +) => CreatePollOptionResponse() + ..duration = json['duration'] as String? + ..pollOption = PollOption.fromJson( + json['poll_option'] as Map, + ); GetPollOptionResponse _$GetPollOptionResponseFromJson( - Map json) => - GetPollOptionResponse() - ..duration = json['duration'] as String? - ..pollOption = - PollOption.fromJson(json['poll_option'] as Map); + Map json, +) => GetPollOptionResponse() + ..duration = json['duration'] as String? + ..pollOption = PollOption.fromJson( + json['poll_option'] as Map, + ); UpdatePollOptionResponse _$UpdatePollOptionResponseFromJson( - Map json) => - UpdatePollOptionResponse() - ..duration = json['duration'] as String? - ..pollOption = - PollOption.fromJson(json['poll_option'] as Map); + Map json, +) => UpdatePollOptionResponse() + ..duration = json['duration'] as String? + ..pollOption = PollOption.fromJson( + json['poll_option'] as Map, + ); CastPollVoteResponse _$CastPollVoteResponseFromJson( - Map json) => - CastPollVoteResponse() - ..duration = json['duration'] as String? - ..vote = PollVote.fromJson(json['vote'] as Map); + Map json, +) => CastPollVoteResponse() + ..duration = json['duration'] as String? + ..vote = PollVote.fromJson(json['vote'] as Map); RemovePollVoteResponse _$RemovePollVoteResponseFromJson( - Map json) => - RemovePollVoteResponse() - ..duration = json['duration'] as String? - ..vote = PollVote.fromJson(json['vote'] as Map); - -QueryPollsResponse _$QueryPollsResponseFromJson(Map json) => - QueryPollsResponse() - ..duration = json['duration'] as String? - ..polls = (json['polls'] as List?) - ?.map((e) => Poll.fromJson(e as Map)) - .toList() ?? - [] - ..next = json['next'] as String?; + Map json, +) => RemovePollVoteResponse() + ..duration = json['duration'] as String? + ..vote = PollVote.fromJson(json['vote'] as Map); + +QueryPollsResponse _$QueryPollsResponseFromJson(Map json) => QueryPollsResponse() + ..duration = json['duration'] as String? + ..polls = (json['polls'] as List?)?.map((e) => Poll.fromJson(e as Map)).toList() ?? [] + ..next = json['next'] as String?; QueryPollVotesResponse _$QueryPollVotesResponseFromJson( - Map json) => - QueryPollVotesResponse() - ..duration = json['duration'] as String? - ..votes = (json['votes'] as List?) - ?.map((e) => PollVote.fromJson(e as Map)) - .toList() ?? - [] - ..next = json['next'] as String?; - -GetThreadResponse _$GetThreadResponseFromJson(Map json) => - GetThreadResponse() - ..duration = json['duration'] as String? - ..thread = Thread.fromJson(json['thread'] as Map); + Map json, +) => QueryPollVotesResponse() + ..duration = json['duration'] as String? + ..votes = (json['votes'] as List?)?.map((e) => PollVote.fromJson(e as Map)).toList() ?? [] + ..next = json['next'] as String?; + +GetThreadResponse _$GetThreadResponseFromJson(Map json) => GetThreadResponse() + ..duration = json['duration'] as String? + ..thread = Thread.fromJson(json['thread'] as Map); UpdateThreadResponse _$UpdateThreadResponseFromJson( - Map json) => - UpdateThreadResponse() - ..duration = json['duration'] as String? - ..thread = Thread.fromJson(json['thread'] as Map); + Map json, +) => UpdateThreadResponse() + ..duration = json['duration'] as String? + ..thread = Thread.fromJson(json['thread'] as Map); QueryThreadsResponse _$QueryThreadsResponseFromJson( - Map json) => - QueryThreadsResponse() - ..duration = json['duration'] as String? - ..threads = (json['threads'] as List?) - ?.map((e) => Thread.fromJson(e as Map)) - .toList() ?? - [] - ..next = json['next'] as String?; - -CreateDraftResponse _$CreateDraftResponseFromJson(Map json) => - CreateDraftResponse() - ..duration = json['duration'] as String? - ..draft = Draft.fromJson(json['draft'] as Map); - -GetDraftResponse _$GetDraftResponseFromJson(Map json) => - GetDraftResponse() - ..duration = json['duration'] as String? - ..draft = Draft.fromJson(json['draft'] as Map); - -QueryDraftsResponse _$QueryDraftsResponseFromJson(Map json) => - QueryDraftsResponse() - ..duration = json['duration'] as String? - ..drafts = (json['drafts'] as List?) - ?.map((e) => Draft.fromJson(e as Map)) - .toList() ?? - [] - ..next = json['next'] as String?; + Map json, +) => QueryThreadsResponse() + ..duration = json['duration'] as String? + ..threads = (json['threads'] as List?)?.map((e) => Thread.fromJson(e as Map)).toList() ?? [] + ..next = json['next'] as String?; + +CreateDraftResponse _$CreateDraftResponseFromJson(Map json) => CreateDraftResponse() + ..duration = json['duration'] as String? + ..draft = Draft.fromJson(json['draft'] as Map); + +GetDraftResponse _$GetDraftResponseFromJson(Map json) => GetDraftResponse() + ..duration = json['duration'] as String? + ..draft = Draft.fromJson(json['draft'] as Map); + +QueryDraftsResponse _$QueryDraftsResponseFromJson(Map json) => QueryDraftsResponse() + ..duration = json['duration'] as String? + ..drafts = (json['drafts'] as List?)?.map((e) => Draft.fromJson(e as Map)).toList() ?? [] + ..next = json['next'] as String?; CreateReminderResponse _$CreateReminderResponseFromJson( - Map json) => - CreateReminderResponse() - ..duration = json['duration'] as String? - ..reminder = - MessageReminder.fromJson(json['reminder'] as Map); + Map json, +) => CreateReminderResponse() + ..duration = json['duration'] as String? + ..reminder = MessageReminder.fromJson( + json['reminder'] as Map, + ); UpdateReminderResponse _$UpdateReminderResponseFromJson( - Map json) => - UpdateReminderResponse() - ..duration = json['duration'] as String? - ..reminder = - MessageReminder.fromJson(json['reminder'] as Map); + Map json, +) => UpdateReminderResponse() + ..duration = json['duration'] as String? + ..reminder = MessageReminder.fromJson( + json['reminder'] as Map, + ); QueryRemindersResponse _$QueryRemindersResponseFromJson( - Map json) => - QueryRemindersResponse() - ..duration = json['duration'] as String? - ..reminders = (json['reminders'] as List?) - ?.map((e) => MessageReminder.fromJson(e as Map)) - .toList() ?? - [] - ..next = json['next'] as String?; + Map json, +) => QueryRemindersResponse() + ..duration = json['duration'] as String? + ..reminders = + (json['reminders'] as List?)?.map((e) => MessageReminder.fromJson(e as Map)).toList() ?? + [] + ..next = json['next'] as String?; GetUnreadCountResponse _$GetUnreadCountResponseFromJson( - Map json) => - GetUnreadCountResponse() - ..duration = json['duration'] as String? - ..totalUnreadCount = (json['total_unread_count'] as num).toInt() - ..totalUnreadThreadsCount = - (json['total_unread_threads_count'] as num).toInt() - ..totalUnreadCountByTeam = - (json['total_unread_count_by_team'] as Map?)?.map( - (k, e) => MapEntry(k, (e as num).toInt()), + Map json, +) => GetUnreadCountResponse() + ..duration = json['duration'] as String? + ..totalUnreadCount = (json['total_unread_count'] as num).toInt() + ..totalUnreadThreadsCount = (json['total_unread_threads_count'] as num).toInt() + ..totalUnreadCountByTeam = (json['total_unread_count_by_team'] as Map?)?.map( + (k, e) => MapEntry(k, (e as num).toInt()), + ) + ..channels = (json['channels'] as List) + .map( + (e) => UnreadCountsChannel.fromJson(e as Map), ) - ..channels = (json['channels'] as List) - .map((e) => UnreadCountsChannel.fromJson(e as Map)) - .toList() - ..channelType = (json['channel_type'] as List) - .map((e) => - UnreadCountsChannelType.fromJson(e as Map)) - .toList() - ..threads = (json['threads'] as List) - .map((e) => UnreadCountsThread.fromJson(e as Map)) - .toList(); + .toList() + ..channelType = (json['channel_type'] as List) + .map( + (e) => UnreadCountsChannelType.fromJson(e as Map), + ) + .toList() + ..threads = (json['threads'] as List) + .map( + (e) => UnreadCountsThread.fromJson(e as Map), + ) + .toList(); UpsertPushPreferencesResponse _$UpsertPushPreferencesResponseFromJson( - Map json) => - UpsertPushPreferencesResponse() - ..duration = json['duration'] as String? - ..userPreferences = (json['user_preferences'] as Map?) - ?.map( - (k, e) => - MapEntry(k, PushPreference.fromJson(e as Map)), - ) ?? - {} - ..userChannelPreferences = - (json['user_channel_preferences'] as Map?)?.map( - (k, e) => MapEntry( - k, - (e as Map).map( - (k, e) => MapEntry( - k, - ChannelPushPreference.fromJson( - e as Map)), - )), - ) ?? - {}; + Map json, +) => UpsertPushPreferencesResponse() + ..duration = json['duration'] as String? + ..userPreferences = + (json['user_preferences'] as Map?)?.map( + (k, e) => MapEntry(k, PushPreference.fromJson(e as Map)), + ) ?? + {} + ..userChannelPreferences = + (json['user_channel_preferences'] as Map?)?.map( + (k, e) => MapEntry( + k, + (e as Map).map( + (k, e) => MapEntry( + k, + ChannelPushPreference.fromJson(e as Map), + ), + ), + ), + ) ?? + {}; GetActiveLiveLocationsResponse _$GetActiveLiveLocationsResponseFromJson( - Map json) => - GetActiveLiveLocationsResponse() - ..duration = json['duration'] as String? - ..activeLiveLocations = (json['active_live_locations'] as List) - .map((e) => Location.fromJson(e as Map)) - .toList(); + Map json, +) => GetActiveLiveLocationsResponse() + ..duration = json['duration'] as String? + ..activeLiveLocations = (json['active_live_locations'] as List) + .map((e) => Location.fromJson(e as Map)) + .toList(); diff --git a/packages/stream_chat/lib/src/core/api/sort_order.dart b/packages/stream_chat/lib/src/core/api/sort_order.dart index e04ca10264..2a1c7cad58 100644 --- a/packages/stream_chat/lib/src/core/api/sort_order.dart +++ b/packages/stream_chat/lib/src/core/api/sort_order.dart @@ -21,7 +21,7 @@ enum NullOrdering { /// Null values appear at the end of the sorted list, /// regardless of sort direction (ASC or DESC). - nullsLast; + nullsLast, } /// A sort specification for objects that implement [ComparableFieldProvider]. @@ -47,8 +47,8 @@ class SortOption { this.field, { this.nullOrdering = NullOrdering.nullsFirst, Comparator? comparator, - }) : direction = SortOption.DESC, - _comparator = comparator; + }) : direction = SortOption.DESC, + _comparator = comparator; /// Creates a SortOption for ascending order sorting by the specified field. /// @@ -61,8 +61,8 @@ class SortOption { this.field, { this.nullOrdering = NullOrdering.nullsLast, Comparator? comparator, - }) : direction = SortOption.ASC, - _comparator = comparator; + }) : direction = SortOption.ASC, + _comparator = comparator; /// Ascending order (1) static const ASC = 1; @@ -132,8 +132,7 @@ class SortOption { } /// Extension that allows a [SortOrder] to be used as a comparator function. -extension CompositeComparator - on SortOrder { +extension CompositeComparator on SortOrder { /// Compares two objects using all sort options in sequence. /// /// Returns the first non-zero comparison result, or 0 if all comparisons diff --git a/packages/stream_chat/lib/src/core/api/sort_order.g.dart b/packages/stream_chat/lib/src/core/api/sort_order.g.dart index 31f18e61c9..c54ab585ab 100644 --- a/packages/stream_chat/lib/src/core/api/sort_order.g.dart +++ b/packages/stream_chat/lib/src/core/api/sort_order.g.dart @@ -6,9 +6,7 @@ part of 'sort_order.dart'; // JsonSerializableGenerator // ************************************************************************** -Map _$SortOptionToJson( - SortOption instance) => - { - 'field': instance.field, - 'direction': instance.direction, - }; +Map _$SortOptionToJson(SortOption instance) => { + 'field': instance.field, + 'direction': instance.direction, +}; diff --git a/packages/stream_chat/lib/src/core/api/stream_chat_api.dart b/packages/stream_chat/lib/src/core/api/stream_chat_api.dart index 85cbf4aa7e..5ece808a93 100644 --- a/packages/stream_chat/lib/src/core/api/stream_chat_api.dart +++ b/packages/stream_chat/lib/src/core/api/stream_chat_api.dart @@ -28,23 +28,23 @@ class StreamChatApi { TokenManager? tokenManager, ConnectionIdManager? connectionIdManager, SystemEnvironmentManager? systemEnvironmentManager, - AttachmentFileUploaderProvider attachmentFileUploaderProvider = - StreamAttachmentFileUploader.new, + AttachmentFileUploaderProvider attachmentFileUploaderProvider = StreamAttachmentFileUploader.new, Logger? logger, Iterable? interceptors, HttpClientAdapter? httpClientAdapter, - }) : _fileUploaderProvider = attachmentFileUploaderProvider, - _client = client ?? - StreamHttpClient( - apiKey, - options: options, - tokenManager: tokenManager, - connectionIdManager: connectionIdManager, - systemEnvironmentManager: systemEnvironmentManager, - logger: logger, - interceptors: interceptors, - httpClientAdapter: httpClientAdapter, - ); + }) : _fileUploaderProvider = attachmentFileUploaderProvider, + _client = + client ?? + StreamHttpClient( + apiKey, + options: options, + tokenManager: tokenManager, + connectionIdManager: connectionIdManager, + systemEnvironmentManager: systemEnvironmentManager, + logger: logger, + interceptors: interceptors, + httpClientAdapter: httpClientAdapter, + ); final StreamHttpClient _client; final AttachmentFileUploaderProvider _fileUploaderProvider; @@ -90,7 +90,6 @@ class StreamChatApi { GeneralApi? _general; /// Class responsible for uploading images and files to a given channel - AttachmentFileUploader get fileUploader => - _fileUploader ??= _fileUploaderProvider.call(_client); + AttachmentFileUploader get fileUploader => _fileUploader ??= _fileUploaderProvider.call(_client); AttachmentFileUploader? _fileUploader; } diff --git a/packages/stream_chat/lib/src/core/api/user_api.dart b/packages/stream_chat/lib/src/core/api/user_api.dart index 668e7ac70a..00a38f0ad4 100644 --- a/packages/stream_chat/lib/src/core/api/user_api.dart +++ b/packages/stream_chat/lib/src/core/api/user_api.dart @@ -118,8 +118,7 @@ class UserApi { '/users/live_locations', data: json.encode({ 'message_id': messageId, - if (createdByDeviceId != null) - 'created_by_device_id': createdByDeviceId, + if (createdByDeviceId != null) 'created_by_device_id': createdByDeviceId, if (location?.latitude case final latitude) 'latitude': latitude, if (location?.longitude case final longitude) 'longitude': longitude, if (endAt != null) 'end_at': endAt.toIso8601String(), diff --git a/packages/stream_chat/lib/src/core/error/chat_error_code.dart b/packages/stream_chat/lib/src/core/error/chat_error_code.dart index d597000ad6..b35c010da2 100644 --- a/packages/stream_chat/lib/src/core/error/chat_error_code.dart +++ b/packages/stream_chat/lib/src/core/error/chat_error_code.dart @@ -94,10 +94,8 @@ enum ChatErrorCode { } const _errorCodeWithDescription = { - ChatErrorCode.undefinedToken: - MapEntry(1000, 'Unauthorised, token not defined'), - ChatErrorCode.inputError: - MapEntry(4, 'Wrong data/parameter is sent to the API'), + ChatErrorCode.undefinedToken: MapEntry(1000, 'Unauthorised, token not defined'), + ChatErrorCode.inputError: MapEntry(4, 'Wrong data/parameter is sent to the API'), ChatErrorCode.duplicateUsername: MapEntry( 6, 'Duplicate username is sent while enforce_unique_usernames is enabled', @@ -112,36 +110,24 @@ const _errorCodeWithDescription = { 21, 'Multiple Levels Reply is not supported - the API only supports 1 level deep reply threads', ), - ChatErrorCode.customCommandEndpointCall: - MapEntry(45, 'Custom Command handler returned an error'), - ChatErrorCode.customCommandEndpointMissing: - MapEntry(44, 'App config does not have custom_action_handler_url'), - ChatErrorCode.authenticationError: - MapEntry(5, 'Unauthenticated, problem with authentication'), + ChatErrorCode.customCommandEndpointCall: MapEntry(45, 'Custom Command handler returned an error'), + ChatErrorCode.customCommandEndpointMissing: MapEntry(44, 'App config does not have custom_action_handler_url'), + ChatErrorCode.authenticationError: MapEntry(5, 'Unauthenticated, problem with authentication'), ChatErrorCode.tokenExpired: MapEntry(40, 'Unauthenticated, token expired'), - ChatErrorCode.tokenBeforeIssuedAt: - MapEntry(42, 'Unauthenticated, token date incorrect'), - ChatErrorCode.tokenNotValid: - MapEntry(41, 'Unauthenticated, token not valid yet'), - ChatErrorCode.tokenSignatureInvalid: - MapEntry(43, 'Unauthenticated, token signature invalid'), + ChatErrorCode.tokenBeforeIssuedAt: MapEntry(42, 'Unauthenticated, token date incorrect'), + ChatErrorCode.tokenNotValid: MapEntry(41, 'Unauthenticated, token not valid yet'), + ChatErrorCode.tokenSignatureInvalid: MapEntry(43, 'Unauthenticated, token signature invalid'), ChatErrorCode.accessKeyError: MapEntry(2, 'Access Key invalid'), - ChatErrorCode.notAllowed: - MapEntry(17, 'Unauthorised / forbidden to make request'), + ChatErrorCode.notAllowed: MapEntry(17, 'Unauthorised / forbidden to make request'), ChatErrorCode.appSuspended: MapEntry(99, 'App suspended'), - ChatErrorCode.cooldownError: - MapEntry(60, 'User tried to post a message during the cooldown period'), + ChatErrorCode.cooldownError: MapEntry(60, 'User tried to post a message during the cooldown period'), ChatErrorCode.doesNotExist: MapEntry(16, 'Resource not found'), ChatErrorCode.requestTimeout: MapEntry(23, 'Request timed out'), ChatErrorCode.payloadTooBig: MapEntry(22, 'Payload too big'), - ChatErrorCode.rateLimitError: - MapEntry(9, 'Too many requests in a certain time frame'), - ChatErrorCode.maximumHeaderSizeExceeded: - MapEntry(24, 'Request headers are too large'), - ChatErrorCode.internalSystemError: - MapEntry(-1, 'Something goes wrong in the system'), - ChatErrorCode.noAccessToChannels: - MapEntry(70, 'No access to requested channels'), + ChatErrorCode.rateLimitError: MapEntry(9, 'Too many requests in a certain time frame'), + ChatErrorCode.maximumHeaderSizeExceeded: MapEntry(24, 'Request headers are too large'), + ChatErrorCode.internalSystemError: MapEntry(-1, 'Something goes wrong in the system'), + ChatErrorCode.noAccessToChannels: MapEntry(70, 'No access to requested channels'), }; const _authenticationErrors = [ @@ -156,8 +142,8 @@ const _authenticationErrors = [ ]; /// -ChatErrorCode? chatErrorCodeFromCode(int code) => _errorCodeWithDescription.keys - .firstWhereOrNull((key) => _errorCodeWithDescription[key]!.key == code); +ChatErrorCode? chatErrorCodeFromCode(int code) => + _errorCodeWithDescription.keys.firstWhereOrNull((key) => _errorCodeWithDescription[key]!.key == code); /// extension ChatErrorCodeX on ChatErrorCode { diff --git a/packages/stream_chat/lib/src/core/error/stream_chat_error.dart b/packages/stream_chat/lib/src/core/error/stream_chat_error.dart index 5e88fb3bc9..b2925940e5 100644 --- a/packages/stream_chat/lib/src/core/error/stream_chat_error.dart +++ b/packages/stream_chat/lib/src/core/error/stream_chat_error.dart @@ -78,10 +78,10 @@ class StreamChatNetworkError extends StreamChatError { this.data, StackTrace? stacktrace, this.isRequestCancelledError = false, - }) : code = errorCode.code, - statusCode = statusCode ?? data?.statusCode, - stackTrace = stacktrace ?? StackTrace.current, - super(errorCode.message); + }) : code = errorCode.code, + statusCode = statusCode ?? data?.statusCode, + stackTrace = stacktrace ?? StackTrace.current, + super(errorCode.message); /// StreamChatNetworkError.raw({ @@ -91,8 +91,8 @@ class StreamChatNetworkError extends StreamChatError { this.data, StackTrace? stacktrace, this.isRequestCancelledError = false, - }) : stackTrace = stacktrace ?? StackTrace.current, - super(message); + }) : stackTrace = stacktrace ?? StackTrace.current, + super(message); /// factory StreamChatNetworkError.fromDioException(DioException exception) { @@ -106,10 +106,7 @@ class StreamChatNetworkError extends StreamChatError { } return StreamChatNetworkError.raw( code: errorResponse?.code ?? -1, - message: errorResponse?.message ?? - response?.statusMessage ?? - exception.message ?? - '', + message: errorResponse?.message ?? response?.statusMessage ?? exception.message ?? '', statusCode: errorResponse?.statusCode ?? response?.statusCode, data: errorResponse, stacktrace: exception.stackTrace, diff --git a/packages/stream_chat/lib/src/core/http/interceptor/logging_interceptor.dart b/packages/stream_chat/lib/src/core/http/interceptor/logging_interceptor.dart index 6d6f74c301..258489471e 100644 --- a/packages/stream_chat/lib/src/core/http/interceptor/logging_interceptor.dart +++ b/packages/stream_chat/lib/src/core/http/interceptor/logging_interceptor.dart @@ -125,8 +125,7 @@ class LoggingInterceptor extends Interceptor { final uri = exception.response?.requestOptions.uri; _printBoxed( _logPrintError, - header: - 'DioException ║ Status: ${exception.response?.statusCode} ${exception.response?.statusMessage}', + header: 'DioException ║ Status: ${exception.response?.statusCode} ${exception.response?.statusMessage}', text: uri.toString(), ); if (exception.response != null && exception.response?.data != null) { @@ -152,8 +151,7 @@ class LoggingInterceptor extends Interceptor { _printResponseHeader(_logPrintResponse, response); if (responseHeader) { final responseHeaders = {}; - response.headers - .forEach((k, list) => responseHeaders[k] = list.toString()); + response.headers.forEach((k, list) => responseHeaders[k] = list.toString()); _printMapAsTable(_logPrintResponse, responseHeaders, header: 'Headers'); } @@ -197,8 +195,7 @@ class LoggingInterceptor extends Interceptor { final method = response.requestOptions.method; _printBoxed( logPrint, - header: - 'Response ║ $method ║ Status: ${response.statusCode} ${response.statusMessage}', + header: 'Response ║ $method ║ Status: ${response.statusCode} ${response.statusMessage}', text: uri.toString(), ); } @@ -216,8 +213,7 @@ class LoggingInterceptor extends Interceptor { void Function(Object) logPrint, [ String pre = '', String suf = '╝', - ]) => - logPrint('$pre${'═' * maxWidth}$suf'); + ]) => logPrint('$pre${'═' * maxWidth}$suf'); void _printKV(void Function(Object) logPrint, String? key, Object? v) { final pre = '╟ $key: '; @@ -234,11 +230,13 @@ class LoggingInterceptor extends Interceptor { void _printBlock(void Function(Object) logPrint, String msg) { final lines = (msg.length / maxWidth).ceil(); for (var i = 0; i < lines; ++i) { - logPrint((i >= 0 ? '║ ' : '') + - msg.substring( - i * maxWidth, - math.min(i * maxWidth + maxWidth, msg.length), - )); + logPrint( + (i >= 0 ? '║ ' : '') + + msg.substring( + i * maxWidth, + math.min(i * maxWidth + maxWidth, msg.length), + ), + ); } } @@ -286,10 +284,12 @@ class LoggingInterceptor extends Interceptor { if (msg.length + indent.length > linWidth) { final lines = (msg.length / linWidth).ceil(); for (var i = 0; i < lines; ++i) { - logPrint('║${_indent(_tabs)} ${msg.substring( - i * linWidth, - math.min(i * linWidth + linWidth, msg.length), - )}'); + logPrint( + '║${_indent(_tabs)} ${msg.substring( + i * linWidth, + math.min(i * linWidth + linWidth, msg.length), + )}', + ); } } else { logPrint('║${_indent(_tabs)} $key: $msg${!isLast ? ',' : ''}'); @@ -332,16 +332,13 @@ class LoggingInterceptor extends Interceptor { }) { if (map == null || map.isEmpty) return; logPrint('╔ $header '); - map.forEach((dynamic key, dynamic value) => - _printKV(logPrint, key.toString(), value)); + map.forEach((dynamic key, dynamic value) => _printKV(logPrint, key.toString(), value)); _printLine(logPrint, '╚'); } - void _logPrintRequest(Object object) => - logPrint(InterceptStep.request, object); + void _logPrintRequest(Object object) => logPrint(InterceptStep.request, object); - void _logPrintResponse(Object object) => - logPrint(InterceptStep.response, object); + void _logPrintResponse(Object object) => logPrint(InterceptStep.response, object); void _logPrintError(Object object) => logPrint(InterceptStep.error, object); } diff --git a/packages/stream_chat/lib/src/core/http/stream_chat_dio_error.dart b/packages/stream_chat/lib/src/core/http/stream_chat_dio_error.dart index d59a032c63..25b75fd82e 100644 --- a/packages/stream_chat/lib/src/core/http/stream_chat_dio_error.dart +++ b/packages/stream_chat/lib/src/core/http/stream_chat_dio_error.dart @@ -12,9 +12,9 @@ class StreamChatDioError extends DioException { StackTrace? stackTrace, super.message, }) : super( - error: error, - stackTrace: stackTrace ?? StackTrace.current, - ); + error: error, + stackTrace: stackTrace ?? StackTrace.current, + ); @override final StreamChatNetworkError error; diff --git a/packages/stream_chat/lib/src/core/http/stream_http_client.dart b/packages/stream_chat/lib/src/core/http/stream_http_client.dart index 16141c0a26..7c887246e0 100644 --- a/packages/stream_chat/lib/src/core/http/stream_http_client.dart +++ b/packages/stream_chat/lib/src/core/http/stream_http_client.dart @@ -29,8 +29,8 @@ class StreamHttpClient { Logger? logger, Iterable? interceptors, HttpClientAdapter? httpClientAdapter, - }) : _options = options ?? const StreamHttpClientOptions(), - httpClient = dio ?? Dio() { + }) : _options = options ?? const StreamHttpClientOptions(), + httpClient = dio ?? Dio() { httpClient ..options.baseUrl = _options.baseUrl ..options.receiveTimeout = _options.receiveTimeout @@ -47,8 +47,7 @@ class StreamHttpClient { ..interceptors.addAll([ AdditionalHeadersInterceptor(systemEnvironmentManager), if (tokenManager != null) AuthInterceptor(this, tokenManager), - if (connectionIdManager != null) - ConnectionIdInterceptor(connectionIdManager), + if (connectionIdManager != null) ConnectionIdInterceptor(connectionIdManager), ...interceptors ?? [ // Add a default logging interceptor if no interceptors are diff --git a/packages/stream_chat/lib/src/core/http/stream_http_client_options.dart b/packages/stream_chat/lib/src/core/http/stream_http_client_options.dart index ad18c2544f..a98a7904c2 100644 --- a/packages/stream_chat/lib/src/core/http/stream_http_client_options.dart +++ b/packages/stream_chat/lib/src/core/http/stream_http_client_options.dart @@ -2,13 +2,19 @@ part of 'stream_http_client.dart'; const _defaultBaseURL = 'https://chat.stream-io-api.com'; +/// The default connect timeout for the api request +const kDefaultConnectTimeout = Duration(seconds: 30); + +/// The default receive timeout for the api request +const kDefaultReceiveTimeout = Duration(seconds: 30); + /// Client options to modify [StreamHttpClient] class StreamHttpClientOptions { /// Instantiates a new [StreamHttpClientOptions] const StreamHttpClientOptions({ String? baseUrl, - this.connectTimeout = const Duration(seconds: 30), - this.receiveTimeout = const Duration(seconds: 30), + this.connectTimeout = kDefaultConnectTimeout, + this.receiveTimeout = kDefaultReceiveTimeout, this.queryParameters = const {}, this.headers = const {}, }) : baseUrl = baseUrl ?? _defaultBaseURL; diff --git a/packages/stream_chat/lib/src/core/http/system_environment_manager.dart b/packages/stream_chat/lib/src/core/http/system_environment_manager.dart index 42bd01c3ea..bb4648e7d3 100644 --- a/packages/stream_chat/lib/src/core/http/system_environment_manager.dart +++ b/packages/stream_chat/lib/src/core/http/system_environment_manager.dart @@ -12,14 +12,14 @@ class SystemEnvironmentManager { SystemEnvironmentManager({ SystemEnvironment? environment, }) : _environment = switch (environment) { - final env? => env, - _ => SystemEnvironment( - sdkName: 'stream-chat', - sdkIdentifier: 'dart', - sdkVersion: PACKAGE_VERSION, - osName: CurrentPlatform.name, - ), - }; + final env? => env, + _ => SystemEnvironment( + sdkName: 'stream-chat', + sdkIdentifier: 'dart', + sdkVersion: PACKAGE_VERSION, + osName: CurrentPlatform.name, + ), + }; /// Returns the Stream client user agent string based on the current /// [environment] value. diff --git a/packages/stream_chat/lib/src/core/http/token.dart b/packages/stream_chat/lib/src/core/http/token.dart index 2472a1f7f5..df1114a0ca 100644 --- a/packages/stream_chat/lib/src/core/http/token.dart +++ b/packages/stream_chat/lib/src/core/http/token.dart @@ -29,10 +29,10 @@ class Token extends Equatable { /// The token that can be used when user is unknown. /// Is used by `anonymous` token provider. factory Token.anonymous({String? userId}) => Token._( - rawValue: '', - userId: userId ?? randomId(), - authType: AuthType.anonymous, - ); + rawValue: '', + userId: userId ?? randomId(), + authType: AuthType.anonymous, + ); /// Creates a [Token] instance from the provided [rawValue] if it's valid. factory Token.fromRawValue(String rawValue) { diff --git a/packages/stream_chat/lib/src/core/http/token_manager.dart b/packages/stream_chat/lib/src/core/http/token_manager.dart index e5af0ddc33..96b90bdcc0 100644 --- a/packages/stream_chat/lib/src/core/http/token_manager.dart +++ b/packages/stream_chat/lib/src/core/http/token_manager.dart @@ -12,9 +12,9 @@ class TokenManager { String? userId, Token? token, TokenProvider? tokenProvider, - }) : _userId = userId, - _token = token, - _provider = tokenProvider; + }) : _userId = userId, + _token = token, + _provider = tokenProvider; String? _type; Token? _token; diff --git a/packages/stream_chat/lib/src/core/models/action.g.dart b/packages/stream_chat/lib/src/core/models/action.g.dart index 2c9148d92b..ec604d553f 100644 --- a/packages/stream_chat/lib/src/core/models/action.g.dart +++ b/packages/stream_chat/lib/src/core/models/action.g.dart @@ -7,17 +7,17 @@ part of 'action.dart'; // ************************************************************************** Action _$ActionFromJson(Map json) => Action( - name: json['name'] as String, - style: json['style'] as String? ?? 'default', - text: json['text'] as String, - type: json['type'] as String, - value: json['value'] as String?, - ); + name: json['name'] as String, + style: json['style'] as String? ?? 'default', + text: json['text'] as String, + type: json['type'] as String, + value: json['value'] as String?, +); Map _$ActionToJson(Action instance) => { - 'name': instance.name, - 'style': instance.style, - 'text': instance.text, - 'type': instance.type, - 'value': instance.value, - }; + 'name': instance.name, + 'style': instance.style, + 'text': instance.text, + 'type': instance.type, + 'value': instance.value, +}; diff --git a/packages/stream_chat/lib/src/core/models/attachment.dart b/packages/stream_chat/lib/src/core/models/attachment.dart index a4e15241c8..486750cf45 100644 --- a/packages/stream_chat/lib/src/core/models/attachment.dart +++ b/packages/stream_chat/lib/src/core/models/attachment.dart @@ -39,49 +39,48 @@ class Attachment extends Equatable { Map extraData = const {}, this.file, this.uploadState = const UploadState.preparing(), - }) : id = id ?? const Uuid().v4(), - _type = switch (type) { - String() => AttachmentType(type), - _ => null, - }, - title = title ?? file?.name, - localUri = file?.path != null ? Uri.parse(file!.path!) : null, - // For backwards compatibility, - // set 'file_size', 'mime_type' in [extraData]. - extraData = { - ...extraData, - if (file?.size != null) 'file_size': file?.size, - if (file?.mediaType != null) 'mime_type': file?.mediaType?.mimeType, - }; + }) : id = id ?? const Uuid().v4(), + _type = switch (type) { + String() => AttachmentType(type), + _ => null, + }, + title = title ?? file?.name, + localUri = file?.path != null ? Uri.parse(file!.path!) : null, + // For backwards compatibility, + // set 'file_size', 'mime_type' in [extraData]. + extraData = { + ...extraData, + if (file?.size != null) 'file_size': file?.size, + if (file?.mediaType != null) 'mime_type': file?.mediaType?.mimeType, + }; /// Create a new instance from a json - factory Attachment.fromJson(Map json) => - _$AttachmentFromJson( - Serializer.moveToExtraDataFromRoot(json, topLevelFields), - ); + factory Attachment.fromJson(Map json) => _$AttachmentFromJson( + Serializer.moveToExtraDataFromRoot(json, topLevelFields), + ); /// Create a new instance from a db data - factory Attachment.fromData(Map json) => - _$AttachmentFromJson(Serializer.moveToExtraDataFromRoot( - json, - topLevelFields + dbSpecificTopLevelFields, - )); - - factory Attachment.fromOGAttachment(OGAttachmentResponse ogAttachment) => - Attachment( - // If the type is not specified, we default to urlPreview. - type: ogAttachment.type ?? AttachmentType.urlPreview, - title: ogAttachment.title, - titleLink: ogAttachment.titleLink, - text: ogAttachment.text, - imageUrl: ogAttachment.imageUrl, - thumbUrl: ogAttachment.thumbUrl, - authorName: ogAttachment.authorName, - authorLink: ogAttachment.authorLink, - assetUrl: ogAttachment.assetUrl, - ogScrapeUrl: ogAttachment.ogScrapeUrl, - uploadState: const UploadState.success(), - ); + factory Attachment.fromData(Map json) => _$AttachmentFromJson( + Serializer.moveToExtraDataFromRoot( + json, + topLevelFields + dbSpecificTopLevelFields, + ), + ); + + factory Attachment.fromOGAttachment(OGAttachmentResponse ogAttachment) => Attachment( + // If the type is not specified, we default to urlPreview. + type: ogAttachment.type ?? AttachmentType.urlPreview, + title: ogAttachment.title, + titleLink: ogAttachment.titleLink, + text: ogAttachment.text, + imageUrl: ogAttachment.imageUrl, + thumbUrl: ogAttachment.thumbUrl, + authorName: ogAttachment.authorName, + authorLink: ogAttachment.authorLink, + assetUrl: ogAttachment.assetUrl, + ogScrapeUrl: ogAttachment.ogScrapeUrl, + uploadState: const UploadState.success(), + ); ///The attachment type based on the URL resource. This can be: audio, ///image or video @@ -219,8 +218,7 @@ class Attachment extends Equatable { ..removeWhere((key, value) => dbSpecificTopLevelFields.contains(key)); /// Serialize to db data - Map toData() => - Serializer.moveFromExtraDataToRoot(_$AttachmentToJson(this)); + Map toData() => Serializer.moveFromExtraDataToRoot(_$AttachmentToJson(this)); Attachment copyWith({ String? id, @@ -247,33 +245,32 @@ class Attachment extends Equatable { AttachmentFile? file, UploadState? uploadState, Map? extraData, - }) => - Attachment( - id: id ?? this.id, - type: type ?? this.type, - titleLink: titleLink ?? this.titleLink, - title: title ?? this.title, - thumbUrl: thumbUrl ?? this.thumbUrl, - text: text ?? this.text, - pretext: pretext ?? this.pretext, - ogScrapeUrl: ogScrapeUrl ?? this.ogScrapeUrl, - imageUrl: imageUrl ?? this.imageUrl, - footerIcon: footerIcon ?? this.footerIcon, - footer: footer ?? this.footer, - fields: fields ?? this.fields, - fallback: fallback ?? this.fallback, - color: color ?? this.color, - authorName: authorName ?? this.authorName, - authorLink: authorLink ?? this.authorLink, - authorIcon: authorIcon ?? this.authorIcon, - assetUrl: assetUrl ?? this.assetUrl, - actions: actions ?? this.actions, - originalWidth: originalWidth ?? this.originalWidth, - originalHeight: originalHeight ?? this.originalHeight, - file: file ?? this.file, - uploadState: uploadState ?? this.uploadState, - extraData: extraData ?? this.extraData, - ); + }) => Attachment( + id: id ?? this.id, + type: type ?? this.type, + titleLink: titleLink ?? this.titleLink, + title: title ?? this.title, + thumbUrl: thumbUrl ?? this.thumbUrl, + text: text ?? this.text, + pretext: pretext ?? this.pretext, + ogScrapeUrl: ogScrapeUrl ?? this.ogScrapeUrl, + imageUrl: imageUrl ?? this.imageUrl, + footerIcon: footerIcon ?? this.footerIcon, + footer: footer ?? this.footer, + fields: fields ?? this.fields, + fallback: fallback ?? this.fallback, + color: color ?? this.color, + authorName: authorName ?? this.authorName, + authorLink: authorLink ?? this.authorLink, + authorIcon: authorIcon ?? this.authorIcon, + assetUrl: assetUrl ?? this.assetUrl, + actions: actions ?? this.actions, + originalWidth: originalWidth ?? this.originalWidth, + originalHeight: originalHeight ?? this.originalHeight, + file: file ?? this.file, + uploadState: uploadState ?? this.uploadState, + extraData: extraData ?? this.extraData, + ); Attachment merge(Attachment? other) { if (other == null) return this; @@ -306,31 +303,31 @@ class Attachment extends Equatable { @override List get props => [ - id, - type, - titleLink, - title, - thumbUrl, - text, - pretext, - ogScrapeUrl, - imageUrl, - footerIcon, - footer, - fields, - fallback, - color, - authorName, - authorLink, - authorIcon, - assetUrl, - actions, - originalWidth, - originalHeight, - file, - uploadState, - extraData, - ]; + id, + type, + titleLink, + title, + thumbUrl, + text, + pretext, + ogScrapeUrl, + imageUrl, + footerIcon, + footer, + fields, + fallback, + color, + authorName, + authorLink, + authorIcon, + assetUrl, + actions, + originalWidth, + originalHeight, + file, + uploadState, + extraData, + ]; } /// {@template attachmentType} diff --git a/packages/stream_chat/lib/src/core/models/attachment.g.dart b/packages/stream_chat/lib/src/core/models/attachment.g.dart index 8fcc4fe4c6..8dd8e75a8c 100644 --- a/packages/stream_chat/lib/src/core/models/attachment.g.dart +++ b/packages/stream_chat/lib/src/core/models/attachment.g.dart @@ -7,64 +7,58 @@ part of 'attachment.dart'; // ************************************************************************** Attachment _$AttachmentFromJson(Map json) => Attachment( - id: json['id'] as String?, - type: AttachmentType.fromJson(json['type'] as String?), - titleLink: json['title_link'] as String?, - title: json['title'] as String?, - thumbUrl: json['thumb_url'] as String?, - text: json['text'] as String?, - pretext: json['pretext'] as String?, - ogScrapeUrl: json['og_scrape_url'] as String?, - imageUrl: json['image_url'] as String?, - footerIcon: json['footer_icon'] as String?, - footer: json['footer'] as String?, - fields: json['fields'], - fallback: json['fallback'] as String?, - color: json['color'] as String?, - authorName: json['author_name'] as String?, - authorLink: json['author_link'] as String?, - authorIcon: json['author_icon'] as String?, - assetUrl: json['asset_url'] as String?, - actions: (json['actions'] as List?) - ?.map((e) => Action.fromJson(e as Map)) - .toList() ?? - const [], - originalWidth: (json['original_width'] as num?)?.toInt(), - originalHeight: (json['original_height'] as num?)?.toInt(), - extraData: json['extra_data'] as Map? ?? const {}, - file: json['file'] == null - ? null - : AttachmentFile.fromJson(json['file'] as Map), - uploadState: json['upload_state'] == null - ? const UploadState.success() - : UploadState.fromJson(json['upload_state'] as Map), - ); + id: json['id'] as String?, + type: AttachmentType.fromJson(json['type'] as String?), + titleLink: json['title_link'] as String?, + title: json['title'] as String?, + thumbUrl: json['thumb_url'] as String?, + text: json['text'] as String?, + pretext: json['pretext'] as String?, + ogScrapeUrl: json['og_scrape_url'] as String?, + imageUrl: json['image_url'] as String?, + footerIcon: json['footer_icon'] as String?, + footer: json['footer'] as String?, + fields: json['fields'], + fallback: json['fallback'] as String?, + color: json['color'] as String?, + authorName: json['author_name'] as String?, + authorLink: json['author_link'] as String?, + authorIcon: json['author_icon'] as String?, + assetUrl: json['asset_url'] as String?, + actions: + (json['actions'] as List?)?.map((e) => Action.fromJson(e as Map)).toList() ?? const [], + originalWidth: (json['original_width'] as num?)?.toInt(), + originalHeight: (json['original_height'] as num?)?.toInt(), + extraData: json['extra_data'] as Map? ?? const {}, + file: json['file'] == null ? null : AttachmentFile.fromJson(json['file'] as Map), + uploadState: json['upload_state'] == null + ? const UploadState.success() + : UploadState.fromJson(json['upload_state'] as Map), +); -Map _$AttachmentToJson(Attachment instance) => - { - if (AttachmentType.toJson(instance.type) case final value?) 'type': value, - if (instance.titleLink case final value?) 'title_link': value, - if (instance.title case final value?) 'title': value, - if (instance.thumbUrl case final value?) 'thumb_url': value, - if (instance.text case final value?) 'text': value, - if (instance.pretext case final value?) 'pretext': value, - if (instance.ogScrapeUrl case final value?) 'og_scrape_url': value, - if (instance.imageUrl case final value?) 'image_url': value, - if (instance.footerIcon case final value?) 'footer_icon': value, - if (instance.footer case final value?) 'footer': value, - if (instance.fields case final value?) 'fields': value, - if (instance.fallback case final value?) 'fallback': value, - if (instance.color case final value?) 'color': value, - if (instance.authorName case final value?) 'author_name': value, - if (instance.authorLink case final value?) 'author_link': value, - if (instance.authorIcon case final value?) 'author_icon': value, - if (instance.assetUrl case final value?) 'asset_url': value, - if (instance.actions?.map((e) => e.toJson()).toList() case final value?) - 'actions': value, - if (instance.originalWidth case final value?) 'original_width': value, - if (instance.originalHeight case final value?) 'original_height': value, - if (instance.file?.toJson() case final value?) 'file': value, - 'upload_state': instance.uploadState.toJson(), - 'extra_data': instance.extraData, - 'id': instance.id, - }; +Map _$AttachmentToJson(Attachment instance) => { + if (AttachmentType.toJson(instance.type) case final value?) 'type': value, + if (instance.titleLink case final value?) 'title_link': value, + if (instance.title case final value?) 'title': value, + if (instance.thumbUrl case final value?) 'thumb_url': value, + if (instance.text case final value?) 'text': value, + if (instance.pretext case final value?) 'pretext': value, + if (instance.ogScrapeUrl case final value?) 'og_scrape_url': value, + if (instance.imageUrl case final value?) 'image_url': value, + if (instance.footerIcon case final value?) 'footer_icon': value, + if (instance.footer case final value?) 'footer': value, + if (instance.fields case final value?) 'fields': value, + if (instance.fallback case final value?) 'fallback': value, + if (instance.color case final value?) 'color': value, + if (instance.authorName case final value?) 'author_name': value, + if (instance.authorLink case final value?) 'author_link': value, + if (instance.authorIcon case final value?) 'author_icon': value, + if (instance.assetUrl case final value?) 'asset_url': value, + if (instance.actions?.map((e) => e.toJson()).toList() case final value?) 'actions': value, + if (instance.originalWidth case final value?) 'original_width': value, + if (instance.originalHeight case final value?) 'original_height': value, + if (instance.file?.toJson() case final value?) 'file': value, + 'upload_state': instance.uploadState.toJson(), + 'extra_data': instance.extraData, + 'id': instance.id, +}; diff --git a/packages/stream_chat/lib/src/core/models/attachment_file.dart b/packages/stream_chat/lib/src/core/models/attachment_file.dart index b5bb007633..9d476090fa 100644 --- a/packages/stream_chat/lib/src/core/models/attachment_file.dart +++ b/packages/stream_chat/lib/src/core/models/attachment_file.dart @@ -19,23 +19,22 @@ class AttachmentFile { this.path, String? name, this.bytes, - }) : assert( - path != null || bytes != null, - 'Either path or bytes should be != null', - ), - assert( - !CurrentPlatform.isWeb || bytes != null, - 'File by path is not supported in web, Please provide bytes', - ), - assert( - name == null || name.isEmpty || name.contains('.'), - 'Invalid file name, should also contain file extension', - ), - _name = name; + }) : assert( + path != null || bytes != null, + 'Either path or bytes should be != null', + ), + assert( + !CurrentPlatform.isWeb || bytes != null, + 'File by path is not supported in web, Please provide bytes', + ), + assert( + name == null || name.isEmpty || name.contains('.'), + 'Invalid file name, should also contain file extension', + ), + _name = name; /// Create a new instance from a json - factory AttachmentFile.fromJson(Map json) => - _$AttachmentFileFromJson(json); + factory AttachmentFile.fromJson(Map json) => _$AttachmentFileFromJson(json); /// The absolute path for a cached copy of this file. It can be used to /// create a file instance with a descriptor for the given path. @@ -73,15 +72,15 @@ class AttachmentFile { Future toMultipartFile() async { return switch (CurrentPlatform.type) { PlatformType.web => MultipartFile.fromBytes( - bytes!, - filename: name, - contentType: mediaType, - ), + bytes!, + filename: name, + contentType: mediaType, + ), _ => await MultipartFile.fromFile( - path!, - filename: name, - contentType: mediaType, - ), + path!, + filename: name, + contentType: mediaType, + ), }; } @@ -124,8 +123,7 @@ sealed class UploadState with _$UploadState { const factory UploadState.failed({required String error}) = Failed; /// Creates a new instance from a json - factory UploadState.fromJson(Map json) => - _$UploadStateFromJson(json); + factory UploadState.fromJson(Map json) => _$UploadStateFromJson(json); /// Returns true if state is [Preparing] bool get isPreparing => this is Preparing; diff --git a/packages/stream_chat/lib/src/core/models/attachment_file.g.dart b/packages/stream_chat/lib/src/core/models/attachment_file.g.dart index 256713cae0..7ebe7ea111 100644 --- a/packages/stream_chat/lib/src/core/models/attachment_file.g.dart +++ b/packages/stream_chat/lib/src/core/models/attachment_file.g.dart @@ -6,55 +6,52 @@ part of 'attachment_file.dart'; // JsonSerializableGenerator // ************************************************************************** -AttachmentFile _$AttachmentFileFromJson(Map json) => - AttachmentFile( - size: (json['size'] as num?)?.toInt(), - path: json['path'] as String?, - name: json['name'] as String?, - ); - -Map _$AttachmentFileToJson(AttachmentFile instance) => - { - 'path': instance.path, - 'name': instance.name, - 'size': instance.size, - }; +AttachmentFile _$AttachmentFileFromJson(Map json) => AttachmentFile( + size: (json['size'] as num?)?.toInt(), + path: json['path'] as String?, + name: json['name'] as String?, +); + +Map _$AttachmentFileToJson(AttachmentFile instance) => { + 'path': instance.path, + 'name': instance.name, + 'size': instance.size, +}; Preparing _$PreparingFromJson(Map json) => Preparing( - $type: json['runtimeType'] as String?, - ); + $type: json['runtimeType'] as String?, +); Map _$PreparingToJson(Preparing instance) => { - 'runtimeType': instance.$type, - }; + 'runtimeType': instance.$type, +}; InProgress _$InProgressFromJson(Map json) => InProgress( - uploaded: (json['uploaded'] as num).toInt(), - total: (json['total'] as num).toInt(), - $type: json['runtimeType'] as String?, - ); - -Map _$InProgressToJson(InProgress instance) => - { - 'uploaded': instance.uploaded, - 'total': instance.total, - 'runtimeType': instance.$type, - }; + uploaded: (json['uploaded'] as num).toInt(), + total: (json['total'] as num).toInt(), + $type: json['runtimeType'] as String?, +); + +Map _$InProgressToJson(InProgress instance) => { + 'uploaded': instance.uploaded, + 'total': instance.total, + 'runtimeType': instance.$type, +}; Success _$SuccessFromJson(Map json) => Success( - $type: json['runtimeType'] as String?, - ); + $type: json['runtimeType'] as String?, +); Map _$SuccessToJson(Success instance) => { - 'runtimeType': instance.$type, - }; + 'runtimeType': instance.$type, +}; Failed _$FailedFromJson(Map json) => Failed( - error: json['error'] as String, - $type: json['runtimeType'] as String?, - ); + error: json['error'] as String, + $type: json['runtimeType'] as String?, +); Map _$FailedToJson(Failed instance) => { - 'error': instance.error, - 'runtimeType': instance.$type, - }; + 'error': instance.error, + 'runtimeType': instance.$type, +}; diff --git a/packages/stream_chat/lib/src/core/models/attachment_giphy_info.dart b/packages/stream_chat/lib/src/core/models/attachment_giphy_info.dart index bc5d59a326..26ffbb9bce 100644 --- a/packages/stream_chat/lib/src/core/models/attachment_giphy_info.dart +++ b/packages/stream_chat/lib/src/core/models/attachment_giphy_info.dart @@ -17,7 +17,8 @@ enum GiphyInfoType { /// Lower quality with a fixed height with width adjusted according to the /// aspect ratio and played at a lower frame rate. Significantly lower size, /// but visually less appealing. - fixedHeightDownsampled('fixed_height_downsampled'); + fixedHeightDownsampled('fixed_height_downsampled') + ; /// {@macro giphy_info_type} const GiphyInfoType(this.value); diff --git a/packages/stream_chat/lib/src/core/models/banned_user.dart b/packages/stream_chat/lib/src/core/models/banned_user.dart index 4f25ac75fe..7717593b1a 100644 --- a/packages/stream_chat/lib/src/core/models/banned_user.dart +++ b/packages/stream_chat/lib/src/core/models/banned_user.dart @@ -21,8 +21,7 @@ class BannedUser extends Equatable implements ComparableFieldProvider { }); /// Create a new instance from a json - factory BannedUser.fromJson(Map json) => - _$BannedUserFromJson(json); + factory BannedUser.fromJson(Map json) => _$BannedUserFromJson(json); /// Banned user. final User user; @@ -57,27 +56,26 @@ class BannedUser extends Equatable implements ComparableFieldProvider { DateTime? expires, bool? shadow, String? reason, - }) => - BannedUser( - user: user ?? this.user, - bannedBy: bannedBy ?? this.bannedBy, - channel: channel ?? this.channel, - createdAt: createdAt ?? this.createdAt, - expires: expires ?? this.expires, - shadow: shadow ?? this.shadow, - reason: reason ?? this.reason, - ); + }) => BannedUser( + user: user ?? this.user, + bannedBy: bannedBy ?? this.bannedBy, + channel: channel ?? this.channel, + createdAt: createdAt ?? this.createdAt, + expires: expires ?? this.expires, + shadow: shadow ?? this.shadow, + reason: reason ?? this.reason, + ); @override List get props => [ - user, - bannedBy, - channel, - createdAt, - expires, - shadow, - reason, - ]; + user, + bannedBy, + channel, + createdAt, + expires, + shadow, + reason, + ]; @override ComparableField? getComparableField(String sortKey) { diff --git a/packages/stream_chat/lib/src/core/models/banned_user.g.dart b/packages/stream_chat/lib/src/core/models/banned_user.g.dart index 1f2a335b34..024a8fd912 100644 --- a/packages/stream_chat/lib/src/core/models/banned_user.g.dart +++ b/packages/stream_chat/lib/src/core/models/banned_user.g.dart @@ -7,30 +7,21 @@ part of 'banned_user.dart'; // ************************************************************************** BannedUser _$BannedUserFromJson(Map json) => BannedUser( - user: User.fromJson(json['user'] as Map), - bannedBy: json['banned_by'] == null - ? null - : User.fromJson(json['banned_by'] as Map), - channel: json['channel'] == null - ? null - : ChannelModel.fromJson(json['channel'] as Map), - createdAt: json['created_at'] == null - ? null - : DateTime.parse(json['created_at'] as String), - expires: json['expires'] == null - ? null - : DateTime.parse(json['expires'] as String), - shadow: json['shadow'] as bool? ?? false, - reason: json['reason'] as String?, - ); + user: User.fromJson(json['user'] as Map), + bannedBy: json['banned_by'] == null ? null : User.fromJson(json['banned_by'] as Map), + channel: json['channel'] == null ? null : ChannelModel.fromJson(json['channel'] as Map), + createdAt: json['created_at'] == null ? null : DateTime.parse(json['created_at'] as String), + expires: json['expires'] == null ? null : DateTime.parse(json['expires'] as String), + shadow: json['shadow'] as bool? ?? false, + reason: json['reason'] as String?, +); -Map _$BannedUserToJson(BannedUser instance) => - { - 'user': instance.user.toJson(), - 'banned_by': instance.bannedBy?.toJson(), - 'channel': instance.channel?.toJson(), - 'created_at': instance.createdAt?.toIso8601String(), - 'expires': instance.expires?.toIso8601String(), - 'shadow': instance.shadow, - 'reason': instance.reason, - }; +Map _$BannedUserToJson(BannedUser instance) => { + 'user': instance.user.toJson(), + 'banned_by': instance.bannedBy?.toJson(), + 'channel': instance.channel?.toJson(), + 'created_at': instance.createdAt?.toIso8601String(), + 'expires': instance.expires?.toIso8601String(), + 'shadow': instance.shadow, + 'reason': instance.reason, +}; diff --git a/packages/stream_chat/lib/src/core/models/channel_config.dart b/packages/stream_chat/lib/src/core/models/channel_config.dart index dd54e0d40e..32e4760d6c 100644 --- a/packages/stream_chat/lib/src/core/models/channel_config.dart +++ b/packages/stream_chat/lib/src/core/models/channel_config.dart @@ -29,12 +29,11 @@ class ChannelConfig { this.markMessagesPending = false, this.deliveryEvents = false, this.sharedLocations = false, - }) : createdAt = createdAt ?? DateTime.now(), - updatedAt = updatedAt ?? DateTime.now(); + }) : createdAt = createdAt ?? DateTime.now(), + updatedAt = updatedAt ?? DateTime.now(); /// Create a new instance from a json - factory ChannelConfig.fromJson(Map json) => - _$ChannelConfigFromJson(json); + factory ChannelConfig.fromJson(Map json) => _$ChannelConfigFromJson(json); /// Moderation configuration final String automod; diff --git a/packages/stream_chat/lib/src/core/models/channel_config.g.dart b/packages/stream_chat/lib/src/core/models/channel_config.g.dart index 2115711141..9bae9b1ba1 100644 --- a/packages/stream_chat/lib/src/core/models/channel_config.g.dart +++ b/packages/stream_chat/lib/src/core/models/channel_config.g.dart @@ -6,61 +6,52 @@ part of 'channel_config.dart'; // JsonSerializableGenerator // ************************************************************************** -ChannelConfig _$ChannelConfigFromJson(Map json) => - ChannelConfig( - automod: json['automod'] as String? ?? 'flag', - commands: (json['commands'] as List?) - ?.map((e) => Command.fromJson(e as Map)) - .toList() ?? - const [], - connectEvents: json['connect_events'] as bool? ?? false, - createdAt: json['created_at'] == null - ? null - : DateTime.parse(json['created_at'] as String), - updatedAt: json['updated_at'] == null - ? null - : DateTime.parse(json['updated_at'] as String), - maxMessageLength: (json['max_message_length'] as num?)?.toInt() ?? 0, - messageRetention: json['message_retention'] as String? ?? '', - mutes: json['mutes'] as bool? ?? false, - reactions: json['reactions'] as bool? ?? false, - readEvents: json['read_events'] as bool? ?? false, - replies: json['replies'] as bool? ?? false, - search: json['search'] as bool? ?? false, - polls: json['polls'] as bool? ?? false, - typingEvents: json['typing_events'] as bool? ?? false, - uploads: json['uploads'] as bool? ?? false, - urlEnrichment: json['url_enrichment'] as bool? ?? false, - skipLastMsgUpdateForSystemMsgs: - json['skip_last_msg_update_for_system_msgs'] as bool? ?? false, - userMessageReminders: json['user_message_reminders'] as bool? ?? false, - markMessagesPending: json['mark_messages_pending'] as bool? ?? false, - deliveryEvents: json['delivery_events'] as bool? ?? false, - sharedLocations: json['shared_locations'] as bool? ?? false, - ); +ChannelConfig _$ChannelConfigFromJson(Map json) => ChannelConfig( + automod: json['automod'] as String? ?? 'flag', + commands: + (json['commands'] as List?)?.map((e) => Command.fromJson(e as Map)).toList() ?? + const [], + connectEvents: json['connect_events'] as bool? ?? false, + createdAt: json['created_at'] == null ? null : DateTime.parse(json['created_at'] as String), + updatedAt: json['updated_at'] == null ? null : DateTime.parse(json['updated_at'] as String), + maxMessageLength: (json['max_message_length'] as num?)?.toInt() ?? 0, + messageRetention: json['message_retention'] as String? ?? '', + mutes: json['mutes'] as bool? ?? false, + reactions: json['reactions'] as bool? ?? false, + readEvents: json['read_events'] as bool? ?? false, + replies: json['replies'] as bool? ?? false, + search: json['search'] as bool? ?? false, + polls: json['polls'] as bool? ?? false, + typingEvents: json['typing_events'] as bool? ?? false, + uploads: json['uploads'] as bool? ?? false, + urlEnrichment: json['url_enrichment'] as bool? ?? false, + skipLastMsgUpdateForSystemMsgs: json['skip_last_msg_update_for_system_msgs'] as bool? ?? false, + userMessageReminders: json['user_message_reminders'] as bool? ?? false, + markMessagesPending: json['mark_messages_pending'] as bool? ?? false, + deliveryEvents: json['delivery_events'] as bool? ?? false, + sharedLocations: json['shared_locations'] as bool? ?? false, +); -Map _$ChannelConfigToJson(ChannelConfig instance) => - { - 'automod': instance.automod, - 'commands': instance.commands.map((e) => e.toJson()).toList(), - 'connect_events': instance.connectEvents, - 'created_at': instance.createdAt.toIso8601String(), - 'updated_at': instance.updatedAt.toIso8601String(), - 'max_message_length': instance.maxMessageLength, - 'message_retention': instance.messageRetention, - 'mutes': instance.mutes, - 'reactions': instance.reactions, - 'read_events': instance.readEvents, - 'replies': instance.replies, - 'search': instance.search, - 'polls': instance.polls, - 'typing_events': instance.typingEvents, - 'uploads': instance.uploads, - 'url_enrichment': instance.urlEnrichment, - 'skip_last_msg_update_for_system_msgs': - instance.skipLastMsgUpdateForSystemMsgs, - 'user_message_reminders': instance.userMessageReminders, - 'mark_messages_pending': instance.markMessagesPending, - 'delivery_events': instance.deliveryEvents, - 'shared_locations': instance.sharedLocations, - }; +Map _$ChannelConfigToJson(ChannelConfig instance) => { + 'automod': instance.automod, + 'commands': instance.commands.map((e) => e.toJson()).toList(), + 'connect_events': instance.connectEvents, + 'created_at': instance.createdAt.toIso8601String(), + 'updated_at': instance.updatedAt.toIso8601String(), + 'max_message_length': instance.maxMessageLength, + 'message_retention': instance.messageRetention, + 'mutes': instance.mutes, + 'reactions': instance.reactions, + 'read_events': instance.readEvents, + 'replies': instance.replies, + 'search': instance.search, + 'polls': instance.polls, + 'typing_events': instance.typingEvents, + 'uploads': instance.uploads, + 'url_enrichment': instance.urlEnrichment, + 'skip_last_msg_update_for_system_msgs': instance.skipLastMsgUpdateForSystemMsgs, + 'user_message_reminders': instance.userMessageReminders, + 'mark_messages_pending': instance.markMessagesPending, + 'delivery_events': instance.deliveryEvents, + 'shared_locations': instance.sharedLocations, +}; diff --git a/packages/stream_chat/lib/src/core/models/channel_model.dart b/packages/stream_chat/lib/src/core/models/channel_model.dart index 6cc396b744..846b91958e 100644 --- a/packages/stream_chat/lib/src/core/models/channel_model.dart +++ b/packages/stream_chat/lib/src/core/models/channel_model.dart @@ -33,33 +33,31 @@ class ChannelModel { DateTime? truncatedAt, this.messageCount, this.filterTags, - }) : assert( - (cid != null && cid.contains(':')) || (id != null && type != null), - 'provide either a cid or an id and type', - ), - id = id ?? cid!.split(':')[1], - type = type ?? cid!.split(':')[0], - cid = cid ?? '$type:$id', - config = config ?? ChannelConfig(), - createdAt = createdAt ?? DateTime.now(), - updatedAt = updatedAt ?? DateTime.now(), - ownCapabilities = ownCapabilities?.map(ChannelCapability.new).toList(), - - // For backwards compatibility, set 'disabled', 'hidden' - // and 'truncated_at' in [extraData]. - extraData = { - ...extraData, - if (disabled != null) 'disabled': disabled, - if (hidden != null) 'hidden': hidden, - if (truncatedAt != null) - 'truncated_at': truncatedAt.toIso8601String(), - }; + }) : assert( + (cid != null && cid.contains(':')) || (id != null && type != null), + 'provide either a cid or an id and type', + ), + id = id ?? cid!.split(':')[1], + type = type ?? cid!.split(':')[0], + cid = cid ?? '$type:$id', + config = config ?? ChannelConfig(), + createdAt = createdAt ?? DateTime.now(), + updatedAt = updatedAt ?? DateTime.now(), + ownCapabilities = ownCapabilities?.map(ChannelCapability.new).toList(), + + // For backwards compatibility, set 'disabled', 'hidden' + // and 'truncated_at' in [extraData]. + extraData = { + ...extraData, + if (disabled != null) 'disabled': disabled, + if (hidden != null) 'hidden': hidden, + if (truncatedAt != null) 'truncated_at': truncatedAt.toIso8601String(), + }; /// Create a new instance from a json - factory ChannelModel.fromJson(Map json) => - _$ChannelModelFromJson( - Serializer.moveToExtraDataFromRoot(json, topLevelFields), - ); + factory ChannelModel.fromJson(Map json) => _$ChannelModelFromJson( + Serializer.moveToExtraDataFromRoot(json, topLevelFields), + ); /// The id of this channel final String id; @@ -190,8 +188,8 @@ class ChannelModel { /// Serialize to json Map toJson() => Serializer.moveFromExtraDataToRoot( - _$ChannelModelToJson(this), - ); + _$ChannelModelToJson(this), + ); /// Creates a copy of [ChannelModel] with specified attributes overridden. ChannelModel copyWith({ @@ -216,35 +214,35 @@ class ChannelModel { DateTime? truncatedAt, int? messageCount, List? filterTags, - }) => - ChannelModel( - id: id ?? this.id, - type: type ?? this.type, - cid: cid ?? this.cid, - ownCapabilities: ownCapabilities ?? this.ownCapabilities, - config: config ?? this.config, - createdBy: createdBy ?? this.createdBy, - frozen: frozen ?? this.frozen, - lastMessageAt: lastMessageAt ?? this.lastMessageAt, - createdAt: createdAt ?? this.createdAt, - updatedAt: updatedAt ?? this.updatedAt, - deletedAt: deletedAt ?? this.deletedAt, - memberCount: memberCount ?? this.memberCount, - members: members ?? this.members, - extraData: extraData ?? this.extraData, - team: team ?? this.team, - cooldown: cooldown ?? this.cooldown, - disabled: disabled ?? extraData?['disabled'] as bool? ?? this.disabled, - hidden: hidden ?? extraData?['hidden'] as bool? ?? this.hidden, - truncatedAt: truncatedAt ?? - (extraData?['truncated_at'] == null - ? null - // ignore: cast_nullable_to_non_nullable - : DateTime.parse(extraData?['truncated_at'] as String)) ?? - this.truncatedAt, - messageCount: messageCount ?? this.messageCount, - filterTags: filterTags ?? this.filterTags, - ); + }) => ChannelModel( + id: id ?? this.id, + type: type ?? this.type, + cid: cid ?? this.cid, + ownCapabilities: ownCapabilities ?? this.ownCapabilities, + config: config ?? this.config, + createdBy: createdBy ?? this.createdBy, + frozen: frozen ?? this.frozen, + lastMessageAt: lastMessageAt ?? this.lastMessageAt, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedAt: deletedAt ?? this.deletedAt, + memberCount: memberCount ?? this.memberCount, + members: members ?? this.members, + extraData: extraData ?? this.extraData, + team: team ?? this.team, + cooldown: cooldown ?? this.cooldown, + disabled: disabled ?? extraData?['disabled'] as bool? ?? this.disabled, + hidden: hidden ?? extraData?['hidden'] as bool? ?? this.hidden, + truncatedAt: + truncatedAt ?? + (extraData?['truncated_at'] == null + ? null + // ignore: cast_nullable_to_non_nullable + : DateTime.parse(extraData?['truncated_at'] as String)) ?? + this.truncatedAt, + messageCount: messageCount ?? this.messageCount, + filterTags: filterTags ?? this.filterTags, + ); /// Returns a new [ChannelModel] that is a combination of this channelModel /// and the given [other] channelModel. diff --git a/packages/stream_chat/lib/src/core/models/channel_model.g.dart b/packages/stream_chat/lib/src/core/models/channel_model.g.dart index cc64ccc034..d2c1ce62eb 100644 --- a/packages/stream_chat/lib/src/core/models/channel_model.g.dart +++ b/packages/stream_chat/lib/src/core/models/channel_model.g.dart @@ -7,49 +7,30 @@ part of 'channel_model.dart'; // ************************************************************************** ChannelModel _$ChannelModelFromJson(Map json) => ChannelModel( - id: json['id'] as String?, - type: json['type'] as String?, - cid: json['cid'] as String?, - ownCapabilities: (json['own_capabilities'] as List?) - ?.map((e) => e as String) - .toList(), - config: json['config'] == null - ? null - : ChannelConfig.fromJson(json['config'] as Map), - createdBy: json['created_by'] == null - ? null - : User.fromJson(json['created_by'] as Map), - frozen: json['frozen'] as bool? ?? false, - lastMessageAt: json['last_message_at'] == null - ? null - : DateTime.parse(json['last_message_at'] as String), - createdAt: json['created_at'] == null - ? null - : DateTime.parse(json['created_at'] as String), - updatedAt: json['updated_at'] == null - ? null - : DateTime.parse(json['updated_at'] as String), - deletedAt: json['deleted_at'] == null - ? null - : DateTime.parse(json['deleted_at'] as String), - memberCount: (json['member_count'] as num?)?.toInt() ?? 0, - members: (json['members'] as List?) - ?.map((e) => Member.fromJson(e as Map)) - .toList(), - extraData: json['extra_data'] as Map? ?? const {}, - team: json['team'] as String?, - cooldown: (json['cooldown'] as num?)?.toInt() ?? 0, - messageCount: (json['message_count'] as num?)?.toInt(), - filterTags: (json['filter_tags'] as List?) - ?.map((e) => e as String) - .toList(), - ); + id: json['id'] as String?, + type: json['type'] as String?, + cid: json['cid'] as String?, + ownCapabilities: (json['own_capabilities'] as List?)?.map((e) => e as String).toList(), + config: json['config'] == null ? null : ChannelConfig.fromJson(json['config'] as Map), + createdBy: json['created_by'] == null ? null : User.fromJson(json['created_by'] as Map), + frozen: json['frozen'] as bool? ?? false, + lastMessageAt: json['last_message_at'] == null ? null : DateTime.parse(json['last_message_at'] as String), + createdAt: json['created_at'] == null ? null : DateTime.parse(json['created_at'] as String), + updatedAt: json['updated_at'] == null ? null : DateTime.parse(json['updated_at'] as String), + deletedAt: json['deleted_at'] == null ? null : DateTime.parse(json['deleted_at'] as String), + memberCount: (json['member_count'] as num?)?.toInt() ?? 0, + members: (json['members'] as List?)?.map((e) => Member.fromJson(e as Map)).toList(), + extraData: json['extra_data'] as Map? ?? const {}, + team: json['team'] as String?, + cooldown: (json['cooldown'] as num?)?.toInt() ?? 0, + messageCount: (json['message_count'] as num?)?.toInt(), + filterTags: (json['filter_tags'] as List?)?.map((e) => e as String).toList(), +); -Map _$ChannelModelToJson(ChannelModel instance) => - { - 'id': instance.id, - 'type': instance.type, - 'frozen': instance.frozen, - 'cooldown': instance.cooldown, - 'extra_data': instance.extraData, - }; +Map _$ChannelModelToJson(ChannelModel instance) => { + 'id': instance.id, + 'type': instance.type, + 'frozen': instance.frozen, + 'cooldown': instance.cooldown, + 'extra_data': instance.extraData, +}; diff --git a/packages/stream_chat/lib/src/core/models/channel_mute.dart b/packages/stream_chat/lib/src/core/models/channel_mute.dart index c0d29a57c0..a393ae14da 100644 --- a/packages/stream_chat/lib/src/core/models/channel_mute.dart +++ b/packages/stream_chat/lib/src/core/models/channel_mute.dart @@ -17,8 +17,7 @@ class ChannelMute { }); /// Create a new instance from a json - factory ChannelMute.fromJson(Map json) => - _$ChannelMuteFromJson(json); + factory ChannelMute.fromJson(Map json) => _$ChannelMuteFromJson(json); /// The user that performed the muting action final User user; diff --git a/packages/stream_chat/lib/src/core/models/channel_mute.g.dart b/packages/stream_chat/lib/src/core/models/channel_mute.g.dart index f0b973fcbf..4b3576c0ae 100644 --- a/packages/stream_chat/lib/src/core/models/channel_mute.g.dart +++ b/packages/stream_chat/lib/src/core/models/channel_mute.g.dart @@ -7,20 +7,17 @@ part of 'channel_mute.dart'; // ************************************************************************** ChannelMute _$ChannelMuteFromJson(Map json) => ChannelMute( - user: User.fromJson(json['user'] as Map), - channel: ChannelModel.fromJson(json['channel'] as Map), - createdAt: DateTime.parse(json['created_at'] as String), - updatedAt: DateTime.parse(json['updated_at'] as String), - expires: json['expires'] == null - ? null - : DateTime.parse(json['expires'] as String), - ); + user: User.fromJson(json['user'] as Map), + channel: ChannelModel.fromJson(json['channel'] as Map), + createdAt: DateTime.parse(json['created_at'] as String), + updatedAt: DateTime.parse(json['updated_at'] as String), + expires: json['expires'] == null ? null : DateTime.parse(json['expires'] as String), +); -Map _$ChannelMuteToJson(ChannelMute instance) => - { - 'user': instance.user.toJson(), - 'channel': instance.channel.toJson(), - 'created_at': instance.createdAt.toIso8601String(), - 'updated_at': instance.updatedAt.toIso8601String(), - 'expires': instance.expires?.toIso8601String(), - }; +Map _$ChannelMuteToJson(ChannelMute instance) => { + 'user': instance.user.toJson(), + 'channel': instance.channel.toJson(), + 'created_at': instance.createdAt.toIso8601String(), + 'updated_at': instance.updatedAt.toIso8601String(), + 'expires': instance.expires?.toIso8601String(), +}; diff --git a/packages/stream_chat/lib/src/core/models/channel_state.dart b/packages/stream_chat/lib/src/core/models/channel_state.dart index 830170646b..718e4b74f5 100644 --- a/packages/stream_chat/lib/src/core/models/channel_state.dart +++ b/packages/stream_chat/lib/src/core/models/channel_state.dart @@ -92,8 +92,7 @@ class ChannelState implements ComparableFieldProvider { final List? activeLiveLocations; /// Create a new instance from a json - static ChannelState fromJson(Map json) => - _$ChannelStateFromJson(json); + static ChannelState fromJson(Map json) => _$ChannelStateFromJson(json); /// Serialize to json Map toJson() => _$ChannelStateToJson(this); @@ -112,21 +111,20 @@ class ChannelState implements ComparableFieldProvider { List? pendingMessages, ChannelPushPreference? pushPreferences, List? activeLiveLocations, - }) => - ChannelState( - channel: channel ?? this.channel, - messages: messages ?? this.messages, - members: members ?? this.members, - pinnedMessages: pinnedMessages ?? this.pinnedMessages, - watcherCount: watcherCount ?? this.watcherCount, - watchers: watchers ?? this.watchers, - read: read ?? this.read, - membership: membership ?? this.membership, - draft: draft == _nullConst ? this.draft : draft as Draft?, - pendingMessages: pendingMessages ?? this.pendingMessages, - pushPreferences: pushPreferences ?? this.pushPreferences, - activeLiveLocations: activeLiveLocations ?? this.activeLiveLocations, - ); + }) => ChannelState( + channel: channel ?? this.channel, + messages: messages ?? this.messages, + members: members ?? this.members, + pinnedMessages: pinnedMessages ?? this.pinnedMessages, + watcherCount: watcherCount ?? this.watcherCount, + watchers: watchers ?? this.watchers, + read: read ?? this.read, + membership: membership ?? this.membership, + draft: draft == _nullConst ? this.draft : draft as Draft?, + pendingMessages: pendingMessages ?? this.pendingMessages, + pushPreferences: pushPreferences ?? this.pushPreferences, + activeLiveLocations: activeLiveLocations ?? this.activeLiveLocations, + ); @override ComparableField? getComparableField(String sortKey) { diff --git a/packages/stream_chat/lib/src/core/models/channel_state.g.dart b/packages/stream_chat/lib/src/core/models/channel_state.g.dart index cb73c3a42e..a3c6c2692d 100644 --- a/packages/stream_chat/lib/src/core/models/channel_state.g.dart +++ b/packages/stream_chat/lib/src/core/models/channel_state.g.dart @@ -7,60 +7,39 @@ part of 'channel_state.dart'; // ************************************************************************** ChannelState _$ChannelStateFromJson(Map json) => ChannelState( - channel: json['channel'] == null - ? null - : ChannelModel.fromJson(json['channel'] as Map), - messages: (json['messages'] as List?) - ?.map((e) => Message.fromJson(e as Map)) - .toList(), - members: (json['members'] as List?) - ?.map((e) => Member.fromJson(e as Map)) - .toList(), - pinnedMessages: (json['pinned_messages'] as List?) - ?.map((e) => Message.fromJson(e as Map)) - .toList(), - watcherCount: (json['watcher_count'] as num?)?.toInt(), - watchers: (json['watchers'] as List?) - ?.map((e) => User.fromJson(e as Map)) - .toList(), - read: (json['read'] as List?) - ?.map((e) => Read.fromJson(e as Map)) - .toList(), - membership: json['membership'] == null - ? null - : Member.fromJson(json['membership'] as Map), - draft: json['draft'] == null - ? null - : Draft.fromJson(json['draft'] as Map), - pendingMessages: - (ChannelState._pendingMessagesReadValue(json, 'pending_messages') - as List?) - ?.map((e) => Message.fromJson(e as Map)) - .toList(), - pushPreferences: json['push_preferences'] == null - ? null - : ChannelPushPreference.fromJson( - json['push_preferences'] as Map), - activeLiveLocations: (json['active_live_locations'] as List?) - ?.map((e) => Location.fromJson(e as Map)) - .toList(), - ); + channel: json['channel'] == null ? null : ChannelModel.fromJson(json['channel'] as Map), + messages: (json['messages'] as List?)?.map((e) => Message.fromJson(e as Map)).toList(), + members: (json['members'] as List?)?.map((e) => Member.fromJson(e as Map)).toList(), + pinnedMessages: (json['pinned_messages'] as List?) + ?.map((e) => Message.fromJson(e as Map)) + .toList(), + watcherCount: (json['watcher_count'] as num?)?.toInt(), + watchers: (json['watchers'] as List?)?.map((e) => User.fromJson(e as Map)).toList(), + read: (json['read'] as List?)?.map((e) => Read.fromJson(e as Map)).toList(), + membership: json['membership'] == null ? null : Member.fromJson(json['membership'] as Map), + draft: json['draft'] == null ? null : Draft.fromJson(json['draft'] as Map), + pendingMessages: (ChannelState._pendingMessagesReadValue(json, 'pending_messages') as List?) + ?.map((e) => Message.fromJson(e as Map)) + .toList(), + pushPreferences: json['push_preferences'] == null + ? null + : ChannelPushPreference.fromJson(json['push_preferences'] as Map), + activeLiveLocations: (json['active_live_locations'] as List?) + ?.map((e) => Location.fromJson(e as Map)) + .toList(), +); -Map _$ChannelStateToJson(ChannelState instance) => - { - 'channel': instance.channel?.toJson(), - 'messages': instance.messages?.map((e) => e.toJson()).toList(), - 'members': instance.members?.map((e) => e.toJson()).toList(), - 'pinned_messages': - instance.pinnedMessages?.map((e) => e.toJson()).toList(), - 'watcher_count': instance.watcherCount, - 'watchers': instance.watchers?.map((e) => e.toJson()).toList(), - 'read': instance.read?.map((e) => e.toJson()).toList(), - 'membership': instance.membership?.toJson(), - 'draft': instance.draft?.toJson(), - 'pending_messages': - instance.pendingMessages?.map((e) => e.toJson()).toList(), - 'push_preferences': instance.pushPreferences?.toJson(), - 'active_live_locations': - instance.activeLiveLocations?.map((e) => e.toJson()).toList(), - }; +Map _$ChannelStateToJson(ChannelState instance) => { + 'channel': instance.channel?.toJson(), + 'messages': instance.messages?.map((e) => e.toJson()).toList(), + 'members': instance.members?.map((e) => e.toJson()).toList(), + 'pinned_messages': instance.pinnedMessages?.map((e) => e.toJson()).toList(), + 'watcher_count': instance.watcherCount, + 'watchers': instance.watchers?.map((e) => e.toJson()).toList(), + 'read': instance.read?.map((e) => e.toJson()).toList(), + 'membership': instance.membership?.toJson(), + 'draft': instance.draft?.toJson(), + 'pending_messages': instance.pendingMessages?.map((e) => e.toJson()).toList(), + 'push_preferences': instance.pushPreferences?.toJson(), + 'active_live_locations': instance.activeLiveLocations?.map((e) => e.toJson()).toList(), +}; diff --git a/packages/stream_chat/lib/src/core/models/command.dart b/packages/stream_chat/lib/src/core/models/command.dart index 5ba0043c18..0247637560 100644 --- a/packages/stream_chat/lib/src/core/models/command.dart +++ b/packages/stream_chat/lib/src/core/models/command.dart @@ -13,8 +13,7 @@ class Command { }); /// Create a new instance from a json - factory Command.fromJson(Map json) => - _$CommandFromJson(json); + factory Command.fromJson(Map json) => _$CommandFromJson(json); /// The name of the command final String name; diff --git a/packages/stream_chat/lib/src/core/models/command.g.dart b/packages/stream_chat/lib/src/core/models/command.g.dart index 0fe9d54c6e..24c9362156 100644 --- a/packages/stream_chat/lib/src/core/models/command.g.dart +++ b/packages/stream_chat/lib/src/core/models/command.g.dart @@ -7,13 +7,13 @@ part of 'command.dart'; // ************************************************************************** Command _$CommandFromJson(Map json) => Command( - name: json['name'] as String, - description: json['description'] as String, - args: json['args'] as String, - ); + name: json['name'] as String, + description: json['description'] as String, + args: json['args'] as String, +); Map _$CommandToJson(Command instance) => { - 'name': instance.name, - 'description': instance.description, - 'args': instance.args, - }; + 'name': instance.name, + 'description': instance.description, + 'args': instance.args, +}; diff --git a/packages/stream_chat/lib/src/core/models/comparable_field.dart b/packages/stream_chat/lib/src/core/models/comparable_field.dart index f43df58a33..5b192541b2 100644 --- a/packages/stream_chat/lib/src/core/models/comparable_field.dart +++ b/packages/stream_chat/lib/src/core/models/comparable_field.dart @@ -28,7 +28,7 @@ class ComparableField implements Comparable> { (final DateTime a, final DateTime b) => a.compareTo(b), (final bool a, final bool b) when a == b => 0, (final bool a, final bool b) => a && !b ? 1 : -1, // true > false - _ => 0 // All comparisons were equal or incomparable types + _ => 0, // All comparisons were equal or incomparable types }; } } diff --git a/packages/stream_chat/lib/src/core/models/device.g.dart b/packages/stream_chat/lib/src/core/models/device.g.dart index 4de1f064af..2725a082f1 100644 --- a/packages/stream_chat/lib/src/core/models/device.g.dart +++ b/packages/stream_chat/lib/src/core/models/device.g.dart @@ -7,11 +7,11 @@ part of 'device.dart'; // ************************************************************************** Device _$DeviceFromJson(Map json) => Device( - id: json['id'] as String, - pushProvider: json['push_provider'] as String, - ); + id: json['id'] as String, + pushProvider: json['push_provider'] as String, +); Map _$DeviceToJson(Device instance) => { - 'id': instance.id, - 'push_provider': instance.pushProvider, - }; + 'id': instance.id, + 'push_provider': instance.pushProvider, +}; diff --git a/packages/stream_chat/lib/src/core/models/draft.dart b/packages/stream_chat/lib/src/core/models/draft.dart index fa4520114c..c6fb6139b2 100644 --- a/packages/stream_chat/lib/src/core/models/draft.dart +++ b/packages/stream_chat/lib/src/core/models/draft.dart @@ -73,14 +73,14 @@ class Draft extends Equatable implements ComparableFieldProvider { @override List get props => [ - channelCid, - createdAt, - message, - channel, - parentId, - parentMessage, - quotedMessage, - ]; + channelCid, + createdAt, + message, + channel, + parentId, + parentMessage, + quotedMessage, + ]; @override ComparableField? getComparableField(String sortKey) { diff --git a/packages/stream_chat/lib/src/core/models/draft.g.dart b/packages/stream_chat/lib/src/core/models/draft.g.dart index 9a7b7de9ce..1ea7feafc3 100644 --- a/packages/stream_chat/lib/src/core/models/draft.g.dart +++ b/packages/stream_chat/lib/src/core/models/draft.g.dart @@ -7,29 +7,25 @@ part of 'draft.dart'; // ************************************************************************** Draft _$DraftFromJson(Map json) => Draft( - channelCid: json['channel_cid'] as String, - createdAt: DateTime.parse(json['created_at'] as String), - message: DraftMessage.fromJson(json['message'] as Map), - channel: json['channel'] == null - ? null - : ChannelModel.fromJson(json['channel'] as Map), - parentId: json['parent_id'] as String?, - parentMessage: json['parent_message'] == null - ? null - : Message.fromJson(json['parent_message'] as Map), - quotedMessage: json['quoted_message'] == null - ? null - : Message.fromJson(json['quoted_message'] as Map), - ); + channelCid: json['channel_cid'] as String, + createdAt: DateTime.parse(json['created_at'] as String), + message: DraftMessage.fromJson(json['message'] as Map), + channel: json['channel'] == null ? null : ChannelModel.fromJson(json['channel'] as Map), + parentId: json['parent_id'] as String?, + parentMessage: json['parent_message'] == null + ? null + : Message.fromJson(json['parent_message'] as Map), + quotedMessage: json['quoted_message'] == null + ? null + : Message.fromJson(json['quoted_message'] as Map), +); Map _$DraftToJson(Draft instance) => { - 'channel_cid': instance.channelCid, - 'created_at': instance.createdAt.toIso8601String(), - 'message': instance.message.toJson(), - if (instance.channel?.toJson() case final value?) 'channel': value, - if (instance.parentId case final value?) 'parent_id': value, - if (instance.parentMessage?.toJson() case final value?) - 'parent_message': value, - if (instance.quotedMessage?.toJson() case final value?) - 'quoted_message': value, - }; + 'channel_cid': instance.channelCid, + 'created_at': instance.createdAt.toIso8601String(), + 'message': instance.message.toJson(), + if (instance.channel?.toJson() case final value?) 'channel': value, + if (instance.parentId case final value?) 'parent_id': value, + if (instance.parentMessage?.toJson() case final value?) 'parent_message': value, + if (instance.quotedMessage?.toJson() case final value?) 'quoted_message': value, +}; diff --git a/packages/stream_chat/lib/src/core/models/draft_message.dart b/packages/stream_chat/lib/src/core/models/draft_message.dart index 13c373475b..b6ae3dede0 100644 --- a/packages/stream_chat/lib/src/core/models/draft_message.dart +++ b/packages/stream_chat/lib/src/core/models/draft_message.dart @@ -28,16 +28,15 @@ class DraftMessage extends Equatable { this.poll, String? pollId, this.extraData = const {}, - }) : id = id ?? const Uuid().v4(), - type = MessageType(type), - _quotedMessageId = quotedMessageId, - _pollId = pollId; + }) : id = id ?? const Uuid().v4(), + type = MessageType(type), + _quotedMessageId = quotedMessageId, + _pollId = pollId; /// Create a new instance from JSON. - factory DraftMessage.fromJson(Map json) => - _$DraftMessageFromJson( - Serializer.moveToExtraDataFromRoot(json, topLevelFields), - ); + factory DraftMessage.fromJson(Map json) => _$DraftMessageFromJson( + Serializer.moveToExtraDataFromRoot(json, topLevelFields), + ); /// The message ID. This is either created by Stream or set client side when /// the message is added. @@ -167,19 +166,19 @@ class DraftMessage extends Equatable { @override List get props => [ - id, - text, - type, - attachments, - parentId, - showInChannel, - mentionedUsers, - quotedMessageId, - silent, - command, - pollId, - extraData, - ]; + id, + text, + type, + attachments, + parentId, + showInChannel, + mentionedUsers, + quotedMessageId, + silent, + command, + pollId, + extraData, + ]; } /// Extension on [Message] to convert it to a [DraftMessage]. diff --git a/packages/stream_chat/lib/src/core/models/draft_message.g.dart b/packages/stream_chat/lib/src/core/models/draft_message.g.dart index 910ca3b090..b1b96bf505 100644 --- a/packages/stream_chat/lib/src/core/models/draft_message.g.dart +++ b/packages/stream_chat/lib/src/core/models/draft_message.g.dart @@ -7,47 +7,38 @@ part of 'draft_message.dart'; // ************************************************************************** DraftMessage _$DraftMessageFromJson(Map json) => DraftMessage( - id: json['id'] as String?, - text: json['text'] as String?, - type: json['type'] == null - ? MessageType.regular - : MessageType.fromJson(json['type'] as String), - attachments: (json['attachments'] as List?) - ?.map((e) => Attachment.fromJson(e as Map)) - .toList() ?? - const [], - parentId: json['parent_id'] as String?, - showInChannel: json['show_in_channel'] as bool?, - mentionedUsers: (json['mentioned_users'] as List?) - ?.map((e) => User.fromJson(e as Map)) - .toList() ?? - const [], - quotedMessage: json['quoted_message'] == null - ? null - : Message.fromJson(json['quoted_message'] as Map), - quotedMessageId: json['quoted_message_id'] as String?, - silent: json['silent'] as bool? ?? false, - command: json['command'] as String?, - poll: json['poll'] == null - ? null - : Poll.fromJson(json['poll'] as Map), - pollId: json['poll_id'] as String?, - extraData: json['extra_data'] as Map? ?? const {}, - ); + id: json['id'] as String?, + text: json['text'] as String?, + type: json['type'] == null ? MessageType.regular : MessageType.fromJson(json['type'] as String), + attachments: + (json['attachments'] as List?)?.map((e) => Attachment.fromJson(e as Map)).toList() ?? + const [], + parentId: json['parent_id'] as String?, + showInChannel: json['show_in_channel'] as bool?, + mentionedUsers: + (json['mentioned_users'] as List?)?.map((e) => User.fromJson(e as Map)).toList() ?? + const [], + quotedMessage: json['quoted_message'] == null + ? null + : Message.fromJson(json['quoted_message'] as Map), + quotedMessageId: json['quoted_message_id'] as String?, + silent: json['silent'] as bool? ?? false, + command: json['command'] as String?, + poll: json['poll'] == null ? null : Poll.fromJson(json['poll'] as Map), + pollId: json['poll_id'] as String?, + extraData: json['extra_data'] as Map? ?? const {}, +); -Map _$DraftMessageToJson(DraftMessage instance) => - { - 'id': instance.id, - if (instance.text case final value?) 'text': value, - if (MessageType.toJson(instance.type) case final value?) 'type': value, - 'attachments': instance.attachments.map((e) => e.toJson()).toList(), - if (instance.parentId case final value?) 'parent_id': value, - if (instance.showInChannel case final value?) 'show_in_channel': value, - if (User.toIds(instance.mentionedUsers) case final value?) - 'mentioned_users': value, - if (instance.quotedMessageId case final value?) - 'quoted_message_id': value, - 'silent': instance.silent, - if (instance.pollId case final value?) 'poll_id': value, - 'extra_data': instance.extraData, - }; +Map _$DraftMessageToJson(DraftMessage instance) => { + 'id': instance.id, + if (instance.text case final value?) 'text': value, + if (MessageType.toJson(instance.type) case final value?) 'type': value, + 'attachments': instance.attachments.map((e) => e.toJson()).toList(), + if (instance.parentId case final value?) 'parent_id': value, + if (instance.showInChannel case final value?) 'show_in_channel': value, + if (User.toIds(instance.mentionedUsers) case final value?) 'mentioned_users': value, + if (instance.quotedMessageId case final value?) 'quoted_message_id': value, + 'silent': instance.silent, + if (instance.pollId case final value?) 'poll_id': value, + 'extra_data': instance.extraData, +}; diff --git a/packages/stream_chat/lib/src/core/models/event.dart b/packages/stream_chat/lib/src/core/models/event.dart index 1a864ce304..bc2eb27986 100644 --- a/packages/stream_chat/lib/src/core/models/event.dart +++ b/packages/stream_chat/lib/src/core/models/event.dart @@ -52,11 +52,12 @@ class Event { }) : createdAt = createdAt?.toUtc() ?? DateTime.now().toUtc(); /// Create a new instance from a json - factory Event.fromJson(Map json) => - _$EventFromJson(Serializer.moveToExtraDataFromRoot( - json, - topLevelFields, - )); + factory Event.fromJson(Map json) => _$EventFromJson( + Serializer.moveToExtraDataFromRoot( + json, + topLevelFields, + ), + ); /// The type of the event /// [EventType] contains some predefined constant types @@ -227,8 +228,8 @@ class Event { /// Serialize to json Map toJson() => Serializer.moveFromExtraDataToRoot( - _$EventToJson(this), - ); + _$EventToJson(this), + ); /// Creates a copy of [Event] with specified attributes overridden. Event copyWith({ @@ -271,51 +272,48 @@ class Event { DateTime? lastDeliveredAt, String? lastDeliveredMessageId, Map? extraData, - }) => - Event( - type: type ?? this.type, - userId: userId ?? this.userId, - cid: cid ?? this.cid, - connectionId: connectionId ?? this.connectionId, - createdAt: createdAt ?? this.createdAt, - me: me ?? this.me, - user: user ?? this.user, - message: message ?? this.message, - poll: poll ?? this.poll, - pollVote: pollVote ?? this.pollVote, - totalUnreadCount: totalUnreadCount ?? this.totalUnreadCount, - unreadChannels: unreadChannels ?? this.unreadChannels, - reaction: reaction ?? this.reaction, - online: online ?? this.online, - channel: channel ?? this.channel, - member: member ?? this.member, - channelId: channelId ?? this.channelId, - channelType: channelType ?? this.channelType, - channelLastMessageAt: channelLastMessageAt ?? this.channelLastMessageAt, - parentId: parentId ?? this.parentId, - hardDelete: hardDelete ?? this.hardDelete, - deletedForMe: deletedForMe ?? this.deletedForMe, - aiState: aiState ?? this.aiState, - aiMessage: aiMessage ?? this.aiMessage, - messageId: messageId ?? this.messageId, - thread: thread ?? this.thread, - unreadThreadMessages: unreadThreadMessages ?? this.unreadThreadMessages, - unreadThreads: unreadThreads ?? this.unreadThreads, - lastReadAt: lastReadAt ?? this.lastReadAt, - unreadMessages: unreadMessages ?? this.unreadMessages, - lastReadMessageId: lastReadMessageId ?? this.lastReadMessageId, - draft: draft ?? this.draft, - reminder: reminder ?? this.reminder, - pushPreference: pushPreference ?? this.pushPreference, - channelPushPreference: - channelPushPreference ?? this.channelPushPreference, - channelMessageCount: channelMessageCount ?? this.channelMessageCount, - lastDeliveredAt: lastDeliveredAt ?? this.lastDeliveredAt, - lastDeliveredMessageId: - lastDeliveredMessageId ?? this.lastDeliveredMessageId, - isLocal: isLocal, - extraData: extraData ?? this.extraData, - ); + }) => Event( + type: type ?? this.type, + userId: userId ?? this.userId, + cid: cid ?? this.cid, + connectionId: connectionId ?? this.connectionId, + createdAt: createdAt ?? this.createdAt, + me: me ?? this.me, + user: user ?? this.user, + message: message ?? this.message, + poll: poll ?? this.poll, + pollVote: pollVote ?? this.pollVote, + totalUnreadCount: totalUnreadCount ?? this.totalUnreadCount, + unreadChannels: unreadChannels ?? this.unreadChannels, + reaction: reaction ?? this.reaction, + online: online ?? this.online, + channel: channel ?? this.channel, + member: member ?? this.member, + channelId: channelId ?? this.channelId, + channelType: channelType ?? this.channelType, + channelLastMessageAt: channelLastMessageAt ?? this.channelLastMessageAt, + parentId: parentId ?? this.parentId, + hardDelete: hardDelete ?? this.hardDelete, + deletedForMe: deletedForMe ?? this.deletedForMe, + aiState: aiState ?? this.aiState, + aiMessage: aiMessage ?? this.aiMessage, + messageId: messageId ?? this.messageId, + thread: thread ?? this.thread, + unreadThreadMessages: unreadThreadMessages ?? this.unreadThreadMessages, + unreadThreads: unreadThreads ?? this.unreadThreads, + lastReadAt: lastReadAt ?? this.lastReadAt, + unreadMessages: unreadMessages ?? this.unreadMessages, + lastReadMessageId: lastReadMessageId ?? this.lastReadMessageId, + draft: draft ?? this.draft, + reminder: reminder ?? this.reminder, + pushPreference: pushPreference ?? this.pushPreference, + channelPushPreference: channelPushPreference ?? this.channelPushPreference, + channelMessageCount: channelMessageCount ?? this.channelMessageCount, + lastDeliveredAt: lastDeliveredAt ?? this.lastDeliveredAt, + lastDeliveredMessageId: lastDeliveredMessageId ?? this.lastDeliveredMessageId, + isLocal: isLocal, + extraData: extraData ?? this.extraData, + ); } /// {@template aiState} diff --git a/packages/stream_chat/lib/src/core/models/event.g.dart b/packages/stream_chat/lib/src/core/models/event.g.dart index fc7bbe0db0..c3aa215012 100644 --- a/packages/stream_chat/lib/src/core/models/event.g.dart +++ b/packages/stream_chat/lib/src/core/models/event.g.dart @@ -7,138 +7,96 @@ part of 'event.dart'; // ************************************************************************** Event _$EventFromJson(Map json) => Event( - type: json['type'] as String? ?? 'local.event', - userId: json['user_id'] as String?, - cid: json['cid'] as String?, - connectionId: json['connection_id'] as String?, - createdAt: json['created_at'] == null - ? null - : DateTime.parse(json['created_at'] as String), - me: json['me'] == null - ? null - : OwnUser.fromJson(json['me'] as Map), - user: json['user'] == null - ? null - : User.fromJson(json['user'] as Map), - message: json['message'] == null - ? null - : Message.fromJson(json['message'] as Map), - poll: json['poll'] == null - ? null - : Poll.fromJson(json['poll'] as Map), - pollVote: json['poll_vote'] == null - ? null - : PollVote.fromJson(json['poll_vote'] as Map), - totalUnreadCount: (json['total_unread_count'] as num?)?.toInt(), - unreadChannels: (json['unread_channels'] as num?)?.toInt(), - reaction: json['reaction'] == null - ? null - : Reaction.fromJson(json['reaction'] as Map), - online: json['online'] as bool?, - channel: json['channel'] == null - ? null - : ChannelModel.fromJson(json['channel'] as Map), - member: json['member'] == null - ? null - : Member.fromJson(json['member'] as Map), - channelId: json['channel_id'] as String?, - channelType: json['channel_type'] as String?, - channelLastMessageAt: json['channel_last_message_at'] == null - ? null - : DateTime.parse(json['channel_last_message_at'] as String), - parentId: json['parent_id'] as String?, - hardDelete: json['hard_delete'] as bool?, - deletedForMe: json['deleted_for_me'] as bool?, - aiState: $enumDecodeNullable(_$AITypingStateEnumMap, json['ai_state'], - unknownValue: AITypingState.idle), - aiMessage: json['ai_message'] as String?, - messageId: json['message_id'] as String?, - thread: json['thread'] == null - ? null - : Thread.fromJson(json['thread'] as Map), - unreadThreadMessages: (json['unread_thread_messages'] as num?)?.toInt(), - unreadThreads: (json['unread_threads'] as num?)?.toInt(), - lastReadAt: json['last_read_at'] == null - ? null - : DateTime.parse(json['last_read_at'] as String), - unreadMessages: (json['unread_messages'] as num?)?.toInt(), - lastReadMessageId: json['last_read_message_id'] as String?, - draft: json['draft'] == null - ? null - : Draft.fromJson(json['draft'] as Map), - reminder: json['reminder'] == null - ? null - : MessageReminder.fromJson(json['reminder'] as Map), - pushPreference: json['push_preference'] == null - ? null - : PushPreference.fromJson( - json['push_preference'] as Map), - channelPushPreference: json['channel_push_preference'] == null - ? null - : ChannelPushPreference.fromJson( - json['channel_push_preference'] as Map), - channelMessageCount: (json['channel_message_count'] as num?)?.toInt(), - lastDeliveredAt: json['last_delivered_at'] == null - ? null - : DateTime.parse(json['last_delivered_at'] as String), - lastDeliveredMessageId: json['last_delivered_message_id'] as String?, - extraData: json['extra_data'] as Map? ?? const {}, - isLocal: json['is_local'] as bool? ?? false, - ); + type: json['type'] as String? ?? 'local.event', + userId: json['user_id'] as String?, + cid: json['cid'] as String?, + connectionId: json['connection_id'] as String?, + createdAt: json['created_at'] == null ? null : DateTime.parse(json['created_at'] as String), + me: json['me'] == null ? null : OwnUser.fromJson(json['me'] as Map), + user: json['user'] == null ? null : User.fromJson(json['user'] as Map), + message: json['message'] == null ? null : Message.fromJson(json['message'] as Map), + poll: json['poll'] == null ? null : Poll.fromJson(json['poll'] as Map), + pollVote: json['poll_vote'] == null ? null : PollVote.fromJson(json['poll_vote'] as Map), + totalUnreadCount: (json['total_unread_count'] as num?)?.toInt(), + unreadChannels: (json['unread_channels'] as num?)?.toInt(), + reaction: json['reaction'] == null ? null : Reaction.fromJson(json['reaction'] as Map), + online: json['online'] as bool?, + channel: json['channel'] == null ? null : ChannelModel.fromJson(json['channel'] as Map), + member: json['member'] == null ? null : Member.fromJson(json['member'] as Map), + channelId: json['channel_id'] as String?, + channelType: json['channel_type'] as String?, + channelLastMessageAt: json['channel_last_message_at'] == null + ? null + : DateTime.parse(json['channel_last_message_at'] as String), + parentId: json['parent_id'] as String?, + hardDelete: json['hard_delete'] as bool?, + deletedForMe: json['deleted_for_me'] as bool?, + aiState: $enumDecodeNullable(_$AITypingStateEnumMap, json['ai_state'], unknownValue: AITypingState.idle), + aiMessage: json['ai_message'] as String?, + messageId: json['message_id'] as String?, + thread: json['thread'] == null ? null : Thread.fromJson(json['thread'] as Map), + unreadThreadMessages: (json['unread_thread_messages'] as num?)?.toInt(), + unreadThreads: (json['unread_threads'] as num?)?.toInt(), + lastReadAt: json['last_read_at'] == null ? null : DateTime.parse(json['last_read_at'] as String), + unreadMessages: (json['unread_messages'] as num?)?.toInt(), + lastReadMessageId: json['last_read_message_id'] as String?, + draft: json['draft'] == null ? null : Draft.fromJson(json['draft'] as Map), + reminder: json['reminder'] == null ? null : MessageReminder.fromJson(json['reminder'] as Map), + pushPreference: json['push_preference'] == null + ? null + : PushPreference.fromJson(json['push_preference'] as Map), + channelPushPreference: json['channel_push_preference'] == null + ? null + : ChannelPushPreference.fromJson(json['channel_push_preference'] as Map), + channelMessageCount: (json['channel_message_count'] as num?)?.toInt(), + lastDeliveredAt: json['last_delivered_at'] == null ? null : DateTime.parse(json['last_delivered_at'] as String), + lastDeliveredMessageId: json['last_delivered_message_id'] as String?, + extraData: json['extra_data'] as Map? ?? const {}, + isLocal: json['is_local'] as bool? ?? false, +); Map _$EventToJson(Event instance) => { - 'type': instance.type, - if (instance.userId case final value?) 'user_id': value, - if (instance.cid case final value?) 'cid': value, - if (instance.channelId case final value?) 'channel_id': value, - if (instance.channelType case final value?) 'channel_type': value, - if (instance.channelLastMessageAt?.toIso8601String() case final value?) - 'channel_last_message_at': value, - if (instance.connectionId case final value?) 'connection_id': value, - 'created_at': instance.createdAt.toIso8601String(), - if (instance.me?.toJson() case final value?) 'me': value, - if (instance.user?.toJson() case final value?) 'user': value, - if (instance.message?.toJson() case final value?) 'message': value, - if (instance.poll?.toJson() case final value?) 'poll': value, - if (instance.pollVote?.toJson() case final value?) 'poll_vote': value, - if (instance.channel?.toJson() case final value?) 'channel': value, - if (instance.member?.toJson() case final value?) 'member': value, - if (instance.reaction?.toJson() case final value?) 'reaction': value, - if (instance.totalUnreadCount case final value?) - 'total_unread_count': value, - if (instance.unreadChannels case final value?) 'unread_channels': value, - if (instance.online case final value?) 'online': value, - if (instance.parentId case final value?) 'parent_id': value, - 'is_local': instance.isLocal, - if (instance.hardDelete case final value?) 'hard_delete': value, - if (instance.deletedForMe case final value?) 'deleted_for_me': value, - if (_$AITypingStateEnumMap[instance.aiState] case final value?) - 'ai_state': value, - if (instance.aiMessage case final value?) 'ai_message': value, - if (instance.messageId case final value?) 'message_id': value, - if (instance.thread?.toJson() case final value?) 'thread': value, - if (instance.unreadThreadMessages case final value?) - 'unread_thread_messages': value, - if (instance.unreadThreads case final value?) 'unread_threads': value, - if (instance.lastReadAt?.toIso8601String() case final value?) - 'last_read_at': value, - if (instance.unreadMessages case final value?) 'unread_messages': value, - if (instance.lastReadMessageId case final value?) - 'last_read_message_id': value, - if (instance.draft?.toJson() case final value?) 'draft': value, - if (instance.reminder?.toJson() case final value?) 'reminder': value, - if (instance.pushPreference?.toJson() case final value?) - 'push_preference': value, - if (instance.channelPushPreference?.toJson() case final value?) - 'channel_push_preference': value, - if (instance.channelMessageCount case final value?) - 'channel_message_count': value, - if (instance.lastDeliveredAt?.toIso8601String() case final value?) - 'last_delivered_at': value, - if (instance.lastDeliveredMessageId case final value?) - 'last_delivered_message_id': value, - 'extra_data': instance.extraData, - }; + 'type': instance.type, + if (instance.userId case final value?) 'user_id': value, + if (instance.cid case final value?) 'cid': value, + if (instance.channelId case final value?) 'channel_id': value, + if (instance.channelType case final value?) 'channel_type': value, + if (instance.channelLastMessageAt?.toIso8601String() case final value?) 'channel_last_message_at': value, + if (instance.connectionId case final value?) 'connection_id': value, + 'created_at': instance.createdAt.toIso8601String(), + if (instance.me?.toJson() case final value?) 'me': value, + if (instance.user?.toJson() case final value?) 'user': value, + if (instance.message?.toJson() case final value?) 'message': value, + if (instance.poll?.toJson() case final value?) 'poll': value, + if (instance.pollVote?.toJson() case final value?) 'poll_vote': value, + if (instance.channel?.toJson() case final value?) 'channel': value, + if (instance.member?.toJson() case final value?) 'member': value, + if (instance.reaction?.toJson() case final value?) 'reaction': value, + if (instance.totalUnreadCount case final value?) 'total_unread_count': value, + if (instance.unreadChannels case final value?) 'unread_channels': value, + if (instance.online case final value?) 'online': value, + if (instance.parentId case final value?) 'parent_id': value, + 'is_local': instance.isLocal, + if (instance.hardDelete case final value?) 'hard_delete': value, + if (instance.deletedForMe case final value?) 'deleted_for_me': value, + if (_$AITypingStateEnumMap[instance.aiState] case final value?) 'ai_state': value, + if (instance.aiMessage case final value?) 'ai_message': value, + if (instance.messageId case final value?) 'message_id': value, + if (instance.thread?.toJson() case final value?) 'thread': value, + if (instance.unreadThreadMessages case final value?) 'unread_thread_messages': value, + if (instance.unreadThreads case final value?) 'unread_threads': value, + if (instance.lastReadAt?.toIso8601String() case final value?) 'last_read_at': value, + if (instance.unreadMessages case final value?) 'unread_messages': value, + if (instance.lastReadMessageId case final value?) 'last_read_message_id': value, + if (instance.draft?.toJson() case final value?) 'draft': value, + if (instance.reminder?.toJson() case final value?) 'reminder': value, + if (instance.pushPreference?.toJson() case final value?) 'push_preference': value, + if (instance.channelPushPreference?.toJson() case final value?) 'channel_push_preference': value, + if (instance.channelMessageCount case final value?) 'channel_message_count': value, + if (instance.lastDeliveredAt?.toIso8601String() case final value?) 'last_delivered_at': value, + if (instance.lastDeliveredMessageId case final value?) 'last_delivered_message_id': value, + 'extra_data': instance.extraData, +}; const _$AITypingStateEnumMap = { AITypingState.idle: 'AI_STATE_IDLE', diff --git a/packages/stream_chat/lib/src/core/models/filter.dart b/packages/stream_chat/lib/src/core/models/filter.dart index 2122519d0f..fc8dbadd0c 100644 --- a/packages/stream_chat/lib/src/core/models/filter.dart +++ b/packages/stream_chat/lib/src/core/models/filter.dart @@ -53,7 +53,8 @@ enum FilterOperator { nor, /// Matches any list that contains the specified value - contains; + contains + ; @override String toString() { @@ -117,29 +118,22 @@ class Filter extends Equatable { }) : operator = '$operator'; /// An empty filter - const Filter.empty() - : value = const {}, - operator = null, - key = null; + const Filter.empty() : value = const {}, operator = null, key = null; /// Combines the provided filters and matches the values /// matched by all filters. - factory Filter.and(List filters) => - Filter._(operator: FilterOperator.and, value: filters); + factory Filter.and(List filters) => Filter._(operator: FilterOperator.and, value: filters); /// Combines the provided filters and matches the values /// matched by at least one of the filters. - factory Filter.or(List filters) => - Filter._(operator: FilterOperator.or, value: filters); + factory Filter.or(List filters) => Filter._(operator: FilterOperator.or, value: filters); /// Combines the provided filters and matches the values /// not matched by all the filters. - factory Filter.nor(List filters) => - Filter._(operator: FilterOperator.nor, value: filters); + factory Filter.nor(List filters) => Filter._(operator: FilterOperator.nor, value: filters); /// Matches values that are equal to a specified value. - factory Filter.equal(String key, Object value) => - Filter._(operator: FilterOperator.equal, key: key, value: value); + factory Filter.equal(String key, Object value) => Filter._(operator: FilterOperator.equal, key: key, value: value); /// Matches all values that are not equal to a specified value. factory Filter.notEqual(String key, Object value) => @@ -154,8 +148,7 @@ class Filter extends Equatable { Filter._(operator: FilterOperator.greaterOrEqual, key: key, value: value); /// Matches values that are less than a specified value. - factory Filter.less(String key, Object value) => - Filter._(operator: FilterOperator.less, key: key, value: value); + factory Filter.less(String key, Object value) => Filter._(operator: FilterOperator.less, key: key, value: value); /// Matches values that are less than or equal to a specified value. factory Filter.lessOrEqual(String key, Object value) => @@ -170,8 +163,7 @@ class Filter extends Equatable { Filter._(operator: FilterOperator.notIn, key: key, value: values); /// Matches values by performing text search with the specified value. - factory Filter.query(String key, String text) => - Filter._(operator: FilterOperator.query, key: key, value: text); + factory Filter.query(String key, String text) => Filter._(operator: FilterOperator.query, key: key, value: text); /// Matches values with the specified prefix. factory Filter.autoComplete(String key, String text) => diff --git a/packages/stream_chat/lib/src/core/models/location.dart b/packages/stream_chat/lib/src/core/models/location.dart index 665fe4c031..9b66746f24 100644 --- a/packages/stream_chat/lib/src/core/models/location.dart +++ b/packages/stream_chat/lib/src/core/models/location.dart @@ -32,13 +32,12 @@ class Location extends Equatable { DateTime? endAt, DateTime? createdAt, DateTime? updatedAt, - }) : endAt = endAt?.toUtc(), - createdAt = createdAt ?? DateTime.timestamp(), - updatedAt = updatedAt ?? DateTime.timestamp(); + }) : endAt = endAt?.toUtc(), + createdAt = createdAt ?? DateTime.timestamp(), + updatedAt = updatedAt ?? DateTime.timestamp(); /// Create a new instance from a json - factory Location.fromJson(Map json) => - _$LocationFromJson(json); + factory Location.fromJson(Map json) => _$LocationFromJson(json); /// The channel CID where the message exists. /// @@ -143,16 +142,16 @@ class Location extends Equatable { @override List get props => [ - channelCid, - channel, - messageId, - message, - userId, - latitude, - longitude, - createdByDeviceId, - endAt, - createdAt, - updatedAt, - ]; + channelCid, + channel, + messageId, + message, + userId, + latitude, + longitude, + createdByDeviceId, + endAt, + createdAt, + updatedAt, + ]; } diff --git a/packages/stream_chat/lib/src/core/models/location.g.dart b/packages/stream_chat/lib/src/core/models/location.g.dart index e1fac9ac92..662fc500d5 100644 --- a/packages/stream_chat/lib/src/core/models/location.g.dart +++ b/packages/stream_chat/lib/src/core/models/location.g.dart @@ -7,33 +7,22 @@ part of 'location.dart'; // ************************************************************************** Location _$LocationFromJson(Map json) => Location( - channelCid: json['channel_cid'] as String?, - channel: json['channel'] == null - ? null - : ChannelModel.fromJson(json['channel'] as Map), - messageId: json['message_id'] as String?, - message: json['message'] == null - ? null - : Message.fromJson(json['message'] as Map), - userId: json['user_id'] as String?, - latitude: (json['latitude'] as num).toDouble(), - longitude: (json['longitude'] as num).toDouble(), - createdByDeviceId: json['created_by_device_id'] as String?, - endAt: json['end_at'] == null - ? null - : DateTime.parse(json['end_at'] as String), - createdAt: json['created_at'] == null - ? null - : DateTime.parse(json['created_at'] as String), - updatedAt: json['updated_at'] == null - ? null - : DateTime.parse(json['updated_at'] as String), - ); + channelCid: json['channel_cid'] as String?, + channel: json['channel'] == null ? null : ChannelModel.fromJson(json['channel'] as Map), + messageId: json['message_id'] as String?, + message: json['message'] == null ? null : Message.fromJson(json['message'] as Map), + userId: json['user_id'] as String?, + latitude: (json['latitude'] as num).toDouble(), + longitude: (json['longitude'] as num).toDouble(), + createdByDeviceId: json['created_by_device_id'] as String?, + endAt: json['end_at'] == null ? null : DateTime.parse(json['end_at'] as String), + createdAt: json['created_at'] == null ? null : DateTime.parse(json['created_at'] as String), + updatedAt: json['updated_at'] == null ? null : DateTime.parse(json['updated_at'] as String), +); Map _$LocationToJson(Location instance) => { - 'latitude': instance.latitude, - 'longitude': instance.longitude, - if (instance.createdByDeviceId case final value?) - 'created_by_device_id': value, - if (instance.endAt?.toIso8601String() case final value?) 'end_at': value, - }; + 'latitude': instance.latitude, + 'longitude': instance.longitude, + if (instance.createdByDeviceId case final value?) 'created_by_device_id': value, + if (instance.endAt?.toIso8601String() case final value?) 'end_at': value, +}; diff --git a/packages/stream_chat/lib/src/core/models/member.dart b/packages/stream_chat/lib/src/core/models/member.dart index 452412f7c1..06c7185f1d 100644 --- a/packages/stream_chat/lib/src/core/models/member.dart +++ b/packages/stream_chat/lib/src/core/models/member.dart @@ -28,14 +28,14 @@ class Member extends Equatable implements ComparableFieldProvider { this.archivedAt, this.deletedMessages = const [], this.extraData = const {}, - }) : userId = userId ?? user?.id, - createdAt = createdAt ?? DateTime.now(), - updatedAt = updatedAt ?? DateTime.now(); + }) : userId = userId ?? user?.id, + createdAt = createdAt ?? DateTime.now(), + updatedAt = updatedAt ?? DateTime.now(); /// Create a new instance from a json factory Member.fromJson(Map json) => _$MemberFromJson( - Serializer.moveToExtraDataFromRoot(json, _topLevelFields), - ); + Serializer.moveToExtraDataFromRoot(json, _topLevelFields), + ); /// Known top level fields. /// @@ -128,50 +128,49 @@ class Member extends Equatable implements ComparableFieldProvider { bool? shadowBanned, List? deletedMessages, Map? extraData, - }) => - Member( - user: user ?? this.user, - inviteAcceptedAt: inviteAcceptedAt ?? this.inviteAcceptedAt, - inviteRejectedAt: inviteRejectedAt ?? this.inviteRejectedAt, - invited: invited ?? this.invited, - banned: banned ?? this.banned, - banExpires: banExpires ?? this.banExpires, - shadowBanned: shadowBanned ?? this.shadowBanned, - channelRole: channelRole ?? this.channelRole, - userId: userId ?? this.userId, - isModerator: isModerator ?? this.isModerator, - pinnedAt: pinnedAt ?? this.pinnedAt, - archivedAt: archivedAt ?? this.archivedAt, - createdAt: createdAt ?? this.createdAt, - updatedAt: updatedAt ?? this.updatedAt, - deletedMessages: deletedMessages ?? this.deletedMessages, - extraData: extraData ?? this.extraData, - ); + }) => Member( + user: user ?? this.user, + inviteAcceptedAt: inviteAcceptedAt ?? this.inviteAcceptedAt, + inviteRejectedAt: inviteRejectedAt ?? this.inviteRejectedAt, + invited: invited ?? this.invited, + banned: banned ?? this.banned, + banExpires: banExpires ?? this.banExpires, + shadowBanned: shadowBanned ?? this.shadowBanned, + channelRole: channelRole ?? this.channelRole, + userId: userId ?? this.userId, + isModerator: isModerator ?? this.isModerator, + pinnedAt: pinnedAt ?? this.pinnedAt, + archivedAt: archivedAt ?? this.archivedAt, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedMessages: deletedMessages ?? this.deletedMessages, + extraData: extraData ?? this.extraData, + ); /// Serialize to json Map toJson() => Serializer.moveFromExtraDataToRoot( - _$MemberToJson(this), - ); + _$MemberToJson(this), + ); @override List get props => [ - user, - inviteAcceptedAt, - inviteRejectedAt, - invited, - channelRole, - userId, - isModerator, - banned, - banExpires, - shadowBanned, - pinnedAt, - archivedAt, - createdAt, - updatedAt, - deletedMessages, - extraData, - ]; + user, + inviteAcceptedAt, + inviteRejectedAt, + invited, + channelRole, + userId, + isModerator, + banned, + banExpires, + shadowBanned, + pinnedAt, + archivedAt, + createdAt, + updatedAt, + deletedMessages, + extraData, + ]; @override ComparableField? getComparableField(String sortKey) { diff --git a/packages/stream_chat/lib/src/core/models/member.g.dart b/packages/stream_chat/lib/src/core/models/member.g.dart index abdaf29d65..730051a2db 100644 --- a/packages/stream_chat/lib/src/core/models/member.g.dart +++ b/packages/stream_chat/lib/src/core/models/member.g.dart @@ -7,58 +7,39 @@ part of 'member.dart'; // ************************************************************************** Member _$MemberFromJson(Map json) => Member( - user: json['user'] == null - ? null - : User.fromJson(json['user'] as Map), - inviteAcceptedAt: json['invite_accepted_at'] == null - ? null - : DateTime.parse(json['invite_accepted_at'] as String), - inviteRejectedAt: json['invite_rejected_at'] == null - ? null - : DateTime.parse(json['invite_rejected_at'] as String), - invited: json['invited'] as bool? ?? false, - channelRole: json['channel_role'] as String?, - userId: json['user_id'] as String?, - isModerator: json['is_moderator'] as bool? ?? false, - createdAt: json['created_at'] == null - ? null - : DateTime.parse(json['created_at'] as String), - updatedAt: json['updated_at'] == null - ? null - : DateTime.parse(json['updated_at'] as String), - banned: json['banned'] as bool? ?? false, - banExpires: json['ban_expires'] == null - ? null - : DateTime.parse(json['ban_expires'] as String), - shadowBanned: json['shadow_banned'] as bool? ?? false, - pinnedAt: json['pinned_at'] == null - ? null - : DateTime.parse(json['pinned_at'] as String), - archivedAt: json['archived_at'] == null - ? null - : DateTime.parse(json['archived_at'] as String), - deletedMessages: (json['deleted_messages'] as List?) - ?.map((e) => e as String) - .toList() ?? - const [], - extraData: json['extra_data'] as Map? ?? const {}, - ); + user: json['user'] == null ? null : User.fromJson(json['user'] as Map), + inviteAcceptedAt: json['invite_accepted_at'] == null ? null : DateTime.parse(json['invite_accepted_at'] as String), + inviteRejectedAt: json['invite_rejected_at'] == null ? null : DateTime.parse(json['invite_rejected_at'] as String), + invited: json['invited'] as bool? ?? false, + channelRole: json['channel_role'] as String?, + userId: json['user_id'] as String?, + isModerator: json['is_moderator'] as bool? ?? false, + createdAt: json['created_at'] == null ? null : DateTime.parse(json['created_at'] as String), + updatedAt: json['updated_at'] == null ? null : DateTime.parse(json['updated_at'] as String), + banned: json['banned'] as bool? ?? false, + banExpires: json['ban_expires'] == null ? null : DateTime.parse(json['ban_expires'] as String), + shadowBanned: json['shadow_banned'] as bool? ?? false, + pinnedAt: json['pinned_at'] == null ? null : DateTime.parse(json['pinned_at'] as String), + archivedAt: json['archived_at'] == null ? null : DateTime.parse(json['archived_at'] as String), + deletedMessages: (json['deleted_messages'] as List?)?.map((e) => e as String).toList() ?? const [], + extraData: json['extra_data'] as Map? ?? const {}, +); Map _$MemberToJson(Member instance) => { - 'user': instance.user?.toJson(), - 'invite_accepted_at': instance.inviteAcceptedAt?.toIso8601String(), - 'invite_rejected_at': instance.inviteRejectedAt?.toIso8601String(), - 'invited': instance.invited, - 'channel_role': instance.channelRole, - 'user_id': instance.userId, - 'is_moderator': instance.isModerator, - 'banned': instance.banned, - 'ban_expires': instance.banExpires?.toIso8601String(), - 'shadow_banned': instance.shadowBanned, - 'pinned_at': instance.pinnedAt?.toIso8601String(), - 'archived_at': instance.archivedAt?.toIso8601String(), - 'created_at': instance.createdAt.toIso8601String(), - 'updated_at': instance.updatedAt.toIso8601String(), - 'deleted_messages': instance.deletedMessages, - 'extra_data': instance.extraData, - }; + 'user': instance.user?.toJson(), + 'invite_accepted_at': instance.inviteAcceptedAt?.toIso8601String(), + 'invite_rejected_at': instance.inviteRejectedAt?.toIso8601String(), + 'invited': instance.invited, + 'channel_role': instance.channelRole, + 'user_id': instance.userId, + 'is_moderator': instance.isModerator, + 'banned': instance.banned, + 'ban_expires': instance.banExpires?.toIso8601String(), + 'shadow_banned': instance.shadowBanned, + 'pinned_at': instance.pinnedAt?.toIso8601String(), + 'archived_at': instance.archivedAt?.toIso8601String(), + 'created_at': instance.createdAt.toIso8601String(), + 'updated_at': instance.updatedAt.toIso8601String(), + 'deleted_messages': instance.deletedMessages, + 'extra_data': instance.extraData, +}; diff --git a/packages/stream_chat/lib/src/core/models/message.dart b/packages/stream_chat/lib/src/core/models/message.dart index 7c849e972b..12b32cbbf5 100644 --- a/packages/stream_chat/lib/src/core/models/message.dart +++ b/packages/stream_chat/lib/src/core/models/message.dart @@ -70,14 +70,14 @@ class Message extends Equatable implements ComparableFieldProvider { this.reminder, this.channelRole, this.sharedLocation, - }) : id = id ?? const Uuid().v4(), - type = MessageType(type), - pinExpires = pinExpires?.toUtc(), - remoteCreatedAt = createdAt, - remoteUpdatedAt = updatedAt, - remoteDeletedAt = deletedAt, - _quotedMessageId = quotedMessageId, - _pollId = pollId; + }) : id = id ?? const Uuid().v4(), + type = MessageType(type), + pinExpires = pinExpires?.toUtc(), + remoteCreatedAt = createdAt, + remoteUpdatedAt = updatedAt, + remoteDeletedAt = deletedAt, + _quotedMessageId = quotedMessageId, + _pollId = pollId; /// Create a new instance from JSON. factory Message.fromJson(Map json) { @@ -453,18 +453,14 @@ class Message extends Equatable implements ComparableFieldProvider { bool? deletedForMe, }) { assert(() { - if (pinExpires is! DateTime && - pinExpires != null && - pinExpires is! _NullConst) { + if (pinExpires is! DateTime && pinExpires != null && pinExpires is! _NullConst) { throw ArgumentError('`pinExpires` can only be set as DateTime or null'); } return true; }(), 'Validate type for pinExpires'); assert(() { - if (quotedMessage is! Message && - quotedMessage != null && - quotedMessage is! _NullConst) { + if (quotedMessage is! Message && quotedMessage != null && quotedMessage is! _NullConst) { throw ArgumentError( '`quotedMessage` can only be set as Message or null', ); @@ -473,9 +469,7 @@ class Message extends Equatable implements ComparableFieldProvider { }(), 'Validate type for quotedMessage'); assert(() { - if (quotedMessageId is! String && - quotedMessageId != null && - quotedMessageId is! _NullConst) { + if (quotedMessageId is! String && quotedMessageId != null && quotedMessageId is! _NullConst) { throw ArgumentError( '`quotedMessage` can only be set as String or null', ); @@ -495,12 +489,8 @@ class Message extends Equatable implements ComparableFieldProvider { latestReactions: latestReactions ?? this.latestReactions, ownReactions: ownReactions ?? this.ownReactions, parentId: parentId ?? this.parentId, - quotedMessage: quotedMessage == _nullConst - ? this.quotedMessage - : quotedMessage as Message?, - quotedMessageId: quotedMessageId == _nullConst - ? _quotedMessageId - : quotedMessageId as String?, + quotedMessage: quotedMessage == _nullConst ? this.quotedMessage : quotedMessage as Message?, + quotedMessageId: quotedMessageId == _nullConst ? _quotedMessageId : quotedMessageId as String?, replyCount: replyCount ?? this.replyCount, threadParticipants: threadParticipants ?? this.threadParticipants, showInChannel: showInChannel ?? this.showInChannel, @@ -515,8 +505,7 @@ class Message extends Equatable implements ComparableFieldProvider { user: user ?? this.user, pinned: pinned ?? this.pinned, pinnedAt: pinnedAt ?? this.pinnedAt, - pinExpires: - pinExpires == _nullConst ? this.pinExpires : pinExpires as DateTime?, + pinExpires: pinExpires == _nullConst ? this.pinExpires : pinExpires as DateTime?, pinnedBy: pinnedBy ?? this.pinnedBy, poll: poll ?? this.poll, pollId: pollId ?? _pollId, @@ -526,8 +515,7 @@ class Message extends Equatable implements ComparableFieldProvider { restrictedVisibility: restrictedVisibility ?? this.restrictedVisibility, moderation: moderation ?? this.moderation, draft: draft == _nullConst ? this.draft : draft as Draft?, - reminder: - reminder == _nullConst ? this.reminder : reminder as MessageReminder?, + reminder: reminder == _nullConst ? this.reminder : reminder as MessageReminder?, channelRole: channelRole ?? this.channelRole, sharedLocation: sharedLocation ?? this.sharedLocation, deletedForMe: deletedForMe ?? this.deletedForMe, @@ -606,48 +594,48 @@ class Message extends Equatable implements ComparableFieldProvider { @override List get props => [ - id, - text, - type, - attachments, - mentionedUsers, - reactionGroups, - latestReactions, - ownReactions, - parentId, - quotedMessage, - quotedMessageId, - replyCount, - threadParticipants, - showInChannel, - shadowed, - silent, - command, - localCreatedAt, - remoteCreatedAt, - localUpdatedAt, - remoteUpdatedAt, - localDeletedAt, - remoteDeletedAt, - messageTextUpdatedAt, - user, - pinned, - pinnedAt, - pinExpires, - pinnedBy, - poll, - pollId, - extraData, - state, - i18n, - restrictedVisibility, - moderation, - draft, - reminder, - channelRole, - sharedLocation, - deletedForMe, - ]; + id, + text, + type, + attachments, + mentionedUsers, + reactionGroups, + latestReactions, + ownReactions, + parentId, + quotedMessage, + quotedMessageId, + replyCount, + threadParticipants, + showInChannel, + shadowed, + silent, + command, + localCreatedAt, + remoteCreatedAt, + localUpdatedAt, + remoteUpdatedAt, + localDeletedAt, + remoteDeletedAt, + messageTextUpdatedAt, + user, + pinned, + pinnedAt, + pinExpires, + pinnedBy, + poll, + pollId, + extraData, + state, + i18n, + restrictedVisibility, + moderation, + draft, + reminder, + channelRole, + sharedLocation, + deletedForMe, + ]; @override ComparableField? getComparableField(String sortKey) { diff --git a/packages/stream_chat/lib/src/core/models/message.g.dart b/packages/stream_chat/lib/src/core/models/message.g.dart index 4b47a343c6..fe9d4721fd 100644 --- a/packages/stream_chat/lib/src/core/models/message.g.dart +++ b/packages/stream_chat/lib/src/core/models/message.g.dart @@ -7,114 +7,81 @@ part of 'message.dart'; // ************************************************************************** Message _$MessageFromJson(Map json) => Message( - id: json['id'] as String?, - text: json['text'] as String?, - type: json['type'] == null - ? MessageType.regular - : MessageType.fromJson(json['type'] as String), - attachments: (json['attachments'] as List?) - ?.map((e) => Attachment.fromJson(e as Map)) - .toList() ?? - const [], - mentionedUsers: (json['mentioned_users'] as List?) - ?.map((e) => User.fromJson(e as Map)) - .toList() ?? - const [], - silent: json['silent'] as bool? ?? false, - shadowed: json['shadowed'] as bool? ?? false, - reactionGroups: (Message._reactionGroupsReadValue(json, 'reaction_groups') - as Map?) - ?.map( - (k, e) => - MapEntry(k, ReactionGroup.fromJson(e as Map)), - ), - latestReactions: (json['latest_reactions'] as List?) - ?.map((e) => Reaction.fromJson(e as Map)) - .toList(), - ownReactions: (json['own_reactions'] as List?) - ?.map((e) => Reaction.fromJson(e as Map)) - .toList(), - parentId: json['parent_id'] as String?, - quotedMessage: json['quoted_message'] == null - ? null - : Message.fromJson(json['quoted_message'] as Map), - quotedMessageId: json['quoted_message_id'] as String?, - replyCount: (json['reply_count'] as num?)?.toInt() ?? 0, - threadParticipants: (json['thread_participants'] as List?) - ?.map((e) => User.fromJson(e as Map)) - .toList(), - showInChannel: json['show_in_channel'] as bool?, - command: json['command'] as String?, - createdAt: json['created_at'] == null - ? null - : DateTime.parse(json['created_at'] as String), - updatedAt: json['updated_at'] == null - ? null - : DateTime.parse(json['updated_at'] as String), - deletedAt: json['deleted_at'] == null - ? null - : DateTime.parse(json['deleted_at'] as String), - deletedForMe: json['deleted_for_me'] as bool?, - messageTextUpdatedAt: json['message_text_updated_at'] == null - ? null - : DateTime.parse(json['message_text_updated_at'] as String), - user: json['user'] == null - ? null - : User.fromJson(json['user'] as Map), - pinned: json['pinned'] as bool? ?? false, - pinnedAt: json['pinned_at'] == null - ? null - : DateTime.parse(json['pinned_at'] as String), - pinExpires: json['pin_expires'] == null - ? null - : DateTime.parse(json['pin_expires'] as String), - pinnedBy: json['pinned_by'] == null - ? null - : User.fromJson(json['pinned_by'] as Map), - poll: json['poll'] == null - ? null - : Poll.fromJson(json['poll'] as Map), - pollId: json['poll_id'] as String?, - extraData: json['extra_data'] as Map? ?? const {}, - i18n: (json['i18n'] as Map?)?.map( - (k, e) => MapEntry(k, e as String), - ), - restrictedVisibility: (json['restricted_visibility'] as List?) - ?.map((e) => e as String) - .toList(), - moderation: Message._moderationReadValue(json, 'moderation') == null - ? null - : Moderation.fromJson(Message._moderationReadValue(json, 'moderation') - as Map), - draft: json['draft'] == null - ? null - : Draft.fromJson(json['draft'] as Map), - reminder: json['reminder'] == null - ? null - : MessageReminder.fromJson(json['reminder'] as Map), - channelRole: - Message._channelRoleReadValue(json, 'channel_role') as String?, - sharedLocation: json['shared_location'] == null - ? null - : Location.fromJson(json['shared_location'] as Map), - ); + id: json['id'] as String?, + text: json['text'] as String?, + type: json['type'] == null ? MessageType.regular : MessageType.fromJson(json['type'] as String), + attachments: + (json['attachments'] as List?)?.map((e) => Attachment.fromJson(e as Map)).toList() ?? + const [], + mentionedUsers: + (json['mentioned_users'] as List?)?.map((e) => User.fromJson(e as Map)).toList() ?? + const [], + silent: json['silent'] as bool? ?? false, + shadowed: json['shadowed'] as bool? ?? false, + reactionGroups: (Message._reactionGroupsReadValue(json, 'reaction_groups') as Map?)?.map( + (k, e) => MapEntry(k, ReactionGroup.fromJson(e as Map)), + ), + latestReactions: (json['latest_reactions'] as List?) + ?.map((e) => Reaction.fromJson(e as Map)) + .toList(), + ownReactions: (json['own_reactions'] as List?) + ?.map((e) => Reaction.fromJson(e as Map)) + .toList(), + parentId: json['parent_id'] as String?, + quotedMessage: json['quoted_message'] == null + ? null + : Message.fromJson(json['quoted_message'] as Map), + quotedMessageId: json['quoted_message_id'] as String?, + replyCount: (json['reply_count'] as num?)?.toInt() ?? 0, + threadParticipants: (json['thread_participants'] as List?) + ?.map((e) => User.fromJson(e as Map)) + .toList(), + showInChannel: json['show_in_channel'] as bool?, + command: json['command'] as String?, + createdAt: json['created_at'] == null ? null : DateTime.parse(json['created_at'] as String), + updatedAt: json['updated_at'] == null ? null : DateTime.parse(json['updated_at'] as String), + deletedAt: json['deleted_at'] == null ? null : DateTime.parse(json['deleted_at'] as String), + deletedForMe: json['deleted_for_me'] as bool?, + messageTextUpdatedAt: json['message_text_updated_at'] == null + ? null + : DateTime.parse(json['message_text_updated_at'] as String), + user: json['user'] == null ? null : User.fromJson(json['user'] as Map), + pinned: json['pinned'] as bool? ?? false, + pinnedAt: json['pinned_at'] == null ? null : DateTime.parse(json['pinned_at'] as String), + pinExpires: json['pin_expires'] == null ? null : DateTime.parse(json['pin_expires'] as String), + pinnedBy: json['pinned_by'] == null ? null : User.fromJson(json['pinned_by'] as Map), + poll: json['poll'] == null ? null : Poll.fromJson(json['poll'] as Map), + pollId: json['poll_id'] as String?, + extraData: json['extra_data'] as Map? ?? const {}, + i18n: (json['i18n'] as Map?)?.map( + (k, e) => MapEntry(k, e as String), + ), + restrictedVisibility: (json['restricted_visibility'] as List?)?.map((e) => e as String).toList(), + moderation: Message._moderationReadValue(json, 'moderation') == null + ? null + : Moderation.fromJson(Message._moderationReadValue(json, 'moderation') as Map), + draft: json['draft'] == null ? null : Draft.fromJson(json['draft'] as Map), + reminder: json['reminder'] == null ? null : MessageReminder.fromJson(json['reminder'] as Map), + channelRole: Message._channelRoleReadValue(json, 'channel_role') as String?, + sharedLocation: json['shared_location'] == null + ? null + : Location.fromJson(json['shared_location'] as Map), +); Map _$MessageToJson(Message instance) => { - 'id': instance.id, - 'text': instance.text, - if (MessageType.toJson(instance.type) case final value?) 'type': value, - 'attachments': instance.attachments.map((e) => e.toJson()).toList(), - 'mentioned_users': User.toIds(instance.mentionedUsers), - 'parent_id': instance.parentId, - 'quoted_message_id': instance.quotedMessageId, - 'show_in_channel': instance.showInChannel, - 'silent': instance.silent, - 'pinned': instance.pinned, - 'pin_expires': instance.pinExpires?.toIso8601String(), - 'poll_id': instance.pollId, - if (instance.restrictedVisibility case final value?) - 'restricted_visibility': value, - if (instance.sharedLocation?.toJson() case final value?) - 'shared_location': value, - 'extra_data': instance.extraData, - }; + 'id': instance.id, + 'text': instance.text, + if (MessageType.toJson(instance.type) case final value?) 'type': value, + 'attachments': instance.attachments.map((e) => e.toJson()).toList(), + 'mentioned_users': User.toIds(instance.mentionedUsers), + 'parent_id': instance.parentId, + 'quoted_message_id': instance.quotedMessageId, + 'show_in_channel': instance.showInChannel, + 'silent': instance.silent, + 'pinned': instance.pinned, + 'pin_expires': instance.pinExpires?.toIso8601String(), + 'poll_id': instance.pollId, + if (instance.restrictedVisibility case final value?) 'restricted_visibility': value, + if (instance.sharedLocation?.toJson() case final value?) 'shared_location': value, + 'extra_data': instance.extraData, +}; diff --git a/packages/stream_chat/lib/src/core/models/message_delete_scope.dart b/packages/stream_chat/lib/src/core/models/message_delete_scope.dart index c08f1ea01c..9a53a777bf 100644 --- a/packages/stream_chat/lib/src/core/models/message_delete_scope.dart +++ b/packages/stream_chat/lib/src/core/models/message_delete_scope.dart @@ -28,8 +28,7 @@ sealed class MessageDeleteScope with _$MessageDeleteScope { }) = DeleteForAll; /// Creates a instance of [MessageDeleteScope] from a JSON map. - factory MessageDeleteScope.fromJson(Map json) => - _$MessageDeleteScopeFromJson(json); + factory MessageDeleteScope.fromJson(Map json) => _$MessageDeleteScopeFromJson(json); // region Predefined Scopes diff --git a/packages/stream_chat/lib/src/core/models/message_delete_scope.g.dart b/packages/stream_chat/lib/src/core/models/message_delete_scope.g.dart index 0998631d2a..75f4373b42 100644 --- a/packages/stream_chat/lib/src/core/models/message_delete_scope.g.dart +++ b/packages/stream_chat/lib/src/core/models/message_delete_scope.g.dart @@ -7,21 +7,19 @@ part of 'message_delete_scope.dart'; // ************************************************************************** DeleteForMe _$DeleteForMeFromJson(Map json) => DeleteForMe( - $type: json['runtimeType'] as String?, - ); + $type: json['runtimeType'] as String?, +); -Map _$DeleteForMeToJson(DeleteForMe instance) => - { - 'runtimeType': instance.$type, - }; +Map _$DeleteForMeToJson(DeleteForMe instance) => { + 'runtimeType': instance.$type, +}; DeleteForAll _$DeleteForAllFromJson(Map json) => DeleteForAll( - hard: json['hard'] as bool? ?? false, - $type: json['runtimeType'] as String?, - ); + hard: json['hard'] as bool? ?? false, + $type: json['runtimeType'] as String?, +); -Map _$DeleteForAllToJson(DeleteForAll instance) => - { - 'hard': instance.hard, - 'runtimeType': instance.$type, - }; +Map _$DeleteForAllToJson(DeleteForAll instance) => { + 'hard': instance.hard, + 'runtimeType': instance.$type, +}; diff --git a/packages/stream_chat/lib/src/core/models/message_delivery.g.dart b/packages/stream_chat/lib/src/core/models/message_delivery.g.dart index 7a4f1431b9..96a3c10157 100644 --- a/packages/stream_chat/lib/src/core/models/message_delivery.g.dart +++ b/packages/stream_chat/lib/src/core/models/message_delivery.g.dart @@ -6,8 +6,7 @@ part of 'message_delivery.dart'; // JsonSerializableGenerator // ************************************************************************** -Map _$MessageDeliveryToJson(MessageDelivery instance) => - { - 'cid': instance.channelCid, - 'id': instance.messageId, - }; +Map _$MessageDeliveryToJson(MessageDelivery instance) => { + 'cid': instance.channelCid, + 'id': instance.messageId, +}; diff --git a/packages/stream_chat/lib/src/core/models/message_reminder.dart b/packages/stream_chat/lib/src/core/models/message_reminder.dart index d816e06148..23128f53aa 100644 --- a/packages/stream_chat/lib/src/core/models/message_reminder.dart +++ b/packages/stream_chat/lib/src/core/models/message_reminder.dart @@ -38,12 +38,11 @@ class MessageReminder extends Equatable implements ComparableFieldProvider { this.remindAt, DateTime? createdAt, DateTime? updatedAt, - }) : createdAt = createdAt ?? DateTime.now(), - updatedAt = updatedAt ?? DateTime.now(); + }) : createdAt = createdAt ?? DateTime.now(), + updatedAt = updatedAt ?? DateTime.now(); /// Create a new instance from a json - factory MessageReminder.fromJson(Map json) => - _$MessageReminderFromJson(json); + factory MessageReminder.fromJson(Map json) => _$MessageReminderFromJson(json); /// The channel CID where the message exists. final String channelCid; @@ -124,16 +123,16 @@ class MessageReminder extends Equatable implements ComparableFieldProvider { @override List get props => [ - channelCid, - channel, - messageId, - message, - userId, - user, - remindAt, - createdAt, - updatedAt, - ]; + channelCid, + channel, + messageId, + message, + userId, + user, + remindAt, + createdAt, + updatedAt, + ]; @override ComparableField? getComparableField(String sortKey) { diff --git a/packages/stream_chat/lib/src/core/models/message_reminder.g.dart b/packages/stream_chat/lib/src/core/models/message_reminder.g.dart index 1562ace757..2df37be652 100644 --- a/packages/stream_chat/lib/src/core/models/message_reminder.g.dart +++ b/packages/stream_chat/lib/src/core/models/message_reminder.g.dart @@ -6,37 +6,23 @@ part of 'message_reminder.dart'; // JsonSerializableGenerator // ************************************************************************** -MessageReminder _$MessageReminderFromJson(Map json) => - MessageReminder( - channelCid: json['channel_cid'] as String, - channel: json['channel'] == null - ? null - : ChannelModel.fromJson(json['channel'] as Map), - messageId: json['message_id'] as String, - message: json['message'] == null - ? null - : Message.fromJson(json['message'] as Map), - userId: json['user_id'] as String, - user: json['user'] == null - ? null - : User.fromJson(json['user'] as Map), - remindAt: json['remind_at'] == null - ? null - : DateTime.parse(json['remind_at'] as String), - createdAt: json['created_at'] == null - ? null - : DateTime.parse(json['created_at'] as String), - updatedAt: json['updated_at'] == null - ? null - : DateTime.parse(json['updated_at'] as String), - ); +MessageReminder _$MessageReminderFromJson(Map json) => MessageReminder( + channelCid: json['channel_cid'] as String, + channel: json['channel'] == null ? null : ChannelModel.fromJson(json['channel'] as Map), + messageId: json['message_id'] as String, + message: json['message'] == null ? null : Message.fromJson(json['message'] as Map), + userId: json['user_id'] as String, + user: json['user'] == null ? null : User.fromJson(json['user'] as Map), + remindAt: json['remind_at'] == null ? null : DateTime.parse(json['remind_at'] as String), + createdAt: json['created_at'] == null ? null : DateTime.parse(json['created_at'] as String), + updatedAt: json['updated_at'] == null ? null : DateTime.parse(json['updated_at'] as String), +); -Map _$MessageReminderToJson(MessageReminder instance) => - { - 'channel_cid': instance.channelCid, - 'message_id': instance.messageId, - 'user_id': instance.userId, - 'remind_at': instance.remindAt?.toIso8601String(), - 'created_at': instance.createdAt.toIso8601String(), - 'updated_at': instance.updatedAt.toIso8601String(), - }; +Map _$MessageReminderToJson(MessageReminder instance) => { + 'channel_cid': instance.channelCid, + 'message_id': instance.messageId, + 'user_id': instance.userId, + 'remind_at': instance.remindAt?.toIso8601String(), + 'created_at': instance.createdAt.toIso8601String(), + 'updated_at': instance.updatedAt.toIso8601String(), +}; diff --git a/packages/stream_chat/lib/src/core/models/message_state.dart b/packages/stream_chat/lib/src/core/models/message_state.dart index 141bdfd1d8..bcd7f20550 100644 --- a/packages/stream_chat/lib/src/core/models/message_state.dart +++ b/packages/stream_chat/lib/src/core/models/message_state.dart @@ -147,8 +147,7 @@ extension MessageStateX on MessageState { } /// Returns true if the message is in failed deleting state. - bool get isDeletingFailed => - isSoftDeletingFailed || isHardDeletingFailed || isDeletingForMeFailed; + bool get isDeletingFailed => isSoftDeletingFailed || isHardDeletingFailed || isDeletingForMeFailed; /// Returns true if the message is in failed soft deleting state. bool get isSoftDeletingFailed { @@ -215,8 +214,7 @@ sealed class MessageState with _$MessageState { }) = MessageFailed; /// Creates a new instance from a json - factory MessageState.fromJson(Map json) => - _$MessageStateFromJson(json); + factory MessageState.fromJson(Map json) => _$MessageStateFromJson(json); // region Factory Constructors for Common States @@ -387,8 +385,7 @@ sealed class OutgoingState with _$OutgoingState { }) = Deleting; /// Creates a new instance from a json - factory OutgoingState.fromJson(Map json) => - _$OutgoingStateFromJson(json); + factory OutgoingState.fromJson(Map json) => _$OutgoingStateFromJson(json); } /// Represents the completed state of a message. @@ -406,8 +403,7 @@ sealed class CompletedState with _$CompletedState { }) = Deleted; /// Creates a new instance from a json - factory CompletedState.fromJson(Map json) => - _$CompletedStateFromJson(json); + factory CompletedState.fromJson(Map json) => _$CompletedStateFromJson(json); } /// Represents the failed state of a message. @@ -437,8 +433,7 @@ sealed class FailedState with _$FailedState { }) = DeletingFailed; /// Creates a new instance from a json - factory FailedState.fromJson(Map json) => - _$FailedStateFromJson(json); + factory FailedState.fromJson(Map json) => _$FailedStateFromJson(json); } // coverage:ignore-start @@ -759,19 +754,14 @@ extension FailedStatePatternMatching on FailedState { TResult when({ required TResult Function(bool skipPush, bool skipEnrichUrl) sendingFailed, required TResult Function(bool skipPush, bool skipEnrichUrl) updatingFailed, - required TResult Function( - Map? set, List? unset, bool skipEnrichUrl) - partialUpdatingFailed, + required TResult Function(Map? set, List? unset, bool skipEnrichUrl) partialUpdatingFailed, required TResult Function(MessageDeleteScope scope) deletingFailed, }) { final failedState = this; return switch (failedState) { - SendingFailed() => - sendingFailed(failedState.skipPush, failedState.skipEnrichUrl), - UpdatingFailed() => - updatingFailed(failedState.skipPush, failedState.skipEnrichUrl), - PartialUpdatingFailed() => partialUpdatingFailed( - failedState.set, failedState.unset, failedState.skipEnrichUrl), + SendingFailed() => sendingFailed(failedState.skipPush, failedState.skipEnrichUrl), + UpdatingFailed() => updatingFailed(failedState.skipPush, failedState.skipEnrichUrl), + PartialUpdatingFailed() => partialUpdatingFailed(failedState.set, failedState.unset, failedState.skipEnrichUrl), DeletingFailed() => deletingFailed(failedState.scope), }; } @@ -781,19 +771,14 @@ extension FailedStatePatternMatching on FailedState { TResult? whenOrNull({ TResult? Function(bool skipPush, bool skipEnrichUrl)? sendingFailed, TResult? Function(bool skipPush, bool skipEnrichUrl)? updatingFailed, - required TResult Function( - Map? set, List? unset, bool skipEnrichUrl) - partialUpdatingFailed, + required TResult Function(Map? set, List? unset, bool skipEnrichUrl) partialUpdatingFailed, TResult? Function(MessageDeleteScope scope)? deletingFailed, }) { final failedState = this; return switch (failedState) { - SendingFailed() => - sendingFailed?.call(failedState.skipPush, failedState.skipEnrichUrl), - UpdatingFailed() => - updatingFailed?.call(failedState.skipPush, failedState.skipEnrichUrl), - PartialUpdatingFailed() => partialUpdatingFailed( - failedState.set, failedState.unset, failedState.skipEnrichUrl), + SendingFailed() => sendingFailed?.call(failedState.skipPush, failedState.skipEnrichUrl), + UpdatingFailed() => updatingFailed?.call(failedState.skipPush, failedState.skipEnrichUrl), + PartialUpdatingFailed() => partialUpdatingFailed(failedState.set, failedState.unset, failedState.skipEnrichUrl), DeletingFailed() => deletingFailed?.call(failedState.scope), }; } @@ -803,20 +788,15 @@ extension FailedStatePatternMatching on FailedState { TResult maybeWhen({ TResult Function(bool skipPush, bool skipEnrichUrl)? sendingFailed, TResult Function(bool skipPush, bool skipEnrichUrl)? updatingFailed, - required TResult Function( - Map? set, List? unset, bool skipEnrichUrl) - partialUpdatingFailed, + required TResult Function(Map? set, List? unset, bool skipEnrichUrl) partialUpdatingFailed, TResult Function(MessageDeleteScope scope)? deletingFailed, required TResult orElse(), }) { final failedState = this; final result = switch (failedState) { - SendingFailed() => - sendingFailed?.call(failedState.skipPush, failedState.skipEnrichUrl), - UpdatingFailed() => - updatingFailed?.call(failedState.skipPush, failedState.skipEnrichUrl), - PartialUpdatingFailed() => partialUpdatingFailed( - failedState.set, failedState.unset, failedState.skipEnrichUrl), + SendingFailed() => sendingFailed?.call(failedState.skipPush, failedState.skipEnrichUrl), + UpdatingFailed() => updatingFailed?.call(failedState.skipPush, failedState.skipEnrichUrl), + PartialUpdatingFailed() => partialUpdatingFailed(failedState.set, failedState.unset, failedState.skipEnrichUrl), DeletingFailed() => deletingFailed?.call(failedState.scope), }; @@ -828,8 +808,7 @@ extension FailedStatePatternMatching on FailedState { TResult map({ required TResult Function(SendingFailed value) sendingFailed, required TResult Function(UpdatingFailed value) updatingFailed, - required TResult Function(PartialUpdatingFailed value) - partialUpdatingFailed, + required TResult Function(PartialUpdatingFailed value) partialUpdatingFailed, required TResult Function(DeletingFailed value) deletingFailed, }) { final failedState = this; diff --git a/packages/stream_chat/lib/src/core/models/message_state.g.dart b/packages/stream_chat/lib/src/core/models/message_state.g.dart index 693a642e17..6336fe611a 100644 --- a/packages/stream_chat/lib/src/core/models/message_state.g.dart +++ b/packages/stream_chat/lib/src/core/models/message_state.g.dart @@ -6,167 +6,148 @@ part of 'message_state.dart'; // JsonSerializableGenerator // ************************************************************************** -MessageInitial _$MessageInitialFromJson(Map json) => - MessageInitial( - $type: json['runtimeType'] as String?, - ); - -Map _$MessageInitialToJson(MessageInitial instance) => - { - 'runtimeType': instance.$type, - }; - -MessageOutgoing _$MessageOutgoingFromJson(Map json) => - MessageOutgoing( - state: OutgoingState.fromJson(json['state'] as Map), - $type: json['runtimeType'] as String?, - ); - -Map _$MessageOutgoingToJson(MessageOutgoing instance) => - { - 'state': instance.state.toJson(), - 'runtimeType': instance.$type, - }; - -MessageCompleted _$MessageCompletedFromJson(Map json) => - MessageCompleted( - state: CompletedState.fromJson(json['state'] as Map), - $type: json['runtimeType'] as String?, - ); - -Map _$MessageCompletedToJson(MessageCompleted instance) => - { - 'state': instance.state.toJson(), - 'runtimeType': instance.$type, - }; - -MessageFailed _$MessageFailedFromJson(Map json) => - MessageFailed( - state: FailedState.fromJson(json['state'] as Map), - reason: json['reason'], - $type: json['runtimeType'] as String?, - ); - -Map _$MessageFailedToJson(MessageFailed instance) => - { - 'state': instance.state.toJson(), - 'reason': instance.reason, - 'runtimeType': instance.$type, - }; +MessageInitial _$MessageInitialFromJson(Map json) => MessageInitial( + $type: json['runtimeType'] as String?, +); + +Map _$MessageInitialToJson(MessageInitial instance) => { + 'runtimeType': instance.$type, +}; + +MessageOutgoing _$MessageOutgoingFromJson(Map json) => MessageOutgoing( + state: OutgoingState.fromJson(json['state'] as Map), + $type: json['runtimeType'] as String?, +); + +Map _$MessageOutgoingToJson(MessageOutgoing instance) => { + 'state': instance.state.toJson(), + 'runtimeType': instance.$type, +}; + +MessageCompleted _$MessageCompletedFromJson(Map json) => MessageCompleted( + state: CompletedState.fromJson(json['state'] as Map), + $type: json['runtimeType'] as String?, +); + +Map _$MessageCompletedToJson(MessageCompleted instance) => { + 'state': instance.state.toJson(), + 'runtimeType': instance.$type, +}; + +MessageFailed _$MessageFailedFromJson(Map json) => MessageFailed( + state: FailedState.fromJson(json['state'] as Map), + reason: json['reason'], + $type: json['runtimeType'] as String?, +); + +Map _$MessageFailedToJson(MessageFailed instance) => { + 'state': instance.state.toJson(), + 'reason': instance.reason, + 'runtimeType': instance.$type, +}; Sending _$SendingFromJson(Map json) => Sending( - $type: json['runtimeType'] as String?, - ); + $type: json['runtimeType'] as String?, +); Map _$SendingToJson(Sending instance) => { - 'runtimeType': instance.$type, - }; + 'runtimeType': instance.$type, +}; Updating _$UpdatingFromJson(Map json) => Updating( - $type: json['runtimeType'] as String?, - ); + $type: json['runtimeType'] as String?, +); Map _$UpdatingToJson(Updating instance) => { - 'runtimeType': instance.$type, - }; + 'runtimeType': instance.$type, +}; Deleting _$DeletingFromJson(Map json) => Deleting( - scope: json['scope'] == null - ? MessageDeleteScope.softDeleteForAll - : MessageDeleteScope.fromJson(json['scope'] as Map), - $type: json['runtimeType'] as String?, - ); + scope: json['scope'] == null + ? MessageDeleteScope.softDeleteForAll + : MessageDeleteScope.fromJson(json['scope'] as Map), + $type: json['runtimeType'] as String?, +); Map _$DeletingToJson(Deleting instance) => { - 'scope': instance.scope.toJson(), - 'runtimeType': instance.$type, - }; + 'scope': instance.scope.toJson(), + 'runtimeType': instance.$type, +}; Sent _$SentFromJson(Map json) => Sent( - $type: json['runtimeType'] as String?, - ); + $type: json['runtimeType'] as String?, +); Map _$SentToJson(Sent instance) => { - 'runtimeType': instance.$type, - }; + 'runtimeType': instance.$type, +}; Updated _$UpdatedFromJson(Map json) => Updated( - $type: json['runtimeType'] as String?, - ); + $type: json['runtimeType'] as String?, +); Map _$UpdatedToJson(Updated instance) => { - 'runtimeType': instance.$type, - }; + 'runtimeType': instance.$type, +}; Deleted _$DeletedFromJson(Map json) => Deleted( - scope: json['scope'] == null - ? MessageDeleteScope.softDeleteForAll - : MessageDeleteScope.fromJson(json['scope'] as Map), - $type: json['runtimeType'] as String?, - ); + scope: json['scope'] == null + ? MessageDeleteScope.softDeleteForAll + : MessageDeleteScope.fromJson(json['scope'] as Map), + $type: json['runtimeType'] as String?, +); Map _$DeletedToJson(Deleted instance) => { - 'scope': instance.scope.toJson(), - 'runtimeType': instance.$type, - }; - -SendingFailed _$SendingFailedFromJson(Map json) => - SendingFailed( - skipPush: json['skip_push'] as bool? ?? false, - skipEnrichUrl: json['skip_enrich_url'] as bool? ?? false, - $type: json['runtimeType'] as String?, - ); - -Map _$SendingFailedToJson(SendingFailed instance) => - { - 'skip_push': instance.skipPush, - 'skip_enrich_url': instance.skipEnrichUrl, - 'runtimeType': instance.$type, - }; - -UpdatingFailed _$UpdatingFailedFromJson(Map json) => - UpdatingFailed( - skipPush: json['skip_push'] as bool? ?? false, - skipEnrichUrl: json['skip_enrich_url'] as bool? ?? false, - $type: json['runtimeType'] as String?, - ); - -Map _$UpdatingFailedToJson(UpdatingFailed instance) => - { - 'skip_push': instance.skipPush, - 'skip_enrich_url': instance.skipEnrichUrl, - 'runtimeType': instance.$type, - }; - -PartialUpdatingFailed _$PartialUpdatingFailedFromJson( - Map json) => - PartialUpdatingFailed( - set: json['set'] as Map?, - unset: - (json['unset'] as List?)?.map((e) => e as String).toList(), - skipEnrichUrl: json['skip_enrich_url'] as bool? ?? false, - $type: json['runtimeType'] as String?, - ); - -Map _$PartialUpdatingFailedToJson( - PartialUpdatingFailed instance) => - { - 'set': instance.set, - 'unset': instance.unset, - 'skip_enrich_url': instance.skipEnrichUrl, - 'runtimeType': instance.$type, - }; - -DeletingFailed _$DeletingFailedFromJson(Map json) => - DeletingFailed( - scope: json['scope'] == null - ? MessageDeleteScope.softDeleteForAll - : MessageDeleteScope.fromJson(json['scope'] as Map), - $type: json['runtimeType'] as String?, - ); - -Map _$DeletingFailedToJson(DeletingFailed instance) => - { - 'scope': instance.scope.toJson(), - 'runtimeType': instance.$type, - }; + 'scope': instance.scope.toJson(), + 'runtimeType': instance.$type, +}; + +SendingFailed _$SendingFailedFromJson(Map json) => SendingFailed( + skipPush: json['skip_push'] as bool? ?? false, + skipEnrichUrl: json['skip_enrich_url'] as bool? ?? false, + $type: json['runtimeType'] as String?, +); + +Map _$SendingFailedToJson(SendingFailed instance) => { + 'skip_push': instance.skipPush, + 'skip_enrich_url': instance.skipEnrichUrl, + 'runtimeType': instance.$type, +}; + +UpdatingFailed _$UpdatingFailedFromJson(Map json) => UpdatingFailed( + skipPush: json['skip_push'] as bool? ?? false, + skipEnrichUrl: json['skip_enrich_url'] as bool? ?? false, + $type: json['runtimeType'] as String?, +); + +Map _$UpdatingFailedToJson(UpdatingFailed instance) => { + 'skip_push': instance.skipPush, + 'skip_enrich_url': instance.skipEnrichUrl, + 'runtimeType': instance.$type, +}; + +PartialUpdatingFailed _$PartialUpdatingFailedFromJson(Map json) => PartialUpdatingFailed( + set: json['set'] as Map?, + unset: (json['unset'] as List?)?.map((e) => e as String).toList(), + skipEnrichUrl: json['skip_enrich_url'] as bool? ?? false, + $type: json['runtimeType'] as String?, +); + +Map _$PartialUpdatingFailedToJson(PartialUpdatingFailed instance) => { + 'set': instance.set, + 'unset': instance.unset, + 'skip_enrich_url': instance.skipEnrichUrl, + 'runtimeType': instance.$type, +}; + +DeletingFailed _$DeletingFailedFromJson(Map json) => DeletingFailed( + scope: json['scope'] == null + ? MessageDeleteScope.softDeleteForAll + : MessageDeleteScope.fromJson(json['scope'] as Map), + $type: json['runtimeType'] as String?, +); + +Map _$DeletingFailedToJson(DeletingFailed instance) => { + 'scope': instance.scope.toJson(), + 'runtimeType': instance.$type, +}; diff --git a/packages/stream_chat/lib/src/core/models/moderation.dart b/packages/stream_chat/lib/src/core/models/moderation.dart index e267afc7de..f3133a39a6 100644 --- a/packages/stream_chat/lib/src/core/models/moderation.dart +++ b/packages/stream_chat/lib/src/core/models/moderation.dart @@ -20,8 +20,7 @@ class Moderation extends Equatable { }); /// Create a new instance from a json - factory Moderation.fromJson(Map json) => - _$ModerationFromJson(json); + factory Moderation.fromJson(Map json) => _$ModerationFromJson(json); /// The action taken by the moderation system. @JsonKey( @@ -53,14 +52,14 @@ class Moderation extends Equatable { @override List get props => [ - action, - originalText, - textHarms, - imageHarms, - blocklistMatched, - semanticFilterMatched, - platformCircumvented, - ]; + action, + originalText, + textHarms, + imageHarms, + blocklistMatched, + semanticFilterMatched, + platformCircumvented, + ]; } /// The moderation action performed over the message. diff --git a/packages/stream_chat/lib/src/core/models/moderation.g.dart b/packages/stream_chat/lib/src/core/models/moderation.g.dart index 6f8ef841c1..f287d23d84 100644 --- a/packages/stream_chat/lib/src/core/models/moderation.g.dart +++ b/packages/stream_chat/lib/src/core/models/moderation.g.dart @@ -7,26 +7,21 @@ part of 'moderation.dart'; // ************************************************************************** Moderation _$ModerationFromJson(Map json) => Moderation( - action: ModerationAction.fromJson(json['action'] as String), - originalText: json['original_text'] as String, - textHarms: (json['text_harms'] as List?) - ?.map((e) => e as String) - .toList(), - imageHarms: (json['image_harms'] as List?) - ?.map((e) => e as String) - .toList(), - blocklistMatched: json['blocklist_matched'] as String?, - semanticFilterMatched: json['semantic_filter_matched'] as String?, - platformCircumvented: json['platform_circumvented'] as bool? ?? false, - ); + action: ModerationAction.fromJson(json['action'] as String), + originalText: json['original_text'] as String, + textHarms: (json['text_harms'] as List?)?.map((e) => e as String).toList(), + imageHarms: (json['image_harms'] as List?)?.map((e) => e as String).toList(), + blocklistMatched: json['blocklist_matched'] as String?, + semanticFilterMatched: json['semantic_filter_matched'] as String?, + platformCircumvented: json['platform_circumvented'] as bool? ?? false, +); -Map _$ModerationToJson(Moderation instance) => - { - 'action': ModerationAction.toJson(instance.action), - 'original_text': instance.originalText, - 'text_harms': instance.textHarms, - 'image_harms': instance.imageHarms, - 'blocklist_matched': instance.blocklistMatched, - 'semantic_filter_matched': instance.semanticFilterMatched, - 'platform_circumvented': instance.platformCircumvented, - }; +Map _$ModerationToJson(Moderation instance) => { + 'action': ModerationAction.toJson(instance.action), + 'original_text': instance.originalText, + 'text_harms': instance.textHarms, + 'image_harms': instance.imageHarms, + 'blocklist_matched': instance.blocklistMatched, + 'semantic_filter_matched': instance.semanticFilterMatched, + 'platform_circumvented': instance.platformCircumvented, +}; diff --git a/packages/stream_chat/lib/src/core/models/mute.g.dart b/packages/stream_chat/lib/src/core/models/mute.g.dart index 624df9cb70..b1e3dc7aac 100644 --- a/packages/stream_chat/lib/src/core/models/mute.g.dart +++ b/packages/stream_chat/lib/src/core/models/mute.g.dart @@ -7,19 +7,17 @@ part of 'mute.dart'; // ************************************************************************** Mute _$MuteFromJson(Map json) => Mute( - user: User.fromJson(json['user'] as Map), - target: User.fromJson(json['target'] as Map), - createdAt: DateTime.parse(json['created_at'] as String), - updatedAt: DateTime.parse(json['updated_at'] as String), - expires: json['expires'] == null - ? null - : DateTime.parse(json['expires'] as String), - ); + user: User.fromJson(json['user'] as Map), + target: User.fromJson(json['target'] as Map), + createdAt: DateTime.parse(json['created_at'] as String), + updatedAt: DateTime.parse(json['updated_at'] as String), + expires: json['expires'] == null ? null : DateTime.parse(json['expires'] as String), +); Map _$MuteToJson(Mute instance) => { - 'user': instance.user.toJson(), - 'target': instance.target.toJson(), - 'created_at': instance.createdAt.toIso8601String(), - 'updated_at': instance.updatedAt.toIso8601String(), - 'expires': instance.expires?.toIso8601String(), - }; + 'user': instance.user.toJson(), + 'target': instance.target.toJson(), + 'created_at': instance.createdAt.toIso8601String(), + 'updated_at': instance.updatedAt.toIso8601String(), + 'expires': instance.expires?.toIso8601String(), +}; diff --git a/packages/stream_chat/lib/src/core/models/own_user.dart b/packages/stream_chat/lib/src/core/models/own_user.dart index c685761937..e3e8b83b9b 100644 --- a/packages/stream_chat/lib/src/core/models/own_user.dart +++ b/packages/stream_chat/lib/src/core/models/own_user.dart @@ -40,8 +40,8 @@ class OwnUser extends User { /// Create a new instance from json. factory OwnUser.fromJson(Map json) => _$OwnUserFromJson( - Serializer.moveToExtraDataFromRoot(json, topLevelFields), - ); + Serializer.moveToExtraDataFromRoot(json, topLevelFields), + ); /// Create a new instance from [User] object. factory OwnUser.fromUser(User user) => OwnUser.fromJson(user.toJson()); @@ -74,37 +74,37 @@ class OwnUser extends User { int? avgResponseTime, PushPreference? pushPreferences, PrivacySettings? privacySettings, - }) => - OwnUser( - id: id ?? this.id, - role: role ?? this.role, - name: name ?? - extraData?['name'] as String? ?? - // Using extraData value in order to not use id as name. - this.extraData['name'] as String?, - image: image ?? extraData?['image'] as String? ?? this.image, - banned: banned ?? this.banned, - banExpires: banExpires ?? this.banExpires, - createdAt: createdAt ?? this.createdAt, - updatedAt: updatedAt ?? this.updatedAt, - lastActive: lastActive ?? this.lastActive, - online: online ?? this.online, - extraData: extraData ?? this.extraData, - teams: teams ?? this.teams, - channelMutes: channelMutes ?? this.channelMutes, - devices: devices ?? this.devices, - mutes: mutes ?? this.mutes, - totalUnreadCount: totalUnreadCount ?? this.totalUnreadCount, - unreadChannels: unreadChannels ?? this.unreadChannels, - unreadThreads: unreadThreads ?? this.unreadThreads, - blockedUserIds: blockedUserIds ?? this.blockedUserIds, - language: language ?? this.language, - invisible: invisible ?? this.invisible, - teamsRole: teamsRole ?? this.teamsRole, - avgResponseTime: avgResponseTime ?? this.avgResponseTime, - pushPreferences: pushPreferences ?? this.pushPreferences, - privacySettings: privacySettings ?? this.privacySettings, - ); + }) => OwnUser( + id: id ?? this.id, + role: role ?? this.role, + name: + name ?? + extraData?['name'] as String? ?? + // Using extraData value in order to not use id as name. + this.extraData['name'] as String?, + image: image ?? extraData?['image'] as String? ?? this.image, + banned: banned ?? this.banned, + banExpires: banExpires ?? this.banExpires, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + lastActive: lastActive ?? this.lastActive, + online: online ?? this.online, + extraData: extraData ?? this.extraData, + teams: teams ?? this.teams, + channelMutes: channelMutes ?? this.channelMutes, + devices: devices ?? this.devices, + mutes: mutes ?? this.mutes, + totalUnreadCount: totalUnreadCount ?? this.totalUnreadCount, + unreadChannels: unreadChannels ?? this.unreadChannels, + unreadThreads: unreadThreads ?? this.unreadThreads, + blockedUserIds: blockedUserIds ?? this.blockedUserIds, + language: language ?? this.language, + invisible: invisible ?? this.invisible, + teamsRole: teamsRole ?? this.teamsRole, + avgResponseTime: avgResponseTime ?? this.avgResponseTime, + pushPreferences: pushPreferences ?? this.pushPreferences, + privacySettings: privacySettings ?? this.privacySettings, + ); /// Returns a new [OwnUser] that is a combination of this ownUser /// and the given [other] ownUser. diff --git a/packages/stream_chat/lib/src/core/models/own_user.g.dart b/packages/stream_chat/lib/src/core/models/own_user.g.dart index 21fc564e29..d8e69df5dd 100644 --- a/packages/stream_chat/lib/src/core/models/own_user.g.dart +++ b/packages/stream_chat/lib/src/core/models/own_user.g.dart @@ -7,90 +7,62 @@ part of 'own_user.dart'; // ************************************************************************** OwnUser _$OwnUserFromJson(Map json) => OwnUser( - devices: (json['devices'] as List?) - ?.map((e) => Device.fromJson(e as Map)) - .toList() ?? - const [], - mutes: (json['mutes'] as List?) - ?.map((e) => Mute.fromJson(e as Map)) - .toList() ?? - const [], - totalUnreadCount: (json['total_unread_count'] as num?)?.toInt() ?? 0, - unreadChannels: (json['unread_channels'] as num?)?.toInt() ?? 0, - channelMutes: (json['channel_mutes'] as List?) - ?.map((e) => ChannelMute.fromJson(e as Map)) - .toList() ?? - const [], - unreadThreads: (json['unread_threads'] as num?)?.toInt() ?? 0, - blockedUserIds: (json['blocked_user_ids'] as List?) - ?.map((e) => e as String) - .toList() ?? - const [], - pushPreferences: json['push_preferences'] == null - ? null - : PushPreference.fromJson( - json['push_preferences'] as Map), - privacySettings: json['privacy_settings'] == null - ? null - : PrivacySettings.fromJson( - json['privacy_settings'] as Map), - id: json['id'] as String, - role: json['role'] as String?, - createdAt: json['created_at'] == null - ? null - : DateTime.parse(json['created_at'] as String), - updatedAt: json['updated_at'] == null - ? null - : DateTime.parse(json['updated_at'] as String), - lastActive: json['last_active'] == null - ? null - : DateTime.parse(json['last_active'] as String), - online: json['online'] as bool? ?? false, - extraData: json['extra_data'] as Map? ?? const {}, - banned: json['banned'] as bool? ?? false, - banExpires: json['ban_expires'] == null - ? null - : DateTime.parse(json['ban_expires'] as String), - teams: - (json['teams'] as List?)?.map((e) => e as String).toList() ?? - const [], - language: json['language'] as String?, - invisible: json['invisible'] as bool?, - teamsRole: (json['teams_role'] as Map?)?.map( - (k, e) => MapEntry(k, e as String), - ), - avgResponseTime: (json['avg_response_time'] as num?)?.toInt(), - ); + devices: + (json['devices'] as List?)?.map((e) => Device.fromJson(e as Map)).toList() ?? const [], + mutes: (json['mutes'] as List?)?.map((e) => Mute.fromJson(e as Map)).toList() ?? const [], + totalUnreadCount: (json['total_unread_count'] as num?)?.toInt() ?? 0, + unreadChannels: (json['unread_channels'] as num?)?.toInt() ?? 0, + channelMutes: + (json['channel_mutes'] as List?)?.map((e) => ChannelMute.fromJson(e as Map)).toList() ?? + const [], + unreadThreads: (json['unread_threads'] as num?)?.toInt() ?? 0, + blockedUserIds: (json['blocked_user_ids'] as List?)?.map((e) => e as String).toList() ?? const [], + pushPreferences: json['push_preferences'] == null + ? null + : PushPreference.fromJson(json['push_preferences'] as Map), + privacySettings: json['privacy_settings'] == null + ? null + : PrivacySettings.fromJson(json['privacy_settings'] as Map), + id: json['id'] as String, + role: json['role'] as String?, + createdAt: json['created_at'] == null ? null : DateTime.parse(json['created_at'] as String), + updatedAt: json['updated_at'] == null ? null : DateTime.parse(json['updated_at'] as String), + lastActive: json['last_active'] == null ? null : DateTime.parse(json['last_active'] as String), + online: json['online'] as bool? ?? false, + extraData: json['extra_data'] as Map? ?? const {}, + banned: json['banned'] as bool? ?? false, + banExpires: json['ban_expires'] == null ? null : DateTime.parse(json['ban_expires'] as String), + teams: (json['teams'] as List?)?.map((e) => e as String).toList() ?? const [], + language: json['language'] as String?, + invisible: json['invisible'] as bool?, + teamsRole: (json['teams_role'] as Map?)?.map( + (k, e) => MapEntry(k, e as String), + ), + avgResponseTime: (json['avg_response_time'] as num?)?.toInt(), +); Map _$OwnUserToJson(OwnUser instance) => { - 'id': instance.id, - if (instance.role case final value?) 'role': value, - 'teams': instance.teams, - if (instance.createdAt?.toIso8601String() case final value?) - 'created_at': value, - if (instance.updatedAt?.toIso8601String() case final value?) - 'updated_at': value, - if (instance.lastActive?.toIso8601String() case final value?) - 'last_active': value, - 'online': instance.online, - 'banned': instance.banned, - if (instance.banExpires?.toIso8601String() case final value?) - 'ban_expires': value, - if (instance.language case final value?) 'language': value, - if (instance.invisible case final value?) 'invisible': value, - if (instance.teamsRole case final value?) 'teams_role': value, - if (instance.avgResponseTime case final value?) - 'avg_response_time': value, - 'extra_data': instance.extraData, - 'devices': instance.devices.map((e) => e.toJson()).toList(), - 'mutes': instance.mutes.map((e) => e.toJson()).toList(), - 'channel_mutes': instance.channelMutes.map((e) => e.toJson()).toList(), - 'total_unread_count': instance.totalUnreadCount, - 'unread_channels': instance.unreadChannels, - 'unread_threads': instance.unreadThreads, - 'blocked_user_ids': instance.blockedUserIds, - if (instance.pushPreferences?.toJson() case final value?) - 'push_preferences': value, - if (instance.privacySettings?.toJson() case final value?) - 'privacy_settings': value, - }; + 'id': instance.id, + if (instance.role case final value?) 'role': value, + 'teams': instance.teams, + if (instance.createdAt?.toIso8601String() case final value?) 'created_at': value, + if (instance.updatedAt?.toIso8601String() case final value?) 'updated_at': value, + if (instance.lastActive?.toIso8601String() case final value?) 'last_active': value, + 'online': instance.online, + 'banned': instance.banned, + if (instance.banExpires?.toIso8601String() case final value?) 'ban_expires': value, + if (instance.language case final value?) 'language': value, + if (instance.invisible case final value?) 'invisible': value, + if (instance.teamsRole case final value?) 'teams_role': value, + if (instance.avgResponseTime case final value?) 'avg_response_time': value, + 'extra_data': instance.extraData, + 'devices': instance.devices.map((e) => e.toJson()).toList(), + 'mutes': instance.mutes.map((e) => e.toJson()).toList(), + 'channel_mutes': instance.channelMutes.map((e) => e.toJson()).toList(), + 'total_unread_count': instance.totalUnreadCount, + 'unread_channels': instance.unreadChannels, + 'unread_threads': instance.unreadThreads, + 'blocked_user_ids': instance.blockedUserIds, + if (instance.pushPreferences?.toJson() case final value?) 'push_preferences': value, + if (instance.privacySettings?.toJson() case final value?) 'privacy_settings': value, +}; diff --git a/packages/stream_chat/lib/src/core/models/poll.dart b/packages/stream_chat/lib/src/core/models/poll.dart index dd61feac2b..a0fb4bfc1b 100644 --- a/packages/stream_chat/lib/src/core/models/poll.dart +++ b/packages/stream_chat/lib/src/core/models/poll.dart @@ -57,9 +57,9 @@ class Poll extends Equatable implements ComparableFieldProvider { this.createdBy, this.ownVotesAndAnswers = const [], this.extraData = const {}, - }) : id = id ?? const Uuid().v4(), - createdAt = createdAt ?? DateTime.now(), - updatedAt = updatedAt ?? DateTime.now(); + }) : id = id ?? const Uuid().v4(), + createdAt = createdAt ?? DateTime.now(), + updatedAt = updatedAt ?? DateTime.now(); /// Create a new instance from a json factory Poll.fromJson(Map json) => @@ -167,8 +167,7 @@ class Poll extends Equatable implements ComparableFieldProvider { final Map extraData; /// Serialize to json - Map toJson() => - Serializer.moveFromExtraDataToRoot(_$PollToJson(this)); + Map toJson() => Serializer.moveFromExtraDataToRoot(_$PollToJson(this)); /// Creates a copy of [Poll] with specified attributes overridden. Poll copyWith({ @@ -193,33 +192,29 @@ class Poll extends Equatable implements ComparableFieldProvider { DateTime? createdAt, DateTime? updatedAt, Map? extraData, - }) => - Poll( - id: id ?? this.id, - name: name ?? this.name, - description: description ?? this.description, - options: options ?? this.options, - votingVisibility: votingVisibility ?? this.votingVisibility, - enforceUniqueVote: enforceUniqueVote ?? this.enforceUniqueVote, - maxVotesAllowed: maxVotesAllowed == _nullConst - ? this.maxVotesAllowed - : maxVotesAllowed as int?, - allowUserSuggestedOptions: - allowUserSuggestedOptions ?? this.allowUserSuggestedOptions, - allowAnswers: allowAnswers ?? this.allowAnswers, - isClosed: isClosed ?? this.isClosed, - voteCountsByOption: voteCountsByOption ?? this.voteCountsByOption, - ownVotesAndAnswers: ownVotesAndAnswers ?? this.ownVotesAndAnswers, - voteCount: voteCount ?? this.voteCount, - answersCount: answersCount ?? this.answersCount, - latestVotesByOption: latestVotesByOption ?? this.latestVotesByOption, - latestAnswers: latestAnswers ?? this.latestAnswers, - createdById: createdById ?? this.createdById, - createdBy: createdBy ?? this.createdBy, - createdAt: createdAt ?? this.createdAt, - updatedAt: updatedAt ?? this.updatedAt, - extraData: extraData ?? this.extraData, - ); + }) => Poll( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + options: options ?? this.options, + votingVisibility: votingVisibility ?? this.votingVisibility, + enforceUniqueVote: enforceUniqueVote ?? this.enforceUniqueVote, + maxVotesAllowed: maxVotesAllowed == _nullConst ? this.maxVotesAllowed : maxVotesAllowed as int?, + allowUserSuggestedOptions: allowUserSuggestedOptions ?? this.allowUserSuggestedOptions, + allowAnswers: allowAnswers ?? this.allowAnswers, + isClosed: isClosed ?? this.isClosed, + voteCountsByOption: voteCountsByOption ?? this.voteCountsByOption, + ownVotesAndAnswers: ownVotesAndAnswers ?? this.ownVotesAndAnswers, + voteCount: voteCount ?? this.voteCount, + answersCount: answersCount ?? this.answersCount, + latestVotesByOption: latestVotesByOption ?? this.latestVotesByOption, + latestAnswers: latestAnswers ?? this.latestAnswers, + createdById: createdById ?? this.createdById, + createdBy: createdBy ?? this.createdBy, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + extraData: extraData ?? this.extraData, + ); /// Known top level fields. /// @@ -250,27 +245,27 @@ class Poll extends Equatable implements ComparableFieldProvider { @override List get props => [ - id, - name, - description, - options, - votingVisibility, - enforceUniqueVote, - maxVotesAllowed, - allowUserSuggestedOptions, - allowAnswers, - isClosed, - voteCountsByOption, - ownVotesAndAnswers, - voteCount, - answersCount, - latestVotesByOption, - latestAnswers, - createdById, - createdBy, - createdAt, - updatedAt, - ]; + id, + name, + description, + options, + votingVisibility, + enforceUniqueVote, + maxVotesAllowed, + allowUserSuggestedOptions, + allowAnswers, + isClosed, + voteCountsByOption, + ownVotesAndAnswers, + voteCount, + answersCount, + latestVotesByOption, + latestAnswers, + createdById, + createdBy, + createdAt, + updatedAt, + ]; @override ComparableField? getComparableField(String sortKey) { @@ -319,31 +314,28 @@ extension PollX on Poll { /// Whether the poll is already closed and the provided option is the one, /// and **the only one** with the most votes. - bool isOptionWinner(PollOption option) => - isClosed && isOptionWithMostVotes(option); + bool isOptionWinner(PollOption option) => isClosed && isOptionWithMostVotes(option); /// Whether the poll is already closed and the provided option is one of that /// has the most votes. - bool isOptionOneOfTheWinners(PollOption option) => - isClosed && isOptionWithMaximumVotes(option); + bool isOptionOneOfTheWinners(PollOption option) => isClosed && isOptionWithMaximumVotes(option); /// Whether the provided option is the one, and **the only one** with the most /// votes. bool isOptionWithMostVotes(PollOption option) { final optionsWithMostVotes = { for (final entry in voteCountsByOption.entries) - if (entry.value == currentMaximumVoteCount) entry.key: entry.value + if (entry.value == currentMaximumVoteCount) entry.key: entry.value, }; - return optionsWithMostVotes.length == 1 && - optionsWithMostVotes[option.id] != null; + return optionsWithMostVotes.length == 1 && optionsWithMostVotes[option.id] != null; } /// Whether the provided option is one of that has the most votes. bool isOptionWithMaximumVotes(PollOption option) { final optionsWithMostVotes = { for (final entry in voteCountsByOption.entries) - if (entry.value == currentMaximumVoteCount) entry.key: entry.value + if (entry.value == currentMaximumVoteCount) entry.key: entry.value, }; return optionsWithMostVotes[option.id] != null; @@ -368,6 +360,5 @@ extension PollX on Poll { /// Returns a Boolean value indicating whether the current user has voted the /// given option. - bool hasCurrentUserVotedFor(PollOption option) => - ownVotesAndAnswers.any((it) => it.optionId == option.id); + bool hasCurrentUserVotedFor(PollOption option) => ownVotesAndAnswers.any((it) => it.optionId == option.id); } diff --git a/packages/stream_chat/lib/src/core/models/poll.g.dart b/packages/stream_chat/lib/src/core/models/poll.g.dart index 475232c4cc..3a4432041e 100644 --- a/packages/stream_chat/lib/src/core/models/poll.g.dart +++ b/packages/stream_chat/lib/src/core/models/poll.g.dart @@ -7,73 +7,55 @@ part of 'poll.dart'; // ************************************************************************** Poll _$PollFromJson(Map json) => Poll( - id: json['id'] as String?, - name: json['name'] as String, - description: json['description'] as String?, - options: (json['options'] as List) - .map((e) => PollOption.fromJson(e as Map)) - .toList(), - votingVisibility: $enumDecodeNullable( - _$VotingVisibilityEnumMap, json['voting_visibility']) ?? - VotingVisibility.public, - enforceUniqueVote: json['enforce_unique_vote'] as bool? ?? true, - maxVotesAllowed: (json['max_votes_allowed'] as num?)?.toInt(), - allowAnswers: json['allow_answers'] as bool? ?? false, - latestAnswers: (json['latest_answers'] as List?) - ?.map((e) => PollVote.fromJson(e as Map)) - .toList() ?? - const [], - answersCount: (json['answers_count'] as num?)?.toInt() ?? 0, - allowUserSuggestedOptions: - json['allow_user_suggested_options'] as bool? ?? false, - isClosed: json['is_closed'] as bool? ?? false, - createdAt: json['created_at'] == null - ? null - : DateTime.parse(json['created_at'] as String), - updatedAt: json['updated_at'] == null - ? null - : DateTime.parse(json['updated_at'] as String), - voteCountsByOption: - (json['vote_counts_by_option'] as Map?)?.map( - (k, e) => MapEntry(k, (e as num).toInt()), - ) ?? - const {}, - voteCount: (json['vote_count'] as num?)?.toInt() ?? 0, - latestVotesByOption: - (json['latest_votes_by_option'] as Map?)?.map( - (k, e) => MapEntry( - k, - (e as List) - .map( - (e) => PollVote.fromJson(e as Map)) - .toList()), - ) ?? - const {}, - createdById: json['created_by_id'] as String?, - createdBy: json['created_by'] == null - ? null - : User.fromJson(json['created_by'] as Map), - ownVotesAndAnswers: (json['own_votes'] as List?) - ?.map((e) => PollVote.fromJson(e as Map)) - .toList() ?? - const [], - extraData: json['extra_data'] as Map? ?? const {}, - ); + id: json['id'] as String?, + name: json['name'] as String, + description: json['description'] as String?, + options: (json['options'] as List).map((e) => PollOption.fromJson(e as Map)).toList(), + votingVisibility: + $enumDecodeNullable(_$VotingVisibilityEnumMap, json['voting_visibility']) ?? VotingVisibility.public, + enforceUniqueVote: json['enforce_unique_vote'] as bool? ?? true, + maxVotesAllowed: (json['max_votes_allowed'] as num?)?.toInt(), + allowAnswers: json['allow_answers'] as bool? ?? false, + latestAnswers: + (json['latest_answers'] as List?)?.map((e) => PollVote.fromJson(e as Map)).toList() ?? + const [], + answersCount: (json['answers_count'] as num?)?.toInt() ?? 0, + allowUserSuggestedOptions: json['allow_user_suggested_options'] as bool? ?? false, + isClosed: json['is_closed'] as bool? ?? false, + createdAt: json['created_at'] == null ? null : DateTime.parse(json['created_at'] as String), + updatedAt: json['updated_at'] == null ? null : DateTime.parse(json['updated_at'] as String), + voteCountsByOption: + (json['vote_counts_by_option'] as Map?)?.map( + (k, e) => MapEntry(k, (e as num).toInt()), + ) ?? + const {}, + voteCount: (json['vote_count'] as num?)?.toInt() ?? 0, + latestVotesByOption: + (json['latest_votes_by_option'] as Map?)?.map( + (k, e) => MapEntry(k, (e as List).map((e) => PollVote.fromJson(e as Map)).toList()), + ) ?? + const {}, + createdById: json['created_by_id'] as String?, + createdBy: json['created_by'] == null ? null : User.fromJson(json['created_by'] as Map), + ownVotesAndAnswers: + (json['own_votes'] as List?)?.map((e) => PollVote.fromJson(e as Map)).toList() ?? + const [], + extraData: json['extra_data'] as Map? ?? const {}, +); Map _$PollToJson(Poll instance) => { - 'id': instance.id, - 'name': instance.name, - 'description': instance.description, - 'options': instance.options.map((e) => e.toJson()).toList(), - 'voting_visibility': - _$VotingVisibilityEnumMap[instance.votingVisibility]!, - 'enforce_unique_vote': instance.enforceUniqueVote, - 'max_votes_allowed': instance.maxVotesAllowed, - 'allow_user_suggested_options': instance.allowUserSuggestedOptions, - 'allow_answers': instance.allowAnswers, - 'is_closed': instance.isClosed, - 'extra_data': instance.extraData, - }; + 'id': instance.id, + 'name': instance.name, + 'description': instance.description, + 'options': instance.options.map((e) => e.toJson()).toList(), + 'voting_visibility': _$VotingVisibilityEnumMap[instance.votingVisibility]!, + 'enforce_unique_vote': instance.enforceUniqueVote, + 'max_votes_allowed': instance.maxVotesAllowed, + 'allow_user_suggested_options': instance.allowUserSuggestedOptions, + 'allow_answers': instance.allowAnswers, + 'is_closed': instance.isClosed, + 'extra_data': instance.extraData, +}; const _$VotingVisibilityEnumMap = { VotingVisibility.anonymous: 'anonymous', diff --git a/packages/stream_chat/lib/src/core/models/poll_option.dart b/packages/stream_chat/lib/src/core/models/poll_option.dart index 9ef99d07e3..fb8448af05 100644 --- a/packages/stream_chat/lib/src/core/models/poll_option.dart +++ b/packages/stream_chat/lib/src/core/models/poll_option.dart @@ -23,10 +23,9 @@ class PollOption extends Equatable { }); /// Create a new instance from a json - factory PollOption.fromJson(Map json) => - _$PollOptionFromJson( - Serializer.moveToExtraDataFromRoot(json, topLevelFields), - ); + factory PollOption.fromJson(Map json) => _$PollOptionFromJson( + Serializer.moveToExtraDataFromRoot(json, topLevelFields), + ); /// The unique identifier of the poll option. @JsonKey(includeIfNull: false) @@ -39,20 +38,18 @@ class PollOption extends Equatable { final Map extraData; /// Serialize to json - Map toJson() => - Serializer.moveFromExtraDataToRoot(_$PollOptionToJson(this)); + Map toJson() => Serializer.moveFromExtraDataToRoot(_$PollOptionToJson(this)); /// Creates a copy of [PollOption] with specified attributes overridden. PollOption copyWith({ Object? id = _nullConst, String? text, Map? extraData, - }) => - PollOption( - id: id == _nullConst ? this.id : id as String?, - text: text ?? this.text, - extraData: extraData ?? this.extraData, - ); + }) => PollOption( + id: id == _nullConst ? this.id : id as String?, + text: text ?? this.text, + extraData: extraData ?? this.extraData, + ); /// Known top level fields. /// diff --git a/packages/stream_chat/lib/src/core/models/poll_option.g.dart b/packages/stream_chat/lib/src/core/models/poll_option.g.dart index b7db997113..1f8ba16aa2 100644 --- a/packages/stream_chat/lib/src/core/models/poll_option.g.dart +++ b/packages/stream_chat/lib/src/core/models/poll_option.g.dart @@ -7,14 +7,13 @@ part of 'poll_option.dart'; // ************************************************************************** PollOption _$PollOptionFromJson(Map json) => PollOption( - id: json['id'] as String?, - text: json['text'] as String, - extraData: json['extra_data'] as Map? ?? const {}, - ); + id: json['id'] as String?, + text: json['text'] as String, + extraData: json['extra_data'] as Map? ?? const {}, +); -Map _$PollOptionToJson(PollOption instance) => - { - if (instance.id case final value?) 'id': value, - 'text': instance.text, - 'extra_data': instance.extraData, - }; +Map _$PollOptionToJson(PollOption instance) => { + if (instance.id case final value?) 'id': value, + 'text': instance.text, + 'extra_data': instance.extraData, +}; diff --git a/packages/stream_chat/lib/src/core/models/poll_vote.dart b/packages/stream_chat/lib/src/core/models/poll_vote.dart index a48bb03d9e..3f979cc13c 100644 --- a/packages/stream_chat/lib/src/core/models/poll_vote.dart +++ b/packages/stream_chat/lib/src/core/models/poll_vote.dart @@ -20,17 +20,16 @@ class PollVote extends Equatable implements ComparableFieldProvider { DateTime? updatedAt, this.userId, this.user, - }) : assert( - optionId != null || answerText != null, - 'Either optionId or answerText must be provided', - ), - isAnswer = answerText != null, - createdAt = createdAt ?? DateTime.now(), - updatedAt = updatedAt ?? DateTime.now(); + }) : assert( + optionId != null || answerText != null, + 'Either optionId or answerText must be provided', + ), + isAnswer = answerText != null, + createdAt = createdAt ?? DateTime.now(), + updatedAt = updatedAt ?? DateTime.now(); /// Create a new instance from a json - factory PollVote.fromJson(Map json) => - _$PollVoteFromJson(json); + factory PollVote.fromJson(Map json) => _$PollVoteFromJson(json); /// The unique identifier of the poll vote. @JsonKey(includeIfNull: false) @@ -81,30 +80,29 @@ class PollVote extends Equatable implements ComparableFieldProvider { DateTime? updatedAt, String? userId, User? user, - }) => - PollVote( - id: id ?? this.id, - pollId: pollId ?? this.pollId, - optionId: optionId ?? this.optionId, - answerText: answerText ?? this.answerText, - createdAt: createdAt ?? this.createdAt, - updatedAt: updatedAt ?? this.updatedAt, - userId: userId ?? this.userId, - user: user ?? this.user, - ); + }) => PollVote( + id: id ?? this.id, + pollId: pollId ?? this.pollId, + optionId: optionId ?? this.optionId, + answerText: answerText ?? this.answerText, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + userId: userId ?? this.userId, + user: user ?? this.user, + ); @override List get props => [ - id, - pollId, - optionId, - isAnswer, - answerText, - createdAt, - updatedAt, - userId, - user, - ]; + id, + pollId, + optionId, + isAnswer, + answerText, + createdAt, + updatedAt, + userId, + user, + ]; @override ComparableField? getComparableField(String sortKey) { diff --git a/packages/stream_chat/lib/src/core/models/poll_vote.g.dart b/packages/stream_chat/lib/src/core/models/poll_vote.g.dart index 1ceb22c1d8..8eedbb3061 100644 --- a/packages/stream_chat/lib/src/core/models/poll_vote.g.dart +++ b/packages/stream_chat/lib/src/core/models/poll_vote.g.dart @@ -7,24 +7,18 @@ part of 'poll_vote.dart'; // ************************************************************************** PollVote _$PollVoteFromJson(Map json) => PollVote( - id: json['id'] as String?, - pollId: json['poll_id'] as String?, - optionId: json['option_id'] as String?, - answerText: json['answer_text'] as String?, - createdAt: json['created_at'] == null - ? null - : DateTime.parse(json['created_at'] as String), - updatedAt: json['updated_at'] == null - ? null - : DateTime.parse(json['updated_at'] as String), - userId: json['user_id'] as String?, - user: json['user'] == null - ? null - : User.fromJson(json['user'] as Map), - ); + id: json['id'] as String?, + pollId: json['poll_id'] as String?, + optionId: json['option_id'] as String?, + answerText: json['answer_text'] as String?, + createdAt: json['created_at'] == null ? null : DateTime.parse(json['created_at'] as String), + updatedAt: json['updated_at'] == null ? null : DateTime.parse(json['updated_at'] as String), + userId: json['user_id'] as String?, + user: json['user'] == null ? null : User.fromJson(json['user'] as Map), +); Map _$PollVoteToJson(PollVote instance) => { - if (instance.id case final value?) 'id': value, - if (instance.optionId case final value?) 'option_id': value, - if (instance.answerText case final value?) 'answer_text': value, - }; + if (instance.id case final value?) 'id': value, + if (instance.optionId case final value?) 'option_id': value, + if (instance.answerText case final value?) 'answer_text': value, +}; diff --git a/packages/stream_chat/lib/src/core/models/privacy_settings.g.dart b/packages/stream_chat/lib/src/core/models/privacy_settings.g.dart index e433675833..b888255ad6 100644 --- a/packages/stream_chat/lib/src/core/models/privacy_settings.g.dart +++ b/packages/stream_chat/lib/src/core/models/privacy_settings.g.dart @@ -6,57 +6,44 @@ part of 'privacy_settings.dart'; // JsonSerializableGenerator // ************************************************************************** -PrivacySettings _$PrivacySettingsFromJson(Map json) => - PrivacySettings( - typingIndicators: json['typing_indicators'] == null - ? null - : TypingIndicators.fromJson( - json['typing_indicators'] as Map), - readReceipts: json['read_receipts'] == null - ? null - : ReadReceipts.fromJson( - json['read_receipts'] as Map), - deliveryReceipts: json['delivery_receipts'] == null - ? null - : DeliveryReceipts.fromJson( - json['delivery_receipts'] as Map), - ); - -Map _$PrivacySettingsToJson(PrivacySettings instance) => - { - if (instance.typingIndicators?.toJson() case final value?) - 'typing_indicators': value, - if (instance.readReceipts?.toJson() case final value?) - 'read_receipts': value, - if (instance.deliveryReceipts?.toJson() case final value?) - 'delivery_receipts': value, - }; - -TypingIndicators _$TypingIndicatorsFromJson(Map json) => - TypingIndicators( - enabled: json['enabled'] as bool? ?? true, - ); - -Map _$TypingIndicatorsToJson(TypingIndicators instance) => - { - 'enabled': instance.enabled, - }; +PrivacySettings _$PrivacySettingsFromJson(Map json) => PrivacySettings( + typingIndicators: json['typing_indicators'] == null + ? null + : TypingIndicators.fromJson(json['typing_indicators'] as Map), + readReceipts: json['read_receipts'] == null + ? null + : ReadReceipts.fromJson(json['read_receipts'] as Map), + deliveryReceipts: json['delivery_receipts'] == null + ? null + : DeliveryReceipts.fromJson(json['delivery_receipts'] as Map), +); + +Map _$PrivacySettingsToJson(PrivacySettings instance) => { + if (instance.typingIndicators?.toJson() case final value?) 'typing_indicators': value, + if (instance.readReceipts?.toJson() case final value?) 'read_receipts': value, + if (instance.deliveryReceipts?.toJson() case final value?) 'delivery_receipts': value, +}; + +TypingIndicators _$TypingIndicatorsFromJson(Map json) => TypingIndicators( + enabled: json['enabled'] as bool? ?? true, +); + +Map _$TypingIndicatorsToJson(TypingIndicators instance) => { + 'enabled': instance.enabled, +}; ReadReceipts _$ReadReceiptsFromJson(Map json) => ReadReceipts( - enabled: json['enabled'] as bool? ?? true, - ); - -Map _$ReadReceiptsToJson(ReadReceipts instance) => - { - 'enabled': instance.enabled, - }; - -DeliveryReceipts _$DeliveryReceiptsFromJson(Map json) => - DeliveryReceipts( - enabled: json['enabled'] as bool? ?? true, - ); - -Map _$DeliveryReceiptsToJson(DeliveryReceipts instance) => - { - 'enabled': instance.enabled, - }; + enabled: json['enabled'] as bool? ?? true, +); + +Map _$ReadReceiptsToJson(ReadReceipts instance) => { + 'enabled': instance.enabled, +}; + +DeliveryReceipts _$DeliveryReceiptsFromJson(Map json) => DeliveryReceipts( + enabled: json['enabled'] as bool? ?? true, +); + +Map _$DeliveryReceiptsToJson(DeliveryReceipts instance) => { + 'enabled': instance.enabled, +}; diff --git a/packages/stream_chat/lib/src/core/models/push_preference.dart b/packages/stream_chat/lib/src/core/models/push_preference.dart index 00ba266a1d..a3de2f8b3f 100644 --- a/packages/stream_chat/lib/src/core/models/push_preference.dart +++ b/packages/stream_chat/lib/src/core/models/push_preference.dart @@ -82,8 +82,7 @@ class PushPreference extends Equatable { }); /// Create a new instance from a json - factory PushPreference.fromJson(Map json) => - _$PushPreferenceFromJson(json); + factory PushPreference.fromJson(Map json) => _$PushPreferenceFromJson(json); /// Push preference for calls final CallLevel? callLevel; @@ -111,8 +110,7 @@ class ChannelPushPreference extends Equatable { }); /// Create a new instance from a json - factory ChannelPushPreference.fromJson(Map json) => - _$ChannelPushPreferenceFromJson(json); + factory ChannelPushPreference.fromJson(Map json) => _$ChannelPushPreferenceFromJson(json); /// Push preference for chat messages final ChatLevel? chatLevel; diff --git a/packages/stream_chat/lib/src/core/models/push_preference.g.dart b/packages/stream_chat/lib/src/core/models/push_preference.g.dart index 971c0bd1f9..1cc2cc6cc1 100644 --- a/packages/stream_chat/lib/src/core/models/push_preference.g.dart +++ b/packages/stream_chat/lib/src/core/models/push_preference.g.dart @@ -6,47 +6,32 @@ part of 'push_preference.dart'; // JsonSerializableGenerator // ************************************************************************** -Map _$PushPreferenceInputToJson( - PushPreferenceInput instance) => - { - if (instance.channelCid case final value?) 'channel_cid': value, - if (instance.callLevel case final value?) 'call_level': value, - if (instance.chatLevel case final value?) 'chat_level': value, - if (instance.disabledUntil?.toIso8601String() case final value?) - 'disabled_until': value, - if (instance.removeDisable case final value?) 'remove_disable': value, - }; +Map _$PushPreferenceInputToJson(PushPreferenceInput instance) => { + if (instance.channelCid case final value?) 'channel_cid': value, + if (instance.callLevel case final value?) 'call_level': value, + if (instance.chatLevel case final value?) 'chat_level': value, + if (instance.disabledUntil?.toIso8601String() case final value?) 'disabled_until': value, + if (instance.removeDisable case final value?) 'remove_disable': value, +}; -PushPreference _$PushPreferenceFromJson(Map json) => - PushPreference( - callLevel: json['call_level'] as CallLevel?, - chatLevel: json['chat_level'] as ChatLevel?, - disabledUntil: json['disabled_until'] == null - ? null - : DateTime.parse(json['disabled_until'] as String), - ); +PushPreference _$PushPreferenceFromJson(Map json) => PushPreference( + callLevel: json['call_level'] as CallLevel?, + chatLevel: json['chat_level'] as ChatLevel?, + disabledUntil: json['disabled_until'] == null ? null : DateTime.parse(json['disabled_until'] as String), +); -Map _$PushPreferenceToJson(PushPreference instance) => - { - if (instance.callLevel case final value?) 'call_level': value, - if (instance.chatLevel case final value?) 'chat_level': value, - if (instance.disabledUntil?.toIso8601String() case final value?) - 'disabled_until': value, - }; +Map _$PushPreferenceToJson(PushPreference instance) => { + if (instance.callLevel case final value?) 'call_level': value, + if (instance.chatLevel case final value?) 'chat_level': value, + if (instance.disabledUntil?.toIso8601String() case final value?) 'disabled_until': value, +}; -ChannelPushPreference _$ChannelPushPreferenceFromJson( - Map json) => - ChannelPushPreference( - chatLevel: json['chat_level'] as ChatLevel?, - disabledUntil: json['disabled_until'] == null - ? null - : DateTime.parse(json['disabled_until'] as String), - ); +ChannelPushPreference _$ChannelPushPreferenceFromJson(Map json) => ChannelPushPreference( + chatLevel: json['chat_level'] as ChatLevel?, + disabledUntil: json['disabled_until'] == null ? null : DateTime.parse(json['disabled_until'] as String), +); -Map _$ChannelPushPreferenceToJson( - ChannelPushPreference instance) => - { - if (instance.chatLevel case final value?) 'chat_level': value, - if (instance.disabledUntil?.toIso8601String() case final value?) - 'disabled_until': value, - }; +Map _$ChannelPushPreferenceToJson(ChannelPushPreference instance) => { + if (instance.chatLevel case final value?) 'chat_level': value, + if (instance.disabledUntil?.toIso8601String() case final value?) 'disabled_until': value, +}; diff --git a/packages/stream_chat/lib/src/core/models/reaction.dart b/packages/stream_chat/lib/src/core/models/reaction.dart index dc62fd7ce7..c60c7b85b8 100644 --- a/packages/stream_chat/lib/src/core/models/reaction.dart +++ b/packages/stream_chat/lib/src/core/models/reaction.dart @@ -1,5 +1,6 @@ import 'package:equatable/equatable.dart'; import 'package:json_annotation/json_annotation.dart'; +import 'package:stream_chat/src/core/models/comparable_field.dart'; import 'package:stream_chat/src/core/models/user.dart'; import 'package:stream_chat/src/core/util/serializer.dart'; @@ -7,7 +8,7 @@ part 'reaction.g.dart'; /// The class that defines a reaction @JsonSerializable() -class Reaction extends Equatable { +class Reaction extends Equatable implements ComparableFieldProvider { /// Constructor used for json serialization Reaction({ this.messageId, @@ -19,16 +20,17 @@ class Reaction extends Equatable { DateTime? createdAt, DateTime? updatedAt, this.extraData = const {}, - }) : userId = userId ?? user?.id, - createdAt = createdAt ?? DateTime.timestamp(), - updatedAt = updatedAt ?? DateTime.timestamp(); + }) : userId = userId ?? user?.id, + createdAt = createdAt ?? DateTime.timestamp(), + updatedAt = updatedAt ?? DateTime.timestamp(); /// Create a new instance from a json - factory Reaction.fromJson(Map json) => - _$ReactionFromJson(Serializer.moveToExtraDataFromRoot( - json, - topLevelFields, - )); + factory Reaction.fromJson(Map json) => _$ReactionFromJson( + Serializer.moveToExtraDataFromRoot( + json, + topLevelFields, + ), + ); /// The messageId to which the reaction belongs @JsonKey(includeToJson: false) @@ -77,8 +79,8 @@ class Reaction extends Equatable { /// Serialize to json Map toJson() => Serializer.moveFromExtraDataToRoot( - _$ReactionToJson(this), - ); + _$ReactionToJson(this), + ); /// Creates a copy of [Reaction] with specified attributes overridden. Reaction copyWith({ @@ -91,43 +93,63 @@ class Reaction extends Equatable { DateTime? createdAt, DateTime? updatedAt, Map? extraData, - }) => - Reaction( - messageId: messageId ?? this.messageId, - type: type ?? this.type, - user: user ?? this.user, - userId: userId ?? this.userId, - score: score ?? this.score, - emojiCode: emojiCode ?? this.emojiCode, - createdAt: createdAt ?? this.createdAt, - updatedAt: updatedAt ?? this.updatedAt, - extraData: extraData ?? this.extraData, - ); + }) => Reaction( + messageId: messageId ?? this.messageId, + type: type ?? this.type, + user: user ?? this.user, + userId: userId ?? this.userId, + score: score ?? this.score, + emojiCode: emojiCode ?? this.emojiCode, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + extraData: extraData ?? this.extraData, + ); /// Returns a new [Reaction] that is a combination of this reaction and the /// given [other] reaction. Reaction merge(Reaction other) => copyWith( - messageId: other.messageId, - type: other.type, - user: other.user, - userId: other.userId, - score: other.score, - emojiCode: other.emojiCode, - createdAt: other.createdAt, - updatedAt: other.updatedAt, - extraData: other.extraData, - ); + messageId: other.messageId, + type: other.type, + user: other.user, + userId: other.userId, + score: other.score, + emojiCode: other.emojiCode, + createdAt: other.createdAt, + updatedAt: other.updatedAt, + extraData: other.extraData, + ); @override List get props => [ - messageId, - type, - user, - userId, - score, - emojiCode, - createdAt, - updatedAt, - extraData, - ]; + messageId, + type, + user, + userId, + score, + emojiCode, + createdAt, + updatedAt, + extraData, + ]; + + @override + ComparableField? getComparableField(String sortKey) { + final value = switch (sortKey) { + ReactionSortKey.createdAt => createdAt, + _ => null, + }; + + return ComparableField.fromValue(value); + } +} + +/// Extension type representing sortable fields for [Reaction]. +/// +/// This type provides type-safe keys that can be used for sorting reactions +/// in queries. Each constant represents a field that can be sorted on. +extension type const ReactionSortKey(String key) implements String { + /// Sort reactions by their creation date. + /// + /// This is the default sort field (in ascending order). + static const createdAt = ReactionSortKey('created_at'); } diff --git a/packages/stream_chat/lib/src/core/models/reaction.g.dart b/packages/stream_chat/lib/src/core/models/reaction.g.dart index f29c4e0931..56436be596 100644 --- a/packages/stream_chat/lib/src/core/models/reaction.g.dart +++ b/packages/stream_chat/lib/src/core/models/reaction.g.dart @@ -7,26 +7,20 @@ part of 'reaction.dart'; // ************************************************************************** Reaction _$ReactionFromJson(Map json) => Reaction( - messageId: json['message_id'] as String?, - type: json['type'] as String, - user: json['user'] == null - ? null - : User.fromJson(json['user'] as Map), - userId: json['user_id'] as String?, - score: (json['score'] as num?)?.toInt() ?? 1, - emojiCode: json['emoji_code'] as String?, - createdAt: json['created_at'] == null - ? null - : DateTime.parse(json['created_at'] as String), - updatedAt: json['updated_at'] == null - ? null - : DateTime.parse(json['updated_at'] as String), - extraData: json['extra_data'] as Map? ?? const {}, - ); + messageId: json['message_id'] as String?, + type: json['type'] as String, + user: json['user'] == null ? null : User.fromJson(json['user'] as Map), + userId: json['user_id'] as String?, + score: (json['score'] as num?)?.toInt() ?? 1, + emojiCode: json['emoji_code'] as String?, + createdAt: json['created_at'] == null ? null : DateTime.parse(json['created_at'] as String), + updatedAt: json['updated_at'] == null ? null : DateTime.parse(json['updated_at'] as String), + extraData: json['extra_data'] as Map? ?? const {}, +); Map _$ReactionToJson(Reaction instance) => { - 'type': instance.type, - 'score': instance.score, - if (instance.emojiCode case final value?) 'emoji_code': value, - 'extra_data': instance.extraData, - }; + 'type': instance.type, + 'score': instance.score, + if (instance.emojiCode case final value?) 'emoji_code': value, + 'extra_data': instance.extraData, +}; diff --git a/packages/stream_chat/lib/src/core/models/reaction_group.dart b/packages/stream_chat/lib/src/core/models/reaction_group.dart index 480156de42..9be6a5150b 100644 --- a/packages/stream_chat/lib/src/core/models/reaction_group.dart +++ b/packages/stream_chat/lib/src/core/models/reaction_group.dart @@ -12,12 +12,11 @@ class ReactionGroup extends Equatable { this.sumScores = 0, DateTime? firstReactionAt, DateTime? lastReactionAt, - }) : firstReactionAt = firstReactionAt ?? DateTime.timestamp(), - lastReactionAt = lastReactionAt ?? DateTime.timestamp(); + }) : firstReactionAt = firstReactionAt ?? DateTime.timestamp(), + lastReactionAt = lastReactionAt ?? DateTime.timestamp(); /// Create a new instance from a json - factory ReactionGroup.fromJson(Map json) => - _$ReactionGroupFromJson(json); + factory ReactionGroup.fromJson(Map json) => _$ReactionGroupFromJson(json); /// The number of users that reacted with this reaction. final int count; @@ -51,11 +50,11 @@ class ReactionGroup extends Equatable { @override List get props => [ - count, - sumScores, - firstReactionAt, - lastReactionAt, - ]; + count, + sumScores, + firstReactionAt, + lastReactionAt, + ]; } /// A group of comparators for sorting [ReactionGroup]s. diff --git a/packages/stream_chat/lib/src/core/models/reaction_group.g.dart b/packages/stream_chat/lib/src/core/models/reaction_group.g.dart index 65e1ceedd7..9c686e7f0a 100644 --- a/packages/stream_chat/lib/src/core/models/reaction_group.g.dart +++ b/packages/stream_chat/lib/src/core/models/reaction_group.g.dart @@ -6,22 +6,16 @@ part of 'reaction_group.dart'; // JsonSerializableGenerator // ************************************************************************** -ReactionGroup _$ReactionGroupFromJson(Map json) => - ReactionGroup( - count: (json['count'] as num?)?.toInt() ?? 0, - sumScores: (json['sum_scores'] as num?)?.toInt() ?? 0, - firstReactionAt: json['first_reaction_at'] == null - ? null - : DateTime.parse(json['first_reaction_at'] as String), - lastReactionAt: json['last_reaction_at'] == null - ? null - : DateTime.parse(json['last_reaction_at'] as String), - ); +ReactionGroup _$ReactionGroupFromJson(Map json) => ReactionGroup( + count: (json['count'] as num?)?.toInt() ?? 0, + sumScores: (json['sum_scores'] as num?)?.toInt() ?? 0, + firstReactionAt: json['first_reaction_at'] == null ? null : DateTime.parse(json['first_reaction_at'] as String), + lastReactionAt: json['last_reaction_at'] == null ? null : DateTime.parse(json['last_reaction_at'] as String), +); -Map _$ReactionGroupToJson(ReactionGroup instance) => - { - 'count': instance.count, - 'sum_scores': instance.sumScores, - 'first_reaction_at': instance.firstReactionAt.toIso8601String(), - 'last_reaction_at': instance.lastReactionAt.toIso8601String(), - }; +Map _$ReactionGroupToJson(ReactionGroup instance) => { + 'count': instance.count, + 'sum_scores': instance.sumScores, + 'first_reaction_at': instance.firstReactionAt.toIso8601String(), + 'last_reaction_at': instance.lastReactionAt.toIso8601String(), +}; diff --git a/packages/stream_chat/lib/src/core/models/read.dart b/packages/stream_chat/lib/src/core/models/read.dart index 165528946f..5952ad4b5c 100644 --- a/packages/stream_chat/lib/src/core/models/read.dart +++ b/packages/stream_chat/lib/src/core/models/read.dart @@ -58,8 +58,7 @@ class Read extends Equatable { user: user ?? this.user, unreadMessages: unreadMessages ?? this.unreadMessages, lastDeliveredAt: lastDeliveredAt ?? this.lastDeliveredAt, - lastDeliveredMessageId: - lastDeliveredMessageId ?? this.lastDeliveredMessageId, + lastDeliveredMessageId: lastDeliveredMessageId ?? this.lastDeliveredMessageId, ); } @@ -79,13 +78,13 @@ class Read extends Equatable { @override List get props => [ - lastRead, - lastReadMessageId, - user, - unreadMessages, - lastDeliveredAt, - lastDeliveredMessageId, - ]; + lastRead, + lastReadMessageId, + user, + unreadMessages, + lastDeliveredAt, + lastDeliveredMessageId, + ]; } /// Helper extension methods for [Iterable]<[Read]>. diff --git a/packages/stream_chat/lib/src/core/models/read.g.dart b/packages/stream_chat/lib/src/core/models/read.g.dart index 3e9d03fb0e..9f4358fcf5 100644 --- a/packages/stream_chat/lib/src/core/models/read.g.dart +++ b/packages/stream_chat/lib/src/core/models/read.g.dart @@ -7,21 +7,19 @@ part of 'read.dart'; // ************************************************************************** Read _$ReadFromJson(Map json) => Read( - lastRead: DateTime.parse(json['last_read'] as String), - user: User.fromJson(json['user'] as Map), - lastReadMessageId: json['last_read_message_id'] as String?, - unreadMessages: (json['unread_messages'] as num?)?.toInt(), - lastDeliveredAt: json['last_delivered_at'] == null - ? null - : DateTime.parse(json['last_delivered_at'] as String), - lastDeliveredMessageId: json['last_delivered_message_id'] as String?, - ); + lastRead: DateTime.parse(json['last_read'] as String), + user: User.fromJson(json['user'] as Map), + lastReadMessageId: json['last_read_message_id'] as String?, + unreadMessages: (json['unread_messages'] as num?)?.toInt(), + lastDeliveredAt: json['last_delivered_at'] == null ? null : DateTime.parse(json['last_delivered_at'] as String), + lastDeliveredMessageId: json['last_delivered_message_id'] as String?, +); Map _$ReadToJson(Read instance) => { - 'last_read': instance.lastRead.toIso8601String(), - 'user': instance.user.toJson(), - 'unread_messages': instance.unreadMessages, - 'last_read_message_id': instance.lastReadMessageId, - 'last_delivered_at': instance.lastDeliveredAt?.toIso8601String(), - 'last_delivered_message_id': instance.lastDeliveredMessageId, - }; + 'last_read': instance.lastRead.toIso8601String(), + 'user': instance.user.toJson(), + 'unread_messages': instance.unreadMessages, + 'last_read_message_id': instance.lastReadMessageId, + 'last_delivered_at': instance.lastDeliveredAt?.toIso8601String(), + 'last_delivered_message_id': instance.lastDeliveredMessageId, +}; diff --git a/packages/stream_chat/lib/src/core/models/thread.dart b/packages/stream_chat/lib/src/core/models/thread.dart index 3a26e15230..4074ca0158 100644 --- a/packages/stream_chat/lib/src/core/models/thread.dart +++ b/packages/stream_chat/lib/src/core/models/thread.dart @@ -44,12 +44,12 @@ class Thread extends Equatable implements ComparableFieldProvider { this.read = const [], this.draft, this.extraData = const {}, - }) : createdAt = createdAt ?? DateTime.now(), - updatedAt = updatedAt ?? DateTime.now(); + }) : createdAt = createdAt ?? DateTime.now(), + updatedAt = updatedAt ?? DateTime.now(); /// Create a new instance from a json - factory Thread.fromJson(Map json) => _$ThreadFromJson( - Serializer.moveToExtraDataFromRoot(json, topLevelFields)); + factory Thread.fromJson(Map json) => + _$ThreadFromJson(Serializer.moveToExtraDataFromRoot(json, topLevelFields)); /// The active participant count in the thread. final int? activeParticipantCount; @@ -109,8 +109,7 @@ class Thread extends Equatable implements ComparableFieldProvider { final Map extraData; /// Serialize to json - Map toJson() => - Serializer.moveFromExtraDataToRoot(_$ThreadToJson(this)); + Map toJson() => Serializer.moveFromExtraDataToRoot(_$ThreadToJson(this)); /// Creates a copy of [Thread] with specified attributes overridden. Thread copyWith({ @@ -133,29 +132,27 @@ class Thread extends Equatable implements ComparableFieldProvider { List? read, Object? draft = _nullConst, Map? extraData, - }) => - Thread( - activeParticipantCount: - activeParticipantCount ?? this.activeParticipantCount, - channel: channel ?? this.channel, - channelCid: channelCid ?? this.channelCid, - createdAt: createdAt ?? this.createdAt, - updatedAt: updatedAt ?? this.updatedAt, - deletedAt: deletedAt ?? this.deletedAt, - createdByUserId: createdByUserId ?? this.createdByUserId, - createdBy: createdBy ?? this.createdBy, - title: title ?? this.title, - parentMessageId: parentMessageId ?? this.parentMessageId, - parentMessage: parentMessage ?? this.parentMessage, - replyCount: replyCount ?? this.replyCount, - participantCount: participantCount ?? this.participantCount, - threadParticipants: threadParticipants ?? this.threadParticipants, - lastMessageAt: lastMessageAt ?? this.lastMessageAt, - latestReplies: latestReplies ?? this.latestReplies, - read: read ?? this.read, - draft: draft == _nullConst ? this.draft : draft as Draft?, - extraData: extraData ?? this.extraData, - ); + }) => Thread( + activeParticipantCount: activeParticipantCount ?? this.activeParticipantCount, + channel: channel ?? this.channel, + channelCid: channelCid ?? this.channelCid, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedAt: deletedAt ?? this.deletedAt, + createdByUserId: createdByUserId ?? this.createdByUserId, + createdBy: createdBy ?? this.createdBy, + title: title ?? this.title, + parentMessageId: parentMessageId ?? this.parentMessageId, + parentMessage: parentMessage ?? this.parentMessage, + replyCount: replyCount ?? this.replyCount, + participantCount: participantCount ?? this.participantCount, + threadParticipants: threadParticipants ?? this.threadParticipants, + lastMessageAt: lastMessageAt ?? this.lastMessageAt, + latestReplies: latestReplies ?? this.latestReplies, + read: read ?? this.read, + draft: draft == _nullConst ? this.draft : draft as Draft?, + extraData: extraData ?? this.extraData, + ); /// Merge this thread with the [other] thread. Thread merge(Thread? other) { @@ -209,25 +206,25 @@ class Thread extends Equatable implements ComparableFieldProvider { @override List get props => [ - activeParticipantCount, - channelCid, - channel, - createdAt, - updatedAt, - deletedAt, - createdByUserId, - createdBy, - title, - parentMessageId, - parentMessage, - replyCount, - participantCount, - threadParticipants, - lastMessageAt, - latestReplies, - read, - draft, - ]; + activeParticipantCount, + channelCid, + channel, + createdAt, + updatedAt, + deletedAt, + createdByUserId, + createdBy, + title, + parentMessageId, + parentMessage, + replyCount, + participantCount, + threadParticipants, + lastMessageAt, + latestReplies, + read, + draft, + ]; @override ComparableField? getComparableField(String sortKey) { @@ -269,8 +266,7 @@ extension type const ThreadSortKey(String key) implements String { static const participantCount = ThreadSortKey('participant_count'); /// Sort threads by their active participant count. - static const activeParticipantCount = - ThreadSortKey('active_participant_count'); + static const activeParticipantCount = ThreadSortKey('active_participant_count'); /// Sort threads by their parent message id. static const parentMessageId = ThreadSortKey('parent_message_id'); diff --git a/packages/stream_chat/lib/src/core/models/thread.g.dart b/packages/stream_chat/lib/src/core/models/thread.g.dart index ed5b27b28b..7d5d46cd30 100644 --- a/packages/stream_chat/lib/src/core/models/thread.g.dart +++ b/packages/stream_chat/lib/src/core/models/thread.g.dart @@ -7,73 +7,53 @@ part of 'thread.dart'; // ************************************************************************** Thread _$ThreadFromJson(Map json) => Thread( - activeParticipantCount: - (json['active_participant_count'] as num?)?.toInt(), - channel: json['channel'] == null - ? null - : ChannelModel.fromJson(json['channel'] as Map), - channelCid: json['channel_cid'] as String, - parentMessageId: json['parent_message_id'] as String, - parentMessage: json['parent_message'] == null - ? null - : Message.fromJson(json['parent_message'] as Map), - createdByUserId: json['created_by_user_id'] as String, - createdBy: json['created_by'] == null - ? null - : User.fromJson(json['created_by'] as Map), - replyCount: (json['reply_count'] as num).toInt(), - participantCount: (json['participant_count'] as num).toInt(), - threadParticipants: (json['thread_participants'] as List?) - ?.map( - (e) => ThreadParticipant.fromJson(e as Map)) - .toList() ?? - const [], - lastMessageAt: json['last_message_at'] == null - ? null - : DateTime.parse(json['last_message_at'] as String), - createdAt: json['created_at'] == null - ? null - : DateTime.parse(json['created_at'] as String), - updatedAt: json['updated_at'] == null - ? null - : DateTime.parse(json['updated_at'] as String), - deletedAt: json['deleted_at'] == null - ? null - : DateTime.parse(json['deleted_at'] as String), - title: json['title'] as String?, - latestReplies: (json['latest_replies'] as List?) - ?.map((e) => Message.fromJson(e as Map)) - .toList() ?? - const [], - read: (json['read'] as List?) - ?.map((e) => Read.fromJson(e as Map)) - .toList() ?? - const [], - draft: json['draft'] == null - ? null - : Draft.fromJson(json['draft'] as Map), - extraData: json['extra_data'] as Map? ?? const {}, - ); + activeParticipantCount: (json['active_participant_count'] as num?)?.toInt(), + channel: json['channel'] == null ? null : ChannelModel.fromJson(json['channel'] as Map), + channelCid: json['channel_cid'] as String, + parentMessageId: json['parent_message_id'] as String, + parentMessage: json['parent_message'] == null + ? null + : Message.fromJson(json['parent_message'] as Map), + createdByUserId: json['created_by_user_id'] as String, + createdBy: json['created_by'] == null ? null : User.fromJson(json['created_by'] as Map), + replyCount: (json['reply_count'] as num).toInt(), + participantCount: (json['participant_count'] as num).toInt(), + threadParticipants: + (json['thread_participants'] as List?) + ?.map((e) => ThreadParticipant.fromJson(e as Map)) + .toList() ?? + const [], + lastMessageAt: json['last_message_at'] == null ? null : DateTime.parse(json['last_message_at'] as String), + createdAt: json['created_at'] == null ? null : DateTime.parse(json['created_at'] as String), + updatedAt: json['updated_at'] == null ? null : DateTime.parse(json['updated_at'] as String), + deletedAt: json['deleted_at'] == null ? null : DateTime.parse(json['deleted_at'] as String), + title: json['title'] as String?, + latestReplies: + (json['latest_replies'] as List?)?.map((e) => Message.fromJson(e as Map)).toList() ?? + const [], + read: (json['read'] as List?)?.map((e) => Read.fromJson(e as Map)).toList() ?? const [], + draft: json['draft'] == null ? null : Draft.fromJson(json['draft'] as Map), + extraData: json['extra_data'] as Map? ?? const {}, +); Map _$ThreadToJson(Thread instance) => { - 'active_participant_count': instance.activeParticipantCount, - 'channel_cid': instance.channelCid, - 'channel': instance.channel?.toJson(), - 'created_at': instance.createdAt.toIso8601String(), - 'updated_at': instance.updatedAt.toIso8601String(), - 'deleted_at': instance.deletedAt?.toIso8601String(), - 'created_by_user_id': instance.createdByUserId, - 'created_by': instance.createdBy?.toJson(), - 'title': instance.title, - 'parent_message_id': instance.parentMessageId, - 'parent_message': instance.parentMessage?.toJson(), - 'reply_count': instance.replyCount, - 'participant_count': instance.participantCount, - 'thread_participants': - instance.threadParticipants.map((e) => e.toJson()).toList(), - 'last_message_at': instance.lastMessageAt?.toIso8601String(), - 'latest_replies': instance.latestReplies.map((e) => e.toJson()).toList(), - 'read': instance.read?.map((e) => e.toJson()).toList(), - 'draft': instance.draft?.toJson(), - 'extra_data': instance.extraData, - }; + 'active_participant_count': instance.activeParticipantCount, + 'channel_cid': instance.channelCid, + 'channel': instance.channel?.toJson(), + 'created_at': instance.createdAt.toIso8601String(), + 'updated_at': instance.updatedAt.toIso8601String(), + 'deleted_at': instance.deletedAt?.toIso8601String(), + 'created_by_user_id': instance.createdByUserId, + 'created_by': instance.createdBy?.toJson(), + 'title': instance.title, + 'parent_message_id': instance.parentMessageId, + 'parent_message': instance.parentMessage?.toJson(), + 'reply_count': instance.replyCount, + 'participant_count': instance.participantCount, + 'thread_participants': instance.threadParticipants.map((e) => e.toJson()).toList(), + 'last_message_at': instance.lastMessageAt?.toIso8601String(), + 'latest_replies': instance.latestReplies.map((e) => e.toJson()).toList(), + 'read': instance.read?.map((e) => e.toJson()).toList(), + 'draft': instance.draft?.toJson(), + 'extra_data': instance.extraData, +}; diff --git a/packages/stream_chat/lib/src/core/models/thread_participant.dart b/packages/stream_chat/lib/src/core/models/thread_participant.dart index fece374619..96d5a7276c 100644 --- a/packages/stream_chat/lib/src/core/models/thread_participant.dart +++ b/packages/stream_chat/lib/src/core/models/thread_participant.dart @@ -22,8 +22,7 @@ class ThreadParticipant extends Equatable { }); /// Create a new instance from a json - factory ThreadParticipant.fromJson(Map json) => - _$ThreadParticipantFromJson(json); + factory ThreadParticipant.fromJson(Map json) => _$ThreadParticipantFromJson(json); /// The channel cid this thread participant belongs to. final String channelCid; @@ -63,27 +62,26 @@ class ThreadParticipant extends Equatable { String? threadId, String? userId, User? user, - }) => - ThreadParticipant( - channelCid: channelCid ?? this.channelCid, - createdAt: createdAt ?? this.createdAt, - lastReadAt: lastReadAt ?? this.lastReadAt, - lastThreadMessageAt: lastThreadMessageAt ?? this.lastThreadMessageAt, - leftThreadAt: leftThreadAt ?? this.leftThreadAt, - threadId: threadId ?? this.threadId, - userId: userId ?? this.userId, - user: user ?? this.user, - ); + }) => ThreadParticipant( + channelCid: channelCid ?? this.channelCid, + createdAt: createdAt ?? this.createdAt, + lastReadAt: lastReadAt ?? this.lastReadAt, + lastThreadMessageAt: lastThreadMessageAt ?? this.lastThreadMessageAt, + leftThreadAt: leftThreadAt ?? this.leftThreadAt, + threadId: threadId ?? this.threadId, + userId: userId ?? this.userId, + user: user ?? this.user, + ); @override List get props => [ - channelCid, - createdAt, - lastReadAt, - lastThreadMessageAt, - leftThreadAt, - threadId, - userId, - user, - ]; + channelCid, + createdAt, + lastReadAt, + lastThreadMessageAt, + leftThreadAt, + threadId, + userId, + user, + ]; } diff --git a/packages/stream_chat/lib/src/core/models/thread_participant.g.dart b/packages/stream_chat/lib/src/core/models/thread_participant.g.dart index 4a410c404b..ec1d860052 100644 --- a/packages/stream_chat/lib/src/core/models/thread_participant.g.dart +++ b/packages/stream_chat/lib/src/core/models/thread_participant.g.dart @@ -6,32 +6,26 @@ part of 'thread_participant.dart'; // JsonSerializableGenerator // ************************************************************************** -ThreadParticipant _$ThreadParticipantFromJson(Map json) => - ThreadParticipant( - channelCid: json['channel_cid'] as String, - createdAt: DateTime.parse(json['created_at'] as String), - lastReadAt: DateTime.parse(json['last_read_at'] as String), - lastThreadMessageAt: json['last_thread_message_at'] == null - ? null - : DateTime.parse(json['last_thread_message_at'] as String), - leftThreadAt: json['left_thread_at'] == null - ? null - : DateTime.parse(json['left_thread_at'] as String), - threadId: json['thread_id'] as String?, - userId: json['user_id'] as String?, - user: json['user'] == null - ? null - : User.fromJson(json['user'] as Map), - ); +ThreadParticipant _$ThreadParticipantFromJson(Map json) => ThreadParticipant( + channelCid: json['channel_cid'] as String, + createdAt: DateTime.parse(json['created_at'] as String), + lastReadAt: DateTime.parse(json['last_read_at'] as String), + lastThreadMessageAt: json['last_thread_message_at'] == null + ? null + : DateTime.parse(json['last_thread_message_at'] as String), + leftThreadAt: json['left_thread_at'] == null ? null : DateTime.parse(json['left_thread_at'] as String), + threadId: json['thread_id'] as String?, + userId: json['user_id'] as String?, + user: json['user'] == null ? null : User.fromJson(json['user'] as Map), +); -Map _$ThreadParticipantToJson(ThreadParticipant instance) => - { - 'channel_cid': instance.channelCid, - 'created_at': instance.createdAt.toIso8601String(), - 'last_read_at': instance.lastReadAt.toIso8601String(), - 'last_thread_message_at': instance.lastThreadMessageAt?.toIso8601String(), - 'left_thread_at': instance.leftThreadAt?.toIso8601String(), - 'thread_id': instance.threadId, - 'user_id': instance.userId, - 'user': instance.user?.toJson(), - }; +Map _$ThreadParticipantToJson(ThreadParticipant instance) => { + 'channel_cid': instance.channelCid, + 'created_at': instance.createdAt.toIso8601String(), + 'last_read_at': instance.lastReadAt.toIso8601String(), + 'last_thread_message_at': instance.lastThreadMessageAt?.toIso8601String(), + 'left_thread_at': instance.leftThreadAt?.toIso8601String(), + 'thread_id': instance.threadId, + 'user_id': instance.userId, + 'user': instance.user?.toJson(), +}; diff --git a/packages/stream_chat/lib/src/core/models/unread_counts.dart b/packages/stream_chat/lib/src/core/models/unread_counts.dart index d3e265fa76..91a0b7dfe5 100644 --- a/packages/stream_chat/lib/src/core/models/unread_counts.dart +++ b/packages/stream_chat/lib/src/core/models/unread_counts.dart @@ -15,8 +15,7 @@ class UnreadCountsChannel { }); /// Create a new instance from a json. - factory UnreadCountsChannel.fromJson(Map json) => - _$UnreadCountsChannelFromJson(json); + factory UnreadCountsChannel.fromJson(Map json) => _$UnreadCountsChannelFromJson(json); /// The unique identifier of the channel (format: "type:id"). final String channelId; @@ -45,8 +44,7 @@ class UnreadCountsThread { }); /// Create a new instance from a json. - factory UnreadCountsThread.fromJson(Map json) => - _$UnreadCountsThreadFromJson(json); + factory UnreadCountsThread.fromJson(Map json) => _$UnreadCountsThreadFromJson(json); /// Number of unread messages in this thread. final int unreadCount; @@ -78,8 +76,7 @@ class UnreadCountsChannelType { }); /// Create a new instance from a json. - factory UnreadCountsChannelType.fromJson(Map json) => - _$UnreadCountsChannelTypeFromJson(json); + factory UnreadCountsChannelType.fromJson(Map json) => _$UnreadCountsChannelTypeFromJson(json); /// The type of channel (e.g., "messaging", "livestream", "team"). final String channelType; diff --git a/packages/stream_chat/lib/src/core/models/unread_counts.g.dart b/packages/stream_chat/lib/src/core/models/unread_counts.g.dart index bd149b5a75..95ff1053b9 100644 --- a/packages/stream_chat/lib/src/core/models/unread_counts.g.dart +++ b/packages/stream_chat/lib/src/core/models/unread_counts.g.dart @@ -6,49 +6,40 @@ part of 'unread_counts.dart'; // JsonSerializableGenerator // ************************************************************************** -UnreadCountsChannel _$UnreadCountsChannelFromJson(Map json) => - UnreadCountsChannel( - channelId: json['channel_id'] as String, - unreadCount: (json['unread_count'] as num).toInt(), - lastRead: DateTime.parse(json['last_read'] as String), - ); - -Map _$UnreadCountsChannelToJson( - UnreadCountsChannel instance) => - { - 'channel_id': instance.channelId, - 'unread_count': instance.unreadCount, - 'last_read': instance.lastRead.toIso8601String(), - }; - -UnreadCountsThread _$UnreadCountsThreadFromJson(Map json) => - UnreadCountsThread( - unreadCount: (json['unread_count'] as num).toInt(), - lastRead: DateTime.parse(json['last_read'] as String), - lastReadMessageId: json['last_read_message_id'] as String, - parentMessageId: json['parent_message_id'] as String, - ); - -Map _$UnreadCountsThreadToJson(UnreadCountsThread instance) => - { - 'unread_count': instance.unreadCount, - 'last_read': instance.lastRead.toIso8601String(), - 'last_read_message_id': instance.lastReadMessageId, - 'parent_message_id': instance.parentMessageId, - }; - -UnreadCountsChannelType _$UnreadCountsChannelTypeFromJson( - Map json) => - UnreadCountsChannelType( - channelType: json['channel_type'] as String, - channelCount: (json['channel_count'] as num).toInt(), - unreadCount: (json['unread_count'] as num).toInt(), - ); - -Map _$UnreadCountsChannelTypeToJson( - UnreadCountsChannelType instance) => - { - 'channel_type': instance.channelType, - 'channel_count': instance.channelCount, - 'unread_count': instance.unreadCount, - }; +UnreadCountsChannel _$UnreadCountsChannelFromJson(Map json) => UnreadCountsChannel( + channelId: json['channel_id'] as String, + unreadCount: (json['unread_count'] as num).toInt(), + lastRead: DateTime.parse(json['last_read'] as String), +); + +Map _$UnreadCountsChannelToJson(UnreadCountsChannel instance) => { + 'channel_id': instance.channelId, + 'unread_count': instance.unreadCount, + 'last_read': instance.lastRead.toIso8601String(), +}; + +UnreadCountsThread _$UnreadCountsThreadFromJson(Map json) => UnreadCountsThread( + unreadCount: (json['unread_count'] as num).toInt(), + lastRead: DateTime.parse(json['last_read'] as String), + lastReadMessageId: json['last_read_message_id'] as String, + parentMessageId: json['parent_message_id'] as String, +); + +Map _$UnreadCountsThreadToJson(UnreadCountsThread instance) => { + 'unread_count': instance.unreadCount, + 'last_read': instance.lastRead.toIso8601String(), + 'last_read_message_id': instance.lastReadMessageId, + 'parent_message_id': instance.parentMessageId, +}; + +UnreadCountsChannelType _$UnreadCountsChannelTypeFromJson(Map json) => UnreadCountsChannelType( + channelType: json['channel_type'] as String, + channelCount: (json['channel_count'] as num).toInt(), + unreadCount: (json['unread_count'] as num).toInt(), +); + +Map _$UnreadCountsChannelTypeToJson(UnreadCountsChannelType instance) => { + 'channel_type': instance.channelType, + 'channel_count': instance.channelCount, + 'unread_count': instance.unreadCount, +}; diff --git a/packages/stream_chat/lib/src/core/models/user.dart b/packages/stream_chat/lib/src/core/models/user.dart index 3c45ee5a86..9d6e6e3207 100644 --- a/packages/stream_chat/lib/src/core/models/user.dart +++ b/packages/stream_chat/lib/src/core/models/user.dart @@ -49,13 +49,12 @@ class User extends Equatable implements ComparableFieldProvider { this.teamsRole, this.avgResponseTime, Map extraData = const {}, - }) : - // For backwards compatibility, set 'name', 'image' in [extraData]. - extraData = { - ...extraData, - if (name != null) 'name': name, - if (image != null) 'image': image, - }; + }) : // For backwards compatibility, set 'name', 'image' in [extraData]. + extraData = { + ...extraData, + if (name != null) 'name': name, + if (image != null) 'image': image, + }; /// Create a new instance from json. factory User.fromJson(Map json) => @@ -138,7 +137,7 @@ class User extends Equatable implements ComparableFieldProvider { /// The roles for the user in the teams. /// /// eg: `{'teamId': 'role', 'teamId2': 'role2'}` - final Map< /*Team*/ String, /*Role*/ String>? teamsRole; + final Map? teamsRole; /// The average response time of the user in seconds. final int? avgResponseTime; @@ -147,13 +146,12 @@ class User extends Equatable implements ComparableFieldProvider { final Map extraData; /// List of users to list of userIds. - static List? toIds(List? users) => - users?.map((u) => u.id).toList(); + static List? toIds(List? users) => users?.map((u) => u.id).toList(); /// Serialize to json. Map toJson() => Serializer.moveFromExtraDataToRoot( - _$UserToJson(this), - ); + _$UserToJson(this), + ); /// Creates a copy of [User] with specified attributes overridden. User copyWith({ @@ -173,44 +171,44 @@ class User extends Equatable implements ComparableFieldProvider { bool? invisible, Map? teamsRole, int? avgResponseTime, - }) => - User( - id: id ?? this.id, - role: role ?? this.role, - name: name ?? - extraData?['name'] as String? ?? - // Using extraData value in order to not use id as name. - this.extraData['name'] as String?, - image: image ?? extraData?['image'] as String? ?? this.image, - createdAt: createdAt ?? this.createdAt, - updatedAt: updatedAt ?? this.updatedAt, - lastActive: lastActive ?? this.lastActive, - online: online ?? this.online, - extraData: extraData ?? this.extraData, - banned: banned ?? this.banned, - banExpires: banExpires ?? this.banExpires, - teams: teams ?? this.teams, - language: language ?? this.language, - invisible: invisible ?? this.invisible, - teamsRole: teamsRole ?? this.teamsRole, - avgResponseTime: avgResponseTime ?? this.avgResponseTime, - ); + }) => User( + id: id ?? this.id, + role: role ?? this.role, + name: + name ?? + extraData?['name'] as String? ?? + // Using extraData value in order to not use id as name. + this.extraData['name'] as String?, + image: image ?? extraData?['image'] as String? ?? this.image, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + lastActive: lastActive ?? this.lastActive, + online: online ?? this.online, + extraData: extraData ?? this.extraData, + banned: banned ?? this.banned, + banExpires: banExpires ?? this.banExpires, + teams: teams ?? this.teams, + language: language ?? this.language, + invisible: invisible ?? this.invisible, + teamsRole: teamsRole ?? this.teamsRole, + avgResponseTime: avgResponseTime ?? this.avgResponseTime, + ); @override List get props => [ - id, - role, - lastActive, - online, - extraData, - banned, - banExpires, - teams, - language, - invisible, - teamsRole, - avgResponseTime, - ]; + id, + role, + lastActive, + online, + extraData, + banned, + banExpires, + teams, + language, + invisible, + teamsRole, + avgResponseTime, + ]; @override ComparableField? getComparableField(String sortKey) { diff --git a/packages/stream_chat/lib/src/core/models/user.g.dart b/packages/stream_chat/lib/src/core/models/user.g.dart index f3a8e5868d..03cb7553d0 100644 --- a/packages/stream_chat/lib/src/core/models/user.g.dart +++ b/packages/stream_chat/lib/src/core/models/user.g.dart @@ -7,52 +7,37 @@ part of 'user.dart'; // ************************************************************************** User _$UserFromJson(Map json) => User( - id: json['id'] as String, - role: json['role'] as String?, - createdAt: json['created_at'] == null - ? null - : DateTime.parse(json['created_at'] as String), - updatedAt: json['updated_at'] == null - ? null - : DateTime.parse(json['updated_at'] as String), - lastActive: json['last_active'] == null - ? null - : DateTime.parse(json['last_active'] as String), - online: json['online'] as bool? ?? false, - banned: json['banned'] as bool? ?? false, - banExpires: json['ban_expires'] == null - ? null - : DateTime.parse(json['ban_expires'] as String), - teams: - (json['teams'] as List?)?.map((e) => e as String).toList() ?? - const [], - language: json['language'] as String?, - invisible: json['invisible'] as bool?, - teamsRole: (json['teams_role'] as Map?)?.map( - (k, e) => MapEntry(k, e as String), - ), - avgResponseTime: (json['avg_response_time'] as num?)?.toInt(), - extraData: json['extra_data'] as Map? ?? const {}, - ); + id: json['id'] as String, + role: json['role'] as String?, + createdAt: json['created_at'] == null ? null : DateTime.parse(json['created_at'] as String), + updatedAt: json['updated_at'] == null ? null : DateTime.parse(json['updated_at'] as String), + lastActive: json['last_active'] == null ? null : DateTime.parse(json['last_active'] as String), + online: json['online'] as bool? ?? false, + banned: json['banned'] as bool? ?? false, + banExpires: json['ban_expires'] == null ? null : DateTime.parse(json['ban_expires'] as String), + teams: (json['teams'] as List?)?.map((e) => e as String).toList() ?? const [], + language: json['language'] as String?, + invisible: json['invisible'] as bool?, + teamsRole: (json['teams_role'] as Map?)?.map( + (k, e) => MapEntry(k, e as String), + ), + avgResponseTime: (json['avg_response_time'] as num?)?.toInt(), + extraData: json['extra_data'] as Map? ?? const {}, +); Map _$UserToJson(User instance) => { - 'id': instance.id, - if (instance.role case final value?) 'role': value, - 'teams': instance.teams, - if (instance.createdAt?.toIso8601String() case final value?) - 'created_at': value, - if (instance.updatedAt?.toIso8601String() case final value?) - 'updated_at': value, - if (instance.lastActive?.toIso8601String() case final value?) - 'last_active': value, - 'online': instance.online, - 'banned': instance.banned, - if (instance.banExpires?.toIso8601String() case final value?) - 'ban_expires': value, - if (instance.language case final value?) 'language': value, - if (instance.invisible case final value?) 'invisible': value, - if (instance.teamsRole case final value?) 'teams_role': value, - if (instance.avgResponseTime case final value?) - 'avg_response_time': value, - 'extra_data': instance.extraData, - }; + 'id': instance.id, + if (instance.role case final value?) 'role': value, + 'teams': instance.teams, + if (instance.createdAt?.toIso8601String() case final value?) 'created_at': value, + if (instance.updatedAt?.toIso8601String() case final value?) 'updated_at': value, + if (instance.lastActive?.toIso8601String() case final value?) 'last_active': value, + 'online': instance.online, + 'banned': instance.banned, + if (instance.banExpires?.toIso8601String() case final value?) 'ban_expires': value, + if (instance.language case final value?) 'language': value, + if (instance.invisible case final value?) 'invisible': value, + if (instance.teamsRole case final value?) 'teams_role': value, + if (instance.avgResponseTime case final value?) 'avg_response_time': value, + 'extra_data': instance.extraData, +}; diff --git a/packages/stream_chat/lib/src/core/models/user_block.dart b/packages/stream_chat/lib/src/core/models/user_block.dart index 247de32bb8..77cbfe04f4 100644 --- a/packages/stream_chat/lib/src/core/models/user_block.dart +++ b/packages/stream_chat/lib/src/core/models/user_block.dart @@ -17,8 +17,7 @@ class UserBlock extends Equatable { }); /// Create a new instance from a json - factory UserBlock.fromJson(Map json) => - _$UserBlockFromJson(json); + factory UserBlock.fromJson(Map json) => _$UserBlockFromJson(json); /// User that blocked the [blockedUser]. final User user; @@ -45,21 +44,20 @@ class UserBlock extends Equatable { String? userId, String? blockedUserId, DateTime? createdAt, - }) => - UserBlock( - user: user ?? this.user, - blockedUser: blockedUser ?? this.blockedUser, - userId: userId ?? this.userId, - blockedUserId: blockedUserId ?? this.blockedUserId, - createdAt: createdAt ?? this.createdAt, - ); + }) => UserBlock( + user: user ?? this.user, + blockedUser: blockedUser ?? this.blockedUser, + userId: userId ?? this.userId, + blockedUserId: blockedUserId ?? this.blockedUserId, + createdAt: createdAt ?? this.createdAt, + ); @override List get props => [ - user, - blockedUser, - userId, - blockedUserId, - createdAt, - ]; + user, + blockedUser, + userId, + blockedUserId, + createdAt, + ]; } diff --git a/packages/stream_chat/lib/src/core/models/user_block.g.dart b/packages/stream_chat/lib/src/core/models/user_block.g.dart index 00e138211b..6e36cb806f 100644 --- a/packages/stream_chat/lib/src/core/models/user_block.g.dart +++ b/packages/stream_chat/lib/src/core/models/user_block.g.dart @@ -7,21 +7,17 @@ part of 'user_block.dart'; // ************************************************************************** UserBlock _$UserBlockFromJson(Map json) => UserBlock( - user: User.fromJson(json['user'] as Map), - blockedUser: json['blocked_user'] == null - ? null - : User.fromJson(json['blocked_user'] as Map), - userId: json['user_id'] as String?, - blockedUserId: json['blocked_user_id'] as String?, - createdAt: json['created_at'] == null - ? null - : DateTime.parse(json['created_at'] as String), - ); + user: User.fromJson(json['user'] as Map), + blockedUser: json['blocked_user'] == null ? null : User.fromJson(json['blocked_user'] as Map), + userId: json['user_id'] as String?, + blockedUserId: json['blocked_user_id'] as String?, + createdAt: json['created_at'] == null ? null : DateTime.parse(json['created_at'] as String), +); Map _$UserBlockToJson(UserBlock instance) => { - 'user': instance.user.toJson(), - 'blocked_user': instance.blockedUser?.toJson(), - 'user_id': instance.userId, - 'blocked_user_id': instance.blockedUserId, - 'created_at': instance.createdAt?.toIso8601String(), - }; + 'user': instance.user.toJson(), + 'blocked_user': instance.blockedUser?.toJson(), + 'user_id': instance.userId, + 'blocked_user_id': instance.blockedUserId, + 'created_at': instance.createdAt?.toIso8601String(), +}; diff --git a/packages/stream_chat/lib/src/core/util/extension.dart b/packages/stream_chat/lib/src/core/util/extension.dart index 0c756c0fae..51bfe84639 100644 --- a/packages/stream_chat/lib/src/core/util/extension.dart +++ b/packages/stream_chat/lib/src/core/util/extension.dart @@ -14,8 +14,7 @@ extension IterableX on Iterable { extension MapX on Map { /// Returns a new map with null keys or values removed Map get nullProtected { - final nullProtected = {...this} - ..removeWhere((key, value) => key == null || value == null); + final nullProtected = {...this}..removeWhere((key, value) => key == null || value == null); return nullProtected.cast(); } } diff --git a/packages/stream_chat/lib/src/core/util/serializer.dart b/packages/stream_chat/lib/src/core/util/serializer.dart index 9c1cb71f67..5cd992bc11 100644 --- a/packages/stream_chat/lib/src/core/util/serializer.dart +++ b/packages/stream_chat/lib/src/core/util/serializer.dart @@ -11,12 +11,10 @@ class Serializer { ..removeWhere( (key, value) => topLevelFields.contains(key), ); - final rootFields = jsonClone - ..removeWhere((key, value) => extraDataMap.keys.contains(key)); - return rootFields - ..addAll({ - 'extra_data': extraDataMap, - }); + final rootFields = jsonClone..removeWhere((key, value) => extraDataMap.keys.contains(key)); + return rootFields..addAll({ + 'extra_data': extraDataMap, + }); } /// Takes values in `extra_data` key and puts them on the root level of diff --git a/packages/stream_chat/lib/src/core/util/utils.dart b/packages/stream_chat/lib/src/core/util/utils.dart index c220499182..23c8bc8923 100644 --- a/packages/stream_chat/lib/src/core/util/utils.dart +++ b/packages/stream_chat/lib/src/core/util/utils.dart @@ -3,8 +3,7 @@ import 'dart:math' as math; // This alphabet uses `A-Za-z0-9_-` symbols. The genetic algorithm helped // optimize the gzip compression for this alphabet. -const _alphabet = - 'ModuleSymbhasOwnPr-0123456789ABCDEFGHNRVfgctiUvz_KqYTJkLxpZXIjQW'; +const _alphabet = 'ModuleSymbhasOwnPr-0123456789ABCDEFGHNRVfgctiUvz_KqYTJkLxpZXIjQW'; /// Generates a random String id /// Adopted from: https://github.com/ai/nanoid/blob/main/non-secure/index.js diff --git a/packages/stream_chat/lib/src/db/chat_persistence_client.dart b/packages/stream_chat/lib/src/db/chat_persistence_client.dart index b9092b2b36..4daf3f87f6 100644 --- a/packages/stream_chat/lib/src/db/chat_persistence_client.dart +++ b/packages/stream_chat/lib/src/db/chat_persistence_client.dart @@ -147,12 +147,10 @@ abstract class ChatPersistenceClient { }); /// Remove a message by [messageId] - Future deleteMessageById(String messageId) => - deleteMessageByIds([messageId]); + Future deleteMessageById(String messageId) => deleteMessageByIds([messageId]); /// Remove a pinned message by [messageId] - Future deletePinnedMessageById(String messageId) => - deletePinnedMessageByIds([messageId]); + Future deletePinnedMessageById(String messageId) => deletePinnedMessageByIds([messageId]); /// Remove a message by [messageIds] Future deleteMessageByIds(List messageIds); @@ -164,8 +162,7 @@ abstract class ChatPersistenceClient { Future deleteMessageByCid(String cid) => deleteMessageByCids([cid]); /// Remove a pinned message by channel [cid] - Future deletePinnedMessageByCid(String cid) async => - deletePinnedMessageByCids([cid]); + Future deletePinnedMessageByCid(String cid) async => deletePinnedMessageByCids([cid]); /// Remove a message by message [cids] Future deleteMessageByCids(List cids); @@ -206,16 +203,14 @@ abstract class ChatPersistenceClient { /// Updates the message data of a particular channel [cid] with /// the new [messages] data - Future updateMessages(String cid, List messages) => - bulkUpdateMessages({cid: messages}); + Future updateMessages(String cid, List messages) => bulkUpdateMessages({cid: messages}); /// Bulk updates the message data of multiple channels. Future bulkUpdateMessages(Map?> messages); /// Updates the pinned message data of a particular channel [cid] with /// the new [messages] data - Future updatePinnedMessages(String cid, List messages) => - bulkUpdatePinnedMessages({cid: messages}); + Future updatePinnedMessages(String cid, List messages) => bulkUpdatePinnedMessages({cid: messages}); /// Bulk updates the message data of multiple channels. Future bulkUpdatePinnedMessages(Map?> messages); @@ -235,16 +230,14 @@ abstract class ChatPersistenceClient { /// Updates all the members of a particular channle [cid] /// with the new [members] data - Future updateMembers(String cid, List members) => - bulkUpdateMembers({cid: members}); + Future updateMembers(String cid, List members) => bulkUpdateMembers({cid: members}); /// Bulk updates the members data of multiple channels. Future bulkUpdateMembers(Map?> members); /// Updates the read data of a particular channel [cid] with /// the new [reads] data - Future updateReads(String cid, List reads) => - bulkUpdateReads({cid: reads}); + Future updateReads(String cid, List reads) => bulkUpdateReads({cid: reads}); /// Bulk updates the read data of multiple channels. Future bulkUpdateReads(Map?> reads); @@ -314,8 +307,7 @@ abstract class ChatPersistenceClient { } /// Update the channel state data using [channelState] - Future updateChannelState(ChannelState channelState) => - updateChannelStates([channelState]); + Future updateChannelState(ChannelState channelState) => updateChannelStates([channelState]); /// Update list of channel states Future updateChannelStates(List channelStates) async { @@ -355,10 +347,10 @@ abstract class ChatPersistenceClient { final members = state.members; final messages = switch (CurrentPlatform.isWeb) { true => state.messages?.where( - (it) => !it.attachments.any( - (it) => it.uploadState != const UploadState.success(), - ), + (it) => !it.attachments.any( + (it) => it.uploadState != const UploadState.success(), ), + ), _ => state.messages, }; @@ -379,37 +371,45 @@ abstract class ChatPersistenceClient { reactions.addAll(messages?.expand(_expandReactions) ?? []); pinnedReactions.addAll(pinnedMessages?.expand(_expandReactions) ?? []); - polls.addAll([ - ...?messages?.map((it) => it.poll), - ...?pinnedMessages?.map((it) => it.poll), - ].withNullifyer); + polls.addAll( + [ + ...?messages?.map((it) => it.poll), + ...?pinnedMessages?.map((it) => it.poll), + ].withNullifyer, + ); pollVotesToDelete.addAll(polls.map((it) => it.id)); pollVotes.addAll(polls.expand(_expandPollVotes)); - drafts.addAll([ - state.draft, - ...?messages?.map((it) => it.draft), - ...?pinnedMessages?.map((it) => it.draft), - ].nonNulls); - - locations.addAll([ - ...?messages?.map((it) => it.sharedLocation), - ...?pinnedMessages?.map((it) => it.sharedLocation), - ].nonNulls); - - users.addAll([ - channel.createdBy, - ...?messages?.map((it) => it.user), - ...?pinnedMessages?.map((it) => it.user), - ...?reads?.map((it) => it.user), - ...?members?.map((it) => it.user), - ...reactions.map((it) => it.user), - ...pinnedReactions.map((it) => it.user), - ...polls.map((it) => it.createdBy), - ...pollVotes.map((it) => it.user), - ].withNullifyer); + drafts.addAll( + [ + state.draft, + ...?messages?.map((it) => it.draft), + ...?pinnedMessages?.map((it) => it.draft), + ].nonNulls, + ); + + locations.addAll( + [ + ...?messages?.map((it) => it.sharedLocation), + ...?pinnedMessages?.map((it) => it.sharedLocation), + ].nonNulls, + ); + + users.addAll( + [ + channel.createdBy, + ...?messages?.map((it) => it.user), + ...?pinnedMessages?.map((it) => it.user), + ...?reads?.map((it) => it.user), + ...?members?.map((it) => it.user), + ...reactions.map((it) => it.user), + ...pinnedReactions.map((it) => it.user), + ...polls.map((it) => it.createdBy), + ...pollVotes.map((it) => it.user), + ].withNullifyer, + ); } // Removing old members and reactions data as they may have diff --git a/packages/stream_chat/lib/src/event_type.dart b/packages/stream_chat/lib/src/event_type.dart index 3fb40be256..7941395501 100644 --- a/packages/stream_chat/lib/src/event_type.dart +++ b/packages/stream_chat/lib/src/event_type.dart @@ -52,23 +52,19 @@ class EventType { static const String channelDeleted = 'channel.deleted'; /// Event sent when a channel is deleted - static const String notificationChannelDeleted = - 'notification.channel_deleted'; + static const String notificationChannelDeleted = 'notification.channel_deleted'; /// Event sent when a channel is truncated static const String channelTruncated = 'channel.truncated'; /// Event sent when a channel is truncated - static const String notificationChannelTruncated = - 'notification.channel_truncated'; + static const String notificationChannelTruncated = 'notification.channel_truncated'; /// Event sent when the user is added to a channel - static const String notificationAddedToChannel = - 'notification.added_to_channel'; + static const String notificationAddedToChannel = 'notification.added_to_channel'; /// Event sent when the user is removed from a channel - static const String notificationRemovedFromChannel = - 'notification.removed_from_channel'; + static const String notificationRemovedFromChannel = 'notification.removed_from_channel'; /// Event sent when a channel is updated static const String channelUpdated = 'channel.updated'; @@ -104,8 +100,7 @@ class EventType { static const String connectionRecovered = 'connection.recovered'; /// Event sent when the user is accepts an invite - static const String notificationInviteAccepted = - 'notification.invite_accepted'; + static const String notificationInviteAccepted = 'notification.invite_accepted'; /// Event sent when the user is invited static const String notificationInvited = 'notification.invited'; @@ -153,8 +148,7 @@ class EventType { static const String threadUpdated = 'thread.updated'; /// Event sent when a new message is added to a thread. - static const String notificationThreadMessageNew = - 'notification.thread_message_new'; + static const String notificationThreadMessageNew = 'notification.thread_message_new'; /// Event sent when a draft message is either created or updated. static const String draftUpdated = 'draft.updated'; @@ -187,8 +181,7 @@ class EventType { static const String pushPreferenceUpdated = 'push_preference.updated'; /// Local event sent when channel push notification preference is updated. - static const String channelPushPreferenceUpdated = - 'channel.push_preference.updated'; + static const String channelPushPreferenceUpdated = 'channel.push_preference.updated'; /// Event sent when a message is marked as delivered. static const String messageDelivered = 'message.delivered'; diff --git a/packages/stream_chat/lib/src/ws/connect_user_details.g.dart b/packages/stream_chat/lib/src/ws/connect_user_details.g.dart index 435c4febfc..2a77459610 100644 --- a/packages/stream_chat/lib/src/ws/connect_user_details.g.dart +++ b/packages/stream_chat/lib/src/ws/connect_user_details.g.dart @@ -6,14 +6,12 @@ part of 'connect_user_details.dart'; // JsonSerializableGenerator // ************************************************************************** -Map _$ConnectUserDetailsToJson(ConnectUserDetails instance) => - { - 'id': instance.id, - if (instance.name case final value?) 'name': value, - if (instance.image case final value?) 'image': value, - if (instance.language case final value?) 'language': value, - if (instance.invisible case final value?) 'invisible': value, - if (instance.privacySettings?.toJson() case final value?) - 'privacy_settings': value, - if (instance.extraData case final value?) 'extra_data': value, - }; +Map _$ConnectUserDetailsToJson(ConnectUserDetails instance) => { + 'id': instance.id, + if (instance.name case final value?) 'name': value, + if (instance.image case final value?) 'image': value, + if (instance.language case final value?) 'language': value, + if (instance.invisible case final value?) 'invisible': value, + if (instance.privacySettings?.toJson() case final value?) 'privacy_settings': value, + if (instance.extraData case final value?) 'extra_data': value, +}; diff --git a/packages/stream_chat/lib/src/ws/websocket.dart b/packages/stream_chat/lib/src/ws/websocket.dart index d6cb82a716..40f18756e4 100644 --- a/packages/stream_chat/lib/src/ws/websocket.dart +++ b/packages/stream_chat/lib/src/ws/websocket.dart @@ -24,10 +24,11 @@ typedef EventHandler = void Function(Event); /// Typedef used for connecting to a websocket. Method returns a /// [WebSocketChannel] and accepts a connection [url] and an optional /// [Iterable] of `protocols`. -typedef WebSocketChannelProvider = WebSocketChannel Function( - Uri uri, { - Iterable? protocols, -}); +typedef WebSocketChannelProvider = + WebSocketChannel Function( + Uri uri, { + Iterable? protocols, + }); /// A WebSocket connection that reconnects upon failure. class WebSocket with TimerHelper { @@ -132,8 +133,7 @@ class WebSocket with TimerHelper { if (_webSocketChannel != null) { _closeWebSocketChannel(); } - _webSocketChannel = - webSocketChannelProvider?.call(uri) ?? WebSocketChannel.connect(uri); + _webSocketChannel = webSocketChannelProvider?.call(uri) ?? WebSocketChannel.connect(uri); _subscribeToWebSocketChannel(); } @@ -409,8 +409,7 @@ class WebSocket with TimerHelper { } void _onConnectionError(error, [stacktrace]) { - _logger?.warning( - '[onConnectionError] #ws; error occurred', error, stacktrace); + _logger?.warning('[onConnectionError] #ws; error occurred', error, stacktrace); StreamWebSocketError wsError; if (error is WebSocketChannelException) { diff --git a/packages/stream_chat/pubspec.yaml b/packages/stream_chat/pubspec.yaml index 068cfd24e5..13ff93f99e 100644 --- a/packages/stream_chat/pubspec.yaml +++ b/packages/stream_chat/pubspec.yaml @@ -18,7 +18,7 @@ issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues # 2. Add it to the melos.yaml file for future updates. environment: - sdk: ^3.6.2 + sdk: ^3.10.0 dependencies: async: ^2.11.0 diff --git a/packages/stream_chat/test/src/client/channel_delivery_reporter_test.dart b/packages/stream_chat/test/src/client/channel_delivery_reporter_test.dart index 424e00cec9..7f0a529f3e 100644 --- a/packages/stream_chat/test/src/client/channel_delivery_reporter_test.dart +++ b/packages/stream_chat/test/src/client/channel_delivery_reporter_test.dart @@ -59,15 +59,13 @@ void main() { expect(capturedDeliveries, hasLength(2)); expect( capturedDeliveries.any( - (d) => - d.channelCid == 'test:channel-1' && d.messageId == 'message-1', + (d) => d.channelCid == 'test:channel-1' && d.messageId == 'message-1', ), isTrue, ); expect( capturedDeliveries.any( - (d) => - d.channelCid == 'test:channel-2' && d.messageId == 'message-2', + (d) => d.channelCid == 'test:channel-2' && d.messageId == 'message-2', ), isTrue, ); diff --git a/packages/stream_chat/test/src/client/channel_test.dart b/packages/stream_chat/test/src/client/channel_test.dart index 46c20f8387..2ea1f896ed 100644 --- a/packages/stream_chat/test/src/client/channel_test.dart +++ b/packages/stream_chat/test/src/client/channel_test.dart @@ -91,8 +91,7 @@ void main() { expect(channel.extraData['image'], imageUrl); const newImage = 'https://getstream.io/new-image'; - final newChannelInstance = - Channel(client, channelType, channelId, image: newImage); + final newChannelInstance = Channel(client, channelType, channelId, image: newImage); expect(newChannelInstance.image, newImage); expect(newChannelInstance.extraData['image'], newImage); @@ -108,8 +107,7 @@ void main() { expect(channel.extraData['name'], name); const newName = 'New channel name'; - final newChannelInstance = - Channel(client, channelType, channelId, name: newName); + final newChannelInstance = Channel(client, channelType, channelId, name: newName); expect(newChannelInstance.name, newName); expect(newChannelInstance.extraData['name'], newName); @@ -147,13 +145,10 @@ void main() { // mock persistence client final channelThreads = >{}; - when(() => client.chatPersistenceClient.getChannelThreads(channelCid)) - .thenAnswer((_) async => channelThreads); + when(() => client.chatPersistenceClient.getChannelThreads(channelCid)).thenAnswer((_) async => channelThreads); final channelState = _generateChannelState(channelId, channelType); - when(() => client.chatPersistenceClient.getChannelStateByCid(channelCid)) - .thenAnswer((_) async => channelState); - when(() => client.chatPersistenceClient.updateMessages(channelCid, any())) - .thenAnswer((_) => Future.value()); + when(() => client.chatPersistenceClient.getChannelStateByCid(channelCid)).thenAnswer((_) async => channelState); + when(() => client.chatPersistenceClient.updateMessages(channelCid, any())).thenAnswer((_) => Future.value()); // client logger when(() => client.logger).thenReturn(_createLogger('mock-client-logger')); @@ -256,14 +251,15 @@ void main() { user: client.state.currentUser, ); - final sendMessageResponse = SendMessageResponse() - ..message = message.copyWith(state: MessageState.sent); + final sendMessageResponse = SendMessageResponse()..message = message.copyWith(state: MessageState.sent); - when(() => client.sendMessage( - any(that: isSameMessageAs(message)), - channelId, - channelType, - )).thenAnswer((_) async => sendMessageResponse); + when( + () => client.sendMessage( + any(that: isSameMessageAs(message)), + channelId, + channelType, + ), + ).thenAnswer((_) async => sendMessageResponse); expectLater( // skipping first seed message list -> [] messages @@ -289,245 +285,262 @@ void main() { expect(res, isNotNull); expect(res.message.id, message.id); - verify(() => client.sendMessage( - any(that: isSameMessageAs(message)), - channelId, - channelType, - )).called(1); + verify( + () => client.sendMessage( + any(that: isSameMessageAs(message)), + channelId, + channelType, + ), + ).called(1); }); test( - 'should handle StreamChatNetworkError by adding message to retry queue with skipPush: true, skipEnrichUrl: false', - () async { - final message = Message( - id: 'test-message-id', - text: 'Hello world!', - user: client.state.currentUser, - ); + 'should handle StreamChatNetworkError by adding message to retry queue with skipPush: true, skipEnrichUrl: false', + () async { + final message = Message( + id: 'test-message-id', + text: 'Hello world!', + user: client.state.currentUser, + ); - when(() => client.sendMessage( + when( + () => client.sendMessage( any(that: isSameMessageAs(message)), channelId, channelType, skipPush: true, - )).thenThrow(StreamChatNetworkError(ChatErrorCode.notAllowed)); + ), + ).thenThrow(StreamChatNetworkError(ChatErrorCode.notAllowed)); - expectLater( - // skipping first seed message list -> [] messages - channel.state?.messagesStream.skip(1), - emitsInOrder([ - [ - isSameMessageAs( - message.copyWith(state: MessageState.sending), - matchMessageState: true, - ), - ], - [ - isSameMessageAs( - message.copyWith( - state: MessageState.sendingFailed( - skipPush: true, - skipEnrichUrl: false, + expectLater( + // skipping first seed message list -> [] messages + channel.state?.messagesStream.skip(1), + emitsInOrder([ + [ + isSameMessageAs( + message.copyWith(state: MessageState.sending), + matchMessageState: true, + ), + ], + [ + isSameMessageAs( + message.copyWith( + state: MessageState.sendingFailed( + skipPush: true, + skipEnrichUrl: false, + ), ), + matchMessageState: true, ), - matchMessageState: true, - ), - ], - ]), - ); - - try { - await channel.sendMessage( - message, - skipPush: true, + ], + ]), ); - } catch (e) { - expect(e, isA()); - final networkError = e as StreamChatNetworkError; - expect(networkError.code, equals(ChatErrorCode.notAllowed.code)); - } - }); + try { + await channel.sendMessage( + message, + skipPush: true, + ); + } catch (e) { + expect(e, isA()); + + final networkError = e as StreamChatNetworkError; + expect(networkError.code, equals(ChatErrorCode.notAllowed.code)); + } + }, + ); test( - 'should handle StreamChatNetworkError by adding message to retry queue with skipPush: true, skipEnrichUrl: true', - () async { - final message = Message( - id: 'test-message-id-2', - text: 'Hello world!', - user: client.state.currentUser, - ); + 'should handle StreamChatNetworkError by adding message to retry queue with skipPush: true, skipEnrichUrl: true', + () async { + final message = Message( + id: 'test-message-id-2', + text: 'Hello world!', + user: client.state.currentUser, + ); - when(() => client.sendMessage( + when( + () => client.sendMessage( any(that: isSameMessageAs(message)), channelId, channelType, skipPush: true, skipEnrichUrl: true, - )).thenThrow(StreamChatNetworkError(ChatErrorCode.notAllowed)); + ), + ).thenThrow(StreamChatNetworkError(ChatErrorCode.notAllowed)); - expectLater( - // skipping first seed message list -> [] messages - channel.state?.messagesStream.skip(1), - emitsInOrder([ - [ - isSameMessageAs( - message.copyWith(state: MessageState.sending), - matchMessageState: true, - ), - ], - [ - isSameMessageAs( - message.copyWith( - state: MessageState.sendingFailed( - skipPush: true, - skipEnrichUrl: true, + expectLater( + // skipping first seed message list -> [] messages + channel.state?.messagesStream.skip(1), + emitsInOrder([ + [ + isSameMessageAs( + message.copyWith(state: MessageState.sending), + matchMessageState: true, + ), + ], + [ + isSameMessageAs( + message.copyWith( + state: MessageState.sendingFailed( + skipPush: true, + skipEnrichUrl: true, + ), ), + matchMessageState: true, ), - matchMessageState: true, - ), - ], - ]), - ); - - try { - await channel.sendMessage( - message, - skipPush: true, - skipEnrichUrl: true, + ], + ]), ); - } catch (e) { - expect(e, isA()); - final networkError = e as StreamChatNetworkError; - expect(networkError.code, equals(ChatErrorCode.notAllowed.code)); - } - }); + try { + await channel.sendMessage( + message, + skipPush: true, + skipEnrichUrl: true, + ); + } catch (e) { + expect(e, isA()); + + final networkError = e as StreamChatNetworkError; + expect(networkError.code, equals(ChatErrorCode.notAllowed.code)); + } + }, + ); test( - 'should handle StreamChatNetworkError by adding message to retry queue with skipPush: false, skipEnrichUrl: true', - () async { - final message = Message( - id: 'test-message-id-3', - text: 'Hello world!', - user: client.state.currentUser, - ); + 'should handle StreamChatNetworkError by adding message to retry queue with skipPush: false, skipEnrichUrl: true', + () async { + final message = Message( + id: 'test-message-id-3', + text: 'Hello world!', + user: client.state.currentUser, + ); - when(() => client.sendMessage( + when( + () => client.sendMessage( any(that: isSameMessageAs(message)), channelId, channelType, skipEnrichUrl: true, - )).thenThrow(StreamChatNetworkError(ChatErrorCode.notAllowed)); + ), + ).thenThrow(StreamChatNetworkError(ChatErrorCode.notAllowed)); - expectLater( - // skipping first seed message list -> [] messages - channel.state?.messagesStream.skip(1), - emitsInOrder([ - [ - isSameMessageAs( - message.copyWith(state: MessageState.sending), - matchMessageState: true, - ), - ], - [ - isSameMessageAs( - message.copyWith( - state: MessageState.sendingFailed( - skipPush: false, - skipEnrichUrl: true, + expectLater( + // skipping first seed message list -> [] messages + channel.state?.messagesStream.skip(1), + emitsInOrder([ + [ + isSameMessageAs( + message.copyWith(state: MessageState.sending), + matchMessageState: true, + ), + ], + [ + isSameMessageAs( + message.copyWith( + state: MessageState.sendingFailed( + skipPush: false, + skipEnrichUrl: true, + ), ), + matchMessageState: true, ), - matchMessageState: true, - ), - ], - ]), - ); - - try { - await channel.sendMessage( - message, - skipEnrichUrl: true, + ], + ]), ); - } catch (e) { - expect(e, isA()); - final networkError = e as StreamChatNetworkError; - expect(networkError.code, equals(ChatErrorCode.notAllowed.code)); - } - }); + try { + await channel.sendMessage( + message, + skipEnrichUrl: true, + ); + } catch (e) { + expect(e, isA()); + + final networkError = e as StreamChatNetworkError; + expect(networkError.code, equals(ChatErrorCode.notAllowed.code)); + } + }, + ); test( - 'should handle StreamChatNetworkError by adding message to retry queue with skipPush: false, skipEnrichUrl: false', - () async { - final message = Message( - id: 'test-message-id-4', - text: 'Hello world!', - user: client.state.currentUser, - ); + 'should handle StreamChatNetworkError by adding message to retry queue with skipPush: false, skipEnrichUrl: false', + () async { + final message = Message( + id: 'test-message-id-4', + text: 'Hello world!', + user: client.state.currentUser, + ); - when(() => client.sendMessage( + when( + () => client.sendMessage( any(that: isSameMessageAs(message)), channelId, channelType, - )).thenThrow(StreamChatNetworkError(ChatErrorCode.notAllowed)); + ), + ).thenThrow(StreamChatNetworkError(ChatErrorCode.notAllowed)); - expectLater( - // skipping first seed message list -> [] messages - channel.state?.messagesStream.skip(1), - emitsInOrder([ - [ - isSameMessageAs( - message.copyWith(state: MessageState.sending), - matchMessageState: true, - ), - ], - [ - isSameMessageAs( - message.copyWith( - state: MessageState.sendingFailed( - skipPush: false, - skipEnrichUrl: false, + expectLater( + // skipping first seed message list -> [] messages + channel.state?.messagesStream.skip(1), + emitsInOrder([ + [ + isSameMessageAs( + message.copyWith(state: MessageState.sending), + matchMessageState: true, + ), + ], + [ + isSameMessageAs( + message.copyWith( + state: MessageState.sendingFailed( + skipPush: false, + skipEnrichUrl: false, + ), ), + matchMessageState: true, ), - matchMessageState: true, - ), - ], - ]), - ); - - try { - await channel.sendMessage( - message, + ], + ]), ); - } catch (e) { - expect(e, isA()); - final networkError = e as StreamChatNetworkError; - expect(networkError.code, equals(ChatErrorCode.notAllowed.code)); - } - }); + try { + await channel.sendMessage( + message, + ); + } catch (e) { + expect(e, isA()); - test('should update message state even when non-retriable error occurs', - () async { + final networkError = e as StreamChatNetworkError; + expect(networkError.code, equals(ChatErrorCode.notAllowed.code)); + } + }, + ); + + test('should update message state even when non-retriable error occurs', () async { final message = Message( id: 'test-message-id', text: 'Hello world!', user: client.state.currentUser, ); - when(() => client.sendMessage( - any(that: isSameMessageAs(message)), - channelId, - channelType, - )).thenThrow(StreamChatNetworkError.raw( - code: ChatErrorCode.inputError.code, - message: 'Input error', - data: ErrorResponse() - ..code = ChatErrorCode.inputError.code - ..message = 'Input error' - ..statusCode = 400, - )); + when( + () => client.sendMessage( + any(that: isSameMessageAs(message)), + channelId, + channelType, + ), + ).thenThrow( + StreamChatNetworkError.raw( + code: ChatErrorCode.inputError.code, + message: 'Input error', + data: ErrorResponse() + ..code = ChatErrorCode.inputError.code + ..message = 'Input error' + ..statusCode = 400, + ), + ); expectLater( // skipping first seed message list -> [] messages @@ -549,7 +562,7 @@ void main() { ), matchMessageState: true, ), - ] + ], ]), ); @@ -578,36 +591,43 @@ void main() { final sendImageResponse = SendImageResponse()..file = 'test-image-url'; final sendFileResponse = SendFileResponse()..file = 'test-file-url'; - when(() => client.sendImage( - any(), - channelId, - channelType, - onSendProgress: any(named: 'onSendProgress'), - cancelToken: any(named: 'cancelToken'), - extraData: any(named: 'extraData'), - )).thenAnswer((_) async => sendImageResponse); + when( + () => client.sendImage( + any(), + channelId, + channelType, + onSendProgress: any(named: 'onSendProgress'), + cancelToken: any(named: 'cancelToken'), + extraData: any(named: 'extraData'), + ), + ).thenAnswer((_) async => sendImageResponse); - when(() => client.sendFile( - any(), - channelId, - channelType, - onSendProgress: any(named: 'onSendProgress'), - cancelToken: any(named: 'cancelToken'), - extraData: any(named: 'extraData'), - )).thenAnswer((_) async => sendFileResponse); + when( + () => client.sendFile( + any(), + channelId, + channelType, + onSendProgress: any(named: 'onSendProgress'), + cancelToken: any(named: 'cancelToken'), + extraData: any(named: 'extraData'), + ), + ).thenAnswer((_) async => sendFileResponse); - when(() => client.sendMessage( - any(that: isSameMessageAs(message)), - channelId, - channelType, - )).thenAnswer((_) async => SendMessageResponse() - ..message = message.copyWith( - attachments: attachments - .map((it) => - it.copyWith(uploadState: const UploadState.success())) - .toList(growable: false), - state: MessageState.sent, - )); + when( + () => client.sendMessage( + any(that: isSameMessageAs(message)), + channelId, + channelType, + ), + ).thenAnswer( + (_) async => SendMessageResponse() + ..message = message.copyWith( + attachments: attachments + .map((it) => it.copyWith(uploadState: const UploadState.success())) + .toList(growable: false), + state: MessageState.sent, + ), + ); expectLater( // skipping first seed message list -> [] messages @@ -619,10 +639,7 @@ void main() { isSameMessageAs( message.copyWith( state: MessageState.sending, - attachments: [ - ...attachments.map((it) => it.copyWith( - uploadState: const UploadState.preparing())) - ], + attachments: [...attachments.map((it) => it.copyWith(uploadState: const UploadState.preparing()))], ), matchMessageState: true, matchAttachments: true, @@ -634,8 +651,8 @@ void main() { isSameMessageAs( message.copyWith( state: MessageState.sending, - attachments: [...attachments]..[0] = - attachments[0].copyWith( + attachments: [...attachments] + ..[0] = attachments[0].copyWith( uploadState: const UploadState.success(), ), ), @@ -667,10 +684,7 @@ void main() { isSameMessageAs( message.copyWith( state: MessageState.sending, - attachments: [ - ...attachments.map((it) => - it.copyWith(uploadState: const UploadState.success())) - ], + attachments: [...attachments.map((it) => it.copyWith(uploadState: const UploadState.success()))], ), matchMessageState: true, matchAttachments: true, @@ -681,10 +695,7 @@ void main() { isSameMessageAs( message.copyWith( state: MessageState.sent, - attachments: [ - ...attachments.map((it) => - it.copyWith(uploadState: const UploadState.success())) - ], + attachments: [...attachments.map((it) => it.copyWith(uploadState: const UploadState.success()))], ), matchMessageState: true, matchAttachments: true, @@ -707,29 +718,35 @@ void main() { isTrue, ); - verify(() => client.sendImage( - any(), - channelId, - channelType, - onSendProgress: any(named: 'onSendProgress'), - cancelToken: any(named: 'cancelToken'), - extraData: any(named: 'extraData'), - )).called(2); + verify( + () => client.sendImage( + any(), + channelId, + channelType, + onSendProgress: any(named: 'onSendProgress'), + cancelToken: any(named: 'cancelToken'), + extraData: any(named: 'extraData'), + ), + ).called(2); - verify(() => client.sendFile( - any(), - channelId, - channelType, - onSendProgress: any(named: 'onSendProgress'), - cancelToken: any(named: 'cancelToken'), - extraData: any(named: 'extraData'), - )).called(1); + verify( + () => client.sendFile( + any(), + channelId, + channelType, + onSendProgress: any(named: 'onSendProgress'), + cancelToken: any(named: 'cancelToken'), + extraData: any(named: 'extraData'), + ), + ).called(1); - verify(() => client.sendMessage( - any(that: isSameMessageAs(message)), - channelId, - channelType, - )).called(1); + verify( + () => client.sendMessage( + any(that: isSameMessageAs(message)), + channelId, + channelType, + ), + ).called(1); }); test('should not send if the message is invalid', () async { @@ -1127,11 +1144,13 @@ void main() { final draftMessage = DraftMessage(text: 'Draft message text'); setUp(() { - when(() => client.createDraft( - draftMessage, - channelId, - channelType, - )).thenAnswer( + when( + () => client.createDraft( + draftMessage, + channelId, + channelType, + ), + ).thenAnswer( (_) async => CreateDraftResponse() ..draft = Draft( channelCid: channelCid, @@ -1147,11 +1166,13 @@ void main() { expect(res, isNotNull); expect(res.draft.message, draftMessage); - verify(() => channel.client.createDraft( - draftMessage, - channelId, - channelType, - )).called(1); + verify( + () => channel.client.createDraft( + draftMessage, + channelId, + channelType, + ), + ).called(1); }); }); @@ -1159,11 +1180,13 @@ void main() { final draftMessage = DraftMessage(text: 'Draft message text'); setUp(() { - when(() => client.getDraft( - channelId, - channelType, - parentId: any(named: 'parentId'), - )).thenAnswer( + when( + () => client.getDraft( + channelId, + channelType, + parentId: any(named: 'parentId'), + ), + ).thenAnswer( (_) async => GetDraftResponse() ..draft = Draft( channelCid: channelCid, @@ -1179,10 +1202,12 @@ void main() { expect(res, isNotNull); expect(res.draft.message, draftMessage); - verify(() => channel.client.getDraft( - channelId, - channelType, - )).called(1); + verify( + () => channel.client.getDraft( + channelId, + channelType, + ), + ).called(1); }); test('with parentId should pass parentId to client', () async { @@ -1192,21 +1217,25 @@ void main() { expect(res, isNotNull); expect(res.draft.message, draftMessage); - verify(() => channel.client.getDraft( - channelId, - channelType, - parentId: parentId, - )).called(1); + verify( + () => channel.client.getDraft( + channelId, + channelType, + parentId: parentId, + ), + ).called(1); }); }); group('`.deleteDraft`', () { setUp(() { - when(() => client.deleteDraft( - channelId, - channelType, - parentId: any(named: 'parentId'), - )).thenAnswer((_) async => EmptyResponse()); + when( + () => client.deleteDraft( + channelId, + channelType, + parentId: any(named: 'parentId'), + ), + ).thenAnswer((_) async => EmptyResponse()); }); test('should call client.deleteDraft', () async { @@ -1214,10 +1243,12 @@ void main() { expect(res, isNotNull); - verify(() => channel.client.deleteDraft( - channelId, - channelType, - )).called(1); + verify( + () => channel.client.deleteDraft( + channelId, + channelType, + ), + ).called(1); }); test('with parentId should pass parentId to client', () async { @@ -1226,11 +1257,13 @@ void main() { expect(res, isNotNull); - verify(() => channel.client.deleteDraft( - channelId, - channelType, - parentId: parentId, - )).called(1); + verify( + () => channel.client.deleteDraft( + channelId, + channelType, + parentId: parentId, + ), + ).called(1); }); }); @@ -1238,10 +1271,12 @@ void main() { const messageId = 'test-message-id'; setUp(() { - when(() => client.createReminder( - messageId, - remindAt: any(named: 'remindAt'), - )).thenAnswer( + when( + () => client.createReminder( + messageId, + remindAt: any(named: 'remindAt'), + ), + ).thenAnswer( (_) async => CreateReminderResponse() ..reminder = MessageReminder( messageId: messageId, @@ -1271,10 +1306,12 @@ void main() { expect(res.reminder.messageId, messageId); expect(res.reminder.remindAt, remindAt); - verify(() => channel.client.createReminder( - messageId, - remindAt: remindAt, - )).called(1); + verify( + () => channel.client.createReminder( + messageId, + remindAt: remindAt, + ), + ).called(1); }); }); @@ -1282,10 +1319,12 @@ void main() { const messageId = 'test-message-id'; setUp(() { - when(() => client.updateReminder( - messageId, - remindAt: any(named: 'remindAt'), - )).thenAnswer( + when( + () => client.updateReminder( + messageId, + remindAt: any(named: 'remindAt'), + ), + ).thenAnswer( (_) async => UpdateReminderResponse() ..reminder = MessageReminder( messageId: messageId, @@ -1315,10 +1354,12 @@ void main() { expect(res.reminder.messageId, messageId); expect(res.reminder.remindAt, remindAt); - verify(() => channel.client.updateReminder( - messageId, - remindAt: remindAt, - )).called(1); + verify( + () => channel.client.updateReminder( + messageId, + remindAt: remindAt, + ), + ).called(1); }); }); @@ -1347,11 +1388,11 @@ void main() { state: MessageState.sent, ); - final updateMessageResponse = UpdateMessageResponse() - ..message = message; + final updateMessageResponse = UpdateMessageResponse()..message = message; - when(() => client.updateMessage(any(that: isSameMessageAs(message)))) - .thenAnswer((_) async => updateMessageResponse); + when( + () => client.updateMessage(any(that: isSameMessageAs(message))), + ).thenAnswer((_) async => updateMessageResponse); expectLater( // skipping first seed message list -> [] messages @@ -1377,9 +1418,11 @@ void main() { expect(res, isNotNull); expect(res.message.id, message.id); - verify(() => client.updateMessage( - any(that: isSameMessageAs(message)), - )).called(1); + verify( + () => client.updateMessage( + any(that: isSameMessageAs(message)), + ), + ).called(1); }); test('with attachments should work just fine', () async { @@ -1400,34 +1443,41 @@ void main() { final sendImageResponse = SendImageResponse()..file = 'test-image-url'; final sendFileResponse = SendFileResponse()..file = 'test-file-url'; - when(() => client.sendImage( - any(), - channelId, - channelType, - onSendProgress: any(named: 'onSendProgress'), - cancelToken: any(named: 'cancelToken'), - extraData: any(named: 'extraData'), - )).thenAnswer((_) async => sendImageResponse); + when( + () => client.sendImage( + any(), + channelId, + channelType, + onSendProgress: any(named: 'onSendProgress'), + cancelToken: any(named: 'cancelToken'), + extraData: any(named: 'extraData'), + ), + ).thenAnswer((_) async => sendImageResponse); - when(() => client.sendFile( - any(), - channelId, - channelType, - onSendProgress: any(named: 'onSendProgress'), - cancelToken: any(named: 'cancelToken'), - extraData: any(named: 'extraData'), - )).thenAnswer((_) async => sendFileResponse); + when( + () => client.sendFile( + any(), + channelId, + channelType, + onSendProgress: any(named: 'onSendProgress'), + cancelToken: any(named: 'cancelToken'), + extraData: any(named: 'extraData'), + ), + ).thenAnswer((_) async => sendFileResponse); - when(() => client.updateMessage( - any(that: isSameMessageAs(message)), - )).thenAnswer((_) async => UpdateMessageResponse() - ..message = message.copyWith( - state: MessageState.sent, - attachments: attachments - .map((it) => - it.copyWith(uploadState: const UploadState.success())) - .toList(growable: false), - )); + when( + () => client.updateMessage( + any(that: isSameMessageAs(message)), + ), + ).thenAnswer( + (_) async => UpdateMessageResponse() + ..message = message.copyWith( + state: MessageState.sent, + attachments: attachments + .map((it) => it.copyWith(uploadState: const UploadState.success())) + .toList(growable: false), + ), + ); expectLater( // skipping first seed message list -> [] messages @@ -1439,10 +1489,7 @@ void main() { isSameMessageAs( message.copyWith( state: MessageState.updating, - attachments: [ - ...attachments.map((it) => it.copyWith( - uploadState: const UploadState.preparing())) - ], + attachments: [...attachments.map((it) => it.copyWith(uploadState: const UploadState.preparing()))], ), matchMessageState: true, matchAttachments: true, @@ -1454,8 +1501,8 @@ void main() { isSameMessageAs( message.copyWith( state: MessageState.updating, - attachments: [...attachments]..[0] = - attachments[0].copyWith( + attachments: [...attachments] + ..[0] = attachments[0].copyWith( uploadState: const UploadState.success(), ), ), @@ -1487,10 +1534,7 @@ void main() { isSameMessageAs( message.copyWith( state: MessageState.updating, - attachments: [ - ...attachments.map((it) => - it.copyWith(uploadState: const UploadState.success())) - ], + attachments: [...attachments.map((it) => it.copyWith(uploadState: const UploadState.success()))], ), matchMessageState: true, matchAttachments: true, @@ -1501,10 +1545,7 @@ void main() { isSameMessageAs( message.copyWith( state: MessageState.updated, - attachments: [ - ...attachments.map((it) => - it.copyWith(uploadState: const UploadState.success())) - ], + attachments: [...attachments.map((it) => it.copyWith(uploadState: const UploadState.success()))], ), matchMessageState: true, matchAttachments: true, @@ -1527,41 +1568,47 @@ void main() { isTrue, ); - verify(() => client.sendImage( - any(), - channelId, - channelType, - onSendProgress: any(named: 'onSendProgress'), - cancelToken: any(named: 'cancelToken'), - extraData: any(named: 'extraData'), - )).called(2); + verify( + () => client.sendImage( + any(), + channelId, + channelType, + onSendProgress: any(named: 'onSendProgress'), + cancelToken: any(named: 'cancelToken'), + extraData: any(named: 'extraData'), + ), + ).called(2); - verify(() => client.sendFile( - any(), - channelId, - channelType, - onSendProgress: any(named: 'onSendProgress'), - cancelToken: any(named: 'cancelToken'), - extraData: any(named: 'extraData'), - )).called(1); + verify( + () => client.sendFile( + any(), + channelId, + channelType, + onSendProgress: any(named: 'onSendProgress'), + cancelToken: any(named: 'cancelToken'), + extraData: any(named: 'extraData'), + ), + ).called(1); - verify(() => client.updateMessage( - any(that: isSameMessageAs(message)), - )).called(1); + verify( + () => client.updateMessage( + any(that: isSameMessageAs(message)), + ), + ).called(1); }); - test( - 'should update message state even when error is not StreamChatNetworkError', - () async { + test('should update message state even when error is not StreamChatNetworkError', () async { final message = Message( id: 'test-message-id-error-1', state: MessageState.sent, ); - when(() => client.updateMessage( - any(that: isSameMessageAs(message)), - skipEnrichUrl: true, - )).thenThrow(ArgumentError('Invalid argument')); + when( + () => client.updateMessage( + any(that: isSameMessageAs(message)), + skipEnrichUrl: true, + ), + ).thenThrow(ArgumentError('Invalid argument')); expectLater( // skipping first seed message list -> [] messages @@ -1583,7 +1630,7 @@ void main() { ), matchMessageState: true, ), - ] + ], ]), ); @@ -1595,123 +1642,132 @@ void main() { }); test( - 'should add message to retry queue when retriable StreamChatNetworkError occurs with skipPush: false, skipEnrichUrl: true', - () async { - final message = Message( - id: 'test-message-id-retry-1', - state: MessageState.sent, - ); + 'should add message to retry queue when retriable StreamChatNetworkError occurs with skipPush: false, skipEnrichUrl: true', + () async { + final message = Message( + id: 'test-message-id-retry-1', + state: MessageState.sent, + ); - // Create a retriable error (data == null) - when(() => client.updateMessage( + // Create a retriable error (data == null) + when( + () => client.updateMessage( any(that: isSameMessageAs(message)), skipEnrichUrl: true, - )).thenThrow(StreamChatNetworkError.raw( - code: ChatErrorCode.requestTimeout.code, - message: 'Request timed out', - )); + ), + ).thenThrow( + StreamChatNetworkError.raw( + code: ChatErrorCode.requestTimeout.code, + message: 'Request timed out', + ), + ); - expectLater( - // skipping first seed message list -> [] messages - channel.state?.messagesStream.skip(1), - emitsInOrder([ - [ - isSameMessageAs( - message.copyWith(state: MessageState.updating), - matchMessageState: true, - ), - ], - [ - isSameMessageAs( - message.copyWith( - state: MessageState.updatingFailed( - skipPush: false, - skipEnrichUrl: true, + expectLater( + // skipping first seed message list -> [] messages + channel.state?.messagesStream.skip(1), + emitsInOrder([ + [ + isSameMessageAs( + message.copyWith(state: MessageState.updating), + matchMessageState: true, + ), + ], + [ + isSameMessageAs( + message.copyWith( + state: MessageState.updatingFailed( + skipPush: false, + skipEnrichUrl: true, + ), ), + matchMessageState: true, ), - matchMessageState: true, - ), - ], - ]), - ); + ], + ]), + ); - try { - await channel.updateMessage(message, skipEnrichUrl: true); - } catch (e) { - expect(e, isA()); + try { + await channel.updateMessage(message, skipEnrichUrl: true); + } catch (e) { + expect(e, isA()); - final networkError = e as StreamChatNetworkError; - expect(networkError.code, equals(ChatErrorCode.requestTimeout.code)); - expect(networkError.isRetriable, isTrue); - } - }); + final networkError = e as StreamChatNetworkError; + expect(networkError.code, equals(ChatErrorCode.requestTimeout.code)); + expect(networkError.isRetriable, isTrue); + } + }, + ); test( - 'should add message to retry queue when retriable StreamChatNetworkError occurs with skipPush: true, skipEnrichUrl: false', - () async { - final message = Message( - id: 'test-message-id-retry-2', - state: MessageState.sent, - ); + 'should add message to retry queue when retriable StreamChatNetworkError occurs with skipPush: true, skipEnrichUrl: false', + () async { + final message = Message( + id: 'test-message-id-retry-2', + state: MessageState.sent, + ); - // Create a retriable error (data == null) - when(() => client.updateMessage( + // Create a retriable error (data == null) + when( + () => client.updateMessage( any(that: isSameMessageAs(message)), skipPush: true, - )).thenThrow(StreamChatNetworkError.raw( - code: ChatErrorCode.internalSystemError.code, - message: 'Internal system error', - )); + ), + ).thenThrow( + StreamChatNetworkError.raw( + code: ChatErrorCode.internalSystemError.code, + message: 'Internal system error', + ), + ); - expectLater( - // skipping first seed message list -> [] messages - channel.state?.messagesStream.skip(1), - emitsInOrder([ - [ - isSameMessageAs( - message.copyWith(state: MessageState.updating), - matchMessageState: true, - ), - ], - [ - isSameMessageAs( - message.copyWith( - state: MessageState.updatingFailed( - skipPush: true, - skipEnrichUrl: false, + expectLater( + // skipping first seed message list -> [] messages + channel.state?.messagesStream.skip(1), + emitsInOrder([ + [ + isSameMessageAs( + message.copyWith(state: MessageState.updating), + matchMessageState: true, + ), + ], + [ + isSameMessageAs( + message.copyWith( + state: MessageState.updatingFailed( + skipPush: true, + skipEnrichUrl: false, + ), ), + matchMessageState: true, ), - matchMessageState: true, - ), - ], - ]), - ); + ], + ]), + ); - try { - await channel.updateMessage(message, skipPush: true); - } catch (e) { - expect(e, isA()); + try { + await channel.updateMessage(message, skipPush: true); + } catch (e) { + expect(e, isA()); - final networkError = e as StreamChatNetworkError; - expect(networkError.code, - equals(ChatErrorCode.internalSystemError.code)); - expect(networkError.isRetriable, isTrue); - } - }); + final networkError = e as StreamChatNetworkError; + expect(networkError.code, equals(ChatErrorCode.internalSystemError.code)); + expect(networkError.isRetriable, isTrue); + } + }, + ); - test( - 'should handle non-retriable StreamChatNetworkError with skipPush: true, skipEnrichUrl: true', - () async { + test('should handle non-retriable StreamChatNetworkError with skipPush: true, skipEnrichUrl: true', () async { final message = Message( id: 'test-message-id-error-2', state: MessageState.sent, ); - when(() => client.updateMessage( - any(that: isSameMessageAs(message)), - skipPush: true, - skipEnrichUrl: true, - )).thenThrow(StreamChatNetworkError(ChatErrorCode.notAllowed)); + when( + () => client.updateMessage( + any(that: isSameMessageAs(message)), + skipPush: true, + skipEnrichUrl: true, + ), + ).thenThrow(StreamChatNetworkError(ChatErrorCode.notAllowed)); expectLater( // skipping first seed message list -> [] messages @@ -1751,17 +1807,17 @@ void main() { } }); - test( - 'should handle non-retriable StreamChatNetworkError with skipPush: false, skipEnrichUrl: false', - () async { + test('should handle non-retriable StreamChatNetworkError with skipPush: false, skipEnrichUrl: false', () async { final message = Message( id: 'test-message-id-error-3', state: MessageState.sent, ); - when(() => client.updateMessage( - any(that: isSameMessageAs(message)), - )).thenThrow(StreamChatNetworkError(ChatErrorCode.notAllowed)); + when( + () => client.updateMessage( + any(that: isSameMessageAs(message)), + ), + ).thenThrow(StreamChatNetworkError(ChatErrorCode.notAllowed)); expectLater( // skipping first seed message list -> [] messages @@ -1857,9 +1913,7 @@ void main() { }); group('`.partialUpdateMessage` error handling', () { - test( - 'should update message state even when error is not StreamChatNetworkError', - () async { + test('should update message state even when error is not StreamChatNetworkError', () async { final message = Message( id: 'test-message-id-error-partial-1', state: MessageState.sent, @@ -1904,7 +1958,7 @@ void main() { matchText: true, matchMessageState: true, ), - ] + ], ]), ); @@ -1920,151 +1974,154 @@ void main() { }); test( - 'should add message to retry queue when retriable StreamChatNetworkError occurs with skipEnrichUrl: true', - () async { - final message = Message( - id: 'test-message-id-retry-partial-1', - state: MessageState.sent, - ); + 'should add message to retry queue when retriable StreamChatNetworkError occurs with skipEnrichUrl: true', + () async { + final message = Message( + id: 'test-message-id-retry-partial-1', + state: MessageState.sent, + ); - // Add message to channel state first - channel.state?.updateMessage(message); + // Add message to channel state first + channel.state?.updateMessage(message); - const set = {'text': 'Update Message text'}; - const unset = ['pinExpires']; + const set = {'text': 'Update Message text'}; + const unset = ['pinExpires']; - // Create a retriable error (data == null) - when( - () => client.partialUpdateMessage( - message.id, - set: set, - unset: unset, - skipEnrichUrl: true, - ), - ).thenThrow(StreamChatNetworkError.raw( - code: ChatErrorCode.requestTimeout.code, - message: 'Request timed out', - )); + // Create a retriable error (data == null) + when( + () => client.partialUpdateMessage( + message.id, + set: set, + unset: unset, + skipEnrichUrl: true, + ), + ).thenThrow( + StreamChatNetworkError.raw( + code: ChatErrorCode.requestTimeout.code, + message: 'Request timed out', + ), + ); - expectLater( - // skipping first seed message list -> [] messages - channel.state?.messagesStream.skip(1), - emitsInOrder([ - [ - isSameMessageAs( - message.copyWith( - state: MessageState.updating, + expectLater( + // skipping first seed message list -> [] messages + channel.state?.messagesStream.skip(1), + emitsInOrder([ + [ + isSameMessageAs( + message.copyWith( + state: MessageState.updating, + ), + matchText: true, + matchMessageState: true, ), - matchText: true, - matchMessageState: true, - ), - ], - [ - isSameMessageAs( - message.copyWith( - state: MessageState.partialUpdatingFailed( - set: set, - unset: unset, - skipEnrichUrl: true, + ], + [ + isSameMessageAs( + message.copyWith( + state: MessageState.partialUpdatingFailed( + set: set, + unset: unset, + skipEnrichUrl: true, + ), ), + matchText: true, + matchMessageState: true, ), - matchText: true, - matchMessageState: true, - ), - ], - ]), - ); - - try { - await channel.partialUpdateMessage( - message, - set: set, - unset: unset, - skipEnrichUrl: true, + ], + ]), ); - } catch (e) { - expect(e, isA()); - final networkError = e as StreamChatNetworkError; - expect(networkError.code, equals(ChatErrorCode.requestTimeout.code)); - expect(networkError.isRetriable, isTrue); - } - }); + try { + await channel.partialUpdateMessage( + message, + set: set, + unset: unset, + skipEnrichUrl: true, + ); + } catch (e) { + expect(e, isA()); + + final networkError = e as StreamChatNetworkError; + expect(networkError.code, equals(ChatErrorCode.requestTimeout.code)); + expect(networkError.isRetriable, isTrue); + } + }, + ); test( - 'should add message to retry queue when retriable StreamChatNetworkError occurs with skipEnrichUrl: false', - () async { - final message = Message( - id: 'test-message-id-retry-partial-2', - state: MessageState.sent, - ); + 'should add message to retry queue when retriable StreamChatNetworkError occurs with skipEnrichUrl: false', + () async { + final message = Message( + id: 'test-message-id-retry-partial-2', + state: MessageState.sent, + ); - // Add message to channel state first - channel.state?.updateMessage(message); + // Add message to channel state first + channel.state?.updateMessage(message); - const set = {'text': 'Update Message text'}; - const unset = ['pinExpires']; + const set = {'text': 'Update Message text'}; + const unset = ['pinExpires']; - // Create a retriable error (data == null) - when( - () => client.partialUpdateMessage( - message.id, - set: set, - unset: unset, - ), - ).thenThrow(StreamChatNetworkError.raw( - code: ChatErrorCode.internalSystemError.code, - message: 'Internal system error', - )); + // Create a retriable error (data == null) + when( + () => client.partialUpdateMessage( + message.id, + set: set, + unset: unset, + ), + ).thenThrow( + StreamChatNetworkError.raw( + code: ChatErrorCode.internalSystemError.code, + message: 'Internal system error', + ), + ); - expectLater( - // skipping first seed message list -> [] messages - channel.state?.messagesStream.skip(1), - emitsInOrder([ - [ - isSameMessageAs( - message.copyWith( - state: MessageState.updating, + expectLater( + // skipping first seed message list -> [] messages + channel.state?.messagesStream.skip(1), + emitsInOrder([ + [ + isSameMessageAs( + message.copyWith( + state: MessageState.updating, + ), + matchText: true, + matchMessageState: true, ), - matchText: true, - matchMessageState: true, - ), - ], - [ - isSameMessageAs( - message.copyWith( - state: MessageState.partialUpdatingFailed( - set: set, - unset: unset, - skipEnrichUrl: false, + ], + [ + isSameMessageAs( + message.copyWith( + state: MessageState.partialUpdatingFailed( + set: set, + unset: unset, + skipEnrichUrl: false, + ), ), + matchText: true, + matchMessageState: true, ), - matchText: true, - matchMessageState: true, - ), - ], - ]), - ); - - try { - await channel.partialUpdateMessage( - message, - set: set, - unset: unset, + ], + ]), ); - } catch (e) { - expect(e, isA()); - final networkError = e as StreamChatNetworkError; - expect(networkError.code, - equals(ChatErrorCode.internalSystemError.code)); - expect(networkError.isRetriable, isTrue); - } - }); + try { + await channel.partialUpdateMessage( + message, + set: set, + unset: unset, + ); + } catch (e) { + expect(e, isA()); + + final networkError = e as StreamChatNetworkError; + expect(networkError.code, equals(ChatErrorCode.internalSystemError.code)); + expect(networkError.isRetriable, isTrue); + } + }, + ); - test( - 'should handle non-retriable StreamChatNetworkError with skipEnrichUrl: true', - () async { + test('should handle non-retriable StreamChatNetworkError with skipEnrichUrl: true', () async { final message = Message( id: 'test-message-id-error-partial-2', state: MessageState.sent, @@ -2129,9 +2186,7 @@ void main() { } }); - test( - 'should handle non-retriable StreamChatNetworkError with skipEnrichUrl: false', - () async { + test('should handle non-retriable StreamChatNetworkError with skipEnrichUrl: false', () async { final message = Message( id: 'test-message-id-error-partial-3', state: MessageState.sent, @@ -2204,8 +2259,7 @@ void main() { state: MessageState.sent, ); - when(() => client.deleteMessage(messageId)) - .thenAnswer((_) async => EmptyResponse()); + when(() => client.deleteMessage(messageId)).thenAnswer((_) async => EmptyResponse()); expectLater( // skipping first seed message list -> [] messages @@ -2271,11 +2325,9 @@ void main() { verify(() => client.deleteMessage(messageId, hard: true)).called(1); - verify(() => client.deleteImage(any(), channelId, channelType)) - .called(2); + verify(() => client.deleteImage(any(), channelId, channelType)).called(2); - verify(() => client.deleteFile(any(), channelId, channelType)) - .called(1); + verify(() => client.deleteFile(any(), channelId, channelType)).called(1); }); test( @@ -2322,8 +2374,7 @@ void main() { state: MessageState.sent, ); - when(() => client.deleteMessageForMe(messageId)) - .thenAnswer((_) async => EmptyResponse()); + when(() => client.deleteMessageForMe(messageId)).thenAnswer((_) async => EmptyResponse()); expectLater( // skipping first seed message list -> [] messages @@ -2387,19 +2438,22 @@ void main() { }); group('`.pinMessage`', () { - test('should work fine without passing timeoutOrExpirationDate', - () async { + test('should work fine without passing timeoutOrExpirationDate', () async { final message = Message(id: 'test-message-id'); - when(() => client.partialUpdateMessage( - message.id, - set: any(named: 'set'), - unset: any(named: 'unset'), - )).thenAnswer((_) async => UpdateMessageResponse() - ..message = message.copyWith( - pinned: true, - pinExpires: null, - )); + when( + () => client.partialUpdateMessage( + message.id, + set: any(named: 'set'), + unset: any(named: 'unset'), + ), + ).thenAnswer( + (_) async => UpdateMessageResponse() + ..message = message.copyWith( + pinned: true, + pinExpires: null, + ), + ); expectLater( // skipping first seed message list -> [] messages @@ -2426,11 +2480,13 @@ void main() { expect(res.message.pinned, isTrue); expect(res.message.pinExpires, isNull); - verify(() => client.partialUpdateMessage( - message.id, - set: any(named: 'set'), - unset: any(named: 'unset'), - )).called(1); + verify( + () => client.partialUpdateMessage( + message.id, + set: any(named: 'set'), + unset: any(named: 'unset'), + ), + ).called(1); }); test( @@ -2439,17 +2495,21 @@ void main() { final message = Message(id: 'test-message-id'); const timeoutOrExpirationDate = 300; // 300 seconds - when(() => client.partialUpdateMessage( - message.id, - set: any(named: 'set'), - unset: any(named: 'unset'), - )).thenAnswer((_) async => UpdateMessageResponse() - ..message = message.copyWith( - pinned: true, - pinExpires: DateTime.now().add( - const Duration(seconds: timeoutOrExpirationDate), + when( + () => client.partialUpdateMessage( + message.id, + set: any(named: 'set'), + unset: any(named: 'unset'), + ), + ).thenAnswer( + (_) async => UpdateMessageResponse() + ..message = message.copyWith( + pinned: true, + pinExpires: DateTime.now().add( + const Duration(seconds: timeoutOrExpirationDate), + ), ), - )); + ); expectLater( // skipping first seed message list -> [] messages @@ -2479,11 +2539,13 @@ void main() { expect(res.message.pinned, isTrue); expect(res.message.pinExpires, isNotNull); - verify(() => client.partialUpdateMessage( - message.id, - set: any(named: 'set'), - unset: any(named: 'unset'), - )).called(1); + verify( + () => client.partialUpdateMessage( + message.id, + set: any(named: 'set'), + unset: any(named: 'unset'), + ), + ).called(1); }, ); @@ -2491,18 +2553,21 @@ void main() { 'should work fine if passed timeoutOrExpirationDate as DateTime', () async { final message = Message(id: 'test-message-id'); - final timeoutOrExpirationDate = - DateTime.now().add(const Duration(days: 3)); // 3 days - - when(() => client.partialUpdateMessage( - message.id, - set: any(named: 'set'), - unset: any(named: 'unset'), - )).thenAnswer((_) async => UpdateMessageResponse() - ..message = message.copyWith( - pinned: true, - pinExpires: timeoutOrExpirationDate, - )); + final timeoutOrExpirationDate = DateTime.now().add(const Duration(days: 3)); // 3 days + + when( + () => client.partialUpdateMessage( + message.id, + set: any(named: 'set'), + unset: any(named: 'unset'), + ), + ).thenAnswer( + (_) async => UpdateMessageResponse() + ..message = message.copyWith( + pinned: true, + pinExpires: timeoutOrExpirationDate, + ), + ); expectLater( // skipping first seed message list -> [] messages @@ -2533,11 +2598,13 @@ void main() { expect(res.message.pinExpires, isNotNull); expect(res.message.pinExpires, timeoutOrExpirationDate.toUtc()); - verify(() => client.partialUpdateMessage( - message.id, - set: any(named: 'set'), - unset: any(named: 'unset'), - )).called(1); + verify( + () => client.partialUpdateMessage( + message.id, + set: any(named: 'set'), + unset: any(named: 'unset'), + ), + ).called(1); }, ); @@ -2562,11 +2629,12 @@ void main() { test('`.unpinMessage`', () async { final message = Message(id: 'test-message-id', pinned: true); - when(() => client.partialUpdateMessage( - message.id, - set: {'pinned': false}, - )).thenAnswer((_) async => UpdateMessageResponse() - ..message = message.copyWith(pinned: false)); + when( + () => client.partialUpdateMessage( + message.id, + set: {'pinned': false}, + ), + ).thenAnswer((_) async => UpdateMessageResponse()..message = message.copyWith(pinned: false)); expectLater( // skipping first seed message list -> [] messages @@ -2592,10 +2660,12 @@ void main() { expect(res, isNotNull); expect(res.message.pinned, isFalse); - verify(() => client.partialUpdateMessage( - message.id, - set: {'pinned': false}, - )).called(1); + verify( + () => client.partialUpdateMessage( + message.id, + set: {'pinned': false}, + ), + ).called(1); }); group('`.search`', () { @@ -2608,12 +2678,14 @@ void main() { final results = List.generate(3, (index) => GetMessageResponse()); - when(() => client.search( - filter, - query: query, - sort: any(named: 'sort'), - paginationParams: any(named: 'paginationParams'), - )).thenAnswer( + when( + () => client.search( + filter, + query: query, + sort: any(named: 'sort'), + paginationParams: any(named: 'paginationParams'), + ), + ).thenAnswer( (_) async => SearchMessagesResponse()..results = results, ); @@ -2626,12 +2698,14 @@ void main() { expect(res, isNotNull); expect(res.results.length, results.length); - verify(() => client.search( - filter, - query: query, - sort: any(named: 'sort'), - paginationParams: any(named: 'paginationParams'), - )).called(1); + verify( + () => client.search( + filter, + query: query, + sort: any(named: 'sort'), + paginationParams: any(named: 'paginationParams'), + ), + ).called(1); }); test('should work fine with `messageFilters`', () async { @@ -2641,12 +2715,14 @@ void main() { final results = List.generate(3, (index) => GetMessageResponse()); - when(() => client.search( - filter, - messageFilters: messageFilters, - sort: any(named: 'sort'), - paginationParams: any(named: 'paginationParams'), - )).thenAnswer( + when( + () => client.search( + filter, + messageFilters: messageFilters, + sort: any(named: 'sort'), + paginationParams: any(named: 'paginationParams'), + ), + ).thenAnswer( (_) async => SearchMessagesResponse()..results = results, ); @@ -2659,70 +2735,73 @@ void main() { expect(res, isNotNull); expect(res.results.length, results.length); - verify(() => client.search( - filter, - messageFilters: messageFilters, - sort: any(named: 'sort'), - paginationParams: any(named: 'paginationParams'), - )).called(1); + verify( + () => client.search( + filter, + messageFilters: messageFilters, + sort: any(named: 'sort'), + paginationParams: any(named: 'paginationParams'), + ), + ).called(1); }); }); test('`.deleteFile`', () async { const url = 'test-file-url'; - when(() => client.deleteFile(url, channelId, channelType, - cancelToken: any(named: 'cancelToken'))) - .thenAnswer((_) async => EmptyResponse()); + when( + () => client.deleteFile(url, channelId, channelType, cancelToken: any(named: 'cancelToken')), + ).thenAnswer((_) async => EmptyResponse()); final res = await channel.deleteFile(url); expect(res, isNotNull); - verify(() => client.deleteFile(url, channelId, channelType, - cancelToken: any(named: 'cancelToken'))).called(1); + verify(() => client.deleteFile(url, channelId, channelType, cancelToken: any(named: 'cancelToken'))).called(1); }); test('`.deleteImage`', () async { const url = 'test-image-url'; - when(() => client.deleteImage(url, channelId, channelType, - cancelToken: any(named: 'cancelToken'))) - .thenAnswer((_) async => EmptyResponse()); + when( + () => client.deleteImage(url, channelId, channelType, cancelToken: any(named: 'cancelToken')), + ).thenAnswer((_) async => EmptyResponse()); final res = await channel.deleteImage(url); expect(res, isNotNull); - verify(() => client.deleteImage(url, channelId, channelType, - cancelToken: any(named: 'cancelToken'))).called(1); + verify(() => client.deleteImage(url, channelId, channelType, cancelToken: any(named: 'cancelToken'))).called(1); }); test('`.stopAIResponse`', () async { final stopAIEvent = Event(type: EventType.aiIndicatorStop); - when(() => client.sendEvent( - channelId, - channelType, - any(that: isSameEventAs(stopAIEvent)), - )).thenAnswer((_) async => EmptyResponse()); + when( + () => client.sendEvent( + channelId, + channelType, + any(that: isSameEventAs(stopAIEvent)), + ), + ).thenAnswer((_) async => EmptyResponse()); final res = await channel.stopAIResponse(); expect(res, isNotNull); - verify(() => client.sendEvent( - channelId, - channelType, - any(that: isSameEventAs(stopAIEvent)), - )).called(1); + verify( + () => client.sendEvent( + channelId, + channelType, + any(that: isSameEventAs(stopAIEvent)), + ), + ).called(1); }); test('`.sendEvent`', () async { final event = Event(type: 'event.local'); - when(() => client.sendEvent(channelId, channelType, event)) - .thenAnswer((_) async => EmptyResponse()); + when(() => client.sendEvent(channelId, channelType, event)).thenAnswer((_) async => EmptyResponse()); final res = await channel.sendEvent(event); @@ -2801,8 +2880,9 @@ void main() { user: client.state.currentUser, ); - when(() => client.sendReaction(message.id, reaction)) - .thenThrow(StreamChatNetworkError(ChatErrorCode.inputError)); + when( + () => client.sendReaction(message.id, reaction), + ).thenThrow(StreamChatNetworkError(ChatErrorCode.inputError)); expectLater( // skipping first seed message list -> [] messages @@ -2816,7 +2896,7 @@ void main() { type: ReactionGroup( count: 1, sumScores: 1, - ) + ), }, latestReactions: [reaction], ownReactions: [reaction], @@ -2863,7 +2943,7 @@ void main() { prevType: ReactionGroup( count: 1, sumScores: 1, - ) + ), }, state: MessageState.sent, ); @@ -2881,11 +2961,13 @@ void main() { const enforceUnique = true; - when(() => client.sendReaction( - messageId, - newReaction, - enforceUnique: enforceUnique, - )).thenAnswer( + when( + () => client.sendReaction( + messageId, + newReaction, + enforceUnique: enforceUnique, + ), + ).thenAnswer( (_) async => SendReactionResponse() ..message = newMessage ..reaction = newReaction, @@ -2915,11 +2997,13 @@ void main() { expect(res.reaction.type, type); expect(res.reaction.messageId, messageId); - verify(() => client.sendReaction( - messageId, - newReaction, - enforceUnique: enforceUnique, - )).called(1); + verify( + () => client.sendReaction( + messageId, + newReaction, + enforceUnique: enforceUnique, + ), + ).called(1); }, ); }); @@ -2959,7 +3043,7 @@ void main() { type: ReactionGroup( count: 1, sumScores: 1, - ) + ), }, latestReactions: [reaction], ownReactions: [reaction], @@ -2997,14 +3081,13 @@ void main() { user: client.state.currentUser, ); - when(() => client.sendReaction(message.id, reaction)) - .thenThrow(StreamChatNetworkError(ChatErrorCode.inputError)); + when( + () => client.sendReaction(message.id, reaction), + ).thenThrow(StreamChatNetworkError(ChatErrorCode.inputError)); expectLater( // skipping first seed message list -> [] messages - channel.state?.threadsStream - .skip(1) - .map((event) => event['test-parent-id']), + channel.state?.threadsStream.skip(1).map((event) => event['test-parent-id']), emitsInOrder([ [ isSameMessageAs( @@ -3014,7 +3097,7 @@ void main() { type: ReactionGroup( count: 1, sumScores: 1, - ) + ), }, latestReactions: [reaction], ownReactions: [reaction], @@ -3065,7 +3148,7 @@ void main() { prevType: ReactionGroup( count: 1, sumScores: 1, - ) + ), }, state: MessageState.sent, ); @@ -3083,11 +3166,13 @@ void main() { const enforceUnique = true; - when(() => client.sendReaction( - messageId, - newReaction, - enforceUnique: enforceUnique, - )).thenAnswer( + when( + () => client.sendReaction( + messageId, + newReaction, + enforceUnique: enforceUnique, + ), + ).thenAnswer( (_) async => SendReactionResponse() ..message = newMessage ..reaction = newReaction, @@ -3095,9 +3180,7 @@ void main() { expectLater( // skipping first seed message list -> [] messages - channel.state?.threadsStream - .skip(1) - .map((event) => event['test-parent-id']), + channel.state?.threadsStream.skip(1).map((event) => event['test-parent-id']), emitsInOrder([ [ isSameMessageAs( @@ -3120,11 +3203,13 @@ void main() { expect(res.reaction.type, type); expect(res.reaction.messageId, messageId); - verify(() => client.sendReaction( - messageId, - newReaction, - enforceUnique: enforceUnique, - )).called(1); + verify( + () => client.sendReaction( + messageId, + newReaction, + enforceUnique: enforceUnique, + ), + ).called(1); }, ); }); @@ -3147,13 +3232,12 @@ void main() { type: ReactionGroup( count: 1, sumScores: 1, - ) + ), }, state: MessageState.sent, ); - when(() => client.deleteReaction(messageId, type)) - .thenAnswer((_) async => EmptyResponse()); + when(() => client.deleteReaction(messageId, type)).thenAnswer((_) async => EmptyResponse()); expectLater( // skipping first seed message list -> [] messages @@ -3199,13 +3283,14 @@ void main() { type: ReactionGroup( count: 1, sumScores: 1, - ) + ), }, state: MessageState.sent, ); - when(() => client.deleteReaction(messageId, type)) - .thenThrow(StreamChatNetworkError(ChatErrorCode.inputError)); + when( + () => client.deleteReaction(messageId, type), + ).thenThrow(StreamChatNetworkError(ChatErrorCode.inputError)); expectLater( // skipping first seed message list -> [] messages @@ -3264,19 +3349,16 @@ void main() { type: ReactionGroup( count: 1, sumScores: 1, - ) + ), }, state: MessageState.sent, ); - when(() => client.deleteReaction(messageId, type)) - .thenAnswer((_) async => EmptyResponse()); + when(() => client.deleteReaction(messageId, type)).thenAnswer((_) async => EmptyResponse()); expectLater( // skipping first seed message list -> [] messages - channel.state?.threadsStream - .skip(1) - .map((event) => event['test-parent-id']), + channel.state?.threadsStream.skip(1).map((event) => event['test-parent-id']), emitsInOrder([ [ isSameMessageAs( @@ -3321,19 +3403,18 @@ void main() { type: ReactionGroup( count: 1, sumScores: 1, - ) + ), }, state: MessageState.sent, ); - when(() => client.deleteReaction(messageId, type)) - .thenThrow(StreamChatNetworkError(ChatErrorCode.inputError)); + when( + () => client.deleteReaction(messageId, type), + ).thenThrow(StreamChatNetworkError(ChatErrorCode.inputError)); expectLater( // skipping first seed message list -> [] messages - channel.state?.threadsStream - .skip(1) - .map((event) => event['test-parent-id']), + channel.state?.threadsStream.skip(1).map((event) => event['test-parent-id']), emitsInOrder([ [ isSameMessageAs( @@ -3384,8 +3465,7 @@ void main() { extraData: channelData, ); - when(() => client.updateChannel(channelId, channelType, channelData, - message: any(named: 'message'))).thenAnswer( + when(() => client.updateChannel(channelId, channelType, channelData, message: any(named: 'message'))).thenAnswer( (_) async => UpdateChannelResponse() ..channel = channelModel ..message = updateMessage, @@ -3401,8 +3481,7 @@ void main() { expect(res.channel.extraData, channelData); expect(res.message?.id, updateMessage.id); - verify(() => client.updateChannel(channelId, channelType, channelData, - message: any(named: 'message'))).called(1); + verify(() => client.updateChannel(channelId, channelType, channelData, message: any(named: 'message'))).called(1); }); test('`.updateImage`', () async { @@ -3413,11 +3492,13 @@ void main() { extraData: {'image': image}, ); - when(() => client.updateChannelPartial( - channelId, - channelType, - set: {'image': image}, - )).thenAnswer( + when( + () => client.updateChannelPartial( + channelId, + channelType, + set: {'image': image}, + ), + ).thenAnswer( (_) async => PartialUpdateChannelResponse()..channel = channelModel, ); @@ -3426,11 +3507,13 @@ void main() { expect(res, isNotNull); expect(res.channel.extraData['image'], image); - verify(() => client.updateChannelPartial( - channelId, - channelType, - set: {'image': image}, - )).called(1); + verify( + () => client.updateChannelPartial( + channelId, + channelType, + set: {'image': image}, + ), + ).called(1); }); test('`.updateName`', () async { @@ -3441,11 +3524,13 @@ void main() { extraData: {'name': name}, ); - when(() => client.updateChannelPartial( - channelId, - channelType, - set: {'name': name}, - )).thenAnswer( + when( + () => client.updateChannelPartial( + channelId, + channelType, + set: {'name': name}, + ), + ).thenAnswer( (_) async => PartialUpdateChannelResponse()..channel = channelModel, ); @@ -3454,11 +3539,13 @@ void main() { expect(res, isNotNull); expect(res.channel.extraData['name'], name); - verify(() => client.updateChannelPartial( - channelId, - channelType, - set: {'name': name}, - )).called(1); + verify( + () => client.updateChannelPartial( + channelId, + channelType, + set: {'name': name}, + ), + ).called(1); }); test('`.updatePartial`', () async { @@ -3477,12 +3564,14 @@ void main() { }, ); - when(() => client.updateChannelPartial( - channelId, - channelType, - set: set, - unset: unset, - )).thenAnswer( + when( + () => client.updateChannelPartial( + channelId, + channelType, + set: set, + unset: unset, + ), + ).thenAnswer( (_) async => PartialUpdateChannelResponse()..channel = channelModel, ); @@ -3495,17 +3584,18 @@ void main() { {'coolness': 999, ...set}, ); - verify(() => client.updateChannelPartial( - channelId, - channelType, - set: set, - unset: unset, - )).called(1); + verify( + () => client.updateChannelPartial( + channelId, + channelType, + set: set, + unset: unset, + ), + ).called(1); }); test('`.delete`', () async { - when(() => client.deleteChannel(channelId, channelType)) - .thenAnswer((_) async => EmptyResponse()); + when(() => client.deleteChannel(channelId, channelType)).thenAnswer((_) async => EmptyResponse()); final res = await channel.delete(); @@ -3515,8 +3605,7 @@ void main() { }); test('`.truncate`', () async { - when(() => client.truncateChannel(channelId, channelType)) - .thenAnswer((_) async => EmptyResponse()); + when(() => client.truncateChannel(channelId, channelType)).thenAnswer((_) async => EmptyResponse()); final res = await channel.truncate(); @@ -3530,8 +3619,7 @@ void main() { final channelModel = ChannelModel(cid: channelCid); - when(() => client.acceptChannelInvite(channelId, channelType, - message: any(named: 'message'))).thenAnswer( + when(() => client.acceptChannelInvite(channelId, channelType, message: any(named: 'message'))).thenAnswer( (_) async => AcceptInviteResponse() ..channel = channelModel ..message = message, @@ -3543,8 +3631,7 @@ void main() { expect(res.channel.cid, channelModel.cid); expect(res.message?.id, message.id); - verify(() => client.acceptChannelInvite(channelId, channelType, - message: any(named: 'message'))).called(1); + verify(() => client.acceptChannelInvite(channelId, channelType, message: any(named: 'message'))).called(1); }); test('`.rejectInvite`', () async { @@ -3552,8 +3639,7 @@ void main() { final channelModel = ChannelModel(cid: channelCid); - when(() => client.rejectChannelInvite(channelId, channelType, - message: any(named: 'message'))).thenAnswer( + when(() => client.rejectChannelInvite(channelId, channelType, message: any(named: 'message'))).thenAnswer( (_) async => RejectInviteResponse() ..channel = channelModel ..message = message, @@ -3565,8 +3651,7 @@ void main() { expect(res.channel.cid, channelModel.cid); expect(res.message?.id, message.id); - verify(() => client.rejectChannelInvite(channelId, channelType, - message: any(named: 'message'))).called(1); + verify(() => client.rejectChannelInvite(channelId, channelType, message: any(named: 'message'))).called(1); }); test('`.addMembers`', () async { @@ -3574,16 +3659,14 @@ void main() { 3, (index) => Member(userId: 'test-member-id-$index'), ); - final memberIds = members - .map((it) => it.userId) - .whereType() - .toList(growable: false); + final memberIds = members.map((it) => it.userId).whereType().toList(growable: false); final message = Message(id: 'test-message-id', text: 'Members Added'); final channelModel = ChannelModel(cid: channelCid); - when(() => client.addChannelMembers(channelId, channelType, memberIds, - message: any(named: 'message'))).thenAnswer( + when( + () => client.addChannelMembers(channelId, channelType, memberIds, message: any(named: 'message')), + ).thenAnswer( (_) async => AddMembersResponse() ..channel = channelModel ..members = members @@ -3597,8 +3680,9 @@ void main() { expect(res.members.length, members.length); expect(res.message?.id, message.id); - verify(() => client.addChannelMembers(channelId, channelType, memberIds, - message: any(named: 'message'))).called(1); + verify( + () => client.addChannelMembers(channelId, channelType, memberIds, message: any(named: 'message')), + ).called(1); }); test('`.addMembers` with hideHistoryBefore', () async { @@ -3606,22 +3690,21 @@ void main() { 3, (index) => Member(userId: 'test-member-id-$index'), ); - final memberIds = members - .map((it) => it.userId) - .whereType() - .toList(growable: false); + final memberIds = members.map((it) => it.userId).whereType().toList(growable: false); final message = Message(id: 'test-message-id', text: 'Members Added'); final hideHistoryBefore = DateTime.parse('2024-01-01T00:00:00Z'); final channelModel = ChannelModel(cid: channelCid); - when(() => client.addChannelMembers( - channelId, - channelType, - memberIds, - message: message, - hideHistoryBefore: hideHistoryBefore, - )).thenAnswer( + when( + () => client.addChannelMembers( + channelId, + channelType, + memberIds, + message: message, + hideHistoryBefore: hideHistoryBefore, + ), + ).thenAnswer( (_) async => AddMembersResponse() ..channel = channelModel ..members = members @@ -3639,13 +3722,15 @@ void main() { expect(res.members.length, members.length); expect(res.message?.id, message.id); - verify(() => client.addChannelMembers( - channelId, - channelType, - memberIds, - message: message, - hideHistoryBefore: hideHistoryBefore, - )).called(1); + verify( + () => client.addChannelMembers( + channelId, + channelType, + memberIds, + message: message, + hideHistoryBefore: hideHistoryBefore, + ), + ).called(1); }); test('`.inviteMembers`', () async { @@ -3653,16 +3738,14 @@ void main() { 3, (index) => Member(userId: 'test-member-id-$index'), ); - final memberIds = members - .map((it) => it.userId) - .whereType() - .toList(growable: false); + final memberIds = members.map((it) => it.userId).whereType().toList(growable: false); final message = Message(id: 'test-message-id', text: 'Members Invited'); final channelModel = ChannelModel(cid: channelCid); - when(() => client.inviteChannelMembers(channelId, channelType, memberIds, - message: any(named: 'message'))).thenAnswer( + when( + () => client.inviteChannelMembers(channelId, channelType, memberIds, message: any(named: 'message')), + ).thenAnswer( (_) async => InviteMembersResponse() ..channel = channelModel ..members = members @@ -3676,9 +3759,9 @@ void main() { expect(res.members.length, members.length); expect(res.message?.id, message.id); - verify(() => client.inviteChannelMembers( - channelId, channelType, memberIds, - message: any(named: 'message'))).called(1); + verify( + () => client.inviteChannelMembers(channelId, channelType, memberIds, message: any(named: 'message')), + ).called(1); }); test('`.removeMembers`', () async { @@ -3686,16 +3769,14 @@ void main() { 3, (index) => Member(userId: 'test-member-id-$index'), ); - final memberIds = members - .map((it) => it.userId) - .whereType() - .toList(growable: false); + final memberIds = members.map((it) => it.userId).whereType().toList(growable: false); final message = Message(id: 'test-message-id', text: 'Members Removed'); final channelModel = ChannelModel(cid: channelCid); - when(() => client.removeChannelMembers(channelId, channelType, memberIds, - message: any(named: 'message'))).thenAnswer( + when( + () => client.removeChannelMembers(channelId, channelType, memberIds, message: any(named: 'message')), + ).thenAnswer( (_) async => RemoveMembersResponse() ..channel = channelModel ..members = members @@ -3709,9 +3790,9 @@ void main() { expect(res.members.length, members.length); expect(res.message?.id, message.id); - verify(() => client.removeChannelMembers( - channelId, channelType, memberIds, - message: any(named: 'message'))).called(1); + verify( + () => client.removeChannelMembers(channelId, channelType, memberIds, message: any(named: 'message')), + ).called(1); }); group('`.sendAction`', () { @@ -3766,15 +3847,17 @@ void main() { group('`.watch`', () { test('should work fine', () async { - when(() => client.queryChannel( - channelType, - channelId: channelId, - watch: true, - channelData: any(named: 'channelData'), - messagesPagination: any(named: 'messagesPagination'), - membersPagination: any(named: 'membersPagination'), - watchersPagination: any(named: 'watchersPagination'), - )).thenAnswer( + when( + () => client.queryChannel( + channelType, + channelId: channelId, + watch: true, + channelData: any(named: 'channelData'), + messagesPagination: any(named: 'messagesPagination'), + membersPagination: any(named: 'membersPagination'), + watchersPagination: any(named: 'watchersPagination'), + ), + ).thenAnswer( (_) async => _generateChannelState(channelId, channelType), ); @@ -3784,27 +3867,31 @@ void main() { expect(res.channel, isNotNull); expect(res.channel?.cid, channelCid); - verify(() => client.queryChannel( - channelType, - channelId: channelId, - watch: true, - channelData: any(named: 'channelData'), - messagesPagination: any(named: 'messagesPagination'), - membersPagination: any(named: 'membersPagination'), - watchersPagination: any(named: 'watchersPagination'), - )).called(1); + verify( + () => client.queryChannel( + channelType, + channelId: channelId, + watch: true, + channelData: any(named: 'channelData'), + messagesPagination: any(named: 'messagesPagination'), + membersPagination: any(named: 'membersPagination'), + watchersPagination: any(named: 'watchersPagination'), + ), + ).called(1); }); test('should rethrow if `.query` throws', () async { - when(() => client.queryChannel( - channelType, - channelId: channelId, - watch: true, - channelData: any(named: 'channelData'), - messagesPagination: any(named: 'messagesPagination'), - membersPagination: any(named: 'membersPagination'), - watchersPagination: any(named: 'watchersPagination'), - )).thenThrow(StreamChatNetworkError(ChatErrorCode.inputError)); + when( + () => client.queryChannel( + channelType, + channelId: channelId, + watch: true, + channelData: any(named: 'channelData'), + messagesPagination: any(named: 'messagesPagination'), + membersPagination: any(named: 'membersPagination'), + watchersPagination: any(named: 'watchersPagination'), + ), + ).thenThrow(StreamChatNetworkError(ChatErrorCode.inputError)); try { await channel.watch(); @@ -3812,28 +3899,28 @@ void main() { expect(e, isA()); } - verify(() => client.queryChannel( - channelType, - channelId: channelId, - watch: true, - channelData: any(named: 'channelData'), - messagesPagination: any(named: 'messagesPagination'), - membersPagination: any(named: 'membersPagination'), - watchersPagination: any(named: 'watchersPagination'), - )).called(1); + verify( + () => client.queryChannel( + channelType, + channelId: channelId, + watch: true, + channelData: any(named: 'channelData'), + messagesPagination: any(named: 'messagesPagination'), + membersPagination: any(named: 'membersPagination'), + watchersPagination: any(named: 'watchersPagination'), + ), + ).called(1); }); }); test('`.stopWatching`', () async { - when(() => client.stopChannelWatching(channelId, channelType)) - .thenAnswer((_) async => EmptyResponse()); + when(() => client.stopChannelWatching(channelId, channelType)).thenAnswer((_) async => EmptyResponse()); final res = await channel.stopWatching(); expect(res, isNotNull); - verify(() => client.stopChannelWatching(channelId, channelType)) - .called(1); + verify(() => client.stopChannelWatching(channelId, channelType)).called(1); }); test('`.getReplies`', () async { @@ -3892,8 +3979,7 @@ void main() { final messageIds = messages.map((it) => it.id).toList(growable: false); - when(() => client.getMessagesById(channelId, channelType, messageIds)) - .thenAnswer( + when(() => client.getMessagesById(channelId, channelType, messageIds)).thenAnswer( (_) async => GetMessagesByIdResponse()..messages = messages, ); @@ -4006,16 +4092,19 @@ void main() { channel.state!.updateChannelState(stateWithMessages); expect(channel.state!.messages, hasLength(3)); - final newState = _generateChannelState( - channelId, - channelType, - ).copyWith(messages: [ - Message(id: 'msg-before-1', text: 'Message before 1'), - Message(id: 'msg-before-2', text: 'Message before 2'), - Message(id: 'target-message-id', text: 'Target message'), - Message(id: 'msg-after-1', text: 'Message after 1'), - Message(id: 'msg-after-2', text: 'Message after 2'), - ]); + final newState = + _generateChannelState( + channelId, + channelType, + ).copyWith( + messages: [ + Message(id: 'msg-before-1', text: 'Message before 1'), + Message(id: 'msg-before-2', text: 'Message before 2'), + Message(id: 'target-message-id', text: 'Target message'), + Message(id: 'msg-after-1', text: 'Message after 1'), + Message(id: 'msg-after-2', text: 'Message after 2'), + ], + ); when( () => client.queryChannel( @@ -4064,16 +4153,19 @@ void main() { expect(channel.state!.messages, hasLength(3)); final targetDate = DateTime.now(); - final newState = _generateChannelState( - channelId, - channelType, - ).copyWith(messages: [ - Message(id: 'msg-before-1', text: 'Message before 1'), - Message(id: 'msg-before-2', text: 'Message before 2'), - Message(id: 'target-message', text: 'Target message'), - Message(id: 'msg-after-1', text: 'Message after 1'), - Message(id: 'msg-after-2', text: 'Message after 2'), - ]); + final newState = + _generateChannelState( + channelId, + channelType, + ).copyWith( + messages: [ + Message(id: 'msg-before-1', text: 'Message before 1'), + Message(id: 'msg-before-2', text: 'Message before 2'), + Message(id: 'target-message', text: 'Target message'), + Message(id: 'msg-after-1', text: 'Message after 1'), + Message(id: 'msg-after-2', text: 'Message after 2'), + ], + ); when( () => client.queryChannel( @@ -4172,28 +4264,32 @@ void main() { (index) => Member(userId: 'test-user-id-$index'), ); - when(() => client.queryMembers( - channelType, - channelId: channelId, - filter: filter, - members: any(named: 'members'), - sort: any(named: 'sort'), - pagination: any(named: 'pagination'), - )).thenAnswer((_) async => QueryMembersResponse()..members = members); + when( + () => client.queryMembers( + channelType, + channelId: channelId, + filter: filter, + members: any(named: 'members'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenAnswer((_) async => QueryMembersResponse()..members = members); final res = await channel.queryMembers(filter: filter); expect(res, isNotNull); expect(res.members.length, members.length); - verify(() => client.queryMembers( - channelType, - channelId: channelId, - filter: filter, - members: any(named: 'members'), - sort: any(named: 'sort'), - pagination: any(named: 'pagination'), - )).called(1); + verify( + () => client.queryMembers( + channelType, + channelId: channelId, + filter: filter, + members: any(named: 'members'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).called(1); }); test('`.queryBannedUsers`', () async { @@ -4207,59 +4303,70 @@ void main() { ), ); - when(() => client.queryBannedUsers( - filter: filter, - sort: any(named: 'sort'), - pagination: any(named: 'pagination'), - )).thenAnswer((_) async => QueryBannedUsersResponse()..bans = bans); + when( + () => client.queryBannedUsers( + filter: filter, + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenAnswer((_) async => QueryBannedUsersResponse()..bans = bans); final res = await channel.queryBannedUsers(); expect(res, isNotNull); expect(res.bans.length, bans.length); - verify(() => client.queryBannedUsers( - filter: filter, - sort: any(named: 'sort'), - pagination: any(named: 'pagination'), - )).called(1); + verify( + () => client.queryBannedUsers( + filter: filter, + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).called(1); }); test('`.mute`', () async { - when(() => client.muteChannel( - channelCid, - expiration: any(named: 'expiration'), - )).thenAnswer((_) async => EmptyResponse()); + when( + () => client.muteChannel( + channelCid, + expiration: any(named: 'expiration'), + ), + ).thenAnswer((_) async => EmptyResponse()); final res = await channel.mute(); expect(res, isNotNull); - verify(() => client.muteChannel( - channelCid, - expiration: any(named: 'expiration'), - )).called(1); + verify( + () => client.muteChannel( + channelCid, + expiration: any(named: 'expiration'), + ), + ).called(1); }); test('`.mute with expiration`', () async { const expiration = Duration(seconds: 3); - when(() => client.muteChannel( - channelCid, - expiration: expiration, - )).thenAnswer((_) async => EmptyResponse()); + when( + () => client.muteChannel( + channelCid, + expiration: expiration, + ), + ).thenAnswer((_) async => EmptyResponse()); - when(() => client.unmuteChannel(channelCid)) - .thenAnswer((_) async => EmptyResponse()); + when(() => client.unmuteChannel(channelCid)).thenAnswer((_) async => EmptyResponse()); final res = await channel.mute(expiration: expiration); expect(res, isNotNull); - verify(() => client.muteChannel( - channelCid, - expiration: expiration, - )).called(1); + verify( + () => client.muteChannel( + channelCid, + expiration: expiration, + ), + ).called(1); // wait for expiration await Future.delayed(expiration); @@ -4288,22 +4395,25 @@ void main() { cooldown: cooldown, ); - when(() => client.enableSlowdown( - channelId, - channelType, - cooldown, - )).thenAnswer((_) async => PartialUpdateChannelResponse() - ..channel = channelModel); + when( + () => client.enableSlowdown( + channelId, + channelType, + cooldown, + ), + ).thenAnswer((_) async => PartialUpdateChannelResponse()..channel = channelModel); final res = await channel.enableSlowMode(cooldownInterval: 10); expect(res, isNotNull); - verify(() => client.enableSlowdown( - channelId, - channelType, - cooldown, - )).called(1); + verify( + () => client.enableSlowdown( + channelId, + channelType, + cooldown, + ), + ).called(1); }); test('`.disableSlowMode`', () async { @@ -4311,11 +4421,12 @@ void main() { cid: channelCid, ); - when(() => client.disableSlowdown( - channelId, - channelType, - )).thenAnswer((_) async => PartialUpdateChannelResponse() - ..channel = channelModel); + when( + () => client.disableSlowdown( + channelId, + channelType, + ), + ).thenAnswer((_) async => PartialUpdateChannelResponse()..channel = channelModel); final res = await channel.disableSlowMode(); @@ -4328,26 +4439,29 @@ void main() { const userId = 'test-user-id'; const options = {'key': 'value'}; - when(() => client.banUser( - userId, - {'type': channelType, 'id': channelId, ...options}, - )).thenAnswer((_) async => EmptyResponse()); + when( + () => client.banUser( + userId, + {'type': channelType, 'id': channelId, ...options}, + ), + ).thenAnswer((_) async => EmptyResponse()); final res = await channel.banMember(userId, options); expect(res, isNotNull); - verify(() => client.banUser( - userId, - {'type': channelType, 'id': channelId, ...options}, - )).called(1); + verify( + () => client.banUser( + userId, + {'type': channelType, 'id': channelId, ...options}, + ), + ).called(1); }); test('`.unbanUser`', () async { const userId = 'test-user-id'; - when(() => client.unbanUser(userId, any())) - .thenAnswer((_) async => EmptyResponse()); + when(() => client.unbanUser(userId, any())).thenAnswer((_) async => EmptyResponse()); final res = await channel.unbanMember(userId); @@ -4360,26 +4474,29 @@ void main() { const userId = 'test-user-id'; const options = {'key': 'value'}; - when(() => client.shadowBan( - userId, - {'type': channelType, 'id': channelId, ...options}, - )).thenAnswer((_) async => EmptyResponse()); + when( + () => client.shadowBan( + userId, + {'type': channelType, 'id': channelId, ...options}, + ), + ).thenAnswer((_) async => EmptyResponse()); final res = await channel.shadowBan(userId, options); expect(res, isNotNull); - verify(() => client.shadowBan( - userId, - {'type': channelType, 'id': channelId, ...options}, - )).called(1); + verify( + () => client.shadowBan( + userId, + {'type': channelType, 'id': channelId, ...options}, + ), + ).called(1); }); test('`.removeShadowBan`', () async { const userId = 'test-user-id'; - when(() => client.removeShadowBan(userId, any())) - .thenAnswer((_) async => EmptyResponse()); + when(() => client.removeShadowBan(userId, any())).thenAnswer((_) async => EmptyResponse()); final res = await channel.removeShadowBan(userId); @@ -4391,26 +4508,29 @@ void main() { test('`.hide`', () async { const clearHistory = true; - when(() => client.hideChannel( - channelId, - channelType, - clearHistory: clearHistory, - )).thenAnswer((_) async => EmptyResponse()); + when( + () => client.hideChannel( + channelId, + channelType, + clearHistory: clearHistory, + ), + ).thenAnswer((_) async => EmptyResponse()); final res = await channel.hide(clearHistory: clearHistory); expect(res, isNotNull); - verify(() => client.hideChannel( - channelId, - channelType, - clearHistory: clearHistory, - )).called(1); + verify( + () => client.hideChannel( + channelId, + channelType, + clearHistory: clearHistory, + ), + ).called(1); }); test('`.show`', () async { - when(() => client.showChannel(channelId, channelType)) - .thenAnswer((_) async => EmptyResponse()); + when(() => client.showChannel(channelId, channelType)).thenAnswer((_) async => EmptyResponse()); final res = await channel.show(); @@ -4421,8 +4541,7 @@ void main() { // testing archiving test('`.archive`', () async { - when(() => client.archiveChannel( - channelId: channelId, channelType: channelType)).thenAnswer( + when(() => client.archiveChannel(channelId: channelId, channelType: channelType)).thenAnswer( (_) async => FakePartialUpdateMemberResponse(), ); @@ -4430,13 +4549,11 @@ void main() { expect(res, isNotNull); - verify(() => client.archiveChannel( - channelId: channelId, channelType: channelType)).called(1); + verify(() => client.archiveChannel(channelId: channelId, channelType: channelType)).called(1); }); test('`.unarchive`', () async { - when(() => client.unarchiveChannel( - channelId: channelId, channelType: channelType)).thenAnswer( + when(() => client.unarchiveChannel(channelId: channelId, channelType: channelType)).thenAnswer( (_) async => FakePartialUpdateMemberResponse(), ); @@ -4444,36 +4561,32 @@ void main() { expect(res, isNotNull); - verify(() => client.unarchiveChannel( - channelId: channelId, channelType: channelType)).called(1); + verify(() => client.unarchiveChannel(channelId: channelId, channelType: channelType)).called(1); }); // testing pinning test('`.pin`', () async { - when(() => - client.pinChannel(channelId: channelId, channelType: channelType)) - .thenAnswer((_) async => FakePartialUpdateMemberResponse()); + when( + () => client.pinChannel(channelId: channelId, channelType: channelType), + ).thenAnswer((_) async => FakePartialUpdateMemberResponse()); final res = await channel.pin(); expect(res, isNotNull); - verify(() => - client.pinChannel(channelId: channelId, channelType: channelType)) - .called(1); + verify(() => client.pinChannel(channelId: channelId, channelType: channelType)).called(1); }); test('`.unpin`', () async { - when(() => client.unpinChannel( - channelId: channelId, channelType: channelType)) - .thenAnswer((_) async => FakePartialUpdateMemberResponse()); + when( + () => client.unpinChannel(channelId: channelId, channelType: channelType), + ).thenAnswer((_) async => FakePartialUpdateMemberResponse()); final res = await channel.unpin(); expect(res, isNotNull); - verify(() => client.unpinChannel( - channelId: channelId, channelType: channelType)).called(1); + verify(() => client.unpinChannel(channelId: channelId, channelType: channelType)).called(1); }); test('`.on`', () async { @@ -4510,9 +4623,9 @@ void main() { // Set up the mock response for sending message final newMessage = Message(text: 'New message'); - when(() => client.sendMessage(any(), channelId, channelType)) - .thenAnswer((_) async => SendMessageResponse() - ..message = newMessage.copyWith(state: MessageState.sent)); + when( + () => client.sendMessage(any(), channelId, channelType), + ).thenAnswer((_) async => SendMessageResponse()..message = newMessage.copyWith(state: MessageState.sent)); // Send a new message await channel.sendMessage(newMessage); @@ -5341,8 +5454,7 @@ void main() { }, ); - test('should update read state on notification mark unread event', - () async { + test('should update read state on notification mark unread event', () async { // Create the current read state final currentUser = User(id: 'test-user'); final currentRead = Read( @@ -5570,8 +5682,7 @@ void main() { 'should add a new read state if not exist on message delivered event', () async { final newUser = User(id: 'new-user'); - final distantPast = - DateTime.fromMillisecondsSinceEpoch(0, isUtc: true); + final distantPast = DateTime.fromMillisecondsSinceEpoch(0, isUtc: true); // Verify initial state final read = channel.state?.read; @@ -6123,8 +6234,7 @@ void main() { expect(updatedMessage?.reminder, isNull); }); - test('should handle reminder.created event for thread messages', - () async { + test('should handle reminder.created event for thread messages', () async { const messageId = 'test-message-id'; const parentId = 'test-parent-id'; @@ -6175,8 +6285,7 @@ void main() { expect(updatedMessage?.reminder?.remindAt, reminder.remindAt); }); - test('should handle reminder.updated event for thread messages', - () async { + test('should handle reminder.updated event for thread messages', () async { const messageId = 'test-message-id'; const parentId = 'test-parent-id'; @@ -6235,8 +6344,7 @@ void main() { expect(updatedMessage?.reminder?.remindAt, updatedRemindAt); }); - test('should handle reminder.deleted event for thread messages', - () async { + test('should handle reminder.deleted event for thread messages', () async { const messageId = 'test-message-id'; const parentId = 'test-parent-id'; @@ -6868,18 +6976,17 @@ void main() { setUp(() { persistenceClient = MockPersistenceClient(); when(() => client.chatPersistenceClient).thenReturn(persistenceClient); - when(() => persistenceClient.deleteMessagesFromUser( - cid: any(named: 'cid'), - userId: any(named: 'userId'), - hardDelete: any(named: 'hardDelete'), - deletedAt: any(named: 'deletedAt'), - )).thenAnswer((_) async {}); - when(() => persistenceClient.deleteMessageByIds(any())) - .thenAnswer((_) async {}); - when(() => persistenceClient.deletePinnedMessageByIds(any())) - .thenAnswer((_) async {}); - when(() => persistenceClient.getChannelThreads(any())) - .thenAnswer((_) async => >{}); + when( + () => persistenceClient.deleteMessagesFromUser( + cid: any(named: 'cid'), + userId: any(named: 'userId'), + hardDelete: any(named: 'hardDelete'), + deletedAt: any(named: 'deletedAt'), + ), + ).thenAnswer((_) async {}); + when(() => persistenceClient.deleteMessageByIds(any())).thenAnswer((_) async {}); + when(() => persistenceClient.deletePinnedMessageByIds(any())).thenAnswer((_) async {}); + when(() => persistenceClient.getChannelThreads(any())).thenAnswer((_) async => >{}); final channelState = _generateChannelState(channelId, channelType); channel = Channel.fromState(client, channelState); @@ -6945,9 +7052,7 @@ void main() { // Verify user1's messages are soft deleted expect(channel.state?.messages.length, equals(3)); - final deletedMessages = channel.state?.messages - .where((m) => m.user?.id == 'user-1') - .toList(); + final deletedMessages = channel.state?.messages.where((m) => m.user?.id == 'user-1').toList(); expect(deletedMessages?.length, equals(2)); for (final message in deletedMessages!) { expect(message.type, equals(MessageType.deleted)); @@ -6956,8 +7061,7 @@ void main() { } // Verify user2's message is unaffected - final user2Message = - channel.state?.messages.firstWhere((m) => m.id == 'msg-3'); + final user2Message = channel.state?.messages.firstWhere((m) => m.id == 'msg-3'); expect(user2Message?.type, isNot(MessageType.deleted)); expect(user2Message?.deletedAt, isNull); }, @@ -7015,8 +7119,7 @@ void main() { ); // Verify user2's message still exists - final user2Message = - channel.state?.messages.firstWhere((m) => m.id == 'msg-3'); + final user2Message = channel.state?.messages.firstWhere((m) => m.id == 'msg-3'); expect(user2Message, isNotNull); expect(user2Message?.user?.id, equals('user-2')); }, @@ -7195,8 +7298,7 @@ void main() { () => persistenceClient.deleteMessageByIds(['msg-1', 'msg-2']), ).called(1); verify( - () => - persistenceClient.deletePinnedMessageByIds(['msg-1', 'msg-2']), + () => persistenceClient.deletePinnedMessageByIds(['msg-1', 'msg-2']), ).called(1); // Verify user1's messages are removed from state @@ -7241,8 +7343,7 @@ void main() { // Verify message is soft deleted (still in state) expect(channel.state?.messages.length, equals(1)); - expect( - channel.state?.messages.first.type, equals(MessageType.deleted)); + expect(channel.state?.messages.first.type, equals(MessageType.deleted)); }, ); @@ -7329,9 +7430,11 @@ void main() { ).called(1); // Verify in-state messages were also removed from state's persistence - final capturedIds = verify( - () => persistenceClient.deleteMessageByIds(captureAny()), - ).captured.first as List; + final capturedIds = + verify( + () => persistenceClient.deleteMessageByIds(captureAny()), + ).captured.first + as List; expect( capturedIds, @@ -7479,8 +7582,7 @@ void main() { final messageReads = channel.state!.readsOf(message: message); expect(messageReads.length, 2); - expect(messageReads.map((r) => r.user.id), - containsAll(['user-1', 'user-3'])); + expect(messageReads.map((r) => r.user.id), containsAll(['user-1', 'user-3'])); expect(messageReads.map((r) => r.user.id), isNot(contains('user-2'))); expect(messageReads.map((r) => r.user.id), isNot(contains('sender-id'))); }); @@ -7515,15 +7617,12 @@ void main() { channel.state!.updateChannelState( ChannelState( channel: channelState.channel, - read: [ - Read(user: user1, lastRead: now.add(const Duration(seconds: 1))) - ], + read: [Read(user: user1, lastRead: now.add(const Duration(seconds: 1)))], ), ); }); - test('deliveriesOf should return reads that have delivered the message', - () { + test('deliveriesOf should return reads that have delivered the message', () { final now = DateTime.now(); final sender = User(id: 'sender-id', name: 'Sender'); final user1 = User(id: 'user-1', name: 'User 1'); @@ -7579,15 +7678,13 @@ void main() { final deliveries = channel.state!.deliveriesOf(message: message); expect(deliveries.length, 2); - expect( - deliveries.map((r) => r.user.id), containsAll(['user-1', 'user-4'])); + expect(deliveries.map((r) => r.user.id), containsAll(['user-1', 'user-4'])); expect(deliveries.map((r) => r.user.id), isNot(contains('user-2'))); expect(deliveries.map((r) => r.user.id), isNot(contains('user-3'))); expect(deliveries.map((r) => r.user.id), isNot(contains('sender-id'))); }); - test('deliveriesOfStream should emit delivery updates for a message', - () async { + test('deliveriesOfStream should emit delivery updates for a message', () async { final now = DateTime.now(); final sender = User(id: 'sender-id', name: 'Sender'); final user1 = User(id: 'user-1', name: 'User 1'); @@ -7603,8 +7700,7 @@ void main() { final channel = Channel.fromState(client, channelState); addTearDown(channel.dispose); - final deliveriesStream = - channel.state!.deliveriesOfStream(message: message); + final deliveriesStream = channel.state!.deliveriesOfStream(message: message); expectLater( deliveriesStream, @@ -8976,9 +9072,7 @@ void main() { }); group('retryMessage method', () { - test( - 'should call sendMessage with preserved skipPush and skipEnrichUrl parameters', - () async { + test('should call sendMessage with preserved skipPush and skipEnrichUrl parameters', () async { final message = Message( id: 'test-message-id', text: 'Hello, World!', @@ -8988,33 +9082,35 @@ void main() { ), ); - final sendMessageResponse = SendMessageResponse() - ..message = message.copyWith(state: MessageState.sent); + final sendMessageResponse = SendMessageResponse()..message = message.copyWith(state: MessageState.sent); - when(() => client.sendMessage( - any(that: isSameMessageAs(message)), - channelId, - channelType, - skipPush: true, - skipEnrichUrl: true, - )).thenAnswer((_) async => sendMessageResponse); + when( + () => client.sendMessage( + any(that: isSameMessageAs(message)), + channelId, + channelType, + skipPush: true, + skipEnrichUrl: true, + ), + ).thenAnswer((_) async => sendMessageResponse); final result = await channel.retryMessage(message); expect(result, isNotNull); expect(result, isA()); - verify(() => client.sendMessage( - any(that: isSameMessageAs(message)), - channelId, - channelType, - skipPush: true, - skipEnrichUrl: true, - )).called(1); + verify( + () => client.sendMessage( + any(that: isSameMessageAs(message)), + channelId, + channelType, + skipPush: true, + skipEnrichUrl: true, + ), + ).called(1); }); - test('should call sendMessage with preserved skipPush parameter', - () async { + test('should call sendMessage with preserved skipPush parameter', () async { final message = Message( id: 'test-message-id', text: 'Hello, World!', @@ -9024,31 +9120,33 @@ void main() { ), ); - final sendMessageResponse = SendMessageResponse() - ..message = message.copyWith(state: MessageState.sent); + final sendMessageResponse = SendMessageResponse()..message = message.copyWith(state: MessageState.sent); - when(() => client.sendMessage( - any(that: isSameMessageAs(message)), - channelId, - channelType, - skipPush: true, - )).thenAnswer((_) async => sendMessageResponse); + when( + () => client.sendMessage( + any(that: isSameMessageAs(message)), + channelId, + channelType, + skipPush: true, + ), + ).thenAnswer((_) async => sendMessageResponse); final result = await channel.retryMessage(message); expect(result, isNotNull); expect(result, isA()); - verify(() => client.sendMessage( - any(that: isSameMessageAs(message)), - channelId, - channelType, - skipPush: true, - )).called(1); + verify( + () => client.sendMessage( + any(that: isSameMessageAs(message)), + channelId, + channelType, + skipPush: true, + ), + ).called(1); }); - test('should call sendMessage with preserved skipEnrichUrl parameter', - () async { + test('should call sendMessage with preserved skipEnrichUrl parameter', () async { final message = Message( id: 'test-message-id', text: 'Hello, World!', @@ -9058,32 +9156,33 @@ void main() { ), ); - final sendMessageResponse = SendMessageResponse() - ..message = message.copyWith(state: MessageState.sent); + final sendMessageResponse = SendMessageResponse()..message = message.copyWith(state: MessageState.sent); - when(() => client.sendMessage( - any(that: isSameMessageAs(message)), - channelId, - channelType, - skipEnrichUrl: true, - )).thenAnswer((_) async => sendMessageResponse); + when( + () => client.sendMessage( + any(that: isSameMessageAs(message)), + channelId, + channelType, + skipEnrichUrl: true, + ), + ).thenAnswer((_) async => sendMessageResponse); final result = await channel.retryMessage(message); expect(result, isNotNull); expect(result, isA()); - verify(() => client.sendMessage( - any(that: isSameMessageAs(message)), - channelId, - channelType, - skipEnrichUrl: true, - )).called(1); + verify( + () => client.sendMessage( + any(that: isSameMessageAs(message)), + channelId, + channelType, + skipEnrichUrl: true, + ), + ).called(1); }); - test( - 'should call sendMessage with preserved false skipPush and skipEnrichUrl parameters', - () async { + test('should call sendMessage with preserved false skipPush and skipEnrichUrl parameters', () async { final message = Message( id: 'test-message-id', text: 'Hello, World!', @@ -9093,30 +9192,31 @@ void main() { ), ); - final sendMessageResponse = SendMessageResponse() - ..message = message.copyWith(state: MessageState.sent); + final sendMessageResponse = SendMessageResponse()..message = message.copyWith(state: MessageState.sent); - when(() => client.sendMessage( - any(that: isSameMessageAs(message)), - channelId, - channelType, - )).thenAnswer((_) async => sendMessageResponse); + when( + () => client.sendMessage( + any(that: isSameMessageAs(message)), + channelId, + channelType, + ), + ).thenAnswer((_) async => sendMessageResponse); final result = await channel.retryMessage(message); expect(result, isNotNull); expect(result, isA()); - verify(() => client.sendMessage( - any(that: isSameMessageAs(message)), - channelId, - channelType, - )).called(1); + verify( + () => client.sendMessage( + any(that: isSameMessageAs(message)), + channelId, + channelType, + ), + ).called(1); }); - test( - 'should call updateMessage with preserved skipPush, skipEnrichUrl parameter', - () async { + test('should call updateMessage with preserved skipPush, skipEnrichUrl parameter', () async { final message = Message( id: 'test-message-id', text: 'Hello, World!', @@ -9126,30 +9226,31 @@ void main() { ), ); - final updateMessageResponse = UpdateMessageResponse() - ..message = message.copyWith(state: MessageState.updated); + final updateMessageResponse = UpdateMessageResponse()..message = message.copyWith(state: MessageState.updated); - when(() => client.updateMessage( - any(that: isSameMessageAs(message)), - skipPush: true, - skipEnrichUrl: true, - )).thenAnswer((_) async => updateMessageResponse); + when( + () => client.updateMessage( + any(that: isSameMessageAs(message)), + skipPush: true, + skipEnrichUrl: true, + ), + ).thenAnswer((_) async => updateMessageResponse); final result = await channel.retryMessage(message); expect(result, isNotNull); expect(result, isA()); - verify(() => client.updateMessage( - any(that: isSameMessageAs(message)), - skipPush: true, - skipEnrichUrl: true, - )).called(1); + verify( + () => client.updateMessage( + any(that: isSameMessageAs(message)), + skipPush: true, + skipEnrichUrl: true, + ), + ).called(1); }); - test( - 'should call updateMessage with preserved false skipPush, skipEnrichUrl parameter', - () async { + test('should call updateMessage with preserved false skipPush, skipEnrichUrl parameter', () async { final message = Message( id: 'test-message-id', state: MessageState.updatingFailed( @@ -9158,21 +9259,24 @@ void main() { ), ); - final updateMessageResponse = UpdateMessageResponse() - ..message = message.copyWith(state: MessageState.updated); + final updateMessageResponse = UpdateMessageResponse()..message = message.copyWith(state: MessageState.updated); - when(() => client.updateMessage( - any(that: isSameMessageAs(message)), - )).thenAnswer((_) async => updateMessageResponse); + when( + () => client.updateMessage( + any(that: isSameMessageAs(message)), + ), + ).thenAnswer((_) async => updateMessageResponse); final result = await channel.retryMessage(message); expect(result, isNotNull); expect(result, isA()); - verify(() => client.updateMessage( - any(that: isSameMessageAs(message)), - )).called(1); + verify( + () => client.updateMessage( + any(that: isSameMessageAs(message)), + ), + ).called(1); }); test('should call deleteMessage with preserved hard parameter', () async { @@ -9182,54 +9286,59 @@ void main() { state: MessageState.hardDeletingFailed, ); - when(() => client.deleteMessage( - message.id, - hard: true, - )).thenAnswer((_) async => EmptyResponse()); + when( + () => client.deleteMessage( + message.id, + hard: true, + ), + ).thenAnswer((_) async => EmptyResponse()); final result = await channel.retryMessage(message); expect(result, isNotNull); expect(result, isA()); - verify(() => client.deleteMessage( - message.id, - hard: true, - )).called(1); + verify( + () => client.deleteMessage( + message.id, + hard: true, + ), + ).called(1); }); - test('should call deleteMessage with preserved false hard parameter', - () async { + test('should call deleteMessage with preserved false hard parameter', () async { final message = Message( id: 'test-message-id', createdAt: DateTime.now(), state: MessageState.softDeletingFailed, ); - when(() => client.deleteMessage( - message.id, - )).thenAnswer((_) async => EmptyResponse()); + when( + () => client.deleteMessage( + message.id, + ), + ).thenAnswer((_) async => EmptyResponse()); final result = await channel.retryMessage(message); expect(result, isNotNull); expect(result, isA()); - verify(() => client.deleteMessage( - message.id, - )).called(1); + verify( + () => client.deleteMessage( + message.id, + ), + ).called(1); }); - test('should call deleteMessageForMe for deletingForMeFailed state', - () async { + test('should call deleteMessageForMe for deletingForMeFailed state', () async { final message = Message( id: 'test-message-id', createdAt: DateTime.now(), state: MessageState.deletingForMeFailed, ); - when(() => client.deleteMessageForMe(message.id)) - .thenAnswer((_) async => EmptyResponse()); + when(() => client.deleteMessageForMe(message.id)).thenAnswer((_) async => EmptyResponse()); final result = await channel.retryMessage(message); @@ -9239,15 +9348,13 @@ void main() { verify(() => client.deleteMessageForMe(message.id)).called(1); }); - test('should throw AssertionError when message state is not failed', - () async { + test('should throw AssertionError when message state is not failed', () async { final message = Message( id: 'test-message-id', state: MessageState.sent, ); - expect(() => channel.retryMessage(message), - throwsA(isA())); + expect(() => channel.retryMessage(message), throwsA(isA())); }); }); }); diff --git a/packages/stream_chat/test/src/client/client_test.dart b/packages/stream_chat/test/src/client/client_test.dart index 8a65499110..b2690505bd 100644 --- a/packages/stream_chat/test/src/client/client_test.dart +++ b/packages/stream_chat/test/src/client/client_test.dart @@ -75,8 +75,7 @@ void main() { final user = User(id: 'test-user-id'); final token = Token.development(user.id).rawValue; - when(() => api.guest.getGuestUser(any(that: isSameUserAs(user)))) - .thenAnswer( + when(() => api.guest.getGuestUser(any(that: isSameUserAs(user)))).thenAnswer( (_) async => ConnectGuestUserResponse() ..user = user ..accessToken = token, @@ -103,8 +102,9 @@ void main() { test('should throw if `.getGuestUser` fails', () async { final user = User(id: 'test-user-id'); - when(() => api.guest.getGuestUser(any(that: isSameUserAs(user)))) - .thenThrow(StreamChatNetworkError(ChatErrorCode.inputError)); + when( + () => api.guest.getGuestUser(any(that: isSameUserAs(user))), + ).thenThrow(StreamChatNetworkError(ChatErrorCode.inputError)); expectLater( client.wsConnectionStatusStream, @@ -247,8 +247,7 @@ void main() { final user = User(id: 'test-user-id'); final token = Token.development(user.id).rawValue; - when(() => api.guest.getGuestUser(any(that: isSameUserAs(user)))) - .thenAnswer( + when(() => api.guest.getGuestUser(any(that: isSameUserAs(user)))).thenAnswer( (_) async => ConnectGuestUserResponse() ..user = user ..accessToken = token, @@ -331,8 +330,7 @@ void main() { final user = User(id: 'test-user-id'); final token = Token.development(user.id).rawValue; - when(() => api.guest.getGuestUser(any(that: isSameUserAs(user)))) - .thenAnswer( + when(() => api.guest.getGuestUser(any(that: isSameUserAs(user)))).thenAnswer( (_) async => ConnectGuestUserResponse() ..user = user ..accessToken = token, @@ -377,8 +375,7 @@ void main() { setUp(() { final ws = FakeWebSocketWithConnectionError(); - client = StreamChatClient(apiKey, chatApi: api, ws: ws) - ..chatPersistenceClient = persistence; + client = StreamChatClient(apiKey, chatApi: api, ws: ws)..chatPersistenceClient = persistence; }); tearDown(() { @@ -392,9 +389,10 @@ void main() { final token = Token.development(user.id).rawValue; final event = Event( - type: EventType.healthCheck, - connectionId: 'test-connection-id', - me: OwnUser.fromUser(user)); + type: EventType.healthCheck, + connectionId: 'test-connection-id', + me: OwnUser.fromUser(user), + ); when(persistence.getConnectionInfo).thenAnswer((_) async => event); final res = await client.connectUser(user, token); @@ -416,9 +414,10 @@ void main() { } final event = Event( - type: EventType.healthCheck, - connectionId: 'test-connection-id', - me: OwnUser.fromUser(user)); + type: EventType.healthCheck, + connectionId: 'test-connection-id', + me: OwnUser.fromUser(user), + ); when(persistence.getConnectionInfo).thenAnswer((_) async => event); final res = await client.connectUserWithProvider(user, tokenProvider); @@ -437,13 +436,13 @@ void main() { final token = Token.development(user.id).rawValue; final event = Event( - type: EventType.healthCheck, - connectionId: 'test-connection-id', - me: OwnUser.fromUser(user)); + type: EventType.healthCheck, + connectionId: 'test-connection-id', + me: OwnUser.fromUser(user), + ); when(persistence.getConnectionInfo).thenAnswer((_) async => event); - when(() => api.guest.getGuestUser(any(that: isSameUserAs(user)))) - .thenAnswer( + when(() => api.guest.getGuestUser(any(that: isSameUserAs(user)))).thenAnswer( (_) async => ConnectGuestUserResponse() ..user = user ..accessToken = token, @@ -455,8 +454,7 @@ void main() { verify(persistence.getConnectionInfo).called(1); verifyNoMoreInteractions(persistence); - verify(() => api.guest.getGuestUser(any(that: isSameUserAs(user)))) - .called(1); + verify(() => api.guest.getGuestUser(any(that: isSameUserAs(user)))).called(1); verifyNoMoreInteractions(api.guest); }, ); @@ -501,12 +499,10 @@ void main() { }); setUp(() async { - when(() => persistence.updateLastSyncAt(any())) - .thenAnswer((_) => Future.value()); + when(() => persistence.updateLastSyncAt(any())).thenAnswer((_) => Future.value()); when(persistence.getLastSyncAt).thenAnswer((_) async => null); final ws = FakeWebSocket(); - client = StreamChatClient(apiKey, chatApi: api, ws: ws) - ..chatPersistenceClient = persistence; + client = StreamChatClient(apiKey, chatApi: api, ws: ws)..chatPersistenceClient = persistence; await client.connectUser(user, token); await delay(300); expect(client.persistenceEnabled, isTrue); @@ -527,26 +523,25 @@ void main() { reset(persistence); const cids = ['test-cid-1', 'test-cid-2', 'test-cid-3']; final lastSyncAt = DateTime.now(); - when(() => api.general.sync(cids, lastSyncAt)) - .thenAnswer((_) async => SyncResponse() - ..events = [ - Event( - isLocal: false, - type: EventType.healthCheck, - connectionId: 'test-connection-id', - me: OwnUser.fromUser(user), - ), - Event( - isLocal: false, - type: EventType.messageDeleted, - message: Message(id: 'test-message-id'), - ), - ]); - - when(() => persistence.updateConnectionInfo(any())) - .thenAnswer((_) => Future.value()); - when(() => persistence.updateLastSyncAt(any())) - .thenAnswer((_) => Future.value()); + when(() => api.general.sync(cids, lastSyncAt)).thenAnswer( + (_) async => SyncResponse() + ..events = [ + Event( + isLocal: false, + type: EventType.healthCheck, + connectionId: 'test-connection-id', + me: OwnUser.fromUser(user), + ), + Event( + isLocal: false, + type: EventType.messageDeleted, + message: Message(id: 'test-message-id'), + ), + ], + ); + + when(() => persistence.updateConnectionInfo(any())).thenAnswer((_) => Future.value()); + when(() => persistence.updateLastSyncAt(any())).thenAnswer((_) => Future.value()); await client.sync(cids: cids, lastSyncAt: lastSyncAt); @@ -569,26 +564,25 @@ void main() { when(persistence.getChannelCids).thenAnswer((_) async => cids); when(persistence.getLastSyncAt).thenAnswer((_) async => lastSyncAt); - when(() => api.general.sync(cids, lastSyncAt)) - .thenAnswer((_) async => SyncResponse() - ..events = [ - Event( - isLocal: false, - type: EventType.healthCheck, - connectionId: 'test-connection-id', - me: OwnUser.fromUser(user), - ), - Event( - isLocal: false, - type: EventType.messageDeleted, - message: Message(id: 'test-message-id', text: 'Hey!'), - ), - ]); - - when(() => persistence.updateConnectionInfo(any())) - .thenAnswer((_) => Future.value()); - when(() => persistence.updateLastSyncAt(any())) - .thenAnswer((_) => Future.value()); + when(() => api.general.sync(cids, lastSyncAt)).thenAnswer( + (_) async => SyncResponse() + ..events = [ + Event( + isLocal: false, + type: EventType.healthCheck, + connectionId: 'test-connection-id', + me: OwnUser.fromUser(user), + ), + Event( + isLocal: false, + type: EventType.messageDeleted, + message: Message(id: 'test-message-id', text: 'Hey!'), + ), + ], + ); + + when(() => persistence.updateConnectionInfo(any())).thenAnswer((_) => Future.value()); + when(() => persistence.updateLastSyncAt(any())).thenAnswer((_) => Future.value()); await client.sync(); @@ -612,12 +606,14 @@ void main() { ), ); - when(() => persistence.getChannelStates( - filter: any(named: 'filter'), - messageLimit: any(named: 'messageLimit'), - channelStateSort: any(named: 'channelStateSort'), - paginationParams: any(named: 'paginationParams'), - )).thenAnswer((_) async => persistentChannelStates); + when( + () => persistence.getChannelStates( + filter: any(named: 'filter'), + messageLimit: any(named: 'messageLimit'), + channelStateSort: any(named: 'channelStateSort'), + paginationParams: any(named: 'paginationParams'), + ), + ).thenAnswer((_) async => persistentChannelStates); final channelStates = List.generate( 3, @@ -626,35 +622,33 @@ void main() { ), ); - when(() => api.channel.queryChannels( - filter: any(named: 'filter'), - sort: any(named: 'sort'), - state: any(named: 'state'), - watch: any(named: 'watch'), - presence: any(named: 'presence'), - memberLimit: any(named: 'memberLimit'), - messageLimit: any(named: 'messageLimit'), - paginationParams: any(named: 'paginationParams'), - )).thenAnswer( + when( + () => api.channel.queryChannels( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + memberLimit: any(named: 'memberLimit'), + messageLimit: any(named: 'messageLimit'), + paginationParams: any(named: 'paginationParams'), + ), + ).thenAnswer( (_) async => QueryChannelsResponse()..channels = channelStates, ); when(() => persistence.getChannelThreads(any())).thenAnswer( (_) async => >{ for (final channelState in channelStates) - channelState.channel!.cid: [ - Message(id: 'test-message-id', text: 'Test message') - ], + channelState.channel!.cid: [Message(id: 'test-message-id', text: 'Test message')], }, ); - when(() => persistence.updateChannelState(any())) - .thenAnswer((_) async {}); - when(() => persistence.updateChannelThreads(any(), any())) - .thenAnswer((_) async {}); - when(() => persistence.updateChannelQueries(any(), any(), - clearQueryCache: any(named: 'clearQueryCache'))) - .thenAnswer((_) => Future.value()); + when(() => persistence.updateChannelState(any())).thenAnswer((_) async {}); + when(() => persistence.updateChannelThreads(any(), any())).thenAnswer((_) async {}); + when( + () => persistence.updateChannelQueries(any(), any(), clearQueryCache: any(named: 'clearQueryCache')), + ).thenAnswer((_) => Future.value()); expectLater( client.queryChannels(), @@ -670,32 +664,34 @@ void main() { // before our stream starts emitting data await delay(1050); - verify(() => persistence.getChannelStates( - filter: any(named: 'filter'), - messageLimit: any(named: 'messageLimit'), - channelStateSort: any(named: 'channelStateSort'), - paginationParams: any(named: 'paginationParams'), - )).called(1); - - verify(() => api.channel.queryChannels( - filter: any(named: 'filter'), - sort: any(named: 'sort'), - state: any(named: 'state'), - watch: any(named: 'watch'), - presence: any(named: 'presence'), - memberLimit: any(named: 'memberLimit'), - messageLimit: any(named: 'messageLimit'), - paginationParams: any(named: 'paginationParams'), - )).called(1); - - verify(() => persistence.getChannelThreads(any())) - .called(channelStates.length); - verify(() => persistence.updateChannelState(any())) - .called(channelStates.length); - verify(() => persistence.updateChannelThreads(any(), any())) - .called(channelStates.length); - verify(() => persistence.updateChannelQueries(any(), any(), - clearQueryCache: any(named: 'clearQueryCache'))).called(1); + verify( + () => persistence.getChannelStates( + filter: any(named: 'filter'), + messageLimit: any(named: 'messageLimit'), + channelStateSort: any(named: 'channelStateSort'), + paginationParams: any(named: 'paginationParams'), + ), + ).called(1); + + verify( + () => api.channel.queryChannels( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + memberLimit: any(named: 'memberLimit'), + messageLimit: any(named: 'messageLimit'), + paginationParams: any(named: 'paginationParams'), + ), + ).called(1); + + verify(() => persistence.getChannelThreads(any())).called(channelStates.length); + verify(() => persistence.updateChannelState(any())).called(channelStates.length); + verify(() => persistence.updateChannelThreads(any(), any())).called(channelStates.length); + verify( + () => persistence.updateChannelQueries(any(), any(), clearQueryCache: any(named: 'clearQueryCache')), + ).called(1); }, ); @@ -709,37 +705,37 @@ void main() { ), ); - when(() => persistence.getChannelStates( - filter: any(named: 'filter'), - messageLimit: any(named: 'messageLimit'), - channelStateSort: any(named: 'channelStateSort'), - paginationParams: any(named: 'paginationParams'), - )).thenAnswer((_) async => persistentChannelStates); - - when(() => api.channel.queryChannels( - filter: any(named: 'filter'), - sort: any(named: 'sort'), - state: any(named: 'state'), - watch: any(named: 'watch'), - presence: any(named: 'presence'), - memberLimit: any(named: 'memberLimit'), - messageLimit: any(named: 'messageLimit'), - paginationParams: any(named: 'paginationParams'), - )).thenThrow(StreamChatNetworkError(ChatErrorCode.inputError)); + when( + () => persistence.getChannelStates( + filter: any(named: 'filter'), + messageLimit: any(named: 'messageLimit'), + channelStateSort: any(named: 'channelStateSort'), + paginationParams: any(named: 'paginationParams'), + ), + ).thenAnswer((_) async => persistentChannelStates); + + when( + () => api.channel.queryChannels( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + memberLimit: any(named: 'memberLimit'), + messageLimit: any(named: 'messageLimit'), + paginationParams: any(named: 'paginationParams'), + ), + ).thenThrow(StreamChatNetworkError(ChatErrorCode.inputError)); when(() => persistence.getChannelThreads(any())).thenAnswer( (_) async => >{ for (final channelState in persistentChannelStates) - channelState.channel!.cid: [ - Message(id: 'test-message-id', text: 'Test message') - ], + channelState.channel!.cid: [Message(id: 'test-message-id', text: 'Test message')], }, ); - when(() => persistence.updateChannelState(any())) - .thenAnswer((_) async => {}); - when(() => persistence.updateChannelThreads(any(), any())) - .thenAnswer((_) async => {}); + when(() => persistence.updateChannelState(any())).thenAnswer((_) async => {}); + when(() => persistence.updateChannelThreads(any(), any())).thenAnswer((_) async => {}); expectLater( client.queryChannels(), @@ -753,30 +749,31 @@ void main() { // before our stream starts emitting data await delay(1050); - verify(() => persistence.getChannelStates( - filter: any(named: 'filter'), - messageLimit: any(named: 'messageLimit'), - channelStateSort: any(named: 'channelStateSort'), - paginationParams: any(named: 'paginationParams'), - )).called(1); - - verify(() => api.channel.queryChannels( - filter: any(named: 'filter'), - sort: any(named: 'sort'), - state: any(named: 'state'), - watch: any(named: 'watch'), - presence: any(named: 'presence'), - memberLimit: any(named: 'memberLimit'), - messageLimit: any(named: 'messageLimit'), - paginationParams: any(named: 'paginationParams'), - )).called(1); - - verify(() => persistence.getChannelThreads(any())) - .called(persistentChannelStates.length); - verify(() => persistence.updateChannelState(any())) - .called(persistentChannelStates.length); - verify(() => persistence.updateChannelThreads(any(), any())) - .called(persistentChannelStates.length); + verify( + () => persistence.getChannelStates( + filter: any(named: 'filter'), + messageLimit: any(named: 'messageLimit'), + channelStateSort: any(named: 'channelStateSort'), + paginationParams: any(named: 'paginationParams'), + ), + ).called(1); + + verify( + () => api.channel.queryChannels( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + memberLimit: any(named: 'memberLimit'), + messageLimit: any(named: 'messageLimit'), + paginationParams: any(named: 'paginationParams'), + ), + ).called(1); + + verify(() => persistence.getChannelThreads(any())).called(persistentChannelStates.length); + verify(() => persistence.updateChannelState(any())).called(persistentChannelStates.length); + verify(() => persistence.updateChannelThreads(any(), any())).called(persistentChannelStates.length); }, ); }); @@ -835,21 +832,22 @@ void main() { const cids = ['test-cid-1', 'test-cid-2', 'test-cid-3']; final lastSyncAt = DateTime.now(); - when(() => api.general.sync(cids, lastSyncAt)) - .thenAnswer((_) async => SyncResponse() - ..events = [ - Event( - isLocal: false, - type: EventType.healthCheck, - connectionId: 'test-connection-id', - me: OwnUser.fromUser(user), - ), - Event( - isLocal: false, - type: EventType.messageDeleted, - message: Message(id: 'test-message-id'), - ), - ]); + when(() => api.general.sync(cids, lastSyncAt)).thenAnswer( + (_) async => SyncResponse() + ..events = [ + Event( + isLocal: false, + type: EventType.healthCheck, + connectionId: 'test-connection-id', + me: OwnUser.fromUser(user), + ), + Event( + isLocal: false, + type: EventType.messageDeleted, + message: Message(id: 'test-message-id'), + ), + ], + ); await client.sync(cids: cids, lastSyncAt: lastSyncAt); @@ -876,16 +874,18 @@ void main() { ), ); - when(() => api.channel.queryChannels( - filter: any(named: 'filter'), - sort: any(named: 'sort'), - state: any(named: 'state'), - watch: any(named: 'watch'), - presence: any(named: 'presence'), - memberLimit: any(named: 'memberLimit'), - messageLimit: any(named: 'messageLimit'), - paginationParams: any(named: 'paginationParams'), - )).thenAnswer( + when( + () => api.channel.queryChannels( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + memberLimit: any(named: 'memberLimit'), + messageLimit: any(named: 'messageLimit'), + paginationParams: any(named: 'paginationParams'), + ), + ).thenAnswer( (_) async => QueryChannelsResponse()..channels = channelStates, ); @@ -898,7 +898,25 @@ void main() { // before our stream starts emitting data await delay(300); - verify(() => api.channel.queryChannels( + verify( + () => api.channel.queryChannels( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + memberLimit: any(named: 'memberLimit'), + messageLimit: any(named: 'messageLimit'), + paginationParams: any(named: 'paginationParams'), + ), + ).called(1); + }); + + test( + '''should rethrow if `.queryChannelsOnline` throws and persistence channels are empty''', + () async { + when( + () => api.channel.queryChannels( filter: any(named: 'filter'), sort: any(named: 'sort'), state: any(named: 'state'), @@ -907,22 +925,8 @@ void main() { memberLimit: any(named: 'memberLimit'), messageLimit: any(named: 'messageLimit'), paginationParams: any(named: 'paginationParams'), - )).called(1); - }); - - test( - '''should rethrow if `.queryChannelsOnline` throws and persistence channels are empty''', - () async { - when(() => api.channel.queryChannels( - filter: any(named: 'filter'), - sort: any(named: 'sort'), - state: any(named: 'state'), - watch: any(named: 'watch'), - presence: any(named: 'presence'), - memberLimit: any(named: 'memberLimit'), - messageLimit: any(named: 'messageLimit'), - paginationParams: any(named: 'paginationParams'), - )).thenThrow(StreamChatNetworkError(ChatErrorCode.inputError)); + ), + ).thenThrow(StreamChatNetworkError(ChatErrorCode.inputError)); expectLater( client.queryChannels(), @@ -933,16 +937,18 @@ void main() { // before our stream starts emitting data await delay(300); - verify(() => api.channel.queryChannels( - filter: any(named: 'filter'), - sort: any(named: 'sort'), - state: any(named: 'state'), - watch: any(named: 'watch'), - presence: any(named: 'presence'), - memberLimit: any(named: 'memberLimit'), - messageLimit: any(named: 'messageLimit'), - paginationParams: any(named: 'paginationParams'), - )).called(1); + verify( + () => api.channel.queryChannels( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + memberLimit: any(named: 'memberLimit'), + messageLimit: any(named: 'messageLimit'), + paginationParams: any(named: 'paginationParams'), + ), + ).called(1); }, ); }); @@ -953,12 +959,14 @@ void main() { (index) => User(id: 'test-user-id-$index'), ); - when(() => api.user.queryUsers( - presence: any(named: 'presence'), - filter: any(named: 'filter'), - sort: any(named: 'sort'), - pagination: any(named: 'pagination'), - )).thenAnswer((_) async => QueryUsersResponse()..users = users); + when( + () => api.user.queryUsers( + presence: any(named: 'presence'), + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenAnswer((_) async => QueryUsersResponse()..users = users); expectLater( // skipping initial seed event -> {} users @@ -972,12 +980,14 @@ void main() { expect(res, isNotNull); expect(res.users.length, users.length); - verify(() => api.user.queryUsers( - presence: any(named: 'presence'), - filter: any(named: 'filter'), - sort: any(named: 'sort'), - pagination: any(named: 'pagination'), - )).called(1); + verify( + () => api.user.queryUsers( + presence: any(named: 'presence'), + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).called(1); verifyNoMoreInteractions(api.user); }); @@ -993,21 +1003,25 @@ void main() { const cid = 'message:nice-channel'; final filter = Filter.equal('channel_cid', cid); - when(() => api.moderation.queryBannedUsers( - filter: filter, - sort: any(named: 'sort'), - pagination: any(named: 'pagination'), - )).thenAnswer((_) async => QueryBannedUsersResponse()..bans = bans); + when( + () => api.moderation.queryBannedUsers( + filter: filter, + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenAnswer((_) async => QueryBannedUsersResponse()..bans = bans); final res = await client.queryBannedUsers(filter: filter); expect(res, isNotNull); expect(res.bans.length, bans.length); - verify(() => api.moderation.queryBannedUsers( - filter: filter, - sort: any(named: 'sort'), - pagination: any(named: 'pagination'), - )).called(1); + verify( + () => api.moderation.queryBannedUsers( + filter: filter, + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).called(1); verifyNoMoreInteractions(api.moderation); }); @@ -1022,23 +1036,29 @@ void main() { ..message = Message(id: 'test-message-id-$index'), ); - when(() => api.general.searchMessages(filter, - query: any(named: 'query'), - sort: any(named: 'sort'), - pagination: any(named: 'pagination'), - messageFilters: any(named: 'messageFilters'))) - .thenAnswer( - (_) async => SearchMessagesResponse()..results = messages); + when( + () => api.general.searchMessages( + filter, + query: any(named: 'query'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + messageFilters: any(named: 'messageFilters'), + ), + ).thenAnswer((_) async => SearchMessagesResponse()..results = messages); final res = await client.search(filter); expect(res, isNotNull); expect(res.results.length, messages.length); - verify(() => api.general.searchMessages(filter, + verify( + () => api.general.searchMessages( + filter, query: any(named: 'query'), sort: any(named: 'sort'), pagination: any(named: 'pagination'), - messageFilters: any(named: 'messageFilters'))).called(1); + messageFilters: any(named: 'messageFilters'), + ), + ).called(1); verifyNoMoreInteractions(api.general); }); @@ -1049,15 +1069,15 @@ void main() { const fileUrl = 'test-file-url'; - when(() => api.fileUploader.sendFile(file, channelId, channelType)) - .thenAnswer((_) async => SendFileResponse()..file = fileUrl); + when( + () => api.fileUploader.sendFile(file, channelId, channelType), + ).thenAnswer((_) async => SendFileResponse()..file = fileUrl); final res = await client.sendFile(file, channelId, channelType); expect(res, isNotNull); expect(res.file, fileUrl); - verify(() => api.fileUploader.sendFile(file, channelId, channelType)) - .called(1); + verify(() => api.fileUploader.sendFile(file, channelId, channelType)).called(1); verifyNoMoreInteractions(api.fileUploader); }); @@ -1068,15 +1088,15 @@ void main() { const fileUrl = 'test-image-url'; - when(() => api.fileUploader.sendImage(image, channelId, channelType)) - .thenAnswer((_) async => SendImageResponse()..file = fileUrl); + when( + () => api.fileUploader.sendImage(image, channelId, channelType), + ).thenAnswer((_) async => SendImageResponse()..file = fileUrl); final res = await client.sendImage(image, channelId, channelType); expect(res, isNotNull); expect(res.file, fileUrl); - verify(() => api.fileUploader.sendImage(image, channelId, channelType)) - .called(1); + verify(() => api.fileUploader.sendImage(image, channelId, channelType)).called(1); verifyNoMoreInteractions(api.fileUploader); }); @@ -1085,14 +1105,12 @@ void main() { const channelType = 'test-channel-type'; const fileUrl = 'test-file-url'; - when(() => api.fileUploader.deleteFile(fileUrl, channelId, channelType)) - .thenAnswer((_) async => EmptyResponse()); + when(() => api.fileUploader.deleteFile(fileUrl, channelId, channelType)).thenAnswer((_) async => EmptyResponse()); final res = await client.deleteFile(fileUrl, channelId, channelType); expect(res, isNotNull); - verify(() => api.fileUploader.deleteFile(fileUrl, channelId, channelType)) - .called(1); + verify(() => api.fileUploader.deleteFile(fileUrl, channelId, channelType)).called(1); verifyNoMoreInteractions(api.fileUploader); }); @@ -1101,8 +1119,9 @@ void main() { const channelType = 'test-channel-type'; const imageUrl = 'test-image-url'; - when(() => api.fileUploader.deleteImage(imageUrl, channelId, channelType)) - .thenAnswer((_) async => EmptyResponse()); + when( + () => api.fileUploader.deleteImage(imageUrl, channelId, channelType), + ).thenAnswer((_) async => EmptyResponse()); final res = await client.deleteImage(imageUrl, channelId, channelType); expect(res, isNotNull); @@ -1117,8 +1136,7 @@ void main() { final image = AttachmentFile(size: 33, path: 'test-image-path'); const fileUrl = 'test-image-url'; - when(() => api.fileUploader.uploadImage(image)) - .thenAnswer((_) async => UploadImageResponse()..file = fileUrl); + when(() => api.fileUploader.uploadImage(image)).thenAnswer((_) async => UploadImageResponse()..file = fileUrl); final res = await client.uploadImage(image); expect(res, isNotNull); @@ -1132,8 +1150,7 @@ void main() { final file = AttachmentFile(size: 33, path: 'test-file-path'); const fileUrl = 'test-file-url'; - when(() => api.fileUploader.uploadFile(file)) - .thenAnswer((_) async => UploadFileResponse()..file = fileUrl); + when(() => api.fileUploader.uploadFile(file)).thenAnswer((_) async => UploadFileResponse()..file = fileUrl); final res = await client.uploadFile(file); expect(res, isNotNull); @@ -1146,8 +1163,7 @@ void main() { test('`.removeImage`', () async { const imageUrl = 'test-image-url'; - when(() => api.fileUploader.removeImage(imageUrl)) - .thenAnswer((_) async => EmptyResponse()); + when(() => api.fileUploader.removeImage(imageUrl)).thenAnswer((_) async => EmptyResponse()); final res = await client.removeImage(imageUrl); expect(res, isNotNull); @@ -1159,8 +1175,7 @@ void main() { test('`.removeFile`', () async { const fileUrl = 'test-file-url'; - when(() => api.fileUploader.removeFile(fileUrl)) - .thenAnswer((_) async => EmptyResponse()); + when(() => api.fileUploader.removeFile(fileUrl)).thenAnswer((_) async => EmptyResponse()); final res = await client.removeFile(fileUrl); expect(res, isNotNull); @@ -1174,21 +1189,21 @@ void main() { const channelType = 'test-channel-type'; const data = {'name': 'test-channel'}; - when(() => api.channel.updateChannel(channelId, channelType, data)) - .thenAnswer((invocation) async => UpdateChannelResponse() - ..channel = ChannelModel( - id: channelId, - type: channelType, - extraData: {...data}, - )); + when(() => api.channel.updateChannel(channelId, channelType, data)).thenAnswer( + (invocation) async => UpdateChannelResponse() + ..channel = ChannelModel( + id: channelId, + type: channelType, + extraData: {...data}, + ), + ); final res = await client.updateChannel(channelId, channelType, data); expect(res, isNotNull); expect(res.channel.cid, '$channelType:$channelId'); expect(res.channel.extraData['name'], 'test-channel'); - verify(() => api.channel.updateChannel(channelId, channelType, data)) - .called(1); + verify(() => api.channel.updateChannel(channelId, channelType, data)).called(1); verifyNoMoreInteractions(api.channel); }); @@ -1201,14 +1216,14 @@ void main() { }; const unset = ['tag', 'last_name']; - when(() => api.channel.updateChannelPartial(channelId, channelType, - set: set, unset: unset)) - .thenAnswer((invocation) async => PartialUpdateChannelResponse() - ..channel = ChannelModel( - id: channelId, - type: channelType, - extraData: {...set}, - )); + when(() => api.channel.updateChannelPartial(channelId, channelType, set: set, unset: unset)).thenAnswer( + (invocation) async => PartialUpdateChannelResponse() + ..channel = ChannelModel( + id: channelId, + type: channelType, + extraData: {...set}, + ), + ); final res = await client.updateChannelPartial( channelId, @@ -1220,8 +1235,7 @@ void main() { expect(res.channel.cid, '$channelType:$channelId'); expect(res.channel.extraData, set); - verify(() => api.channel.updateChannelPartial(channelId, channelType, - set: set, unset: unset)).called(1); + verify(() => api.channel.updateChannelPartial(channelId, channelType, set: set, unset: unset)).called(1); verifyNoMoreInteractions(api.channel); }); @@ -1229,8 +1243,7 @@ void main() { const id = 'test-device-id'; const provider = PushProvider.firebase; - when(() => api.device.addDevice(id, provider)) - .thenAnswer((_) async => EmptyResponse()); + when(() => api.device.addDevice(id, provider)).thenAnswer((_) async => EmptyResponse()); final res = await client.addDevice(id, provider); expect(res, isNotNull); @@ -1259,11 +1272,13 @@ void main() { ); expect(res, isNotNull); - verify(() => api.device.addDevice( - id, - provider, - pushProviderName: pushProviderName, - )).called(1); + verify( + () => api.device.addDevice( + id, + provider, + pushProviderName: pushProviderName, + ), + ).called(1); verifyNoMoreInteractions(api.device); }); @@ -1276,8 +1291,7 @@ void main() { ), ); - when(() => api.device.getDevices()) - .thenAnswer((_) async => ListDevicesResponse()..devices = devices); + when(() => api.device.getDevices()).thenAnswer((_) async => ListDevicesResponse()..devices = devices); final res = await client.getDevices(); expect(res, isNotNull); @@ -1290,8 +1304,7 @@ void main() { test('`.removeDevice`', () async { const deviceId = 'test-device-id'; - when(() => api.device.removeDevice(deviceId)) - .thenAnswer((_) async => EmptyResponse()); + when(() => api.device.removeDevice(deviceId)).thenAnswer((_) async => EmptyResponse()); final res = await client.removeDevice(deviceId); expect(res, isNotNull); @@ -1411,8 +1424,7 @@ void main() { expect(channel.extraData, channelData); }); - test('should return back in memory channel instance if available', - () async { + test('should return back in memory channel instance if available', () async { const channelType = 'test-channel-type'; const channelId = 'test-channel-id'; const channelData = {'name': 'test-channel-name'}; @@ -1428,22 +1440,24 @@ void main() { channel: ChannelModel(cid: channelCid), ); - when(() => api.channel.queryChannel( - channelType, - channelId: channelId, - channelData: channelData, - state: any(named: 'state'), - watch: any(named: 'watch'), - presence: any(named: 'presence'), - messagesPagination: any(named: 'messagesPagination'), - membersPagination: any(named: 'membersPagination'), - watchersPagination: any(named: 'watchersPagination'), - )).thenAnswer((_) async => channelState); + when( + () => api.channel.queryChannel( + channelType, + channelId: channelId, + channelData: channelData, + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + messagesPagination: any(named: 'messagesPagination'), + membersPagination: any(named: 'membersPagination'), + watchersPagination: any(named: 'watchersPagination'), + ), + ).thenAnswer((_) async => channelState); expectLater( client.state.channelsStream.skip(1), emitsInOrder([ - {channelCid: isCorrectChannelFor(channelState)} + {channelCid: isCorrectChannelFor(channelState)}, ]), ); @@ -1452,17 +1466,19 @@ void main() { final newChannel = client.channel(channelType, id: channelId); expect(newChannel, channel); - verify(() => api.channel.queryChannel( - channelType, - channelId: channelId, - channelData: channelData, - state: any(named: 'state'), - watch: any(named: 'watch'), - presence: any(named: 'presence'), - messagesPagination: any(named: 'messagesPagination'), - membersPagination: any(named: 'membersPagination'), - watchersPagination: any(named: 'watchersPagination'), - )).called(1); + verify( + () => api.channel.queryChannel( + channelType, + channelId: channelId, + channelData: channelData, + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + messagesPagination: any(named: 'messagesPagination'), + membersPagination: any(named: 'membersPagination'), + watchersPagination: any(named: 'watchersPagination'), + ), + ).called(1); }); }); @@ -1476,17 +1492,19 @@ void main() { channel: ChannelModel(cid: channelCid, extraData: channelData), ); - when(() => api.channel.queryChannel( - channelType, - channelId: channelId, - channelData: channelData, - state: any(named: 'state'), - watch: any(named: 'watch'), - presence: any(named: 'presence'), - messagesPagination: any(named: 'messagesPagination'), - membersPagination: any(named: 'membersPagination'), - watchersPagination: any(named: 'watchersPagination'), - )).thenAnswer((_) async => channelState); + when( + () => api.channel.queryChannel( + channelType, + channelId: channelId, + channelData: channelData, + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + messagesPagination: any(named: 'messagesPagination'), + membersPagination: any(named: 'membersPagination'), + watchersPagination: any(named: 'watchersPagination'), + ), + ).thenAnswer((_) async => channelState); final res = await client.createChannel( channelType, @@ -1502,17 +1520,19 @@ void main() { expect(channel.cid, '$channelType:$channelId'); expect(channel.extraData, channelData); - verify(() => api.channel.queryChannel( - channelType, - channelId: channelId, - channelData: channelData, - state: any(named: 'state'), - watch: any(named: 'watch'), - presence: any(named: 'presence'), - messagesPagination: any(named: 'messagesPagination'), - membersPagination: any(named: 'membersPagination'), - watchersPagination: any(named: 'watchersPagination'), - )).called(1); + verify( + () => api.channel.queryChannel( + channelType, + channelId: channelId, + channelData: channelData, + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + messagesPagination: any(named: 'messagesPagination'), + membersPagination: any(named: 'membersPagination'), + watchersPagination: any(named: 'watchersPagination'), + ), + ).called(1); verifyNoMoreInteractions(api.channel); }); @@ -1526,17 +1546,19 @@ void main() { channel: ChannelModel(cid: channelCid, extraData: channelData), ); - when(() => api.channel.queryChannel( - channelType, - channelId: channelId, - channelData: channelData, - state: any(named: 'state'), - watch: any(named: 'watch'), - presence: any(named: 'presence'), - messagesPagination: any(named: 'messagesPagination'), - membersPagination: any(named: 'membersPagination'), - watchersPagination: any(named: 'watchersPagination'), - )).thenAnswer((_) async => channelState); + when( + () => api.channel.queryChannel( + channelType, + channelId: channelId, + channelData: channelData, + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + messagesPagination: any(named: 'messagesPagination'), + membersPagination: any(named: 'membersPagination'), + watchersPagination: any(named: 'watchersPagination'), + ), + ).thenAnswer((_) async => channelState); final res = await client.watchChannel( channelType, @@ -1552,41 +1574,45 @@ void main() { expect(channel.cid, '$channelType:$channelId'); expect(channel.extraData, channelData); - verify(() => api.channel.queryChannel( - channelType, - channelId: channelId, - channelData: channelData, - state: any(named: 'state'), - watch: any(named: 'watch'), - presence: any(named: 'presence'), - messagesPagination: any(named: 'messagesPagination'), - membersPagination: any(named: 'membersPagination'), - watchersPagination: any(named: 'watchersPagination'), - )).called(1); + verify( + () => api.channel.queryChannel( + channelType, + channelId: channelId, + channelData: channelData, + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + messagesPagination: any(named: 'messagesPagination'), + membersPagination: any(named: 'membersPagination'), + watchersPagination: any(named: 'watchersPagination'), + ), + ).called(1); verifyNoMoreInteractions(api.channel); }); test('`.queryChannel`', () async { const channelType = 'test-channel-type'; const channelId = 'test-channel-id'; - const channelData = {'name': 'test-channel-name'}; - const channelCid = '$channelType:$channelId'; - - final channelState = ChannelState( - channel: ChannelModel(cid: channelCid, extraData: channelData), - ); - - when(() => api.channel.queryChannel( - channelType, - channelId: channelId, - channelData: channelData, - state: any(named: 'state'), - watch: any(named: 'watch'), - presence: any(named: 'presence'), - messagesPagination: any(named: 'messagesPagination'), - membersPagination: any(named: 'membersPagination'), - watchersPagination: any(named: 'watchersPagination'), - )).thenAnswer((_) async => channelState); + const channelData = {'name': 'test-channel-name'}; + const channelCid = '$channelType:$channelId'; + + final channelState = ChannelState( + channel: ChannelModel(cid: channelCid, extraData: channelData), + ); + + when( + () => api.channel.queryChannel( + channelType, + channelId: channelId, + channelData: channelData, + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + messagesPagination: any(named: 'messagesPagination'), + membersPagination: any(named: 'membersPagination'), + watchersPagination: any(named: 'watchersPagination'), + ), + ).thenAnswer((_) async => channelState); final res = await client.queryChannel( channelType, @@ -1602,17 +1628,19 @@ void main() { expect(channel.cid, '$channelType:$channelId'); expect(channel.extraData, channelData); - verify(() => api.channel.queryChannel( - channelType, - channelId: channelId, - channelData: channelData, - state: any(named: 'state'), - watch: any(named: 'watch'), - presence: any(named: 'presence'), - messagesPagination: any(named: 'messagesPagination'), - membersPagination: any(named: 'membersPagination'), - watchersPagination: any(named: 'watchersPagination'), - )).called(1); + verify( + () => api.channel.queryChannel( + channelType, + channelId: channelId, + channelData: channelData, + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + messagesPagination: any(named: 'messagesPagination'), + membersPagination: any(named: 'membersPagination'), + watchersPagination: any(named: 'watchersPagination'), + ), + ).called(1); verifyNoMoreInteractions(api.channel); }); @@ -1640,8 +1668,7 @@ void main() { const channelType = 'test-channel-type'; const channelId = 'test-channel-id'; - when(() => api.channel.hideChannel(channelId, channelType)) - .thenAnswer((_) async => EmptyResponse()); + when(() => api.channel.hideChannel(channelId, channelType)).thenAnswer((_) async => EmptyResponse()); final res = await client.hideChannel(channelId, channelType); @@ -1655,8 +1682,7 @@ void main() { const channelType = 'test-channel-type'; const channelId = 'test-channel-id'; - when(() => api.channel.showChannel(channelId, channelType)) - .thenAnswer((_) async => EmptyResponse()); + when(() => api.channel.showChannel(channelId, channelType)).thenAnswer((_) async => EmptyResponse()); final res = await client.showChannel(channelId, channelType); @@ -1670,8 +1696,7 @@ void main() { const channelType = 'test-channel-type'; const channelId = 'test-channel-id'; - when(() => api.channel.deleteChannel(channelId, channelType)) - .thenAnswer((_) async => EmptyResponse()); + when(() => api.channel.deleteChannel(channelId, channelType)).thenAnswer((_) async => EmptyResponse()); final res = await client.deleteChannel(channelId, channelType); @@ -1685,8 +1710,7 @@ void main() { const channelType = 'test-channel-type'; const channelId = 'test-channel-id'; - when(() => api.channel.truncateChannel(channelId, channelType)) - .thenAnswer((_) async => EmptyResponse()); + when(() => api.channel.truncateChannel(channelId, channelType)).thenAnswer((_) async => EmptyResponse()); final res = await client.truncateChannel(channelId, channelType); @@ -1703,8 +1727,7 @@ void main() { const channelId = 'test-channel-id'; const channelCid = '$channelType:$channelId'; - when(() => api.moderation.muteChannel(channelCid)) - .thenAnswer((_) async => EmptyResponse()); + when(() => api.moderation.muteChannel(channelCid)).thenAnswer((_) async => EmptyResponse()); final res = await client.muteChannel(channelCid); @@ -1719,8 +1742,7 @@ void main() { const channelId = 'test-channel-id'; const channelCid = '$channelType:$channelId'; - when(() => api.moderation.unmuteChannel(channelCid)) - .thenAnswer((_) async => EmptyResponse()); + when(() => api.moderation.unmuteChannel(channelCid)).thenAnswer((_) async => EmptyResponse()); final res = await client.unmuteChannel(channelCid); @@ -1737,14 +1759,18 @@ void main() { const set = {'pinned': true}; const unset = ['pinned']; - when(() => api.channel.updateMemberPartial( - channelId: channelId, - channelType: channelType, - set: set, - unset: unset, - )).thenAnswer((_) async => FakePartialUpdateMemberResponse( - channelMember: Member(userId: otherUserId), - )); + when( + () => api.channel.updateMemberPartial( + channelId: channelId, + channelType: channelType, + set: set, + unset: unset, + ), + ).thenAnswer( + (_) async => FakePartialUpdateMemberResponse( + channelMember: Member(userId: otherUserId), + ), + ); final res = await client.partialMemberUpdate( channelId: channelId, @@ -1756,12 +1782,14 @@ void main() { expect(res, isNotNull); expect(res.channelMember.userId, otherUserId); - verify(() => api.channel.updateMemberPartial( - channelId: channelId, - channelType: channelType, - set: set, - unset: unset, - )).called(1); + verify( + () => api.channel.updateMemberPartial( + channelId: channelId, + channelType: channelType, + set: set, + unset: unset, + ), + ).called(1); verifyNoMoreInteractions(api.channel); }); @@ -1771,14 +1799,18 @@ void main() { const set = {'pinned': true}; const unset = ['pinned']; - when(() => api.channel.updateMemberPartial( - channelId: channelId, - channelType: channelType, - set: set, - unset: unset, - )).thenAnswer((_) async => FakePartialUpdateMemberResponse( - channelMember: Member(userId: userId), - )); + when( + () => api.channel.updateMemberPartial( + channelId: channelId, + channelType: channelType, + set: set, + unset: unset, + ), + ).thenAnswer( + (_) async => FakePartialUpdateMemberResponse( + channelMember: Member(userId: userId), + ), + ); final res = await client.partialMemberUpdate( channelId: channelId, @@ -1789,12 +1821,14 @@ void main() { expect(res, isNotNull); expect(res.channelMember.userId, userId); - verify(() => api.channel.updateMemberPartial( - channelId: channelId, - channelType: channelType, - set: set, - unset: unset, - )).called(1); + verify( + () => api.channel.updateMemberPartial( + channelId: channelId, + channelType: channelType, + set: set, + unset: unset, + ), + ).called(1); verifyNoMoreInteractions(api.channel); }); @@ -1802,13 +1836,17 @@ void main() { const channelType = 'test-channel-type'; const channelId = 'test-channel-id'; - when(() => api.channel.updateMemberPartial( - channelId: channelId, - channelType: channelType, - set: const MemberUpdatePayload(pinned: true).toJson(), - )).thenAnswer((_) async => FakePartialUpdateMemberResponse( - channelMember: Member(userId: userId, pinnedAt: DateTime.now()), - )); + when( + () => api.channel.updateMemberPartial( + channelId: channelId, + channelType: channelType, + set: const MemberUpdatePayload(pinned: true).toJson(), + ), + ).thenAnswer( + (_) async => FakePartialUpdateMemberResponse( + channelMember: Member(userId: userId, pinnedAt: DateTime.now()), + ), + ); final res = await client.pinChannel( channelId: channelId, @@ -1817,11 +1855,13 @@ void main() { expect(res, isNotNull); - verify(() => api.channel.updateMemberPartial( - channelId: channelId, - channelType: channelType, - set: const MemberUpdatePayload(pinned: true).toJson(), - )).called(1); + verify( + () => api.channel.updateMemberPartial( + channelId: channelId, + channelType: channelType, + set: const MemberUpdatePayload(pinned: true).toJson(), + ), + ).called(1); verifyNoMoreInteractions(api.channel); }); @@ -1829,13 +1869,17 @@ void main() { const channelType = 'test-channel-type'; const channelId = 'test-channel-id'; - when(() => api.channel.updateMemberPartial( - channelId: channelId, - channelType: channelType, - unset: [MemberUpdateType.pinned.name], - )).thenAnswer((_) async => FakePartialUpdateMemberResponse( - channelMember: Member(userId: userId, pinnedAt: DateTime.now()), - )); + when( + () => api.channel.updateMemberPartial( + channelId: channelId, + channelType: channelType, + unset: [MemberUpdateType.pinned.name], + ), + ).thenAnswer( + (_) async => FakePartialUpdateMemberResponse( + channelMember: Member(userId: userId, pinnedAt: DateTime.now()), + ), + ); final res = await client.unpinChannel( channelId: channelId, @@ -1844,11 +1888,13 @@ void main() { expect(res, isNotNull); - verify(() => api.channel.updateMemberPartial( - channelId: channelId, - channelType: channelType, - unset: [MemberUpdateType.pinned.name], - )).called(1); + verify( + () => api.channel.updateMemberPartial( + channelId: channelId, + channelType: channelType, + unset: [MemberUpdateType.pinned.name], + ), + ).called(1); verifyNoMoreInteractions(api.channel); }); @@ -1856,13 +1902,17 @@ void main() { const channelType = 'test-channel-type'; const channelId = 'test-channel-id'; - when(() => api.channel.updateMemberPartial( - channelId: channelId, - channelType: channelType, - set: const MemberUpdatePayload(archived: true).toJson(), - )).thenAnswer((_) async => FakePartialUpdateMemberResponse( - channelMember: Member(userId: userId, archivedAt: DateTime.now()), - )); + when( + () => api.channel.updateMemberPartial( + channelId: channelId, + channelType: channelType, + set: const MemberUpdatePayload(archived: true).toJson(), + ), + ).thenAnswer( + (_) async => FakePartialUpdateMemberResponse( + channelMember: Member(userId: userId, archivedAt: DateTime.now()), + ), + ); final res = await client.archiveChannel( channelId: channelId, @@ -1871,11 +1921,13 @@ void main() { expect(res, isNotNull); - verify(() => api.channel.updateMemberPartial( - channelId: channelId, - channelType: channelType, - set: const MemberUpdatePayload(archived: true).toJson(), - )).called(1); + verify( + () => api.channel.updateMemberPartial( + channelId: channelId, + channelType: channelType, + set: const MemberUpdatePayload(archived: true).toJson(), + ), + ).called(1); verifyNoMoreInteractions(api.channel); }); @@ -1883,13 +1935,17 @@ void main() { const channelType = 'test-channel-type'; const channelId = 'test-channel-id'; - when(() => api.channel.updateMemberPartial( - channelId: channelId, - channelType: channelType, - unset: [MemberUpdateType.archived.name], - )).thenAnswer((_) async => FakePartialUpdateMemberResponse( - channelMember: Member(userId: userId, pinnedAt: DateTime.now()), - )); + when( + () => api.channel.updateMemberPartial( + channelId: channelId, + channelType: channelType, + unset: [MemberUpdateType.archived.name], + ), + ).thenAnswer( + (_) async => FakePartialUpdateMemberResponse( + channelMember: Member(userId: userId, pinnedAt: DateTime.now()), + ), + ); final res = await client.unarchiveChannel( channelId: channelId, @@ -1898,11 +1954,13 @@ void main() { expect(res, isNotNull); - verify(() => api.channel.updateMemberPartial( - channelId: channelId, - channelType: channelType, - unset: [MemberUpdateType.archived.name], - )).called(1); + verify( + () => api.channel.updateMemberPartial( + channelId: channelId, + channelType: channelType, + unset: [MemberUpdateType.archived.name], + ), + ).called(1); verifyNoMoreInteractions(api.channel); }); @@ -1911,16 +1969,15 @@ void main() { const channelId = 'test-channel-id'; const channelCid = '$channelType:$channelId'; - when(() => api.channel.acceptChannelInvite(channelId, channelType)) - .thenAnswer((_) async => - AcceptInviteResponse()..channel = ChannelModel(cid: channelCid)); + when( + () => api.channel.acceptChannelInvite(channelId, channelType), + ).thenAnswer((_) async => AcceptInviteResponse()..channel = ChannelModel(cid: channelCid)); final res = await client.acceptChannelInvite(channelId, channelType); expect(res, isNotNull); expect(res.channel.cid, channelCid); - verify(() => api.channel.acceptChannelInvite(channelId, channelType)) - .called(1); + verify(() => api.channel.acceptChannelInvite(channelId, channelType)).called(1); verifyNoMoreInteractions(api.channel); }); @@ -1929,16 +1986,15 @@ void main() { const channelId = 'test-channel-id'; const channelCid = '$channelType:$channelId'; - when(() => api.channel.rejectChannelInvite(channelId, channelType)) - .thenAnswer((_) async => - RejectInviteResponse()..channel = ChannelModel(cid: channelCid)); + when( + () => api.channel.rejectChannelInvite(channelId, channelType), + ).thenAnswer((_) async => RejectInviteResponse()..channel = ChannelModel(cid: channelCid)); final res = await client.rejectChannelInvite(channelId, channelType); expect(res, isNotNull); expect(res.channel.cid, channelCid); - verify(() => api.channel.rejectChannelInvite(channelId, channelType)) - .called(1); + verify(() => api.channel.rejectChannelInvite(channelId, channelType)).called(1); verifyNoMoreInteractions(api.channel); }); @@ -1954,10 +2010,11 @@ void main() { final memberIds = members.map((e) => e.userId!).toList(growable: false); - when(() => api.channel.addMembers(channelId, channelType, memberIds)) - .thenAnswer((_) async => AddMembersResponse() - ..channel = ChannelModel(cid: channelCid) - ..members = members); + when(() => api.channel.addMembers(channelId, channelType, memberIds)).thenAnswer( + (_) async => AddMembersResponse() + ..channel = ChannelModel(cid: channelCid) + ..members = members, + ); final res = await client.addChannelMembers( channelId, @@ -1988,14 +2045,18 @@ void main() { final memberIds = members.map((e) => e.userId!).toList(growable: false); final hideHistoryBefore = DateTime.parse('2024-01-01T00:00:00Z'); - when(() => api.channel.addMembers( - channelId, - channelType, - memberIds, - hideHistoryBefore: hideHistoryBefore, - )).thenAnswer((_) async => AddMembersResponse() - ..channel = ChannelModel(cid: channelCid) - ..members = members); + when( + () => api.channel.addMembers( + channelId, + channelType, + memberIds, + hideHistoryBefore: hideHistoryBefore, + ), + ).thenAnswer( + (_) async => AddMembersResponse() + ..channel = ChannelModel(cid: channelCid) + ..members = members, + ); final res = await client.addChannelMembers( channelId, @@ -2031,10 +2092,11 @@ void main() { final memberIds = members.map((e) => e.userId!).toList(growable: false); - when(() => api.channel.removeMembers(channelId, channelType, memberIds)) - .thenAnswer((_) async => RemoveMembersResponse() - ..channel = ChannelModel(cid: channelCid) - ..members = members); + when(() => api.channel.removeMembers(channelId, channelType, memberIds)).thenAnswer( + (_) async => RemoveMembersResponse() + ..channel = ChannelModel(cid: channelCid) + ..members = members, + ); final res = await client.removeChannelMembers( channelId, @@ -2064,11 +2126,11 @@ void main() { final memberIds = members.map((e) => e.userId!).toList(growable: false); - when(() => api.channel - .inviteChannelMembers(channelId, channelType, memberIds)) - .thenAnswer((_) async => InviteMembersResponse() - ..channel = ChannelModel(cid: channelCid) - ..members = members); + when(() => api.channel.inviteChannelMembers(channelId, channelType, memberIds)).thenAnswer( + (_) async => InviteMembersResponse() + ..channel = ChannelModel(cid: channelCid) + ..members = members, + ); final res = await client.inviteChannelMembers( channelId, @@ -2080,8 +2142,7 @@ void main() { expect(res.channel.cid, channelCid); expect(res.members.length, memberIds.length); - verify(() => api.channel - .inviteChannelMembers(channelId, channelType, memberIds)).called(1); + verify(() => api.channel.inviteChannelMembers(channelId, channelType, memberIds)).called(1); verifyNoMoreInteractions(api.channel); }); @@ -2089,8 +2150,7 @@ void main() { const channelType = 'test-channel-type'; const channelId = 'test-channel-id'; - when(() => api.channel.stopWatching(channelId, channelType)) - .thenAnswer((_) async => EmptyResponse()); + when(() => api.channel.stopWatching(channelId, channelType)).thenAnswer((_) async => EmptyResponse()); final res = await client.stopChannelWatching(channelId, channelType); expect(res, isNotNull); @@ -2105,9 +2165,9 @@ void main() { const messageId = 'test-message-id'; const formData = {'key': 'value'}; - when(() => api.message - .sendAction(channelId, channelType, messageId, formData)) - .thenAnswer((_) async => SendActionResponse()); + when( + () => api.message.sendAction(channelId, channelType, messageId, formData), + ).thenAnswer((_) async => SendActionResponse()); final res = await client.sendAction( channelId, @@ -2118,8 +2178,7 @@ void main() { expect(res, isNotNull); - verify(() => api.message - .sendAction(channelId, channelType, messageId, formData)).called(1); + verify(() => api.message.sendAction(channelId, channelType, messageId, formData)).called(1); verifyNoMoreInteractions(api.message); }); @@ -2127,8 +2186,7 @@ void main() { const channelType = 'test-channel-type'; const channelId = 'test-channel-id'; - when(() => api.channel.markRead(channelId, channelType)) - .thenAnswer((_) async => EmptyResponse()); + when(() => api.channel.markRead(channelId, channelType)).thenAnswer((_) async => EmptyResponse()); final res = await client.markChannelRead(channelId, channelType); @@ -2143,8 +2201,7 @@ void main() { const channelId = 'test-channel-id'; const messageId = 'test-message-id'; - when(() => api.channel.markUnread(channelId, channelType, messageId)) - .thenAnswer((_) async => EmptyResponse()); + when(() => api.channel.markUnread(channelId, channelType, messageId)).thenAnswer((_) async => EmptyResponse()); final res = await client.markChannelUnread( channelId, @@ -2154,8 +2211,7 @@ void main() { expect(res, isNotNull); - verify(() => api.channel.markUnread(channelId, channelType, messageId)) - .called(1); + verify(() => api.channel.markUnread(channelId, channelType, messageId)).called(1); verifyNoMoreInteractions(api.channel); }); @@ -2164,11 +2220,13 @@ void main() { const channelId = 'test-channel-id'; final timestamp = DateTime.parse('2024-01-01T00:00:00Z'); - when(() => api.channel.markUnreadByTimestamp( - channelId, - channelType, - timestamp, - )).thenAnswer((_) async => EmptyResponse()); + when( + () => api.channel.markUnreadByTimestamp( + channelId, + channelType, + timestamp, + ), + ).thenAnswer((_) async => EmptyResponse()); final res = await client.markChannelUnreadByTimestamp( channelId, @@ -2178,11 +2236,13 @@ void main() { expect(res, isNotNull); - verify(() => api.channel.markUnreadByTimestamp( - channelId, - channelType, - timestamp, - )).called(1); + verify( + () => api.channel.markUnreadByTimestamp( + channelId, + channelType, + timestamp, + ), + ).called(1); verifyNoMoreInteractions(api.channel); }); @@ -2266,25 +2326,23 @@ void main() { ], ); - when(() => api.polls.partialUpdatePoll(pollId, set: set, unset: unset)) - .thenAnswer((_) async => UpdatePollResponse()..poll = poll); + when( + () => api.polls.partialUpdatePoll(pollId, set: set, unset: unset), + ).thenAnswer((_) async => UpdatePollResponse()..poll = poll); - final res = - await client.partialUpdatePoll(pollId, set: set, unset: unset); + final res = await client.partialUpdatePoll(pollId, set: set, unset: unset); expect(res, isNotNull); expect(res.poll.id, pollId); expect(res.poll.name, set['name']); - verify(() => api.polls.partialUpdatePoll(pollId, set: set, unset: unset)) - .called(1); + verify(() => api.polls.partialUpdatePoll(pollId, set: set, unset: unset)).called(1); verifyNoMoreInteractions(api.polls); }); test('`.deletePoll`', () async { const pollId = 'test-poll-id'; - when(() => api.polls.deletePoll(pollId)) - .thenAnswer((_) async => EmptyResponse()); + when(() => api.polls.deletePoll(pollId)).thenAnswer((_) async => EmptyResponse()); final res = await client.deletePoll(pollId); expect(res, isNotNull); @@ -2296,15 +2354,14 @@ void main() { test('`.closePoll`', () async { const pollId = 'test-poll-id'; - when(() => api.polls.partialUpdatePoll(pollId, set: {'is_closed': true})) - .thenAnswer((_) async => UpdatePollResponse()); + when( + () => api.polls.partialUpdatePoll(pollId, set: {'is_closed': true}), + ).thenAnswer((_) async => UpdatePollResponse()); final res = await client.closePoll(pollId); expect(res, isNotNull); - verify(() => - api.polls.partialUpdatePoll(pollId, set: {'is_closed': true})) - .called(1); + verify(() => api.polls.partialUpdatePoll(pollId, set: {'is_closed': true})).called(1); verifyNoMoreInteractions(api.polls); }); @@ -2312,8 +2369,9 @@ void main() { const pollId = 'test-poll-id'; const option = PollOption(text: 'Red'); - when(() => api.polls.createPollOption(pollId, option)).thenAnswer( - (_) async => CreatePollOptionResponse()..pollOption = option); + when( + () => api.polls.createPollOption(pollId, option), + ).thenAnswer((_) async => CreatePollOptionResponse()..pollOption = option); final res = await client.createPollOption(pollId, option); expect(res, isNotNull); @@ -2328,8 +2386,9 @@ void main() { const optionId = 'test-option-id'; const option = PollOption(id: optionId, text: 'Red'); - when(() => api.polls.getPollOption(pollId, optionId)).thenAnswer( - (_) async => GetPollOptionResponse()..pollOption = option); + when( + () => api.polls.getPollOption(pollId, optionId), + ).thenAnswer((_) async => GetPollOptionResponse()..pollOption = option); final res = await client.getPollOption(pollId, optionId); expect(res, isNotNull); @@ -2343,8 +2402,9 @@ void main() { const pollId = 'test-poll-id'; const option = PollOption(id: 'test-option-id', text: 'Red'); - when(() => api.polls.updatePollOption(pollId, option)).thenAnswer( - (_) async => UpdatePollOptionResponse()..pollOption = option); + when( + () => api.polls.updatePollOption(pollId, option), + ).thenAnswer((_) async => UpdatePollOptionResponse()..pollOption = option); final res = await client.updatePollOption(pollId, option); expect(res, isNotNull); @@ -2358,8 +2418,7 @@ void main() { const pollId = 'test-poll-id'; const optionId = 'test-option-id'; - when(() => api.polls.deletePollOption(pollId, optionId)) - .thenAnswer((_) async => EmptyResponse()); + when(() => api.polls.deletePollOption(pollId, optionId)).thenAnswer((_) async => EmptyResponse()); final res = await client.deletePollOption(pollId, optionId); expect(res, isNotNull); @@ -2376,21 +2435,19 @@ void main() { // Custom matcher to check if the Vote object has the specified id Matcher matchesVoteOption(String expected) => predicate( - (vote) => vote.optionId == expected, - 'Vote with option $expected', - ); + (vote) => vote.optionId == expected, + 'Vote with option $expected', + ); - when(() => api.polls.castPollVote( - messageId, pollId, any(that: matchesVoteOption(optionId)))) - .thenAnswer((_) async => CastPollVoteResponse()..vote = vote); + when( + () => api.polls.castPollVote(messageId, pollId, any(that: matchesVoteOption(optionId))), + ).thenAnswer((_) async => CastPollVoteResponse()..vote = vote); - final res = - await client.castPollVote(messageId, pollId, optionId: optionId); + final res = await client.castPollVote(messageId, pollId, optionId: optionId); expect(res, isNotNull); expect(res.vote, vote); - verify(() => api.polls.castPollVote( - messageId, pollId, any(that: matchesVoteOption(optionId)))).called(1); + verify(() => api.polls.castPollVote(messageId, pollId, any(that: matchesVoteOption(optionId)))).called(1); verifyNoMoreInteractions(api.polls); }); @@ -2402,22 +2459,19 @@ void main() { // Custom matcher to check if the Vote object has the specified id Matcher matchesVoteAnswer(String expected) => predicate( - (vote) => vote.answerText == expected, - 'Vote with answer $expected', - ); + (vote) => vote.answerText == expected, + 'Vote with answer $expected', + ); - when(() => api.polls.castPollVote( - messageId, pollId, any(that: matchesVoteAnswer(answerText)))) - .thenAnswer((_) async => CastPollVoteResponse()..vote = vote); + when( + () => api.polls.castPollVote(messageId, pollId, any(that: matchesVoteAnswer(answerText))), + ).thenAnswer((_) async => CastPollVoteResponse()..vote = vote); - final res = - await client.addPollAnswer(messageId, pollId, answerText: answerText); + final res = await client.addPollAnswer(messageId, pollId, answerText: answerText); expect(res, isNotNull); expect(res.vote, vote); - verify(() => api.polls.castPollVote( - messageId, pollId, any(that: matchesVoteAnswer(answerText)))) - .called(1); + verify(() => api.polls.castPollVote(messageId, pollId, any(that: matchesVoteAnswer(answerText)))).called(1); verifyNoMoreInteractions(api.polls); }); @@ -2426,14 +2480,12 @@ void main() { const pollId = 'test-poll-id'; const voteId = 'test-vote-id'; - when(() => api.polls.removePollVote(messageId, pollId, voteId)) - .thenAnswer((_) async => RemovePollVoteResponse()); + when(() => api.polls.removePollVote(messageId, pollId, voteId)).thenAnswer((_) async => RemovePollVoteResponse()); final res = await client.removePollVote(messageId, pollId, voteId); expect(res, isNotNull); - verify(() => api.polls.removePollVote(messageId, pollId, voteId)) - .called(1); + verify(() => api.polls.removePollVote(messageId, pollId, voteId)).called(1); verifyNoMoreInteractions(api.polls); }); @@ -2454,11 +2506,13 @@ void main() { ), ); - when(() => api.polls.queryPolls( - filter: filter, - sort: sort, - pagination: pagination, - )).thenAnswer( + when( + () => api.polls.queryPolls( + filter: filter, + sort: sort, + pagination: pagination, + ), + ).thenAnswer( (_) async => QueryPollsResponse()..polls = polls, ); @@ -2470,11 +2524,13 @@ void main() { expect(res, isNotNull); expect(res.polls.length, polls.length); - verify(() => api.polls.queryPolls( - filter: filter, - sort: sort, - pagination: pagination, - )).called(1); + verify( + () => api.polls.queryPolls( + filter: filter, + sort: sort, + pagination: pagination, + ), + ).called(1); verifyNoMoreInteractions(api.polls); }); @@ -2489,12 +2545,14 @@ void main() { (index) => PollVote(id: 'test-vote-id-$index', answerText: 'Red'), ); - when(() => api.polls.queryPollVotes( - pollId, - filter: filter, - sort: sort, - pagination: pagination, - )).thenAnswer( + when( + () => api.polls.queryPollVotes( + pollId, + filter: filter, + sort: sort, + pagination: pagination, + ), + ).thenAnswer( (_) async => QueryPollVotesResponse()..votes = votes, ); @@ -2507,12 +2565,14 @@ void main() { expect(res, isNotNull); expect(res.votes.length, votes.length); - verify(() => api.polls.queryPollVotes( - pollId, - filter: filter, - sort: sort, - pagination: pagination, - )).called(1); + verify( + () => api.polls.queryPollVotes( + pollId, + filter: filter, + sort: sort, + pagination: pagination, + ), + ).called(1); verifyNoMoreInteractions(api.polls); }); @@ -2522,8 +2582,7 @@ void main() { extraData: const {'name': 'test-user'}, ); - when(() => api.user.updateUsers([user])).thenAnswer( - (_) async => UpdateUsersResponse()..users = {user.id: user}); + when(() => api.user.updateUsers([user])).thenAnswer((_) async => UpdateUsersResponse()..users = {user.id: user}); final res = await client.updateUser(user); @@ -2551,8 +2610,7 @@ void main() { extraData: {'color': set['color']}, ); - when(() => api.user.partialUpdateUsers([partialUpdateRequest])) - .thenAnswer( + when(() => api.user.partialUpdateUsers([partialUpdateRequest])).thenAnswer( (_) async => UpdateUsersResponse() ..users = { updatedUser.id: updatedUser, @@ -2577,8 +2635,9 @@ void main() { test('`.banUser`', () async { const userId = 'test-user-id'; - when(() => api.moderation.banUser(userId, options: any(named: 'options'))) - .thenAnswer((_) async => EmptyResponse()); + when( + () => api.moderation.banUser(userId, options: any(named: 'options')), + ).thenAnswer((_) async => EmptyResponse()); final res = await client.banUser(userId); @@ -2593,9 +2652,9 @@ void main() { test('`.unbanUser`', () async { const userId = 'test-user-id'; - when(() => - api.moderation.unbanUser(userId, options: any(named: 'options'))) - .thenAnswer((_) async => EmptyResponse()); + when( + () => api.moderation.unbanUser(userId, options: any(named: 'options')), + ).thenAnswer((_) async => EmptyResponse()); final res = await client.unbanUser(userId); @@ -2631,8 +2690,7 @@ void main() { test('`.unblockUser`', () async { const userId = 'test-user-id'; - when(() => api.user.unblockUser(userId)) - .thenAnswer((_) async => EmptyResponse()); + when(() => api.user.unblockUser(userId)).thenAnswer((_) async => EmptyResponse()); final res = await client.unblockUser(userId); @@ -2772,10 +2830,8 @@ void main() { await client.unblockUser(nonBlockedUserId); // Verify - should remain unchanged - expect(client.state.currentUser?.blockedUserIds, - contains(otherBlockedId)); - expect(client.state.currentUser?.blockedUserIds, - isNot(contains(nonBlockedUserId))); + expect(client.state.currentUser?.blockedUserIds, contains(otherBlockedId)); + expect(client.state.currentUser?.blockedUserIds, isNot(contains(nonBlockedUserId))); verify(() => api.user.unblockUser(nonBlockedUserId)).called(1); verifyNoMoreInteractions(api.user); }, @@ -2924,8 +2980,7 @@ void main() { test('`.shadowBan`', () async { const userId = 'test-user-id'; - when(() => api.moderation.banUser(userId, options: {'shadow': true})) - .thenAnswer((_) async => EmptyResponse()); + when(() => api.moderation.banUser(userId, options: {'shadow': true})).thenAnswer((_) async => EmptyResponse()); final res = await client.shadowBan(userId); @@ -2940,8 +2995,7 @@ void main() { test('`.removeShadowBan`', () async { const userId = 'test-user-id'; - when(() => api.moderation.unbanUser(userId, options: {'shadow': true})) - .thenAnswer((_) async => EmptyResponse()); + when(() => api.moderation.unbanUser(userId, options: {'shadow': true})).thenAnswer((_) async => EmptyResponse()); final res = await client.removeShadowBan(userId); @@ -2956,8 +3010,7 @@ void main() { test('`.muteUser`', () async { const userId = 'test-user-id'; - when(() => api.moderation.muteUser(userId)) - .thenAnswer((_) async => EmptyResponse()); + when(() => api.moderation.muteUser(userId)).thenAnswer((_) async => EmptyResponse()); final res = await client.muteUser(userId); @@ -2970,8 +3023,7 @@ void main() { test('`.unmuteUser`', () async { const userId = 'test-user-id'; - when(() => api.moderation.unmuteUser(userId)) - .thenAnswer((_) async => EmptyResponse()); + when(() => api.moderation.unmuteUser(userId)).thenAnswer((_) async => EmptyResponse()); final res = await client.unmuteUser(userId); @@ -2984,8 +3036,7 @@ void main() { test('`.flagMessage`', () async { const messageId = 'test-message-id'; - when(() => api.moderation.flagMessage(messageId)) - .thenAnswer((_) async => EmptyResponse()); + when(() => api.moderation.flagMessage(messageId)).thenAnswer((_) async => EmptyResponse()); final res = await client.flagMessage(messageId); @@ -2998,8 +3049,7 @@ void main() { test('`.unflagMessage`', () async { const messageId = 'test-message-id'; - when(() => api.moderation.unflagMessage(messageId)) - .thenAnswer((_) async => EmptyResponse()); + when(() => api.moderation.unflagMessage(messageId)).thenAnswer((_) async => EmptyResponse()); final res = await client.unflagMessage(messageId); @@ -3012,8 +3062,7 @@ void main() { test('`.flagUser`', () async { const userId = 'test-message-id'; - when(() => api.moderation.flagUser(userId)) - .thenAnswer((_) async => EmptyResponse()); + when(() => api.moderation.flagUser(userId)).thenAnswer((_) async => EmptyResponse()); final res = await client.flagUser(userId); @@ -3026,8 +3075,7 @@ void main() { test('`.unflagUser`', () async { const userId = 'test-message-id'; - when(() => api.moderation.unflagUser(userId)) - .thenAnswer((_) async => EmptyResponse()); + when(() => api.moderation.unflagUser(userId)).thenAnswer((_) async => EmptyResponse()); final res = await client.unflagUser(userId); @@ -3054,8 +3102,9 @@ void main() { ]; when(() => api.user.getActiveLiveLocations()).thenAnswer( - (_) async => GetActiveLiveLocationsResponse() // - ..activeLiveLocations = locations, + (_) async => + GetActiveLiveLocationsResponse() // + ..activeLiveLocations = locations, ); // Initial state should be empty @@ -3393,8 +3442,7 @@ void main() { }); test('`.markAllRead`', () async { - when(() => api.channel.markAllRead()) - .thenAnswer((_) async => EmptyResponse()); + when(() => api.channel.markAllRead()).thenAnswer((_) async => EmptyResponse()); final res = await client.markAllRead(); expect(res, isNotNull); @@ -3415,8 +3463,7 @@ void main() { ), ]; - when(() => api.channel.markChannelsDelivered(deliveries)) - .thenAnswer((_) async => EmptyResponse()); + when(() => api.channel.markChannelsDelivered(deliveries)).thenAnswer((_) async => EmptyResponse()); final res = await client.markChannelsDelivered(deliveries); expect(res, isNotNull); @@ -3441,11 +3488,13 @@ void main() { final res = await client.sendEvent(channelId, channelType, event); expect(res, isNotNull); - verify(() => api.channel.sendEvent( - channelId, - channelType, - any(that: isSameEventAs(event)), - )).called(1); + verify( + () => api.channel.sendEvent( + channelId, + channelType, + any(that: isSameEventAs(event)), + ), + ).called(1); verifyNoMoreInteractions(api.channel); }); @@ -3484,8 +3533,7 @@ void main() { const messageId = 'test-message-id'; const reactionType = 'like'; - when(() => api.message.deleteReaction(messageId, reactionType)) - .thenAnswer((_) async => EmptyResponse()); + when(() => api.message.deleteReaction(messageId, reactionType)).thenAnswer((_) async => EmptyResponse()); final res = await client.deleteReaction(messageId, reactionType); expect(res, isNotNull); @@ -3501,19 +3549,21 @@ void main() { const channelId = 'test-channel-id'; const channelType = 'test-channel-type'; - when(() => api.message.sendMessage( - channelId, channelType, any(that: isSameMessageAs(message)))) - .thenAnswer((_) async => SendMessageResponse()..message = message); + when( + () => api.message.sendMessage(channelId, channelType, any(that: isSameMessageAs(message))), + ).thenAnswer((_) async => SendMessageResponse()..message = message); final res = await client.sendMessage(message, channelId, channelType); expect(res, isNotNull); expect(res.message, isSameMessageAs(message)); - verify(() => api.message.sendMessage( - channelId, - channelType, - any(that: isSameMessageAs(message)), - )).called(1); + verify( + () => api.message.sendMessage( + channelId, + channelType, + any(that: isSameMessageAs(message)), + ), + ).called(1); verifyNoMoreInteractions(api.message); }); @@ -3546,11 +3596,13 @@ void main() { expect(res, isNotNull); expect(res.draft.message, isSameDraftMessageAs(message)); - verify(() => api.message.createDraft( - channelId, - channelType, - any(that: isSameDraftMessageAs(message)), - )).called(1); + verify( + () => api.message.createDraft( + channelId, + channelType, + any(that: isSameDraftMessageAs(message)), + ), + ).called(1); verifyNoMoreInteractions(api.message); }); @@ -3559,8 +3611,7 @@ void main() { const channelId = 'test-channel-id'; const channelType = 'test-channel-type'; - when(() => api.message.deleteDraft(channelId, channelType)) - .thenAnswer((_) async => EmptyResponse()); + when(() => api.message.deleteDraft(channelId, channelType)).thenAnswer((_) async => EmptyResponse()); final res = await client.deleteDraft(channelId, channelType); expect(res, isNotNull); @@ -3575,13 +3626,14 @@ void main() { final message = DraftMessage(id: 'test-message-id', text: 'Hello!'); - when(() => api.message.getDraft(channelId, channelType)) - .thenAnswer((_) async => GetDraftResponse() - ..draft = Draft( - channelCid: '$channelType:$channelId', - createdAt: DateTime.now(), - message: message, - )); + when(() => api.message.getDraft(channelId, channelType)).thenAnswer( + (_) async => GetDraftResponse() + ..draft = Draft( + channelCid: '$channelType:$channelId', + createdAt: DateTime.now(), + message: message, + ), + ); final res = await client.getDraft(channelId, channelType); @@ -3601,11 +3653,10 @@ void main() { channelCid: '$channelType:$channelId', createdAt: DateTime.now(), message: DraftMessage(id: 'test-message-id', text: 'Hello!'), - ) + ), ]; - when(() => api.message.queryDrafts()) - .thenAnswer((_) async => QueryDraftsResponse()..drafts = drafts); + when(() => api.message.queryDrafts()).thenAnswer((_) async => QueryDraftsResponse()..drafts = drafts); final res = await client.queryDrafts(); @@ -3624,8 +3675,7 @@ void main() { (index) => Message(id: 'test-message-id-$index'), ); - when(() => api.message.getReplies(parentId)) - .thenAnswer((_) async => QueryRepliesResponse()..messages = messages); + when(() => api.message.getReplies(parentId)).thenAnswer((_) async => QueryRepliesResponse()..messages = messages); final res = await client.getReplies(parentId); expect(res, isNotNull); @@ -3646,8 +3696,9 @@ void main() { ), ); - when(() => api.message.getReactions(messageId)).thenAnswer( - (_) async => QueryReactionsResponse()..reactions = reactions); + when( + () => api.message.getReactions(messageId), + ).thenAnswer((_) async => QueryReactionsResponse()..reactions = reactions); final res = await client.getReactions(messageId); expect(res, isNotNull); @@ -3658,11 +3709,40 @@ void main() { verifyNoMoreInteractions(api.message); }); + test('`.queryReactions`', () async { + const messageId = 'test-message-id'; + + final reactions = List.generate( + 3, + (index) => Reaction( + type: 'test-reactions-type-$index', + messageId: messageId, + ), + ); + + when( + () => api.message.queryReactions(messageId), + ).thenAnswer( + (_) async => QueryReactionsResponse() + ..reactions = reactions + ..next = null, + ); + + final res = await client.queryReactions(messageId); + expect(res, isNotNull); + expect(res.reactions.length, reactions.length); + expect(res.reactions.every((it) => it.messageId == messageId), isTrue); + + verify(() => api.message.queryReactions(messageId)).called(1); + verifyNoMoreInteractions(api.message); + }); + test('`.updateMessage`', () async { final message = Message(id: 'test-message-id', text: 'Hello!'); - when(() => api.message.updateMessage(any(that: isSameMessageAs(message)))) - .thenAnswer((_) async => UpdateMessageResponse()..message = message); + when( + () => api.message.updateMessage(any(that: isSameMessageAs(message))), + ).thenAnswer((_) async => UpdateMessageResponse()..message = message); final res = await client.updateMessage(message); expect(res, isNotNull); @@ -3677,8 +3757,7 @@ void main() { test('`.deleteMessage`', () async { const messageId = 'test-message-id'; - when(() => api.message.deleteMessage(messageId, hard: false)) - .thenAnswer((_) async => EmptyResponse()); + when(() => api.message.deleteMessage(messageId, hard: false)).thenAnswer((_) async => EmptyResponse()); final res = await client.deleteMessage(messageId); expect(res, isNotNull); @@ -3690,14 +3769,12 @@ void main() { test('`.deleteMessageForMe`', () async { const messageId = 'test-message-id'; - when(() => api.message.deleteMessage(messageId, deleteForMe: true)) - .thenAnswer((_) async => EmptyResponse()); + when(() => api.message.deleteMessage(messageId, deleteForMe: true)).thenAnswer((_) async => EmptyResponse()); final res = await client.deleteMessageForMe(messageId); expect(res, isNotNull); - verify(() => api.message.deleteMessage(messageId, deleteForMe: true)) - .called(1); + verify(() => api.message.deleteMessage(messageId, deleteForMe: true)).called(1); verifyNoMoreInteractions(api.message); }); @@ -3705,8 +3782,7 @@ void main() { const messageId = 'test-message-id'; final message = Message(id: messageId); - when(() => api.message.getMessage(messageId)) - .thenAnswer((_) async => GetMessageResponse()..message = message); + when(() => api.message.getMessage(messageId)).thenAnswer((_) async => GetMessageResponse()..message = message); final res = await client.getMessage(messageId); expect(res, isNotNull); @@ -3774,11 +3850,13 @@ void main() { final updateMessageResponse = UpdateMessageResponse() ..message = message.copyWith(text: set['text'], pinExpires: null); - when(() => api.message.partialUpdateMessage( - message.id, - set: set, - unset: unset, - )).thenAnswer((_) async => updateMessageResponse); + when( + () => api.message.partialUpdateMessage( + message.id, + set: set, + unset: unset, + ), + ).thenAnswer((_) async => updateMessageResponse); final res = await client.partialUpdateMessage( messageId, @@ -3792,30 +3870,35 @@ void main() { expect(res.message.text, set['text']); expect(res.message.pinExpires, isNull); - verify(() => api.message.partialUpdateMessage( - message.id, - set: set, - unset: unset, - )).called(1); + verify( + () => api.message.partialUpdateMessage( + message.id, + set: set, + unset: unset, + ), + ).called(1); verifyNoMoreInteractions(api.message); }); group('`.pinMessage`', () { - test('should work fine without passing timeoutOrExpirationDate', - () async { + test('should work fine without passing timeoutOrExpirationDate', () async { const messageId = 'test-message-id'; final message = Message(id: messageId); - when(() => api.message.partialUpdateMessage( - messageId, - set: any(named: 'set'), - unset: any(named: 'unset'), - )).thenAnswer((_) async => UpdateMessageResponse() - ..message = message.copyWith( - pinned: true, - pinExpires: null, - state: MessageState.sent, - )); + when( + () => api.message.partialUpdateMessage( + messageId, + set: any(named: 'set'), + unset: any(named: 'unset'), + ), + ).thenAnswer( + (_) async => UpdateMessageResponse() + ..message = message.copyWith( + pinned: true, + pinExpires: null, + state: MessageState.sent, + ), + ); final res = await client.pinMessage(messageId); @@ -3823,11 +3906,13 @@ void main() { expect(res.message.pinned, isTrue); expect(res.message.pinExpires, isNull); - verify(() => api.message.partialUpdateMessage( - messageId, - set: any(named: 'set'), - unset: any(named: 'unset'), - )).called(1); + verify( + () => api.message.partialUpdateMessage( + messageId, + set: any(named: 'set'), + unset: any(named: 'unset'), + ), + ).called(1); verifyNoMoreInteractions(api.message); }); @@ -3838,18 +3923,22 @@ void main() { final message = Message(id: messageId); const timeoutOrExpirationDate = 300; // 300 seconds - when(() => api.message.partialUpdateMessage( - message.id, - set: any(named: 'set'), - unset: any(named: 'unset'), - )).thenAnswer((_) async => UpdateMessageResponse() - ..message = message.copyWith( - pinned: true, - pinExpires: DateTime.now().add( - const Duration(seconds: timeoutOrExpirationDate), + when( + () => api.message.partialUpdateMessage( + message.id, + set: any(named: 'set'), + unset: any(named: 'unset'), + ), + ).thenAnswer( + (_) async => UpdateMessageResponse() + ..message = message.copyWith( + pinned: true, + pinExpires: DateTime.now().add( + const Duration(seconds: timeoutOrExpirationDate), + ), + state: MessageState.sent, ), - state: MessageState.sent, - )); + ); final res = await client.pinMessage( messageId, @@ -3860,11 +3949,13 @@ void main() { expect(res.message.pinned, isTrue); expect(res.message.pinExpires, isNotNull); - verify(() => api.message.partialUpdateMessage( - messageId, - set: any(named: 'set'), - unset: any(named: 'unset'), - )).called(1); + verify( + () => api.message.partialUpdateMessage( + messageId, + set: any(named: 'set'), + unset: any(named: 'unset'), + ), + ).called(1); verifyNoMoreInteractions(api.message); }, ); @@ -3874,19 +3965,22 @@ void main() { () async { const messageId = 'test-message-id'; final message = Message(id: messageId); - final timeoutOrExpirationDate = - DateTime.now().add(const Duration(days: 3)); // 3 days - - when(() => api.message.partialUpdateMessage( - messageId, - set: any(named: 'set'), - unset: any(named: 'unset'), - )).thenAnswer((_) async => UpdateMessageResponse() - ..message = message.copyWith( - pinned: true, - pinExpires: timeoutOrExpirationDate, - state: MessageState.sent, - )); + final timeoutOrExpirationDate = DateTime.now().add(const Duration(days: 3)); // 3 days + + when( + () => api.message.partialUpdateMessage( + messageId, + set: any(named: 'set'), + unset: any(named: 'unset'), + ), + ).thenAnswer( + (_) async => UpdateMessageResponse() + ..message = message.copyWith( + pinned: true, + pinExpires: timeoutOrExpirationDate, + state: MessageState.sent, + ), + ); final res = await client.pinMessage( messageId, @@ -3898,11 +3992,13 @@ void main() { expect(res.message.pinExpires, isNotNull); expect(res.message.pinExpires, timeoutOrExpirationDate.toUtc()); - verify(() => api.message.partialUpdateMessage( - messageId, - set: any(named: 'set'), - unset: any(named: 'unset'), - )).called(1); + verify( + () => api.message.partialUpdateMessage( + messageId, + set: any(named: 'set'), + unset: any(named: 'unset'), + ), + ).called(1); verifyNoMoreInteractions(api.message); }, ); @@ -3929,30 +4025,35 @@ void main() { const messageId = 'test-message-id'; final message = Message(id: messageId, pinned: true); - when(() => api.message.partialUpdateMessage( - messageId, - set: {'pinned': false}, - )).thenAnswer((_) async => UpdateMessageResponse() - ..message = message.copyWith( - pinned: false, - state: MessageState.sent, - )); + when( + () => api.message.partialUpdateMessage( + messageId, + set: {'pinned': false}, + ), + ).thenAnswer( + (_) async => UpdateMessageResponse() + ..message = message.copyWith( + pinned: false, + state: MessageState.sent, + ), + ); final res = await client.unpinMessage(messageId); expect(res, isNotNull); expect(res.message.pinned, isFalse); - verify(() => api.message.partialUpdateMessage( - messageId, - set: {'pinned': false}, - )).called(1); + verify( + () => api.message.partialUpdateMessage( + messageId, + set: {'pinned': false}, + ), + ).called(1); verifyNoMoreInteractions(api.message); }); test('`.enrichUrl`', () async { - const url = - 'https://www.techyourchance.com/finite-state-machine-with-unit-tests-real-world-example'; + const url = 'https://www.techyourchance.com/finite-state-machine-with-unit-tests-real-world-example'; when(() => api.general.enrichUrl(url)).thenAnswer( (_) async => OGAttachmentResponse() @@ -4335,8 +4436,7 @@ void main() { expect(channel2.state?.messages.length, equals(0)); // Verify other user's message is unaffected - final safeMessage = - channel1.state?.messages.firstWhere((m) => m.id == 'msg-3'); + final safeMessage = channel1.state?.messages.firstWhere((m) => m.id == 'msg-3'); expect(safeMessage?.user?.id, equals('other-user')); }, ); diff --git a/packages/stream_chat/test/src/core/api/attachment_file_uploader_test.dart b/packages/stream_chat/test/src/core/api/attachment_file_uploader_test.dart index 5250dbbc9a..1b319718e1 100644 --- a/packages/stream_chat/test/src/core/api/attachment_file_uploader_test.dart +++ b/packages/stream_chat/test/src/core/api/attachment_file_uploader_test.dart @@ -19,10 +19,10 @@ void main() { }); Response successResponse(String path, {Object? data}) => Response( - data: data, - requestOptions: RequestOptions(path: path), - statusCode: 200, - ); + data: data, + requestOptions: RequestOptions(path: path), + statusCode: 200, + ); test('sendImage', () async { const channelId = 'test-channel-id'; @@ -37,12 +37,19 @@ void main() { ); final multipartFile = await attachmentFile.toMultipartFile(); - when(() => client.postFile( - path, - any(that: isSameMultipartFileAs(multipartFile)), - )).thenAnswer((_) async => successResponse(path, data: { + when( + () => client.postFile( + path, + any(that: isSameMultipartFileAs(multipartFile)), + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { 'file': 'test-file-url', - })); + }, + ), + ); final res = await fileUploader.sendImage( attachmentFile, @@ -54,10 +61,12 @@ void main() { expect(res.file, isNotNull); expect(res.file, isNotEmpty); - verify(() => client.postFile( - path, - any(that: isSameMultipartFileAs(multipartFile)), - )).called(1); + verify( + () => client.postFile( + path, + any(that: isSameMultipartFileAs(multipartFile)), + ), + ).called(1); verifyNoMoreInteractions(client); }); @@ -74,12 +83,19 @@ void main() { ); final multipartFile = await attachmentFile.toMultipartFile(); - when(() => client.postFile( - path, - any(that: isSameMultipartFileAs(multipartFile)), - )).thenAnswer((_) async => successResponse(path, data: { + when( + () => client.postFile( + path, + any(that: isSameMultipartFileAs(multipartFile)), + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { 'file': 'test-file-url', - })); + }, + ), + ); final res = await fileUploader.sendFile( attachmentFile, @@ -91,10 +107,12 @@ void main() { expect(res.file, isNotNull); expect(res.file, isNotEmpty); - verify(() => client.postFile( - path, - any(that: isSameMultipartFileAs(multipartFile)), - )).called(1); + verify( + () => client.postFile( + path, + any(that: isSameMultipartFileAs(multipartFile)), + ), + ).called(1); verifyNoMoreInteractions(client); }); @@ -105,8 +123,9 @@ void main() { const url = 'test-image-url'; - when(() => client.delete(path, queryParameters: {'url': url})).thenAnswer( - (_) async => successResponse(path, data: {})); + when( + () => client.delete(path, queryParameters: {'url': url}), + ).thenAnswer((_) async => successResponse(path, data: {})); final res = await fileUploader.deleteImage(url, channelId, channelType); @@ -123,8 +142,9 @@ void main() { const url = 'test-file-url'; - when(() => client.delete(path, queryParameters: {'url': url})).thenAnswer( - (_) async => successResponse(path, data: {})); + when( + () => client.delete(path, queryParameters: {'url': url}), + ).thenAnswer((_) async => successResponse(path, data: {})); final res = await fileUploader.deleteFile(url, channelId, channelType); @@ -144,12 +164,19 @@ void main() { ); final multipartFile = await attachmentFile.toMultipartFile(); - when(() => client.postFile( - path, - any(that: isSameMultipartFileAs(multipartFile)), - )).thenAnswer((_) async => successResponse(path, data: { + when( + () => client.postFile( + path, + any(that: isSameMultipartFileAs(multipartFile)), + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { 'file': 'test-image-url', - })); + }, + ), + ); final res = await fileUploader.uploadImage(attachmentFile); @@ -157,10 +184,12 @@ void main() { expect(res.file, isNotNull); expect(res.file, isNotEmpty); - verify(() => client.postFile( - path, - any(that: isSameMultipartFileAs(multipartFile)), - )).called(1); + verify( + () => client.postFile( + path, + any(that: isSameMultipartFileAs(multipartFile)), + ), + ).called(1); verifyNoMoreInteractions(client); }); @@ -174,12 +203,19 @@ void main() { ); final multipartFile = await attachmentFile.toMultipartFile(); - when(() => client.postFile( - path, - any(that: isSameMultipartFileAs(multipartFile)), - )).thenAnswer((_) async => successResponse(path, data: { + when( + () => client.postFile( + path, + any(that: isSameMultipartFileAs(multipartFile)), + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { 'file': 'test-file-url', - })); + }, + ), + ); final res = await fileUploader.uploadFile(attachmentFile); @@ -187,10 +223,12 @@ void main() { expect(res.file, isNotNull); expect(res.file, isNotEmpty); - verify(() => client.postFile( - path, - any(that: isSameMultipartFileAs(multipartFile)), - )).called(1); + verify( + () => client.postFile( + path, + any(that: isSameMultipartFileAs(multipartFile)), + ), + ).called(1); verifyNoMoreInteractions(client); }); @@ -198,8 +236,9 @@ void main() { const path = '/uploads/image'; const url = 'test-image-url'; - when(() => client.delete(path, queryParameters: {'url': url})).thenAnswer( - (_) async => successResponse(path, data: {})); + when( + () => client.delete(path, queryParameters: {'url': url}), + ).thenAnswer((_) async => successResponse(path, data: {})); final res = await fileUploader.removeImage(url); @@ -213,8 +252,9 @@ void main() { const path = '/uploads/file'; const url = 'test-file-url'; - when(() => client.delete(path, queryParameters: {'url': url})).thenAnswer( - (_) async => successResponse(path, data: {})); + when( + () => client.delete(path, queryParameters: {'url': url}), + ).thenAnswer((_) async => successResponse(path, data: {})); final res = await fileUploader.removeFile(url); diff --git a/packages/stream_chat/test/src/core/api/channel_api_test.dart b/packages/stream_chat/test/src/core/api/channel_api_test.dart index ae5a0dba81..1c4ae95c13 100644 --- a/packages/stream_chat/test/src/core/api/channel_api_test.dart +++ b/packages/stream_chat/test/src/core/api/channel_api_test.dart @@ -9,8 +9,7 @@ import 'package:test/test.dart'; import '../../mocks.dart'; void main() { - String _getChannelUrl(String channelId, String channelType) => - '/channels/$channelType/$channelId'; + String _getChannelUrl(String channelId, String channelType) => '/channels/$channelType/$channelId'; ChannelState _generateChannelState( String channelId, @@ -53,10 +52,10 @@ void main() { } Response successResponse(String path, {Object? data}) => Response( - data: data, - requestOptions: RequestOptions(path: path), - statusCode: 200, - ); + data: data, + requestOptions: RequestOptions(path: path), + statusCode: 200, + ); late final client = MockHttpClient(); late ChannelApi channelApi; @@ -88,13 +87,17 @@ void main() { 'watchers': watchersPagination, }; - when(() => client.post( - path, - data: data, - )).thenAnswer((_) async => successResponse( - path, - data: channelState.toJson(), - )); + when( + () => client.post( + path, + data: data, + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: channelState.toJson(), + ), + ); final res = await channelApi.queryChannel( channelType, @@ -143,20 +146,24 @@ void main() { 'message_limit': messageLimit, // pagination - ...const PaginationParams().toJson() + ...const PaginationParams().toJson(), }); - when(() => client.get( - path, - queryParameters: { - 'payload': payload, - }, - )).thenAnswer((_) async => successResponse( - path, - data: { - 'channels': [channelState.toJson()] - }, - )); + when( + () => client.get( + path, + queryParameters: { + 'payload': payload, + }, + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { + 'channels': [channelState.toJson()], + }, + ), + ); final res = await channelApi.queryChannels( filter: filter, @@ -176,8 +183,7 @@ void main() { test('markAllRead', () async { const path = '/channels/read'; - when(() => client.post(path, data: {})).thenAnswer( - (_) async => successResponse(path, data: {})); + when(() => client.post(path, data: {})).thenAnswer((_) async => successResponse(path, data: {})); final res = await channelApi.markAllRead(); @@ -201,18 +207,23 @@ void main() { extraData: data, ); - when(() => client.post( - path, - data: any( - named: 'data', - that: wrapMatcher((Map v) => - containsPair('data', data).matches(v, {}) && - contains('message').matches(v, {})), - ), - )).thenAnswer((_) async => successResponse(path, data: { + when( + () => client.post( + path, + data: any( + named: 'data', + that: wrapMatcher((Map v) => containsPair('data', data).matches(v, {}) && contains('message').matches(v, {})), + ), + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { 'channel': channelModel.toJson(), 'message': message.toJson(), - })); + }, + ), + ); final res = await channelApi.updateChannel( channelId, @@ -249,9 +260,14 @@ void main() { when( () => client.patch(path, data: {'set': set, 'unset': unset}), - ).thenAnswer((_) async => successResponse(path, data: { + ).thenAnswer( + (_) async => successResponse( + path, + data: { 'channel': channelModel.toJson(), - })); + }, + ), + ); final res = await channelApi.updateChannelPartial( channelId, @@ -277,16 +293,23 @@ void main() { final path = _getChannelUrl(channelId, channelType); - when(() => client.post( - path, - data: { - 'accept_invite': true, - 'message': message, - }, - )).thenAnswer((_) async => successResponse(path, data: { + when( + () => client.post( + path, + data: { + 'accept_invite': true, + 'message': message, + }, + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { 'channel': channelModel.toJson(), 'message': message.toJson(), - })); + }, + ), + ); final res = await channelApi.acceptChannelInvite( channelId, @@ -311,16 +334,23 @@ void main() { final path = _getChannelUrl(channelId, channelType); - when(() => client.post( - path, - data: { - 'reject_invite': true, - 'message': message, - }, - )).thenAnswer((_) async => successResponse(path, data: { + when( + () => client.post( + path, + data: { + 'reject_invite': true, + 'message': message, + }, + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { 'channel': channelModel.toJson(), 'message': message.toJson(), - })); + }, + ), + ); final res = await channelApi.rejectChannelInvite( channelId, @@ -345,16 +375,23 @@ void main() { final path = _getChannelUrl(channelId, channelType); - when(() => client.post( - path, - data: { - 'invites': memberIds, - 'message': message, - }, - )).thenAnswer((_) async => successResponse(path, data: { + when( + () => client.post( + path, + data: { + 'invites': memberIds, + 'message': message, + }, + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { 'channel': channelModel.toJson(), 'message': message.toJson(), - })); + }, + ), + ); final res = await channelApi.inviteChannelMembers( channelId, @@ -381,17 +418,24 @@ void main() { final path = _getChannelUrl(channelId, channelType); - when(() => client.post( - path, - data: { - 'add_members': memberIds, - 'message': message, - 'hide_history': hideHistory, - }, - )).thenAnswer((_) async => successResponse(path, data: { + when( + () => client.post( + path, + data: { + 'add_members': memberIds, + 'message': message, + 'hide_history': hideHistory, + }, + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { 'channel': channelModel.toJson(), 'message': message.toJson(), - })); + }, + ), + ); final res = await channelApi.addMembers( channelId, @@ -419,17 +463,24 @@ void main() { final path = _getChannelUrl(channelId, channelType); - when(() => client.post( - path, - data: { - 'add_members': memberIds, - 'message': message, - 'hide_history_before': hideHistoryBefore.toUtc().toIso8601String(), - }, - )).thenAnswer((_) async => successResponse(path, data: { + when( + () => client.post( + path, + data: { + 'add_members': memberIds, + 'message': message, + 'hide_history_before': hideHistoryBefore.toUtc().toIso8601String(), + }, + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { 'channel': channelModel.toJson(), 'message': message.toJson(), - })); + }, + ), + ); final res = await channelApi.addMembers( channelId, @@ -447,8 +498,7 @@ void main() { verifyNoMoreInteractions(client); }); - test('addMembers with hideHistoryBefore takes precedence over hideHistory', - () async { + test('addMembers with hideHistoryBefore takes precedence over hideHistory', () async { const channelId = 'test-channel-id'; const channelType = 'test-channel-type'; const memberIds = ['test-member-id-1', 'test-member-id-2']; @@ -459,17 +509,24 @@ void main() { final path = _getChannelUrl(channelId, channelType); - when(() => client.post( - path, - data: { - 'add_members': memberIds, - 'message': message, - 'hide_history_before': hideHistoryBefore.toUtc().toIso8601String(), - }, - )).thenAnswer((_) async => successResponse(path, data: { + when( + () => client.post( + path, + data: { + 'add_members': memberIds, + 'message': message, + 'hide_history_before': hideHistoryBefore.toUtc().toIso8601String(), + }, + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { 'channel': channelModel.toJson(), 'message': message.toJson(), - })); + }, + ), + ); final res = await channelApi.addMembers( channelId, @@ -497,16 +554,23 @@ void main() { final path = _getChannelUrl(channelId, channelType); - when(() => client.post( - path, - data: { - 'remove_members': memberIds, - 'message': message, - }, - )).thenAnswer((_) async => successResponse(path, data: { + when( + () => client.post( + path, + data: { + 'remove_members': memberIds, + 'message': message, + }, + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { 'channel': channelModel.toJson(), 'message': message.toJson(), - })); + }, + ), + ); final res = await channelApi.removeMembers( channelId, @@ -530,8 +594,9 @@ void main() { final path = '${_getChannelUrl(channelId, channelType)}/event'; - when(() => client.post(path, data: {'event': event})).thenAnswer( - (_) async => successResponse(path, data: {})); + when( + () => client.post(path, data: {'event': event}), + ).thenAnswer((_) async => successResponse(path, data: {})); final res = await channelApi.sendEvent(channelId, channelType, event); @@ -547,8 +612,7 @@ void main() { final path = _getChannelUrl(channelId, channelType); - when(() => client.delete(path)).thenAnswer( - (_) async => successResponse(path, data: {})); + when(() => client.delete(path)).thenAnswer((_) async => successResponse(path, data: {})); final res = await channelApi.deleteChannel(channelId, channelType); @@ -564,21 +628,23 @@ void main() { final path = '${_getChannelUrl(channelId, channelType)}/truncate'; - when(() => client.post( - path, - data: {}, - )) - .thenAnswer( - (_) async => successResponse(path, data: {})); + when( + () => client.post( + path, + data: {}, + ), + ).thenAnswer((_) async => successResponse(path, data: {})); final res = await channelApi.truncateChannel(channelId, channelType); expect(res, isNotNull); - verify(() => client.post( - path, - data: {}, - )).called(1); + verify( + () => client.post( + path, + data: {}, + ), + ).called(1); verifyNoMoreInteractions(client); }); @@ -638,21 +704,23 @@ void main() { final path = '${_getChannelUrl(channelId, channelType)}/show'; - when(() => client.post( - path, - data: {}, - )) - .thenAnswer( - (_) async => successResponse(path, data: {})); + when( + () => client.post( + path, + data: {}, + ), + ).thenAnswer((_) async => successResponse(path, data: {})); final res = await channelApi.showChannel(channelId, channelType); expect(res, isNotNull); - verify(() => client.post( - path, - data: {}, - )).called(1); + verify( + () => client.post( + path, + data: {}, + ), + ).called(1); verifyNoMoreInteractions(client); }); @@ -663,14 +731,14 @@ void main() { final path = '${_getChannelUrl(channelId, channelType)}/read'; - when(() => client.post( - path, - data: { - 'message_id': messageId, - }, - )) - .thenAnswer( - (_) async => successResponse(path, data: {})); + when( + () => client.post( + path, + data: { + 'message_id': messageId, + }, + ), + ).thenAnswer((_) async => successResponse(path, data: {})); final res = await channelApi.markRead( channelId, @@ -691,12 +759,12 @@ void main() { final path = '${_getChannelUrl(channelId, channelType)}/unread'; - when(() => client.post( - path, - data: {'message_id': messageId}, - )) - .thenAnswer( - (_) async => successResponse(path, data: {})); + when( + () => client.post( + path, + data: {'message_id': messageId}, + ), + ).thenAnswer((_) async => successResponse(path, data: {})); final res = await channelApi.markUnread( channelId, @@ -717,14 +785,14 @@ void main() { final path = '${_getChannelUrl(channelId, channelType)}/unread'; - when(() => client.post( - path, - data: { - 'message_timestamp': timestamp.toUtc().toIso8601String(), - }, - )) - .thenAnswer( - (_) async => successResponse(path, data: {})); + when( + () => client.post( + path, + data: { + 'message_timestamp': timestamp.toUtc().toIso8601String(), + }, + ), + ).thenAnswer((_) async => successResponse(path, data: {})); final res = await channelApi.markUnreadByTimestamp( channelId, @@ -745,17 +813,22 @@ void main() { final path = '${_getChannelUrl(channelId, channelType)}/member/{user_id}'; const archivedAt = '2025-04-10 10:27:03.150349'; - when(() => client.patch( - path, - data: { - 'set': {'archived': true}, + when( + () => client.patch( + path, + data: { + 'set': {'archived': true}, + }, + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { + 'channel_member': { + 'archived_at': archivedAt, }, - )).thenAnswer( - (_) async => successResponse(path, data: { - 'channel_member': { - 'archived_at': archivedAt, - } - }), + }, + ), ); final res = await channelApi.updateMemberPartial( @@ -777,14 +850,15 @@ void main() { final path = '${_getChannelUrl(channelId, channelType)}/member/{user_id}'; - when(() => client.patch( - path, - data: { - 'unset': ['archived'], - }, - )).thenAnswer( - (_) async => successResponse(path, - data: {'channel_member': {}}), + when( + () => client.patch( + path, + data: { + 'unset': ['archived'], + }, + ), + ).thenAnswer( + (_) async => successResponse(path, data: {'channel_member': {}}), ); final res = await channelApi.updateMemberPartial( @@ -807,17 +881,22 @@ void main() { final path = '${_getChannelUrl(channelId, channelType)}/member/{user_id}'; const pinnedAt = '2025-04-10 10:27:03.150349'; - when(() => client.patch( - path, - data: { - 'set': {'pinned': true}, + when( + () => client.patch( + path, + data: { + 'set': {'pinned': true}, + }, + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { + 'channel_member': { + 'pinned_at': pinnedAt, }, - )).thenAnswer( - (_) async => successResponse(path, data: { - 'channel_member': { - 'pinned_at': pinnedAt, - } - }), + }, + ), ); final res = await channelApi.updateMemberPartial( @@ -839,14 +918,15 @@ void main() { final path = '${_getChannelUrl(channelId, channelType)}/member/{user_id}'; - when(() => client.patch( - path, - data: { - 'unset': ['pinned'], - }, - )).thenAnswer( - (_) async => successResponse(path, - data: {'channel_member': {}}), + when( + () => client.patch( + path, + data: { + 'unset': ['pinned'], + }, + ), + ).thenAnswer( + (_) async => successResponse(path, data: {'channel_member': {}}), ); final res = await channelApi.updateMemberPartial( @@ -868,8 +948,7 @@ void main() { final path = '${_getChannelUrl(channelId, channelType)}/stop-watching'; - when(() => client.post(path, data: {})).thenAnswer( - (_) async => successResponse(path, data: {})); + when(() => client.post(path, data: {})).thenAnswer((_) async => successResponse(path, data: {})); final res = await channelApi.stopWatching(channelId, channelType); @@ -895,20 +974,34 @@ void main() { extraData: set, ); - when(() => client.patch(path, data: { + when( + () => client.patch( + path, + data: { 'set': set, - })).thenAnswer((_) async => successResponse(path, data: { + }, + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { 'channel': channelModel.toJson(), - })); + }, + ), + ); - final res = - await channelApi.enableSlowdown(channelId, channelType, cooldown); + final res = await channelApi.enableSlowdown(channelId, channelType, cooldown); expect(res, isNotNull); - verify(() => client.patch(path, data: { + verify( + () => client.patch( + path, + data: { 'set': set, - })).called(1); + }, + ), + ).called(1); verifyNoMoreInteractions(client); }); @@ -924,19 +1017,34 @@ void main() { type: channelType, ); - when(() => client.patch(path, data: { + when( + () => client.patch( + path, + data: { 'unset': unset, - })).thenAnswer((_) async => successResponse(path, data: { + }, + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { 'channel': channelModel.toJson(), - })); + }, + ), + ); final res = await channelApi.disableSlowdown(channelId, channelType); expect(res, isNotNull); - verify(() => client.patch(path, data: { + verify( + () => client.patch( + path, + data: { 'unset': unset, - })).called(1); + }, + ), + ).called(1); verifyNoMoreInteractions(client); }); @@ -954,24 +1062,30 @@ void main() { ), ]; - when(() => client.post( - path, - data: any(named: 'data'), - )).thenAnswer((_) async => successResponse( - path, - data: {}, - )); + when( + () => client.post( + path, + data: any(named: 'data'), + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: {}, + ), + ); final res = await channelApi.markChannelsDelivered(deliveries); expect(res, isNotNull); - verify(() => client.post( - path, - data: jsonEncode({ - 'latest_delivered_messages': deliveries, - }), - )).called(1); + verify( + () => client.post( + path, + data: jsonEncode({ + 'latest_delivered_messages': deliveries, + }), + ), + ).called(1); verifyNoMoreInteractions(client); }); } diff --git a/packages/stream_chat/test/src/core/api/device_api_test.dart b/packages/stream_chat/test/src/core/api/device_api_test.dart index c9d06a843d..8318d8dda6 100644 --- a/packages/stream_chat/test/src/core/api/device_api_test.dart +++ b/packages/stream_chat/test/src/core/api/device_api_test.dart @@ -8,10 +8,10 @@ import '../../mocks.dart'; void main() { Response successResponse(String path, {Object? data}) => Response( - data: data, - requestOptions: RequestOptions(path: path), - statusCode: 200, - ); + data: data, + requestOptions: RequestOptions(path: path), + statusCode: 200, + ); late final client = MockHttpClient(); late DeviceApi deviceApi; @@ -40,19 +40,16 @@ void main() { path, data: data, ); - }).thenAnswer( - (_) async => successResponse(path, data: {})); + }).thenAnswer((_) async => successResponse(path, data: {})); - final res = - await deviceApi.addDevice(deviceId, pushProviderMapEntry.value); + final res = await deviceApi.addDevice(deviceId, pushProviderMapEntry.value); expect(res, isNotNull); verify(() => client.post(path, data: data)).called(1); } verifyNoMoreInteractions(client); - expect(pushProvidersMap.length, PushProvider.values.length, - reason: 'All PushProvider should be tested'); + expect(pushProvidersMap.length, PushProvider.values.length, reason: 'All PushProvider should be tested'); }); test('addDevice should work with pushProviderName', () async { @@ -62,16 +59,16 @@ void main() { const path = '/devices'; - when(() => client.post( - path, - data: { - 'id': deviceId, - 'push_provider': pushProvider.name, - 'push_provider_name': pushProviderName, - }, - )) - .thenAnswer( - (_) async => successResponse(path, data: {})); + when( + () => client.post( + path, + data: { + 'id': deviceId, + 'push_provider': pushProvider.name, + 'push_provider_name': pushProviderName, + }, + ), + ).thenAnswer((_) async => successResponse(path, data: {})); final res = await deviceApi.addDevice( deviceId, @@ -97,9 +94,12 @@ void main() { ); when(() => client.get(path)).thenAnswer( - (_) async => successResponse(path, data: { - 'devices': [...devices.map((it) => it.toJson())] - }), + (_) async => successResponse( + path, + data: { + 'devices': [...devices.map((it) => it.toJson())], + }, + ), ); final res = await deviceApi.getDevices(); @@ -141,10 +141,13 @@ void main() { ]; when(() => client.post(path, data: any(named: 'data'))).thenAnswer( - (_) async => successResponse(path, data: { - 'user_preferences': {}, - 'user_channel_preferences': {}, - }), + (_) async => successResponse( + path, + data: { + 'user_preferences': {}, + 'user_channel_preferences': {}, + }, + ), ); final res = await deviceApi.setPushPreferences(preferences); @@ -170,10 +173,13 @@ void main() { ]; when(() => client.post(path, data: any(named: 'data'))).thenAnswer( - (_) async => successResponse(path, data: { - 'user_preferences': {}, - 'user_channel_preferences': {}, - }), + (_) async => successResponse( + path, + data: { + 'user_preferences': {}, + 'user_channel_preferences': {}, + }, + ), ); final res = await deviceApi.setPushPreferences(preferences); @@ -195,10 +201,13 @@ void main() { ]; when(() => client.post(path, data: any(named: 'data'))).thenAnswer( - (_) async => successResponse(path, data: { - 'user_preferences': {}, - 'user_channel_preferences': {}, - }), + (_) async => successResponse( + path, + data: { + 'user_preferences': {}, + 'user_channel_preferences': {}, + }, + ), ); final res = await deviceApi.setPushPreferences(preferences); diff --git a/packages/stream_chat/test/src/core/api/general_api_test.dart b/packages/stream_chat/test/src/core/api/general_api_test.dart index 52135981fe..cd41156ce1 100644 --- a/packages/stream_chat/test/src/core/api/general_api_test.dart +++ b/packages/stream_chat/test/src/core/api/general_api_test.dart @@ -10,10 +10,10 @@ import '../../mocks.dart'; void main() { Response successResponse(String path, {Object? data}) => Response( - data: data, - requestOptions: RequestOptions(path: path), - statusCode: 200, - ); + data: data, + requestOptions: RequestOptions(path: path), + statusCode: 200, + ); late final client = MockHttpClient(); late GeneralApi generalApi; @@ -28,20 +28,26 @@ void main() { const path = '/sync'; - final events = - List.generate(3, (index) => Event(type: 'test-event-type-$index')); + final events = List.generate(3, (index) => Event(type: 'test-event-type-$index')); final data = { 'channel_cids': cids, 'last_sync_at': lastSyncAt.toUtc().toIso8601String(), }; - when(() => client.post( - path, - data: data, - )).thenAnswer((_) async => successResponse(path, data: { - 'events': [...events.map((it) => it.toJson())] - })); + when( + () => client.post( + path, + data: data, + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { + 'events': [...events.map((it) => it.toJson())], + }, + ), + ); final res = await generalApi.sync(cids, lastSyncAt); @@ -204,14 +210,21 @@ void main() { ...pagination.toJson(), }); - when(() => client.get( - path, - queryParameters: { - 'payload': payload, - }, - )).thenAnswer((_) async => successResponse(path, data: { - 'members': [...members.map((it) => it.toJson())] - })); + when( + () => client.get( + path, + queryParameters: { + 'payload': payload, + }, + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { + 'members': [...members.map((it) => it.toJson())], + }, + ), + ); final res = await generalApi.queryMembers( channelType, @@ -251,14 +264,21 @@ void main() { ...pagination.toJson(), }); - when(() => client.get( - path, - queryParameters: { - 'payload': payload, - }, - )).thenAnswer((_) async => successResponse(path, data: { - 'members': [...members.map((it) => it.toJson())] - })); + when( + () => client.get( + path, + queryParameters: { + 'payload': payload, + }, + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { + 'members': [...members.map((it) => it.toJson())], + }, + ), + ); final res = await generalApi.queryMembers( channelType, @@ -280,18 +300,24 @@ void main() { test('enrichUrl', () async { const path = '/og'; - const url = - 'https://www.techyourchance.com/finite-state-machine-with-unit-tests-real-world-example'; + const url = 'https://www.techyourchance.com/finite-state-machine-with-unit-tests-real-world-example'; - when(() => client.get( - path, - queryParameters: {'url': url}, - )).thenAnswer((_) async => successResponse(path, data: { + when( + () => client.get( + path, + queryParameters: {'url': url}, + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { 'type': 'image', 'og_scrape_url': url, 'author_name': 'TechYourChance', 'title': 'Finite State Machine with Unit Tests: Real World Example', - })); + }, + ), + ); final res = await generalApi.enrichUrl(url); diff --git a/packages/stream_chat/test/src/core/api/guest_api_test.dart b/packages/stream_chat/test/src/core/api/guest_api_test.dart index 7db74b4a31..40f83e7bee 100644 --- a/packages/stream_chat/test/src/core/api/guest_api_test.dart +++ b/packages/stream_chat/test/src/core/api/guest_api_test.dart @@ -8,10 +8,10 @@ import '../../mocks.dart'; void main() { Response successResponse(String path, {Object? data}) => Response( - data: data, - requestOptions: RequestOptions(path: path), - statusCode: 200, - ); + data: data, + requestOptions: RequestOptions(path: path), + statusCode: 200, + ); late final client = MockHttpClient(); late GuestApi guestApi; @@ -26,13 +26,20 @@ void main() { const path = '/guest'; - when(() => client.post( - path, - data: {'user': user}, - )).thenAnswer((_) async => successResponse(path, data: { + when( + () => client.post( + path, + data: {'user': user}, + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { 'access_token': accessToken, 'user': user.toJson(), - })); + }, + ), + ); final res = await guestApi.getGuestUser(user); diff --git a/packages/stream_chat/test/src/core/api/message_api_test.dart b/packages/stream_chat/test/src/core/api/message_api_test.dart index bfe9bd7815..0cfeacdd83 100644 --- a/packages/stream_chat/test/src/core/api/message_api_test.dart +++ b/packages/stream_chat/test/src/core/api/message_api_test.dart @@ -10,10 +10,10 @@ import '../../mocks.dart'; void main() { Response successResponse(String path, {Object? data}) => Response( - data: data, - requestOptions: RequestOptions(path: path), - statusCode: 200, - ); + data: data, + requestOptions: RequestOptions(path: path), + statusCode: 200, + ); late final client = MockHttpClient(); late MessageApi messageApi; @@ -29,16 +29,23 @@ void main() { const path = '/channels/$channelType/$channelId/message'; - when(() => client.post( - path, - data: { - 'message': message, - 'skip_push': false, - 'skip_enrich_url': false, - }, - )).thenAnswer((_) async => successResponse(path, data: { + when( + () => client.post( + path, + data: { + 'message': message, + 'skip_push': false, + 'skip_enrich_url': false, + }, + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { 'message': message.toJson(), - })); + }, + ), + ); final res = await messageApi.sendMessage(channelId, channelType, message); @@ -56,16 +63,23 @@ void main() { const path = '/channels/$channelType/$channelId/message'; - when(() => client.post( - path, - data: { - 'message': message, - 'skip_push': true, - 'skip_enrich_url': false, - }, - )).thenAnswer((_) async => successResponse(path, data: { + when( + () => client.post( + path, + data: { + 'message': message, + 'skip_push': true, + 'skip_enrich_url': false, + }, + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { 'message': message.toJson(), - })); + }, + ), + ); final res = await messageApi.sendMessage( channelId, @@ -93,12 +107,19 @@ void main() { (index) => Message(id: 'test-message-id-$index'), ); - when(() => client.get( - path, - queryParameters: {'ids': messageIds.join(',')}, - )).thenAnswer((_) async => successResponse(path, data: { + when( + () => client.get( + path, + queryParameters: {'ids': messageIds.join(',')}, + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { 'messages': [...messages.map((it) => it.toJson())], - })); + }, + ), + ); final res = await messageApi.getMessagesById( channelId, @@ -122,8 +143,7 @@ void main() { final message = Message(id: messageId); - when(() => client.get(path)).thenAnswer((_) async => - successResponse(path, data: {'message': message.toJson()})); + when(() => client.get(path)).thenAnswer((_) async => successResponse(path, data: {'message': message.toJson()})); final res = await messageApi.getMessage(messageId); @@ -139,14 +159,16 @@ void main() { final path = '/messages/${message.id}'; - when(() => client.post( - path, - data: { - 'message': message, - 'skip_push': false, - 'skip_enrich_url': false, - }, - )).thenAnswer( + when( + () => client.post( + path, + data: { + 'message': message, + 'skip_push': false, + 'skip_enrich_url': false, + }, + ), + ).thenAnswer( (_) async => successResponse(path, data: {'message': message.toJson()}), ); @@ -168,14 +190,16 @@ void main() { const path = '/messages/$messageId'; final message = Message(id: 'test-message-id', text: set['text']); - when(() => client.put( - path, - data: { - 'set': set, - 'unset': unset, - 'skip_enrich_url': false, - }, - )).thenAnswer( + when( + () => client.put( + path, + data: { + 'set': set, + 'unset': unset, + 'skip_enrich_url': false, + }, + ), + ).thenAnswer( (_) async => successResponse(path, data: {'message': message.toJson()}), ); @@ -190,14 +214,16 @@ void main() { expect(res.message.text, set['text']); expect(res.message.pinExpires, isNull); - verify(() => client.put( - path, - data: { - 'set': set, - 'unset': unset, - 'skip_enrich_url': false, - }, - )).called(1); + verify( + () => client.put( + path, + data: { + 'set': set, + 'unset': unset, + 'skip_enrich_url': false, + }, + ), + ).called(1); verifyNoMoreInteractions(client); }); @@ -227,17 +253,17 @@ void main() { const path = '/messages/$messageId/action'; - when(() => client.post( - path, - data: { - 'id': channelId, - 'type': channelType, - 'form_data': formData, - 'message_id': messageId, - }, - )) - .thenAnswer( - (_) async => successResponse(path, data: {})); + when( + () => client.post( + path, + data: { + 'id': channelId, + 'type': channelType, + 'form_data': formData, + 'message_id': messageId, + }, + ), + ).thenAnswer((_) async => successResponse(path, data: {})); final res = await messageApi.sendAction( channelId, @@ -261,17 +287,24 @@ void main() { final message = Message(id: messageId); final reaction = Reaction(type: reactionType, messageId: messageId); - when(() => client.post( - path, - data: jsonEncode({ - 'reaction': reaction.toJson(), - 'skip_push': false, - 'enforce_unique': false, - }), - )).thenAnswer((_) async => successResponse(path, data: { + when( + () => client.post( + path, + data: jsonEncode({ + 'reaction': reaction.toJson(), + 'skip_push': false, + 'enforce_unique': false, + }), + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { 'message': message.toJson(), 'reaction': {...reaction.toJson(), 'message_id': messageId}, - })); + }, + ), + ); final res = await messageApi.sendReaction(messageId, reaction); @@ -293,17 +326,24 @@ void main() { final message = Message(id: messageId); final reaction = Reaction(type: reactionType, messageId: messageId); - when(() => client.post( - path, - data: jsonEncode({ - 'reaction': reaction.toJson(), - 'skip_push': false, - 'enforce_unique': true, - }), - )).thenAnswer((_) async => successResponse(path, data: { + when( + () => client.post( + path, + data: jsonEncode({ + 'reaction': reaction.toJson(), + 'skip_push': false, + 'enforce_unique': true, + }), + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { 'message': message.toJson(), 'reaction': {...reaction.toJson(), 'message_id': messageId}, - })); + }, + ), + ); final res = await messageApi.sendReaction( messageId, @@ -326,8 +366,7 @@ void main() { const path = '/messages/$messageId/reaction/$reactionType'; - when(() => client.delete(path)).thenAnswer( - (_) async => successResponse(path, data: {})); + when(() => client.delete(path)).thenAnswer((_) async => successResponse(path, data: {})); final res = await messageApi.deleteReaction(messageId, reactionType); @@ -351,18 +390,25 @@ void main() { ), ); - when(() => client.get( - path, - queryParameters: { - ...const PaginationParams().toJson(), - }, - )).thenAnswer((_) async => successResponse(path, data: { + when( + () => client.get( + path, + queryParameters: { + ...const PaginationParams().toJson(), + }, + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { 'reactions': [ ...reactions.map( (it) => {...it.toJson(), 'message_id': messageId}, ), - ] - })); + ], + }, + ), + ); final res = await messageApi.getReactions(messageId, pagination: options); @@ -391,17 +437,24 @@ void main() { }, ); - when(() => client.post( - path, - data: {'language': language}, - )).thenAnswer((_) async => successResponse(path, data: { + when( + () => client.post( + path, + data: {'language': language}, + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { 'message': { ...translatedMessage.toJson(), 'i18n': { language: translatedMessageText, }, }, - })); + }, + ), + ); final res = await messageApi.translateMessage(messageId, language); @@ -427,14 +480,21 @@ void main() { ), ); - when(() => client.get( - path, - queryParameters: { - ...options.toJson(), - }, - )).thenAnswer((_) async => successResponse(path, data: { - 'messages': [...messages.map((it) => it.toJson())] - })); + when( + () => client.get( + path, + queryParameters: { + ...options.toJson(), + }, + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { + 'messages': [...messages.map((it) => it.toJson())], + }, + ), + ); final res = await messageApi.getReplies(parentId, options: options); @@ -464,13 +524,17 @@ void main() { message: draftMessage, ); - when(() => client.post( - path, - data: jsonEncode({'message': draftMessage}), - )).thenAnswer((_) async => successResponse( - path, - data: {'draft': draft.toJson()}, - )); + when( + () => client.post( + path, + data: jsonEncode({'message': draftMessage}), + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: {'draft': draft.toJson()}, + ), + ); final res = await messageApi.createDraft( channelId, @@ -493,11 +557,12 @@ void main() { const path = '/channels/$channelType/$channelId/draft'; - when(() => client.delete(path, queryParameters: {})) - .thenAnswer((_) async => successResponse( - path, - data: {}, - )); + when(() => client.delete(path, queryParameters: {})).thenAnswer( + (_) async => successResponse( + path, + data: {}, + ), + ); final res = await messageApi.deleteDraft( channelId, @@ -517,11 +582,12 @@ void main() { const path = '/channels/$channelType/$channelId/draft'; - when(() => client.delete(path, queryParameters: {'parent_id': parentId})) - .thenAnswer((_) async => successResponse( - path, - data: {}, - )); + when(() => client.delete(path, queryParameters: {'parent_id': parentId})).thenAnswer( + (_) async => successResponse( + path, + data: {}, + ), + ); final res = await messageApi.deleteDraft( channelId, @@ -551,10 +617,14 @@ void main() { message: draftMessage, ); - when(() => client.get(path, queryParameters: {})) - .thenAnswer((_) async => successResponse(path, data: { - 'draft': draft.toJson(), - })); + when(() => client.get(path, queryParameters: {})).thenAnswer( + (_) async => successResponse( + path, + data: { + 'draft': draft.toJson(), + }, + ), + ); final res = await messageApi.getDraft( channelId, @@ -589,12 +659,19 @@ void main() { parentId: parentId, ); - when(() => client.get( - path, - queryParameters: {'parent_id': parentId}, - )).thenAnswer((_) async => successResponse(path, data: { + when( + () => client.get( + path, + queryParameters: {'parent_id': parentId}, + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { 'draft': draft.toJson(), - })); + }, + ), + ); final res = await messageApi.getDraft( channelId, @@ -612,6 +689,84 @@ void main() { verifyNoMoreInteractions(client); }); + test('queryReactions', () async { + const messageId = 'test-message-id'; + const path = '/messages/$messageId/reactions'; + + final reactions = List.generate( + 3, + (index) => Reaction( + type: 'test-reaction-type-$index', + messageId: messageId, + ), + ); + + when( + () => client.post(path, data: any(named: 'data')), + ).thenAnswer( + (_) async => successResponse( + path, + data: { + 'reactions': [ + ...reactions.map( + (it) => {...it.toJson(), 'message_id': messageId}, + ), + ], + 'next': null, + }, + ), + ); + + final res = await messageApi.queryReactions(messageId); + + expect(res, isNotNull); + expect(res.reactions.length, reactions.length); + expect(res.reactions.every((it) => it.messageId == messageId), isTrue); + expect(res.next, isNull); + + verify(() => client.post(path, data: any(named: 'data'))).called(1); + verifyNoMoreInteractions(client); + }); + + test('queryReactions with next cursor', () async { + const messageId = 'test-message-id'; + const path = '/messages/$messageId/reactions'; + const nextCursor = 'next_page_cursor'; + + final reactions = List.generate( + 3, + (index) => Reaction( + type: 'test-reaction-type-$index', + messageId: messageId, + ), + ); + + when( + () => client.post(path, data: any(named: 'data')), + ).thenAnswer( + (_) async => successResponse( + path, + data: { + 'reactions': [ + ...reactions.map( + (it) => {...it.toJson(), 'message_id': messageId}, + ), + ], + 'next': nextCursor, + }, + ), + ); + + final res = await messageApi.queryReactions(messageId); + + expect(res, isNotNull); + expect(res.reactions.length, reactions.length); + expect(res.next, nextCursor); + + verify(() => client.post(path, data: any(named: 'data'))).called(1); + verifyNoMoreInteractions(client); + }); + test('queryDrafts', () async { const path = '/drafts/query'; @@ -624,22 +779,31 @@ void main() { ), ); - when(() => client.post( - path, - data: any(named: 'data'), - )).thenAnswer((_) async => successResponse(path, data: { + when( + () => client.post( + path, + data: any(named: 'data'), + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { 'drafts': [...draftMessages.map((it) => it.toJson())], - })); + }, + ), + ); final res = await messageApi.queryDrafts(); expect(res, isNotNull); expect(res.drafts.length, draftMessages.length); - verify(() => client.post( - path, - data: any(named: 'data'), - )).called(1); + verify( + () => client.post( + path, + data: any(named: 'data'), + ), + ).called(1); verifyNoMoreInteractions(client); }); diff --git a/packages/stream_chat/test/src/core/api/moderation_api_test.dart b/packages/stream_chat/test/src/core/api/moderation_api_test.dart index 69d85fd91d..c5c9cfccfc 100644 --- a/packages/stream_chat/test/src/core/api/moderation_api_test.dart +++ b/packages/stream_chat/test/src/core/api/moderation_api_test.dart @@ -7,10 +7,10 @@ import '../../mocks.dart'; void main() { Response successResponse(String path, {Object? data}) => Response( - data: data, - requestOptions: RequestOptions(path: path), - statusCode: 200, - ); + data: data, + requestOptions: RequestOptions(path: path), + statusCode: 200, + ); late final client = MockHttpClient(); late ModerationApi moderationApi; @@ -65,15 +65,15 @@ void main() { const path = '/moderation/mute/channel'; - when(() => client.post( - path, - data: { - 'channel_cid': channelCid, - 'expiration': expiration.inMilliseconds, - }, - )) - .thenAnswer( - (_) async => successResponse(path, data: {})); + when( + () => client.post( + path, + data: { + 'channel_cid': channelCid, + 'expiration': expiration.inMilliseconds, + }, + ), + ).thenAnswer((_) async => successResponse(path, data: {})); final res = await moderationApi.muteChannel( channelCid, @@ -91,12 +91,12 @@ void main() { const path = '/moderation/unmute/channel'; - when(() => client.post( - path, - data: {'channel_cid': channelCid}, - )) - .thenAnswer( - (_) async => successResponse(path, data: {})); + when( + () => client.post( + path, + data: {'channel_cid': channelCid}, + ), + ).thenAnswer((_) async => successResponse(path, data: {})); final res = await moderationApi.unmuteChannel(channelCid); @@ -111,14 +111,14 @@ void main() { const path = '/moderation/flag'; - when(() => client.post( - path, - data: { - 'target_message_id': messageId, - }, - )) - .thenAnswer( - (_) async => successResponse(path, data: {})); + when( + () => client.post( + path, + data: { + 'target_message_id': messageId, + }, + ), + ).thenAnswer((_) async => successResponse(path, data: {})); final res = await moderationApi.flagMessage(messageId); @@ -133,14 +133,14 @@ void main() { const path = '/moderation/unflag'; - when(() => client.post( - path, - data: { - 'target_message_id': messageId, - }, - )) - .thenAnswer( - (_) async => successResponse(path, data: {})); + when( + () => client.post( + path, + data: { + 'target_message_id': messageId, + }, + ), + ).thenAnswer((_) async => successResponse(path, data: {})); final res = await moderationApi.unflagMessage(messageId); @@ -155,11 +155,14 @@ void main() { const path = '/moderation/flag'; - when(() => client.post(path, data: { - 'target_user_id': userId, - })) - .thenAnswer( - (_) async => successResponse(path, data: {})); + when( + () => client.post( + path, + data: { + 'target_user_id': userId, + }, + ), + ).thenAnswer((_) async => successResponse(path, data: {})); final res = await moderationApi.flagUser(userId); @@ -174,14 +177,14 @@ void main() { const path = '/moderation/unflag'; - when(() => client.post( - path, - data: { - 'target_user_id': userId, - }, - )) - .thenAnswer( - (_) async => successResponse(path, data: {})); + when( + () => client.post( + path, + data: { + 'target_user_id': userId, + }, + ), + ).thenAnswer((_) async => successResponse(path, data: {})); final res = await moderationApi.unflagUser(userId); @@ -197,12 +200,15 @@ void main() { const path = '/moderation/ban'; - when(() => client.post(path, data: { - 'target_user_id': targetUserId, - ...options, - })) - .thenAnswer( - (_) async => successResponse(path, data: {})); + when( + () => client.post( + path, + data: { + 'target_user_id': targetUserId, + ...options, + }, + ), + ).thenAnswer((_) async => successResponse(path, data: {})); final res = await moderationApi.banUser(targetUserId, options: options); diff --git a/packages/stream_chat/test/src/core/api/polls_api_test.dart b/packages/stream_chat/test/src/core/api/polls_api_test.dart index 20a3de8d85..2c303218f2 100644 --- a/packages/stream_chat/test/src/core/api/polls_api_test.dart +++ b/packages/stream_chat/test/src/core/api/polls_api_test.dart @@ -15,10 +15,10 @@ import '../../mocks.dart'; void main() { Response successResponse(String path, {Object? data}) => Response( - data: data, - requestOptions: RequestOptions(path: path), - statusCode: 200, - ); + data: data, + requestOptions: RequestOptions(path: path), + statusCode: 200, + ); late final client = MockHttpClient(); late PollsApi pollsApi; @@ -41,12 +41,19 @@ void main() { const path = '/polls'; - when(() => client.post( - path, - data: jsonEncode(poll), - )).thenAnswer((_) async => successResponse(path, data: { + when( + () => client.post( + path, + data: jsonEncode(poll), + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { 'poll': poll.toJson(), - })); + }, + ), + ); final res = await pollsApi.createPoll(poll); @@ -73,10 +80,14 @@ void main() { ], ); - when(() => client.get(path)) - .thenAnswer((_) async => successResponse(path, data: { - 'poll': poll.toJson(), - })); + when(() => client.get(path)).thenAnswer( + (_) async => successResponse( + path, + data: { + 'poll': poll.toJson(), + }, + ), + ); final res = await pollsApi.getPoll(pollId); @@ -101,12 +112,19 @@ void main() { const path = '/polls'; - when(() => client.put( - path, - data: jsonEncode(poll), - )).thenAnswer((_) async => successResponse(path, data: { + when( + () => client.put( + path, + data: jsonEncode(poll), + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { 'poll': poll.toJson(), - })); + }, + ), + ); final res = await pollsApi.updatePoll(poll); @@ -134,12 +152,19 @@ void main() { ], ); - when(() => client.patch( - path, - data: jsonEncode({'set': set, 'unset': unset}), - )).thenAnswer((_) async => successResponse(path, data: { + when( + () => client.patch( + path, + data: jsonEncode({'set': set, 'unset': unset}), + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { 'poll': poll.toJson(), - })); + }, + ), + ); final res = await pollsApi.partialUpdatePoll( pollId, @@ -151,11 +176,15 @@ void main() { expect(res.poll.id, pollId); expect(res.poll.name, set['name']); - verify(() => client.patch(path, + verify( + () => client.patch( + path, data: jsonEncode({ 'set': set, 'unset': unset, - }))).called(1); + }), + ), + ).called(1); verifyNoMoreInteractions(client); }); @@ -164,8 +193,7 @@ void main() { const path = '/polls/$pollId'; - when(() => client.delete(path)).thenAnswer( - (_) async => successResponse(path, data: {})); + when(() => client.delete(path)).thenAnswer((_) async => successResponse(path, data: {})); final res = await pollsApi.deletePoll(pollId); @@ -184,15 +212,22 @@ void main() { const path = '/polls/$pollId/options'; - when(() => client.post( - path, - data: jsonEncode(option), - )).thenAnswer((_) async => successResponse(path, data: { + when( + () => client.post( + path, + data: jsonEncode(option), + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { 'poll_option': option.toJson() ..addAll({ 'id': option.id, }), - })); + }, + ), + ); final res = await pollsApi.createPollOption(pollId, option); @@ -214,13 +249,17 @@ void main() { text: 'test-option-value', ); - when(() => client.get(path)) - .thenAnswer((_) async => successResponse(path, data: { - 'poll_option': option.toJson() - ..addAll({ - 'id': option.id, - }), - })); + when(() => client.get(path)).thenAnswer( + (_) async => successResponse( + path, + data: { + 'poll_option': option.toJson() + ..addAll({ + 'id': option.id, + }), + }, + ), + ); final res = await pollsApi.getPollOption(pollId, optionId); @@ -240,15 +279,22 @@ void main() { const path = '/polls/$pollId/options'; - when(() => client.put( - path, - data: jsonEncode(option), - )).thenAnswer((_) async => successResponse(path, data: { + when( + () => client.put( + path, + data: jsonEncode(option), + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { 'poll_option': option.toJson() ..addAll({ 'id': option.id, }), - })); + }, + ), + ); final res = await pollsApi.updatePollOption(pollId, option); @@ -265,8 +311,7 @@ void main() { const path = '/polls/$pollId/options/$optionId'; - when(() => client.delete(path)).thenAnswer( - (_) async => successResponse(path, data: {})); + when(() => client.delete(path)).thenAnswer((_) async => successResponse(path, data: {})); final res = await pollsApi.deletePollOption(pollId, optionId); @@ -286,14 +331,21 @@ void main() { const path = '/messages/$messageId/polls/$pollId/vote'; - when(() => client.post( - path, - data: jsonEncode({ - 'vote': vote, - }), - )).thenAnswer((_) async => successResponse(path, data: { + when( + () => client.post( + path, + data: jsonEncode({ + 'vote': vote, + }), + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { 'vote': vote.toJson(), - })); + }, + ), + ); final res = await pollsApi.castPollVote(messageId, pollId, vote); @@ -315,10 +367,14 @@ void main() { optionId: 'test-option-id', ); - when(() => client.delete(path)) - .thenAnswer((_) async => successResponse(path, data: { - 'vote': vote.toJson(), - })); + when(() => client.delete(path)).thenAnswer( + (_) async => successResponse( + path, + data: { + 'vote': vote.toJson(), + }, + ), + ); final res = await pollsApi.removePollVote(messageId, pollId, voteId); @@ -359,9 +415,14 @@ void main() { path, data: payload, ), - ).thenAnswer((_) async => successResponse(path, data: { + ).thenAnswer( + (_) async => successResponse( + path, + data: { 'polls': [...polls.map((it) => it.toJson())], - })); + }, + ), + ); final res = await pollsApi.queryPolls( filter: filter, @@ -405,9 +466,14 @@ void main() { path, data: payload, ), - ).thenAnswer((_) async => successResponse(path, data: { + ).thenAnswer( + (_) async => successResponse( + path, + data: { 'votes': [...votes.map((it) => it.toJson())], - })); + }, + ), + ); final res = await pollsApi.queryPollVotes( pollId, diff --git a/packages/stream_chat/test/src/core/api/reminders_api_test.dart b/packages/stream_chat/test/src/core/api/reminders_api_test.dart index e0c3be7061..919bb47661 100644 --- a/packages/stream_chat/test/src/core/api/reminders_api_test.dart +++ b/packages/stream_chat/test/src/core/api/reminders_api_test.dart @@ -15,10 +15,10 @@ import '../../mocks.dart'; void main() { Response successResponse(String path, {Object? data}) => Response( - data: data, - requestOptions: RequestOptions(path: path), - statusCode: 200, - ); + data: data, + requestOptions: RequestOptions(path: path), + statusCode: 200, + ); late final client = MockHttpClient(); late RemindersApi remindersApi; @@ -50,10 +50,14 @@ void main() { ), ]; - when(() => client.post(path, data: jsonEncode({}))) - .thenAnswer((_) async => successResponse(path, data: { - 'reminders': reminders.map((r) => r.toJson()).toList(), - })); + when(() => client.post(path, data: jsonEncode({}))).thenAnswer( + (_) async => successResponse( + path, + data: { + 'reminders': reminders.map((r) => r.toJson()).toList(), + }, + ), + ); final res = await remindersApi.queryReminders(); @@ -89,10 +93,14 @@ void main() { ), ); - when(() => client.post(path, data: expectedPayload)) - .thenAnswer((_) async => successResponse(path, data: { - 'reminders': reminders.map((r) => r.toJson()).toList(), - })); + when(() => client.post(path, data: expectedPayload)).thenAnswer( + (_) async => successResponse( + path, + data: { + 'reminders': reminders.map((r) => r.toJson()).toList(), + }, + ), + ); final res = await remindersApi.queryReminders( filter: filter, @@ -122,10 +130,14 @@ void main() { updatedAt: DateTime(2024, 1, 1), ); - when(() => client.post(path, data: jsonEncode({}))) - .thenAnswer((_) async => successResponse(path, data: { - 'reminder': reminder.toJson(), - })); + when(() => client.post(path, data: jsonEncode({}))).thenAnswer( + (_) async => successResponse( + path, + data: { + 'reminder': reminder.toJson(), + }, + ), + ); final res = await remindersApi.createReminder(messageId); @@ -154,13 +166,16 @@ void main() { 'remind_at': remindAt.toUtc().toIso8601String(), }); - when(() => client.post(path, data: expectedPayload)) - .thenAnswer((_) async => successResponse(path, data: { - 'reminder': reminder.toJson(), - })); + when(() => client.post(path, data: expectedPayload)).thenAnswer( + (_) async => successResponse( + path, + data: { + 'reminder': reminder.toJson(), + }, + ), + ); - final res = - await remindersApi.createReminder(messageId, remindAt: remindAt); + final res = await remindersApi.createReminder(messageId, remindAt: remindAt); expect(res, isNotNull); expect(res.reminder.messageId, messageId); @@ -185,10 +200,14 @@ void main() { updatedAt: DateTime(2024, 1, 2), ); - when(() => client.patch(path, data: jsonEncode({}))) - .thenAnswer((_) async => successResponse(path, data: { - 'reminder': reminder.toJson(), - })); + when(() => client.patch(path, data: jsonEncode({}))).thenAnswer( + (_) async => successResponse( + path, + data: { + 'reminder': reminder.toJson(), + }, + ), + ); final res = await remindersApi.updateReminder(messageId); @@ -217,13 +236,16 @@ void main() { 'remind_at': remindAt.toUtc().toIso8601String(), }); - when(() => client.patch(path, data: expectedPayload)) - .thenAnswer((_) async => successResponse(path, data: { - 'reminder': reminder.toJson(), - })); + when(() => client.patch(path, data: expectedPayload)).thenAnswer( + (_) async => successResponse( + path, + data: { + 'reminder': reminder.toJson(), + }, + ), + ); - final res = - await remindersApi.updateReminder(messageId, remindAt: remindAt); + final res = await remindersApi.updateReminder(messageId, remindAt: remindAt); expect(res, isNotNull); expect(res.reminder.messageId, messageId); @@ -239,8 +261,7 @@ void main() { const messageId = 'test-message-id'; const path = '/messages/$messageId/reminders'; - when(() => client.delete(path)).thenAnswer( - (_) async => successResponse(path, data: {})); + when(() => client.delete(path)).thenAnswer((_) async => successResponse(path, data: {})); final res = await remindersApi.deleteReminder(messageId); diff --git a/packages/stream_chat/test/src/core/api/responses_test.dart b/packages/stream_chat/test/src/core/api/responses_test.dart index 5e3430f38c..644e75b847 100644 --- a/packages/stream_chat/test/src/core/api/responses_test.dart +++ b/packages/stream_chat/test/src/core/api/responses_test.dart @@ -3304,8 +3304,7 @@ void main() { const jsonExample = ''' {"reactions": [{"message_id": "4637f7e4-a06b-42db-ba5a-8d8270dd926f","user_id": "c1c9b454-2bcc-402d-8bb0-2f3706ce1680","user": {"id": "c1c9b454-2bcc-402d-8bb0-2f3706ce1680","role": "user","created_at": "2020-01-28T22:17:30.83015Z","updated_at": "2020-01-28T22:17:31.19435Z","banned": false,"online": false,"image": "https://randomuser.me/api/portraits/women/2.jpg","name": "Mia Denys"},"type": "love","score": 1,"created_at": "2020-01-28T22:17:31.128376Z","updated_at": "2020-01-28T22:17:31.128376Z"}]} '''; - final response = - QueryReactionsResponse.fromJson(json.decode(jsonExample)); + final response = QueryReactionsResponse.fromJson(json.decode(jsonExample)); expect(response.reactions, isA>()); }); @@ -3412,14 +3411,12 @@ void main() { }] } '''; - final response = - SearchMessagesResponse.fromJson(json.decode(jsonExample)); + final response = SearchMessagesResponse.fromJson(json.decode(jsonExample)); expect(response.results, isA>()); }); test('ListDevicesResponse', () { - const jsonExample = - '''{"devices":[{"push_provider":"firebase","id":"test"}],"duration":"0.35ms"}'''; + const jsonExample = '''{"devices":[{"push_provider":"firebase","id":"test"}],"duration":"0.35ms"}'''; final response = ListDevicesResponse.fromJson(json.decode(jsonExample)); expect(response.devices, isA>()); }); @@ -3517,8 +3514,7 @@ void main() { test('ConnectGuestUserResponse', () { const jsonExample = '''{"user":{"id":"guest-ac612aee-25fe-49fb-b1af-969e41f452a0-wild-breeze-7","role":"guest","created_at":"2020-02-03T10:19:01.538434Z","updated_at":"2020-02-03T10:19:01.539543Z","banned":false,"online":false},"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiZ3Vlc3QtYWM2MTJhZWUtMjVmZS00OWZiLWIxYWYtOTY5ZTQxZjQ1MmEwLXdpbGQtYnJlZXplLTcifQ.mmoFGu7oJjpFsp7nFN78UbIpO7gowbuIbyoppsuvbXA","duration":"4.66ms"}'''; - final response = - ConnectGuestUserResponse.fromJson(json.decode(jsonExample)); + final response = ConnectGuestUserResponse.fromJson(json.decode(jsonExample)); expect(response.user, isA()); expect(response.accessToken, isA()); }); @@ -3550,8 +3546,7 @@ void main() { "updated_at": "2020-01-28T22:17:31.092262Z", "mentioned_users": [] }],"duration":"4.66ms"}'''; - final response = - GetMessagesByIdResponse.fromJson(json.decode(jsonExample)); + final response = GetMessagesByIdResponse.fromJson(json.decode(jsonExample)); expect(response.messages, isA>()); }); @@ -4474,8 +4469,7 @@ void main() { final channel1Prefs = user1ChannelPrefs['channel1']!; expect(channel1Prefs.chatLevel, ChatLevel.all); - expect( - channel1Prefs.disabledUntil, DateTime.parse('2024-12-31T23:59:59Z')); + expect(channel1Prefs.disabledUntil, DateTime.parse('2024-12-31T23:59:59Z')); final channel2Prefs = user1ChannelPrefs['channel2']!; expect(channel2Prefs.chatLevel, ChatLevel.none); diff --git a/packages/stream_chat/test/src/core/api/user_api_test.dart b/packages/stream_chat/test/src/core/api/user_api_test.dart index 9a77907e64..ad61fbc94d 100644 --- a/packages/stream_chat/test/src/core/api/user_api_test.dart +++ b/packages/stream_chat/test/src/core/api/user_api_test.dart @@ -10,10 +10,10 @@ import '../../mocks.dart'; void main() { Response successResponse(String path, {Object? data}) => Response( - data: data, - requestOptions: RequestOptions(path: path), - statusCode: 200, - ); + data: data, + requestOptions: RequestOptions(path: path), + statusCode: 200, + ); late final client = MockHttpClient(); late UserApi userApi; @@ -32,16 +32,26 @@ void main() { final users = List.generate(3, (index) => User(id: 'test-user-id-$index')); - when(() => client.get(path, queryParameters: { + when( + () => client.get( + path, + queryParameters: { 'payload': jsonEncode({ 'presence': presence, 'sort': sort, 'filter_conditions': filter, ...pagination.toJson(), }), - })).thenAnswer((_) async => successResponse(path, data: { - 'users': [...users.map((it) => it.toJson())] - })); + }, + ), + ).thenAnswer( + (_) async => successResponse( + path, + data: { + 'users': [...users.map((it) => it.toJson())], + }, + ), + ); final res = await userApi.queryUsers( presence: presence, @@ -66,13 +76,17 @@ void main() { final updatedUsers = {for (final user in users) user.id: user}; - when(() => client.post(path, data: { + when( + () => client.post( + path, + data: { 'users': updatedUsers, - })).thenAnswer((_) async => successResponse(path, - data: { - 'users': updatedUsers - .map((key, value) => MapEntry(key, value.toJson())) - })); + }, + ), + ).thenAnswer( + (_) async => + successResponse(path, data: {'users': updatedUsers.map((key, value) => MapEntry(key, value.toJson()))}), + ); final res = await userApi.updateUsers(users); @@ -93,16 +107,18 @@ void main() { final updatedUser = {user.id: User(id: user.id, extraData: user.set!)}; - when(() => client.patch(path, data: { - 'users': [user], - })).thenAnswer( - (_) async => successResponse( + when( + () => client.patch( path, data: { - 'users': - updatedUser.map((key, value) => MapEntry(key, value.toJson())) + 'users': [user], }, ), + ).thenAnswer( + (_) async => successResponse( + path, + data: {'users': updatedUser.map((key, value) => MapEntry(key, value.toJson()))}, + ), ); final res = await userApi.partialUpdateUsers([user]); @@ -110,9 +126,14 @@ void main() { expect(res, isNotNull); expect(res.users.length, updatedUser.length); - verify(() => client.patch(path, data: { - 'users': [user] - })).called(1); + verify( + () => client.patch( + path, + data: { + 'users': [user], + }, + ), + ).called(1); verifyNoMoreInteractions(client); }); @@ -121,9 +142,14 @@ void main() { const path = '/users/block'; - when(() => client.post(path, data: { + when( + () => client.post( + path, + data: { 'blocked_user_id': targetUserId, - })).thenAnswer( + }, + ), + ).thenAnswer( (_) async => successResponse( path, data: { @@ -138,9 +164,14 @@ void main() { expect(res, isNotNull); - verify(() => client.post(path, data: { + verify( + () => client.post( + path, + data: { 'blocked_user_id': targetUserId, - })).called(1); + }, + ), + ).called(1); verifyNoMoreInteractions(client); }); @@ -149,9 +180,14 @@ void main() { const path = '/users/unblock'; - when(() => client.post(path, data: { + when( + () => client.post( + path, + data: { 'blocked_user_id': targetUserId, - })).thenAnswer( + }, + ), + ).thenAnswer( (_) async => successResponse( path, data: {}, @@ -162,9 +198,14 @@ void main() { expect(res, isNotNull); - verify(() => client.post(path, data: { + verify( + () => client.post( + path, + data: { 'blocked_user_id': targetUserId, - })).called(1); + }, + ), + ).called(1); verifyNoMoreInteractions(client); }); @@ -187,50 +228,53 @@ void main() { const path = '/unread'; when(() => client.get(path)).thenAnswer( - (_) async => successResponse(path, data: { - 'duration': '5.23ms', - 'total_unread_count': 42, - 'total_unread_threads_count': 8, - 'total_unread_count_by_team': {'team-1': 15, 'team-2': 27}, - 'channels': [ - { - 'channel_id': 'messaging:test-channel-1', - 'unread_count': 5, - 'last_read': '2024-01-15T10:30:00.000Z', - }, - { - 'channel_id': 'messaging:test-channel-2', - 'unread_count': 10, - 'last_read': '2024-01-15T09:15:00.000Z', - }, - ], - 'channel_type': [ - { - 'channel_type': 'messaging', - 'channel_count': 3, - 'unread_count': 25, - }, - { - 'channel_type': 'livestream', - 'channel_count': 1, - 'unread_count': 17, - }, - ], - 'threads': [ - { - 'unread_count': 3, - 'last_read': '2024-01-15T10:30:00.000Z', - 'last_read_message_id': 'message-1', - 'parent_message_id': 'parent-message-1', - }, - { - 'unread_count': 5, - 'last_read': '2024-01-15T09:45:00.000Z', - 'last_read_message_id': 'message-2', - 'parent_message_id': 'parent-message-2', - }, - ], - }), + (_) async => successResponse( + path, + data: { + 'duration': '5.23ms', + 'total_unread_count': 42, + 'total_unread_threads_count': 8, + 'total_unread_count_by_team': {'team-1': 15, 'team-2': 27}, + 'channels': [ + { + 'channel_id': 'messaging:test-channel-1', + 'unread_count': 5, + 'last_read': '2024-01-15T10:30:00.000Z', + }, + { + 'channel_id': 'messaging:test-channel-2', + 'unread_count': 10, + 'last_read': '2024-01-15T09:15:00.000Z', + }, + ], + 'channel_type': [ + { + 'channel_type': 'messaging', + 'channel_count': 3, + 'unread_count': 25, + }, + { + 'channel_type': 'livestream', + 'channel_count': 1, + 'unread_count': 17, + }, + ], + 'threads': [ + { + 'unread_count': 3, + 'last_read': '2024-01-15T10:30:00.000Z', + 'last_read_message_id': 'message-1', + 'parent_message_id': 'parent-message-1', + }, + { + 'unread_count': 5, + 'last_read': '2024-01-15T09:45:00.000Z', + 'last_read_message_id': 'message-2', + 'parent_message_id': 'parent-message-2', + }, + ], + }, + ), ); final res = await userApi.getUnreadCount(); diff --git a/packages/stream_chat/test/src/core/error/stream_chat_error_test.dart b/packages/stream_chat/test/src/core/error/stream_chat_error_test.dart index c95afd3cb4..4bba3b898d 100644 --- a/packages/stream_chat/test/src/core/error/stream_chat_error_test.dart +++ b/packages/stream_chat/test/src/core/error/stream_chat_error_test.dart @@ -92,7 +92,8 @@ void main() { const statusCode = 666; const message = 'test-error-message'; final options = RequestOptions(path: 'test-path'); - const data = ''' + const data = + ''' { "code": $code, "StatusCode": $statusCode, diff --git a/packages/stream_chat/test/src/core/http/adapter/custom_adapter_test.dart b/packages/stream_chat/test/src/core/http/adapter/custom_adapter_test.dart index 75c6bb76e5..ce334d3e92 100644 --- a/packages/stream_chat/test/src/core/http/adapter/custom_adapter_test.dart +++ b/packages/stream_chat/test/src/core/http/adapter/custom_adapter_test.dart @@ -15,11 +15,12 @@ void main() { final mockAdapter = MockHttpClientAdapter(); final httpClient = StreamHttpClient(apiKey, httpClientAdapter: mockAdapter); - when(() => mockAdapter.fetch(any(), any(), any())) - .thenAnswer((_) async => ResponseBody( - Stream.value(Uint8List(0)), - 200, - )); + when(() => mockAdapter.fetch(any(), any(), any())).thenAnswer( + (_) async => ResponseBody( + Stream.value(Uint8List(0)), + 200, + ), + ); await httpClient.get('/'); diff --git a/packages/stream_chat/test/src/core/http/interceptor/auth_interceptor_test.dart b/packages/stream_chat/test/src/core/http/interceptor/auth_interceptor_test.dart index 269e7c884d..cc92b01a8f 100644 --- a/packages/stream_chat/test/src/core/http/interceptor/auth_interceptor_test.dart +++ b/packages/stream_chat/test/src/core/http/interceptor/auth_interceptor_test.dart @@ -35,8 +35,7 @@ void main() { expect(queryParams.containsKey('user_id'), isFalse); final token = Token.development('test-user-id'); - when(() => tokenManager.loadToken(refresh: any(named: 'refresh'))) - .thenAnswer((_) async => token); + when(() => tokenManager.loadToken(refresh: any(named: 'refresh'))).thenAnswer((_) async => token); authInterceptor.onRequest(options, handler); @@ -51,8 +50,7 @@ void main() { expect(updatedQueryParams.containsKey('user_id'), isTrue); expect(updatedQueryParams['user_id'], token.userId); - verify(() => tokenManager.loadToken(refresh: any(named: 'refresh'))) - .called(1); + verify(() => tokenManager.loadToken(refresh: any(named: 'refresh'))).called(1); verifyNoMoreInteractions(tokenManager); }, ); @@ -95,13 +93,14 @@ void main() { when(() => tokenManager.isStatic).thenReturn(false); final token = Token.development('test-user-id'); - when(() => tokenManager.loadToken(refresh: true)) - .thenAnswer((_) async => token); + when(() => tokenManager.loadToken(refresh: true)).thenAnswer((_) async => token); - when(() => client.fetch(options)).thenAnswer((_) async => Response( - requestOptions: options, - statusCode: 200, - )); + when(() => client.fetch(options)).thenAnswer( + (_) async => Response( + requestOptions: options, + statusCode: 200, + ), + ); authInterceptor.onError(err, handler); @@ -142,8 +141,7 @@ void main() { when(() => tokenManager.isStatic).thenReturn(false); final token = Token.development('test-user-id'); - when(() => tokenManager.loadToken(refresh: true)) - .thenAnswer((_) async => token); + when(() => tokenManager.loadToken(refresh: true)).thenAnswer((_) async => token); when(() => client.fetch(options)).thenThrow(err); diff --git a/packages/stream_chat/test/src/core/http/stream_http_client_test.dart b/packages/stream_chat/test/src/core/http/stream_http_client_test.dart index 2ddc84e3b4..936af1670b 100644 --- a/packages/stream_chat/test/src/core/http/stream_http_client_test.dart +++ b/packages/stream_chat/test/src/core/http/stream_http_client_test.dart @@ -17,9 +17,9 @@ import '../../mocks.dart'; void main() { Response successResponse(String path) => Response( - requestOptions: RequestOptions(path: path), - statusCode: 200, - ); + requestOptions: RequestOptions(path: path), + statusCode: 200, + ); DioException throwableError( String path, { @@ -53,19 +53,14 @@ void main() { const apiKey = 'api-key'; final client = StreamHttpClient(apiKey); - expect( - client.httpClient.interceptors - .whereType() - .length, - 1); + expect(client.httpClient.interceptors.whereType().length, 1); }); test('AuthInterceptor should be added if tokenManager is provided', () { const apiKey = 'api-key'; final client = StreamHttpClient(apiKey, tokenManager: TokenManager()); - expect( - client.httpClient.interceptors.whereType().length, 1); + expect(client.httpClient.interceptors.whereType().length, 1); }); test( @@ -78,9 +73,7 @@ void main() { ); expect( - client.httpClient.interceptors - .whereType() - .length, + client.httpClient.interceptors.whereType().length, 1, ); }, @@ -175,10 +168,12 @@ void main() { final client = StreamHttpClient('api-key', dio: dio); const path = 'test-get-api-path'; - when(() => dio.get( - path, - options: any(named: 'options'), - )).thenAnswer((_) async => successResponse(path)); + when( + () => dio.get( + path, + options: any(named: 'options'), + ), + ).thenAnswer((_) async => successResponse(path)); final res = await client.get(path); @@ -186,10 +181,12 @@ void main() { expect(res.statusCode, 200); expect(res.requestOptions.path, path); - verify(() => dio.get( - path, - options: any(named: 'options'), - )).called(1); + verify( + () => dio.get( + path, + options: any(named: 'options'), + ), + ).called(1); verifyNoMoreInteractions(dio); }); @@ -202,10 +199,12 @@ void main() { path, error: StreamChatNetworkError(ChatErrorCode.internalSystemError), ); - when(() => dio.get( - path, - options: any(named: 'options'), - )).thenThrow(error); + when( + () => dio.get( + path, + options: any(named: 'options'), + ), + ).thenThrow(error); try { await client.get(path); @@ -214,10 +213,12 @@ void main() { expect(e, StreamChatNetworkError.fromDioException(error)); } - verify(() => dio.get( - path, - options: any(named: 'options'), - )).called(1); + verify( + () => dio.get( + path, + options: any(named: 'options'), + ), + ).called(1); verifyNoMoreInteractions(dio); }); @@ -226,10 +227,12 @@ void main() { final client = StreamHttpClient('api-key', dio: dio); const path = 'test-post-api-path'; - when(() => dio.post( - path, - options: any(named: 'options'), - )).thenAnswer((_) async => successResponse(path)); + when( + () => dio.post( + path, + options: any(named: 'options'), + ), + ).thenAnswer((_) async => successResponse(path)); final res = await client.post(path); @@ -237,10 +240,12 @@ void main() { expect(res.statusCode, 200); expect(res.requestOptions.path, path); - verify(() => dio.post( - path, - options: any(named: 'options'), - )).called(1); + verify( + () => dio.post( + path, + options: any(named: 'options'), + ), + ).called(1); verifyNoMoreInteractions(dio); }); @@ -255,10 +260,12 @@ void main() { path, error: StreamChatNetworkError(ChatErrorCode.internalSystemError), ); - when(() => dio.post( - path, - options: any(named: 'options'), - )).thenThrow(error); + when( + () => dio.post( + path, + options: any(named: 'options'), + ), + ).thenThrow(error); try { await client.post(path); @@ -267,10 +274,12 @@ void main() { expect(e, StreamChatNetworkError.fromDioException(error)); } - verify(() => dio.post( - path, - options: any(named: 'options'), - )).called(1); + verify( + () => dio.post( + path, + options: any(named: 'options'), + ), + ).called(1); verifyNoMoreInteractions(dio); }, ); @@ -280,10 +289,12 @@ void main() { final client = StreamHttpClient('api-key', dio: dio); const path = 'test-delete-api-path'; - when(() => dio.delete( - path, - options: any(named: 'options'), - )).thenAnswer((_) async => successResponse(path)); + when( + () => dio.delete( + path, + options: any(named: 'options'), + ), + ).thenAnswer((_) async => successResponse(path)); final res = await client.delete(path); @@ -291,10 +302,12 @@ void main() { expect(res.statusCode, 200); expect(res.requestOptions.path, path); - verify(() => dio.delete( - path, - options: any(named: 'options'), - )).called(1); + verify( + () => dio.delete( + path, + options: any(named: 'options'), + ), + ).called(1); verifyNoMoreInteractions(dio); }); @@ -309,10 +322,12 @@ void main() { path, error: StreamChatNetworkError(ChatErrorCode.internalSystemError), ); - when(() => dio.delete( - path, - options: any(named: 'options'), - )).thenThrow(error); + when( + () => dio.delete( + path, + options: any(named: 'options'), + ), + ).thenThrow(error); try { await client.delete(path); @@ -321,10 +336,12 @@ void main() { expect(e, StreamChatNetworkError.fromDioException(error)); } - verify(() => dio.delete( - path, - options: any(named: 'options'), - )).called(1); + verify( + () => dio.delete( + path, + options: any(named: 'options'), + ), + ).called(1); verifyNoMoreInteractions(dio); }, ); @@ -334,10 +351,12 @@ void main() { final client = StreamHttpClient('api-key', dio: dio); const path = 'test-patch-api-path'; - when(() => dio.patch( - path, - options: any(named: 'options'), - )).thenAnswer((_) async => successResponse(path)); + when( + () => dio.patch( + path, + options: any(named: 'options'), + ), + ).thenAnswer((_) async => successResponse(path)); final res = await client.patch(path); @@ -345,10 +364,12 @@ void main() { expect(res.statusCode, 200); expect(res.requestOptions.path, path); - verify(() => dio.patch( - path, - options: any(named: 'options'), - )).called(1); + verify( + () => dio.patch( + path, + options: any(named: 'options'), + ), + ).called(1); verifyNoMoreInteractions(dio); }); @@ -363,10 +384,12 @@ void main() { path, error: StreamChatNetworkError(ChatErrorCode.internalSystemError), ); - when(() => dio.patch( - path, - options: any(named: 'options'), - )).thenThrow(error); + when( + () => dio.patch( + path, + options: any(named: 'options'), + ), + ).thenThrow(error); try { await client.patch(path); @@ -375,10 +398,12 @@ void main() { expect(e, StreamChatNetworkError.fromDioException(error)); } - verify(() => dio.patch( - path, - options: any(named: 'options'), - )).called(1); + verify( + () => dio.patch( + path, + options: any(named: 'options'), + ), + ).called(1); verifyNoMoreInteractions(dio); }, ); @@ -388,10 +413,12 @@ void main() { final client = StreamHttpClient('api-key', dio: dio); const path = 'test-put-api-path'; - when(() => dio.put( - path, - options: any(named: 'options'), - )).thenAnswer((_) async => successResponse(path)); + when( + () => dio.put( + path, + options: any(named: 'options'), + ), + ).thenAnswer((_) async => successResponse(path)); final res = await client.put(path); @@ -399,10 +426,12 @@ void main() { expect(res.statusCode, 200); expect(res.requestOptions.path, path); - verify(() => dio.put( - path, - options: any(named: 'options'), - )).called(1); + verify( + () => dio.put( + path, + options: any(named: 'options'), + ), + ).called(1); verifyNoMoreInteractions(dio); }); @@ -417,10 +446,12 @@ void main() { path, error: StreamChatNetworkError(ChatErrorCode.internalSystemError), ); - when(() => dio.put( - path, - options: any(named: 'options'), - )).thenThrow(error); + when( + () => dio.put( + path, + options: any(named: 'options'), + ), + ).thenThrow(error); try { await client.put(path); @@ -429,10 +460,12 @@ void main() { expect(e, StreamChatNetworkError.fromDioException(error)); } - verify(() => dio.put( - path, - options: any(named: 'options'), - )).called(1); + verify( + () => dio.put( + path, + options: any(named: 'options'), + ), + ).called(1); verifyNoMoreInteractions(dio); }, ); @@ -444,11 +477,13 @@ void main() { const path = 'test-delete-api-path'; final file = MultipartFile.fromBytes([]); - when(() => dio.post( - path, - data: any(named: 'data'), - options: any(named: 'options'), - )).thenAnswer((_) async => successResponse(path)); + when( + () => dio.post( + path, + data: any(named: 'data'), + options: any(named: 'options'), + ), + ).thenAnswer((_) async => successResponse(path)); final res = await client.postFile(path, file); @@ -456,11 +491,13 @@ void main() { expect(res.statusCode, 200); expect(res.requestOptions.path, path); - verify(() => dio.post( - path, - data: any(named: 'data'), - options: any(named: 'options'), - )).called(1); + verify( + () => dio.post( + path, + data: any(named: 'data'), + options: any(named: 'options'), + ), + ).called(1); verifyNoMoreInteractions(dio); }); @@ -477,11 +514,13 @@ void main() { path, error: StreamChatNetworkError(ChatErrorCode.internalSystemError), ); - when(() => dio.post( - path, - data: any(named: 'data'), - options: any(named: 'options'), - )).thenThrow(error); + when( + () => dio.post( + path, + data: any(named: 'data'), + options: any(named: 'options'), + ), + ).thenThrow(error); try { await client.postFile(path, file); @@ -490,11 +529,13 @@ void main() { expect(e, StreamChatNetworkError.fromDioException(error)); } - verify(() => dio.post( - path, - data: any(named: 'data'), - options: any(named: 'options'), - )).called(1); + verify( + () => dio.post( + path, + data: any(named: 'data'), + options: any(named: 'options'), + ), + ).called(1); verifyNoMoreInteractions(dio); }, ); @@ -504,10 +545,12 @@ void main() { final client = StreamHttpClient('api-key', dio: dio); const path = 'test-request-api-path'; - when(() => dio.request( - path, - options: any(named: 'options'), - )).thenAnswer((_) async => successResponse(path)); + when( + () => dio.request( + path, + options: any(named: 'options'), + ), + ).thenAnswer((_) async => successResponse(path)); final res = await client.request(path); @@ -515,10 +558,12 @@ void main() { expect(res.statusCode, 200); expect(res.requestOptions.path, path); - verify(() => dio.request( - path, - options: any(named: 'options'), - )).called(1); + verify( + () => dio.request( + path, + options: any(named: 'options'), + ), + ).called(1); verifyNoMoreInteractions(dio); }); @@ -534,10 +579,12 @@ void main() { streamChatDioError: true, error: StreamChatNetworkError(ChatErrorCode.internalSystemError), ); - when(() => dio.request( - path, - options: any(named: 'options'), - )).thenThrow(error); + when( + () => dio.request( + path, + options: any(named: 'options'), + ), + ).thenThrow(error); try { await client.request(path); @@ -546,10 +593,12 @@ void main() { expect(e, error.error); } - verify(() => dio.request( - path, - options: any(named: 'options'), - )).called(1); + verify( + () => dio.request( + path, + options: any(named: 'options'), + ), + ).called(1); verifyNoMoreInteractions(dio); }, ); diff --git a/packages/stream_chat/test/src/core/http/token_manager_test.dart b/packages/stream_chat/test/src/core/http/token_manager_test.dart index 163bafffe2..626fd676b4 100644 --- a/packages/stream_chat/test/src/core/http/token_manager_test.dart +++ b/packages/stream_chat/test/src/core/http/token_manager_test.dart @@ -32,8 +32,7 @@ void main() { expect(tokenManager.userId, isNull); const userId = 'test-user-id'; - Future tokenProvider(String userId) async => - Token.development(userId).rawValue; + Future tokenProvider(String userId) async => Token.development(userId).rawValue; final returnedToken = await tokenManager.setTokenOrProvider( userId, provider: tokenProvider, @@ -65,8 +64,7 @@ void main() { const userId = 'test-user-id'; final token = Token.development(userId); - Future tokenProvider(String userId) async => - Token.development(userId).rawValue; + Future tokenProvider(String userId) async => Token.development(userId).rawValue; try { await tokenManager.setTokenOrProvider( userId, diff --git a/packages/stream_chat/test/src/core/http/token_test.dart b/packages/stream_chat/test/src/core/http/token_test.dart index 0f0e04253e..543ffda6ba 100644 --- a/packages/stream_chat/test/src/core/http/token_test.dart +++ b/packages/stream_chat/test/src/core/http/token_test.dart @@ -43,8 +43,7 @@ void main() { '`.guest` should create a guest-token with provided user and provider', () async { final user = User(id: 'test-user-id'); - Future provider(User user) async => - Token.development(user.id).rawValue; + Future provider(User user) async => Token.development(user.id).rawValue; final token = await Token.guest(user, provider); expect(token, isNotNull); diff --git a/packages/stream_chat/test/src/core/models/attachment_file_test.dart b/packages/stream_chat/test/src/core/models/attachment_file_test.dart index 6dce83f90e..440f1588ab 100644 --- a/packages/stream_chat/test/src/core/models/attachment_file_test.dart +++ b/packages/stream_chat/test/src/core/models/attachment_file_test.dart @@ -8,8 +8,7 @@ import '../../utils.dart'; void main() { group('src/models/attachment_file', () { test('should parse json correctly', () { - final attachment = - AttachmentFile.fromJson(jsonFixture('attachment_file.json')); + final attachment = AttachmentFile.fromJson(jsonFixture('attachment_file.json')); expect(attachment.name, 'test.jpg'); expect(attachment.size, 12); expect( diff --git a/packages/stream_chat/test/src/core/models/attachment_giphy_info_test.dart b/packages/stream_chat/test/src/core/models/attachment_giphy_info_test.dart index d135c217d7..7d044ea81e 100644 --- a/packages/stream_chat/test/src/core/models/attachment_giphy_info_test.dart +++ b/packages/stream_chat/test/src/core/models/attachment_giphy_info_test.dart @@ -17,15 +17,17 @@ void main() { group('GiphyInfoX', () { test('giphyInfo returns valid GiphyInfo object when data is valid', () { - final attachment = Attachment(extraData: const { - 'giphy': { - 'original': { - 'url': 'https://example.com/original.gif', - 'width': '200', - 'height': '150', - } - } - }); + final attachment = Attachment( + extraData: const { + 'giphy': { + 'original': { + 'url': 'https://example.com/original.gif', + 'width': '200', + 'height': '150', + }, + }, + }, + ); final giphyInfo = attachment.giphyInfo(GiphyInfoType.original); @@ -43,17 +45,18 @@ void main() { expect(giphyInfo, isNull); }); - test('giphyInfo returns null when the specific GiphyInfoType is missing', - () { - final attachment = Attachment(extraData: const { - 'giphy': { - 'fixed_height': { - 'url': 'https://example.com/fixed_height.gif', - 'width': '100', - 'height': '100', - } - } - }); + test('giphyInfo returns null when the specific GiphyInfoType is missing', () { + final attachment = Attachment( + extraData: const { + 'giphy': { + 'fixed_height': { + 'url': 'https://example.com/fixed_height.gif', + 'width': '100', + 'height': '100', + }, + }, + }, + ); final giphyInfo = attachment.giphyInfo(GiphyInfoType.original); diff --git a/packages/stream_chat/test/src/core/models/attachment_test.dart b/packages/stream_chat/test/src/core/models/attachment_test.dart index f702956760..4417ef1173 100644 --- a/packages/stream_chat/test/src/core/models/attachment_test.dart +++ b/packages/stream_chat/test/src/core/models/attachment_test.dart @@ -29,8 +29,7 @@ void main() { final channel = Attachment( type: 'giphy', title: 'soo', - titleLink: - 'https://giphy.com/gifs/nrkp3-dance-happy-3o7TKnCdBx5cMg0qti', + titleLink: 'https://giphy.com/gifs/nrkp3-dance-happy-3o7TKnCdBx5cMg0qti', ); expect( @@ -38,8 +37,7 @@ void main() { { 'type': 'giphy', 'title': 'soo', - 'title_link': - 'https://giphy.com/gifs/nrkp3-dance-happy-3o7TKnCdBx5cMg0qti', + 'title_link': 'https://giphy.com/gifs/nrkp3-dance-happy-3o7TKnCdBx5cMg0qti', 'actions': [], }, ); @@ -51,17 +49,12 @@ void main() { expect(attachment.fileSize, 3); expect(attachment.mimeType, 'text/plain'); - expect(attachment.toJson(), { - 'title': 'myfile.txt', - 'actions': [], - 'file_size': 3, - 'mime_type': 'text/plain' - }); + expect(attachment.toJson(), {'title': 'myfile.txt', 'actions': [], 'file_size': 3, 'mime_type': 'text/plain'}); expect(Attachment.fromJson(attachment.toJson()).toJson(), { 'title': 'myfile.txt', 'actions': [], 'file_size': 3, - 'mime_type': 'text/plain' + 'mime_type': 'text/plain', }); // Setting the size and mimeType using extraData should work fine @@ -88,10 +81,13 @@ void main() { // if file is available, should override size and mimeType. final fileThree = AttachmentFile(size: 9, path: 'myfolder/fileThree.png'); - newAttachment = attachment.copyWith(file: fileThree, extraData: { - 'file_size': 88, - 'mime_type': 'application/pdf', - }); + newAttachment = attachment.copyWith( + file: fileThree, + extraData: { + 'file_size': 88, + 'mime_type': 'application/pdf', + }, + ); expect(newAttachment.extraData['file_size'], 9); expect(newAttachment.extraData['mime_type'], 'image/png'); diff --git a/packages/stream_chat/test/src/core/models/channel_state_test.dart b/packages/stream_chat/test/src/core/models/channel_state_test.dart index 8d7410fcbd..70870104ed 100644 --- a/packages/stream_chat/test/src/core/models/channel_state_test.dart +++ b/packages/stream_chat/test/src/core/models/channel_state_test.dart @@ -6,8 +6,7 @@ import '../../utils.dart'; void main() { group('src/models/channel_state', () { test('should parse json correctly', () { - final channelState = - ChannelState.fromJson(jsonFixture('channel_state.json')); + final channelState = ChannelState.fromJson(jsonFixture('channel_state.json')); expect(channelState.channel?.cid, 'team:dev'); expect(channelState.channel?.id, 'dev'); expect(channelState.channel?.team, 'test'); @@ -16,12 +15,9 @@ void main() { expect(channelState.channel?.config, isNotNull); expect(channelState.channel?.config.commands, hasLength(1)); expect(channelState.channel?.config.commands[0], isA()); - expect(channelState.channel?.lastMessageAt, - DateTime.parse('2020-01-30T13:43:41.062362Z')); - expect(channelState.channel?.createdAt, - DateTime.parse('2019-04-03T18:43:33.213373Z')); - expect(channelState.channel?.updatedAt, - DateTime.parse('2019-04-03T18:43:33.213374Z')); + expect(channelState.channel?.lastMessageAt, DateTime.parse('2020-01-30T13:43:41.062362Z')); + expect(channelState.channel?.createdAt, DateTime.parse('2019-04-03T18:43:33.213373Z')); + expect(channelState.channel?.updatedAt, DateTime.parse('2019-04-03T18:43:33.213374Z')); expect(channelState.channel?.createdBy, isA()); expect(channelState.channel?.frozen, true); expect(channelState.channel?.extraData['example'], 1); @@ -147,8 +143,7 @@ void main() { memberCount: 42, ); - final field = - channelState.getComparableField(ChannelSortKey.memberCount); + final field = channelState.getComparableField(ChannelSortKey.memberCount); expect(field, isNotNull); expect(field!.value, equals(42)); }); diff --git a/packages/stream_chat/test/src/core/models/draft_message_test.dart b/packages/stream_chat/test/src/core/models/draft_message_test.dart index 6542e8b4df..0557c58759 100644 --- a/packages/stream_chat/test/src/core/models/draft_message_test.dart +++ b/packages/stream_chat/test/src/core/models/draft_message_test.dart @@ -35,8 +35,7 @@ void main() { expect(draftMessage.extraData, isEmpty); }); - test('should create a valid instance with UUID when id is not provided', - () { + test('should create a valid instance with UUID when id is not provided', () { final messageWithoutId = DraftMessage(text: text); expect(messageWithoutId.id, isNotNull); expect(messageWithoutId.id, isNotEmpty); @@ -156,8 +155,7 @@ void main() { expect(json['poll_id'], equals(pollId)); }); - test('should append command to text field in toJson when command exists', - () { + test('should append command to text field in toJson when command exists', () { final draftWithCommand = DraftMessage( id: id, text: 'Hello world', @@ -244,8 +242,7 @@ void main() { expect(deserializedMessage.id, equals(id)); expect(deserializedMessage.text, equals(text)); - expect(deserializedMessage.extraData['custom_field'], - equals('custom_value')); + expect(deserializedMessage.extraData['custom_field'], equals('custom_value')); expect(deserializedMessage.extraData['priority'], equals(5)); }); diff --git a/packages/stream_chat/test/src/core/models/event_test.dart b/packages/stream_chat/test/src/core/models/event_test.dart index f7847c4c60..82598566e3 100644 --- a/packages/stream_chat/test/src/core/models/event_test.dart +++ b/packages/stream_chat/test/src/core/models/event_test.dart @@ -94,7 +94,7 @@ void main() { 'total_unread_count': 0, 'unread_channels': 0, 'unread_threads': 0, - 'blocked_user_ids': [] + 'blocked_user_ids': [], }, 'user': {'id': 'id', 'teams': [], 'online': false, 'banned': false}, 'total_unread_count': 1, @@ -121,7 +121,7 @@ void main() { 'mentioned_users': [], 'silent': false, }, - } + }, }, ); diff --git a/packages/stream_chat/test/src/core/models/member_test.dart b/packages/stream_chat/test/src/core/models/member_test.dart index 86693a6504..b00884d450 100644 --- a/packages/stream_chat/test/src/core/models/member_test.dart +++ b/packages/stream_chat/test/src/core/models/member_test.dart @@ -105,10 +105,8 @@ void main() { final field1 = recentMember.getComparableField(MemberSortKey.createdAt); final field2 = olderMember.getComparableField(MemberSortKey.createdAt); - expect(field1!.compareTo(field2!), - greaterThan(0)); // More recent > Less recent - expect( - field2.compareTo(field1), lessThan(0)); // Less recent < More recent + expect(field1!.compareTo(field2!), greaterThan(0)); // More recent > Less recent + expect(field2.compareTo(field1), lessThan(0)); // Less recent < More recent }); test('should compare two members correctly using userId', () { @@ -159,10 +157,8 @@ void main() { final field1 = owner.getComparableField(MemberSortKey.channelRole); final field2 = moderator.getComparableField(MemberSortKey.channelRole); - expect(field1!.compareTo(field2!), - greaterThan(0)); // 'owner' > 'moderator' alphabetically - expect(field2.compareTo(field1), - lessThan(0)); // 'moderator' < 'owner' alphabetically + expect(field1!.compareTo(field2!), greaterThan(0)); // 'owner' > 'moderator' alphabetically + expect(field2.compareTo(field1), lessThan(0)); // 'moderator' < 'owner' alphabetically }); test('should compare two members correctly using extraData', () { diff --git a/packages/stream_chat/test/src/core/models/message_reaction_helper_test.dart b/packages/stream_chat/test/src/core/models/message_reaction_helper_test.dart index f24748ea73..0aa5ea5561 100644 --- a/packages/stream_chat/test/src/core/models/message_reaction_helper_test.dart +++ b/packages/stream_chat/test/src/core/models/message_reaction_helper_test.dart @@ -45,10 +45,8 @@ void main() { expect(updatedMessage.reactionGroups!.containsKey('like'), isTrue); expect(updatedMessage.reactionGroups!['like']!.count, 1); expect(updatedMessage.reactionGroups!['like']!.sumScores, 1); - expect(updatedMessage.reactionGroups!['like']!.firstReactionAt, - testReaction.createdAt); - expect(updatedMessage.reactionGroups!['like']!.lastReactionAt, - testReaction.createdAt); + expect(updatedMessage.reactionGroups!['like']!.firstReactionAt, testReaction.createdAt); + expect(updatedMessage.reactionGroups!['like']!.lastReactionAt, testReaction.createdAt); }); test('should add reaction to a message with existing reactions', () { @@ -151,8 +149,7 @@ void main() { expect(updatedMessage.ownReactions, isNotNull); expect(updatedMessage.ownReactions!.length, 2); - final reactionTypes = - updatedMessage.ownReactions!.map((r) => r.type).toList(); + final reactionTypes = updatedMessage.ownReactions!.map((r) => r.type).toList(); expect(reactionTypes, contains('like')); expect(reactionTypes, contains('love')); diff --git a/packages/stream_chat/test/src/core/models/message_state_test.dart b/packages/stream_chat/test/src/core/models/message_state_test.dart index c0351ebd77..513f2b6f13 100644 --- a/packages/stream_chat/test/src/core/models/message_state_test.dart +++ b/packages/stream_chat/test/src/core/models/message_state_test.dart @@ -113,8 +113,7 @@ void main() { test( 'isSent should return true if the message state is MessageCompleted with Sent state', () { - const messageState = - MessageState.completed(state: CompletedState.sent()); + const messageState = MessageState.completed(state: CompletedState.sent()); expect(messageState.isSent, true); }, ); diff --git a/packages/stream_chat/test/src/core/models/message_test.dart b/packages/stream_chat/test/src/core/models/message_test.dart index 2f363f45d7..325b098208 100644 --- a/packages/stream_chat/test/src/core/models/message_test.dart +++ b/packages/stream_chat/test/src/core/models/message_test.dart @@ -15,8 +15,7 @@ void main() { test('should parse json correctly', () { final message = Message.fromJson(jsonFixture('message.json')); expect(message.id, '4637f7e4-a06b-42db-ba5a-8d8270dd926f'); - expect(message.text, - 'https://giphy.com/gifs/the-lion-king-live-action-5zvN79uTGfLMOVfQaA'); + expect(message.text, 'https://giphy.com/gifs/the-lion-king-live-action-5zvN79uTGfLMOVfQaA'); expect(message.type, 'regular'); expect(message.user, isA()); expect(message.silent, isA()); @@ -41,24 +40,19 @@ void main() { test('should serialize to json correctly', () { final message = Message( id: '4637f7e4-a06b-42db-ba5a-8d8270dd926f', - text: - 'https://giphy.com/gifs/the-lion-king-live-action-5zvN79uTGfLMOVfQaA', + text: 'https://giphy.com/gifs/the-lion-king-live-action-5zvN79uTGfLMOVfQaA', attachments: [ Attachment.fromJson(const { 'type': 'giphy', 'author_name': 'GIPHY', 'title': 'The Lion King Disney GIF - Find \u0026 Share on GIPHY', - 'title_link': - 'https://media.giphy.com/media/5zvN79uTGfLMOVfQaA/giphy.gif', + 'title_link': 'https://media.giphy.com/media/5zvN79uTGfLMOVfQaA/giphy.gif', 'text': '''Discover \u0026 share this Lion King Live Action GIF with everyone you know. GIPHY is how you search, share, discover, and create GIFs.''', - 'image_url': - 'https://media.giphy.com/media/5zvN79uTGfLMOVfQaA/giphy.gif', - 'thumb_url': - 'https://media.giphy.com/media/5zvN79uTGfLMOVfQaA/giphy.gif', - 'asset_url': - 'https://media.giphy.com/media/5zvN79uTGfLMOVfQaA/giphy.mp4', - }) + 'image_url': 'https://media.giphy.com/media/5zvN79uTGfLMOVfQaA/giphy.gif', + 'thumb_url': 'https://media.giphy.com/media/5zvN79uTGfLMOVfQaA/giphy.gif', + 'asset_url': 'https://media.giphy.com/media/5zvN79uTGfLMOVfQaA/giphy.mp4', + }), ], showInChannel: true, parentId: 'parentId', @@ -348,22 +342,18 @@ void main() { }); test('should return true when user is in restrictedVisibility list', () { - final message = - Message(restrictedVisibility: const ['user1', 'user2', 'user3']); + final message = Message(restrictedVisibility: const ['user1', 'user2', 'user3']); expect(message.isVisibleTo('user2'), true); }); - test('should return false when user is not in restrictedVisibility list', - () { - final message = - Message(restrictedVisibility: const ['user1', 'user2', 'user3']); + test('should return false when user is not in restrictedVisibility list', () { + final message = Message(restrictedVisibility: const ['user1', 'user2', 'user3']); expect(message.isVisibleTo('user4'), false); }); test('should handle case sensitivity correctly', () { final message = Message(restrictedVisibility: const ['User1', 'USER2']); - expect(message.isVisibleTo('user1'), false, - reason: 'Should be case sensitive'); + expect(message.isVisibleTo('user1'), false, reason: 'Should be case sensitive'); expect(message.isVisibleTo('User1'), true); }); }); @@ -380,15 +370,12 @@ void main() { }); test('should return false when user is in restrictedVisibility list', () { - final message = - Message(restrictedVisibility: const ['user1', 'user2', 'user3']); + final message = Message(restrictedVisibility: const ['user1', 'user2', 'user3']); expect(message.isNotVisibleTo('user2'), false); }); - test('should return true when user is not in restrictedVisibility list', - () { - final message = - Message(restrictedVisibility: const ['user1', 'user2', 'user3']); + test('should return true when user is not in restrictedVisibility list', () { + final message = Message(restrictedVisibility: const ['user1', 'user2', 'user3']); expect(message.isNotVisibleTo('user4'), true); }); diff --git a/packages/stream_chat/test/src/core/models/moderation_test.dart b/packages/stream_chat/test/src/core/models/moderation_test.dart index 4a099d5fc4..2405c44a90 100644 --- a/packages/stream_chat/test/src/core/models/moderation_test.dart +++ b/packages/stream_chat/test/src/core/models/moderation_test.dart @@ -153,8 +153,7 @@ void main() { }); test('should create from custom string correctly', () { - expect(ModerationAction.fromJson('custom'), - const ModerationAction('custom')); + expect(ModerationAction.fromJson('custom'), const ModerationAction('custom')); }); test('should handle legacy v1 moderation actions correctly', () { @@ -179,8 +178,7 @@ void main() { expect(ModerationAction.toJson(ModerationAction.flag), 'flag'); expect(ModerationAction.toJson(ModerationAction.remove), 'remove'); expect(ModerationAction.toJson(ModerationAction.shadow), 'shadow'); - expect(ModerationAction.toJson(const ModerationAction('custom')), - 'custom'); + expect(ModerationAction.toJson(const ModerationAction('custom')), 'custom'); }); test('should serialize legacy v1 action strings correctly', () { diff --git a/packages/stream_chat/test/src/core/models/own_user_test.dart b/packages/stream_chat/test/src/core/models/own_user_test.dart index 10f864ebd8..905a824600 100644 --- a/packages/stream_chat/test/src/core/models/own_user_test.dart +++ b/packages/stream_chat/test/src/core/models/own_user_test.dart @@ -28,8 +28,7 @@ void main() { expect(ownUser.createdAt, DateTime.parse('2020-03-03T16:48:28.853674Z')); expect(ownUser.updatedAt, DateTime.parse('2021-05-26T03:22:20.296181Z')); - expect( - ownUser.lastActive, DateTime.parse('2021-06-16T11:59:59.003453014Z')); + expect(ownUser.lastActive, DateTime.parse('2021-06-16T11:59:59.003453014Z')); expect(ownUser.banned, false); expect(ownUser.online, true); expect(ownUser.devices.length, 1); diff --git a/packages/stream_chat/test/src/core/models/poll_test.dart b/packages/stream_chat/test/src/core/models/poll_test.dart index bb3ba1c0a1..ae44676408 100644 --- a/packages/stream_chat/test/src/core/models/poll_test.dart +++ b/packages/stream_chat/test/src/core/models/poll_test.dart @@ -63,7 +63,7 @@ void main() { expect(json['name'], 'test'); expect(json['description'], isNull); expect(json['options'], [ - {'text': 'option1 text'} + {'text': 'option1 text'}, ]); expect(json['voting_visibility'], 'public'); expect(json['enforce_unique_vote'], true); diff --git a/packages/stream_chat/test/src/core/models/reaction_test.dart b/packages/stream_chat/test/src/core/models/reaction_test.dart index a60ad6e31f..2b9ed6dbeb 100644 --- a/packages/stream_chat/test/src/core/models/reaction_test.dart +++ b/packages/stream_chat/test/src/core/models/reaction_test.dart @@ -23,7 +23,7 @@ void main() { 'online': false, 'banned': false, 'image': 'https://randomuser.me/api/portraits/women/45.jpg', - 'name': 'Daisy Morgan' + 'name': 'Daisy Morgan', }, ); expect(reaction.score, 1); @@ -62,10 +62,8 @@ void main() { final reaction = Reaction.fromJson(jsonFixture('reaction.json')); var newReaction = reaction.copyWith(); expect(newReaction.messageId, '76cd8c82-b557-4e48-9d12-87995d3a0e04'); - expect( - newReaction.createdAt, DateTime.parse('2020-01-28T22:17:31.108742Z')); - expect( - newReaction.updatedAt, DateTime.parse('2020-01-28T22:17:31.108742Z')); + expect(newReaction.createdAt, DateTime.parse('2020-01-28T22:17:31.108742Z')); + expect(newReaction.updatedAt, DateTime.parse('2020-01-28T22:17:31.108742Z')); expect(newReaction.type, 'wow'); expect( newReaction.user?.toJson(), @@ -78,7 +76,7 @@ void main() { 'online': false, 'banned': false, 'image': 'https://randomuser.me/api/portraits/women/45.jpg', - 'name': 'Daisy Morgan' + 'name': 'Daisy Morgan', }, ); expect(newReaction.score, 1); @@ -127,6 +125,41 @@ void main() { expect(newReaction.userId, 'test'); }); + group('ComparableFieldProvider', () { + test('should return ComparableField for reaction.createdAt', () { + final createdAt = DateTime(2020, 1, 28); + final reaction = Reaction(type: 'like', createdAt: createdAt); + + final field = reaction.getComparableField(ReactionSortKey.createdAt); + expect(field, isNotNull); + expect(field!.value, equals(createdAt)); + }); + + test('should return null for non-existent field keys', () { + final reaction = Reaction(type: 'like'); + + final field = reaction.getComparableField('non_existent_key'); + expect(field, isNull); + }); + + test('should compare two reactions correctly using createdAt', () { + final recentReaction = Reaction( + type: 'like', + createdAt: DateTime(2020, 6, 15), + ); + final olderReaction = Reaction( + type: 'like', + createdAt: DateTime(2020, 6, 10), + ); + + final field1 = recentReaction.getComparableField(ReactionSortKey.createdAt); + final field2 = olderReaction.getComparableField(ReactionSortKey.createdAt); + + expect(field1!.compareTo(field2!), greaterThan(0)); // more recent > older + expect(field2.compareTo(field1), lessThan(0)); // older < more recent + }); + }); + test('merge', () { final reaction = Reaction.fromJson(jsonFixture('reaction.json')); final newUserCreateTime = DateTime.now(); diff --git a/packages/stream_chat/test/src/core/models/read_test.dart b/packages/stream_chat/test/src/core/models/read_test.dart index 197fa3f161..4a538b51ab 100644 --- a/packages/stream_chat/test/src/core/models/read_test.dart +++ b/packages/stream_chat/test/src/core/models/read_test.dart @@ -33,12 +33,7 @@ void main() { ); expect(read.toJson(), { - 'user': { - 'id': 'bbb19d9a-ee50-45bc-84e5-0584e79d0c9e', - 'teams': [], - 'online': false, - 'banned': false - }, + 'user': {'id': 'bbb19d9a-ee50-45bc-84e5-0584e79d0c9e', 'teams': [], 'online': false, 'banned': false}, 'last_read': '2020-01-28T22:17:30.966485Z', 'unread_messages': 10, 'last_read_message_id': '8cc1301d-2d47-4305-945a-cd8e19b736d6', diff --git a/packages/stream_chat/test/src/core/models/serialization_test.dart b/packages/stream_chat/test/src/core/models/serialization_test.dart index 63b1428f78..594a3427b1 100644 --- a/packages/stream_chat/test/src/core/models/serialization_test.dart +++ b/packages/stream_chat/test/src/core/models/serialization_test.dart @@ -30,15 +30,14 @@ void main() { }); test('should have empty extraData', () { - final result = Serializer.moveToExtraDataFromRoot({ - 'prop1': 'test', - 'prop2': 123, - 'prop3': true, - }, [ - 'prop1', - 'prop2', - 'prop3' - ]); + final result = Serializer.moveToExtraDataFromRoot( + { + 'prop1': 'test', + 'prop2': 123, + 'prop3': true, + }, + ['prop1', 'prop2', 'prop3'], + ); expect(result, { 'prop1': 'test', diff --git a/packages/stream_chat/test/src/core/models/thread_test.dart b/packages/stream_chat/test/src/core/models/thread_test.dart index 21ab698a59..9e0a04bbee 100644 --- a/packages/stream_chat/test/src/core/models/thread_test.dart +++ b/packages/stream_chat/test/src/core/models/thread_test.dart @@ -91,8 +91,7 @@ void main() { final updatedThread = thread.copyWith(draft: updatedDraft); expect(updatedThread.draft?.message.text, equals('Updated draft')); - expect( - updatedThread.draft?.message.text, isNot(equals(draft.message.text))); + expect(updatedThread.draft?.message.text, isNot(equals(draft.message.text))); // Test copyWith with null draft (removing draft) final removedDraftThread = thread.copyWith(draft: null); @@ -209,8 +208,7 @@ void main() { // Test text equality instead of object identity expect(thread1.draft?.message.text, equals(thread2.draft?.message.text)); - expect(thread1.draft?.message.text, - isNot(equals(thread3.draft?.message.text))); + expect(thread1.draft?.message.text, isNot(equals(thread3.draft?.message.text))); }); }); } diff --git a/packages/stream_chat/test/src/core/models/user_block_test.dart b/packages/stream_chat/test/src/core/models/user_block_test.dart index 3297e0016b..18162de5d7 100644 --- a/packages/stream_chat/test/src/core/models/user_block_test.dart +++ b/packages/stream_chat/test/src/core/models/user_block_test.dart @@ -30,21 +30,11 @@ void main() { expect( userBlock.toJson(), { - 'user': { - 'id': 'user-1', - 'teams': [], - 'online': false, - 'banned': false - }, - 'blocked_user': { - 'id': 'user-2', - 'teams': [], - 'online': false, - 'banned': false - }, + 'user': {'id': 'user-1', 'teams': [], 'online': false, 'banned': false}, + 'blocked_user': {'id': 'user-2', 'teams': [], 'online': false, 'banned': false}, 'user_id': 'user-1', 'blocked_user_id': 'user-2', - 'created_at': '2020-01-28T22:17:30.830150Z' + 'created_at': '2020-01-28T22:17:30.830150Z', }, ); }); diff --git a/packages/stream_chat/test/src/core/models/user_test.dart b/packages/stream_chat/test/src/core/models/user_test.dart index 5e16ea861a..5915e442aa 100644 --- a/packages/stream_chat/test/src/core/models/user_test.dart +++ b/packages/stream_chat/test/src/core/models/user_test.dart @@ -9,8 +9,7 @@ void main() { const id = 'bbb19d9a-ee50-45bc-84e5-0584e79d0c9e'; const role = 'test-role'; const name = 'John'; - const image = - 'https://getstream.io/random_png/?id=cool-shadow-7&name=Cool+shadow'; + const image = 'https://getstream.io/random_png/?id=cool-shadow-7&name=Cool+shadow'; const extraDataStringTest = 'Extra data test'; const extraDataIntTest = 1; const extraDataDoubleTest = 1.1; @@ -352,15 +351,11 @@ void main() { lastActive: DateTime(2023, 6, 10), ); - final field1 = - recentlyActive.getComparableField(UserSortKey.lastActive); - final field2 = - lessRecentlyActive.getComparableField(UserSortKey.lastActive); + final field1 = recentlyActive.getComparableField(UserSortKey.lastActive); + final field2 = lessRecentlyActive.getComparableField(UserSortKey.lastActive); - expect(field1!.compareTo(field2!), - greaterThan(0)); // More recent > Less recent - expect( - field2.compareTo(field1), lessThan(0)); // Less recent < More recent + expect(field1!.compareTo(field2!), greaterThan(0)); // More recent > Less recent + expect(field2.compareTo(field1), lessThan(0)); // Less recent < More recent }); test('should compare two users correctly using banned status', () { diff --git a/packages/stream_chat/test/src/core/util/event_controller_test.dart b/packages/stream_chat/test/src/core/util/event_controller_test.dart index a789d2826b..fdf330a4d0 100644 --- a/packages/stream_chat/test/src/core/util/event_controller_test.dart +++ b/packages/stream_chat/test/src/core/util/event_controller_test.dart @@ -229,9 +229,7 @@ void main() { final event2 = Event(type: EventType.messageUpdated); final streamEvents = []; - controller - .where((event) => event.type == EventType.messageNew) - .listen(streamEvents.add); + controller.where((event) => event.type == EventType.messageNew).listen(streamEvents.add); controller.add(event1); controller.add(event2); diff --git a/packages/stream_chat/test/src/core/util/serializer_test.dart b/packages/stream_chat/test/src/core/util/serializer_test.dart index fbbb96a20c..9ae3fa536b 100644 --- a/packages/stream_chat/test/src/core/util/serializer_test.dart +++ b/packages/stream_chat/test/src/core/util/serializer_test.dart @@ -19,7 +19,7 @@ void main() { 'name': 'Sahil', 'age': 22, 'country': 'India', - } + }, }); }); @@ -30,7 +30,7 @@ void main() { 'name': 'Sahil', 'age': 22, 'country': 'India', - } + }, }); expect(serializer, { 'test': 'test', diff --git a/packages/stream_chat/test/src/db/chat_persistence_client_test.dart b/packages/stream_chat/test/src/db/chat_persistence_client_test.dart index 7676a7963a..555d13ecd0 100644 --- a/packages/stream_chat/test/src/db/chat_persistence_client_test.dart +++ b/packages/stream_chat/test/src/db/chat_persistence_client_test.dart @@ -46,32 +46,27 @@ class TestPersistenceClient extends ChatPersistenceClient { Future deletePinnedMessageByCids(List cids) => Future.value(); @override - Future deletePinnedMessageByIds(List messageIds) => - Future.value(); + Future deletePinnedMessageByIds(List messageIds) => Future.value(); @override - Future deleteMessagesFromUser( - {String? cid, - required String userId, - bool hardDelete = false, - DateTime? deletedAt}) => - throw UnimplementedError(); + Future deleteMessagesFromUser({ + String? cid, + required String userId, + bool hardDelete = false, + DateTime? deletedAt, + }) => throw UnimplementedError(); @override - Future deleteReactionsByMessageId(List messageIds) => - Future.value(); + Future deleteReactionsByMessageId(List messageIds) => Future.value(); @override - Future deletePinnedMessageReactionsByMessageId( - List messageIds) => - Future.value(); + Future deletePinnedMessageReactionsByMessageId(List messageIds) => Future.value(); @override Future deletePollVotesByPollIds(List pollIds) => Future.value(); @override - Future deleteDraftMessageByCid(String cid, {String? parentId}) => - Future.value(); + Future deleteDraftMessageByCid(String cid, {String? parentId}) => Future.value(); @override Future disconnect({bool flush = false}) => throw UnimplementedError(); @@ -80,23 +75,21 @@ class TestPersistenceClient extends ChatPersistenceClient { Future flush() => throw UnimplementedError(); @override - Future getChannelByCid(String cid) async => - ChannelModel(cid: cid); + Future getChannelByCid(String cid) async => ChannelModel(cid: cid); @override Future> getChannelCids() => throw UnimplementedError(); @override - Future> getChannelStates( - {Filter? filter, - SortOrder? channelStateSort, - int? messageLimit, - PaginationParams? paginationParams}) => - throw UnimplementedError(); + Future> getChannelStates({ + Filter? filter, + SortOrder? channelStateSort, + int? messageLimit, + PaginationParams? paginationParams, + }) => throw UnimplementedError(); @override - Future>> getChannelThreads(String cid) => - throw UnimplementedError(); + Future>> getChannelThreads(String cid) => throw UnimplementedError(); @override Future getConnectionInfo() => throw UnimplementedError(); @@ -108,14 +101,10 @@ class TestPersistenceClient extends ChatPersistenceClient { Future> getMembersByCid(String cid) async => []; @override - Future> getMessagesByCid(String cid, - {PaginationParams? messagePagination}) async => - []; + Future> getMessagesByCid(String cid, {PaginationParams? messagePagination}) async => []; @override - Future> getPinnedMessagesByCid(String cid, - {PaginationParams? messagePagination}) async => - []; + Future> getPinnedMessagesByCid(String cid, {PaginationParams? messagePagination}) async => []; @override Future> getReadsByCid(String cid) async => []; @@ -124,22 +113,18 @@ class TestPersistenceClient extends ChatPersistenceClient { Future getDraftMessageByCid( String cid, { String? parentId, - }) async => - Draft( - channelCid: cid, - parentId: parentId, - createdAt: DateTime.now(), - message: DraftMessage(id: 'message-id', text: 'message-text'), - ); + }) async => Draft( + channelCid: cid, + parentId: parentId, + createdAt: DateTime.now(), + message: DraftMessage(id: 'message-id', text: 'message-text'), + ); @override - Future> getReplies(String parentId, - {PaginationParams? options}) => - throw UnimplementedError(); + Future> getReplies(String parentId, {PaginationParams? options}) => throw UnimplementedError(); @override - Future updateChannelQueries(Filter? filter, List cids, - {bool clearQueryCache = false}) => + Future updateChannelQueries(Filter? filter, List cids, {bool clearQueryCache = false}) => throw UnimplementedError(); @override @@ -149,15 +134,13 @@ class TestPersistenceClient extends ChatPersistenceClient { Future updateConnectionInfo(Event event) => throw UnimplementedError(); @override - Future updateLastSyncAt(DateTime lastSyncAt) => - throw UnimplementedError(); + Future updateLastSyncAt(DateTime lastSyncAt) => throw UnimplementedError(); @override Future updateReactions(List reactions) => Future.value(); @override - Future updatePinnedMessageReactions(List reactions) => - Future.value(); + Future updatePinnedMessageReactions(List reactions) => Future.value(); @override Future updatePollVotes(List pollVotes) => Future.value(); @@ -166,20 +149,16 @@ class TestPersistenceClient extends ChatPersistenceClient { Future updateUsers(List users) => Future.value(); @override - Future bulkUpdateMembers(Map?> members) => - Future.value(); + Future bulkUpdateMembers(Map?> members) => Future.value(); @override - Future bulkUpdateMessages(Map?> messages) => - Future.value(); + Future bulkUpdateMessages(Map?> messages) => Future.value(); @override - Future bulkUpdatePinnedMessages(Map?> messages) => - Future.value(); + Future bulkUpdatePinnedMessages(Map?> messages) => Future.value(); @override - Future bulkUpdateReads(Map?> reads) => - Future.value(); + Future bulkUpdateReads(Map?> reads) => Future.value(); @override Future deletePollsByIds(List pollIds) => Future.value(); @@ -203,8 +182,7 @@ class TestPersistenceClient extends ChatPersistenceClient { Future deleteLocationsByCid(String cid) => Future.value(); @override - Future deleteLocationsByMessageIds(List messageIds) => - Future.value(); + Future deleteLocationsByMessageIds(List messageIds) => Future.value(); } void main() { @@ -273,8 +251,8 @@ void main() { user: user, ownReactions: [Reaction(type: 'test', user: user)], latestReactions: [Reaction(type: 'test', user: user)], - ) - ] + ), + ], }; persistenceClient.updateChannelThreads(cid, threads); }); @@ -296,7 +274,7 @@ void main() { user: user, ownReactions: [Reaction(type: 'test', user: user)], latestReactions: [Reaction(type: 'test', user: user)], - ) + ), ], pinnedMessages: [ Message( @@ -305,13 +283,10 @@ void main() { user: user, ownReactions: [Reaction(type: 'test', user: user)], latestReactions: [Reaction(type: 'test', user: user)], - ) + ), ], read: [ - Read( - lastRead: DateTime.now(), - user: user, - lastReadMessageId: 'last-test-message'), + Read(lastRead: DateTime.now(), user: user, lastReadMessageId: 'last-test-message'), ], members: [Member(user: user)], ); diff --git a/packages/stream_chat/test/src/fakes.dart b/packages/stream_chat/test/src/fakes.dart index ff2c538e74..4e561b0884 100644 --- a/packages/stream_chat/test/src/fakes.dart +++ b/packages/stream_chat/test/src/fakes.dart @@ -34,8 +34,7 @@ class FakeTokenManager extends Fake implements TokenManager { String userId, { Token? token, TokenProvider? provider, - }) async => - this.token; + }) async => this.token; @override void reset() {} @@ -48,8 +47,8 @@ class FakePersistenceClient extends Fake implements ChatPersistenceClient { FakePersistenceClient({ DateTime? lastSyncAt, List? channelCids, - }) : _lastSyncAt = lastSyncAt, - _channelCids = channelCids ?? []; + }) : _lastSyncAt = lastSyncAt, + _channelCids = channelCids ?? []; String? _userId; bool _isConnected = false; @@ -144,8 +143,7 @@ class FakeChatApi extends Fake implements StreamChatApi { AttachmentFileUploader? _fileUploader; @override - AttachmentFileUploader get fileUploader => - _fileUploader ??= MockAttachmentFileUploader(); + AttachmentFileUploader get fileUploader => _fileUploader ??= MockAttachmentFileUploader(); } class FakeClientState extends Fake implements ClientState { @@ -218,8 +216,7 @@ class FakeWebSocket extends Fake implements WebSocket { ConnectionStatus get connectionStatus => _connectionStatusController.value; @override - Stream get connectionStatusStream => - _connectionStatusController.stream; + Stream get connectionStatusStream => _connectionStatusController.stream; @override Completer? connectionCompleter; @@ -265,8 +262,7 @@ class FakeWebSocketWithConnectionError extends Fake implements WebSocket { ConnectionStatus get connectionStatus => _connectionStatusController.value; @override - Stream get connectionStatusStream => - _connectionStatusController.stream; + Stream get connectionStatusStream => _connectionStatusController.stream; @override Completer? connectionCompleter; @@ -296,8 +292,7 @@ class FakeWebSocketWithConnectionError extends Fake implements WebSocket { class FakeChannelState extends Fake implements ChannelState {} -class FakePartialUpdateMemberResponse extends Fake - implements PartialUpdateMemberResponse { +class FakePartialUpdateMemberResponse extends Fake implements PartialUpdateMemberResponse { FakePartialUpdateMemberResponse({ Member? channelMember, }) : _channelMember = channelMember ?? Member(); diff --git a/packages/stream_chat/test/src/matchers.dart b/packages/stream_chat/test/src/matchers.dart index 752f980373..27a3b823ac 100644 --- a/packages/stream_chat/test/src/matchers.dart +++ b/packages/stream_chat/test/src/matchers.dart @@ -9,8 +9,7 @@ import 'package:stream_chat/src/core/models/message.dart'; import 'package:stream_chat/src/core/models/user.dart'; import 'package:test/test.dart'; -Matcher isSameMultipartFileAs(MultipartFile targetFile) => - _IsSameMultipartFileAs(targetFile: targetFile); +Matcher isSameMultipartFileAs(MultipartFile targetFile) => _IsSameMultipartFileAs(targetFile: targetFile); class _IsSameMultipartFileAs extends Matcher { const _IsSameMultipartFileAs({required this.targetFile}); @@ -18,16 +17,13 @@ class _IsSameMultipartFileAs extends Matcher { final MultipartFile targetFile; @override - Description describe(Description description) => - description.add('is same multipartFile as $targetFile'); + Description describe(Description description) => description.add('is same multipartFile as $targetFile'); @override - bool matches(covariant MultipartFile file, Map matchState) => - file.length == targetFile.length; + bool matches(covariant MultipartFile file, Map matchState) => file.length == targetFile.length; } -Matcher isSameEventAs(Event targetEvent) => - _IsSameEventAs(targetEvent: targetEvent); +Matcher isSameEventAs(Event targetEvent) => _IsSameEventAs(targetEvent: targetEvent); class _IsSameEventAs extends Matcher { const _IsSameEventAs({required this.targetEvent}); @@ -35,12 +31,10 @@ class _IsSameEventAs extends Matcher { final Event targetEvent; @override - Description describe(Description description) => - description.add('is same event as $targetEvent'); + Description describe(Description description) => description.add('is same event as $targetEvent'); @override - bool matches(covariant Event event, Map matchState) => - event.type == targetEvent.type; + bool matches(covariant Event event, Map matchState) => event.type == targetEvent.type; } Matcher isSameMessageAs( @@ -51,16 +45,15 @@ Matcher isSameMessageAs( bool matchAttachments = false, bool matchAttachmentsUploadState = false, bool matchParentId = false, -}) => - _IsSameMessageAs( - targetMessage: targetMessage, - matchText: matchText, - matchReactions: matchReactions, - matchMessageState: matchMessageState, - matchAttachments: matchAttachments, - matchAttachmentsUploadState: matchAttachmentsUploadState, - matchParentId: matchParentId, - ); +}) => _IsSameMessageAs( + targetMessage: targetMessage, + matchText: matchText, + matchReactions: matchReactions, + matchMessageState: matchMessageState, + matchAttachments: matchAttachments, + matchAttachmentsUploadState: matchAttachmentsUploadState, + matchParentId: matchParentId, +); class _IsSameMessageAs extends Matcher { const _IsSameMessageAs({ @@ -82,8 +75,7 @@ class _IsSameMessageAs extends Matcher { final bool matchParentId; @override - Description describe(Description description) => - description.add('is same message as $targetMessage'); + Description describe(Description description) => description.add('is same message as $targetMessage'); @override bool matches(covariant Message message, Map matchState) { @@ -96,19 +88,13 @@ class _IsSameMessageAs extends Matcher { } if (matchReactions) { matches &= const ListEquality().equals( - message.ownReactions - ?.map((it) => '${it.type}-${it.messageId}') - .toList(), - targetMessage.ownReactions - ?.map((it) => '${it.type}-${it.messageId}') - .toList()); + message.ownReactions?.map((it) => '${it.type}-${it.messageId}').toList(), + targetMessage.ownReactions?.map((it) => '${it.type}-${it.messageId}').toList(), + ); matches &= const ListEquality().equals( - message.latestReactions - ?.map((it) => '${it.type}-${it.messageId}') - .toList(), - targetMessage.latestReactions - ?.map((it) => '${it.type}-${it.messageId}') - .toList()); + message.latestReactions?.map((it) => '${it.type}-${it.messageId}').toList(), + targetMessage.latestReactions?.map((it) => '${it.type}-${it.messageId}').toList(), + ); } if (matchAttachments) { bool matchAttachments() { @@ -142,13 +128,12 @@ Matcher isSameDraftMessageAs( bool matchText = false, bool matchAttachments = false, bool matchParentId = false, -}) => - _IsSameDraftMessageAs( - targetMessage: targetMessage, - matchText: matchText, - matchAttachments: matchAttachments, - matchParentId: matchParentId, - ); +}) => _IsSameDraftMessageAs( + targetMessage: targetMessage, + matchText: matchText, + matchAttachments: matchAttachments, + matchParentId: matchParentId, +); class _IsSameDraftMessageAs extends Matcher { const _IsSameDraftMessageAs({ @@ -164,8 +149,7 @@ class _IsSameDraftMessageAs extends Matcher { final bool matchParentId; @override - Description describe(Description description) => - description.add('is same draft message as $targetMessage'); + Description describe(Description description) => description.add('is same draft message as $targetMessage'); @override bool matches(covariant DraftMessage message, Map matchState) { @@ -199,11 +183,10 @@ class _IsSameDraftMessageAs extends Matcher { Matcher isSameAttachmentAs( Attachment targetAttachment, { bool matchUploadState = false, -}) => - _IsSameAttachmentAs( - targetAttachment: targetAttachment, - matchUploadState: matchUploadState, - ); +}) => _IsSameAttachmentAs( + targetAttachment: targetAttachment, + matchUploadState: matchUploadState, +); class _IsSameAttachmentAs extends Matcher { const _IsSameAttachmentAs({ @@ -215,8 +198,7 @@ class _IsSameAttachmentAs extends Matcher { final bool matchUploadState; @override - Description describe(Description description) => - description.add('is same attachment as $targetAttachment'); + Description describe(Description description) => description.add('is same attachment as $targetAttachment'); @override bool matches(covariant Attachment attachment, Map matchState) { @@ -236,15 +218,13 @@ class _IsSameUserAs extends Matcher { final User targetUser; @override - Description describe(Description description) => - description.add('is same user as $targetUser'); + Description describe(Description description) => description.add('is same user as $targetUser'); @override bool matches(covariant User user, Map matchState) => user.id == targetUser.id; } -Matcher isCorrectChannelFor(ChannelState channelState) => - _IsCorrectChannelFor(channelState: channelState); +Matcher isCorrectChannelFor(ChannelState channelState) => _IsCorrectChannelFor(channelState: channelState); class _IsCorrectChannelFor extends Matcher { const _IsCorrectChannelFor({required this.channelState}); @@ -252,10 +232,8 @@ class _IsCorrectChannelFor extends Matcher { final ChannelState channelState; @override - Description describe(Description description) => - description.add('is correct channel for $channelState'); + Description describe(Description description) => description.add('is correct channel for $channelState'); @override - bool matches(covariant Channel channel, Map matchState) => - channel.cid == channelState.channel?.cid; + bool matches(covariant Channel channel, Map matchState) => channel.cid == channelState.channel?.cid; } diff --git a/packages/stream_chat/test/src/mocks.dart b/packages/stream_chat/test/src/mocks.dart index 9539ee1843..a845274bc4 100644 --- a/packages/stream_chat/test/src/mocks.dart +++ b/packages/stream_chat/test/src/mocks.dart @@ -67,8 +67,7 @@ class MockModerationApi extends Mock implements ModerationApi {} class MockGeneralApi extends Mock implements GeneralApi {} -class MockAttachmentFileUploader extends Mock - implements AttachmentFileUploader {} +class MockAttachmentFileUploader extends Mock implements AttachmentFileUploader {} class MockPersistenceClient extends Mock implements ChatPersistenceClient { String? _userId; @@ -117,11 +116,10 @@ class MockStreamChatClient extends Mock implements StreamChatClient { String? eventType4, ]) { if (eventType == null || eventType == EventType.any) return eventStream; - return eventStream.where((event) => - event.type == eventType || - event.type == eventType2 || - event.type == eventType3 || - event.type == eventType4); + return eventStream.where( + (event) => + event.type == eventType || event.type == eventType2 || event.type == eventType3 || event.type == eventType4, + ); } @override @@ -132,8 +130,7 @@ class MockStreamChatClientWithPersistence extends MockStreamChatClient { ChatPersistenceClient? _persistenceClient; @override - ChatPersistenceClient get chatPersistenceClient => - _persistenceClient ??= MockPersistenceClient(); + ChatPersistenceClient get chatPersistenceClient => _persistenceClient ??= MockPersistenceClient(); @override bool get persistenceEnabled => true; @@ -166,13 +163,10 @@ class MockRetryQueueChannel extends Mock implements Channel { String? eventType3, String? eventType4, ]) { - return client - .on(eventType, eventType2, eventType3, eventType4) - .where((e) => e.cid == cid); + return client.on(eventType, eventType2, eventType3, eventType4).where((e) => e.cid == cid); } } class MockWebSocket extends Mock implements WebSocket {} -class MockChannelDeliveryReporter extends Mock - implements ChannelDeliveryReporter {} +class MockChannelDeliveryReporter extends Mock implements ChannelDeliveryReporter {} diff --git a/packages/stream_chat/test/src/utils.dart b/packages/stream_chat/test/src/utils.dart index 768d2fe296..dd3030d94c 100644 --- a/packages/stream_chat/test/src/utils.dart +++ b/packages/stream_chat/test/src/utils.dart @@ -28,5 +28,4 @@ extension IntX on num { } // Top level util function to delay the code execution -Future delay(num milliseconds) => - Future.delayed(Duration(milliseconds: milliseconds.toInt())); +Future delay(num milliseconds) => Future.delayed(Duration(milliseconds: milliseconds.toInt())); diff --git a/packages/stream_chat/test/src/ws/websocket_test.dart b/packages/stream_chat/test/src/ws/websocket_test.dart index 053a8d78ff..5509310083 100644 --- a/packages/stream_chat/test/src/ws/websocket_test.dart +++ b/packages/stream_chat/test/src/ws/websocket_test.dart @@ -24,8 +24,7 @@ void main() { WebSocketChannel channelProvider( Uri uri, { Iterable? protocols, - }) => - webSocketChannel; + }) => webSocketChannel; webSocket = WebSocket( apiKey: 'api-key', diff --git a/packages/stream_chat_flutter/example/lib/debug/channel_page.dart b/packages/stream_chat_flutter/example/lib/debug/channel_page.dart index a1d4b6a419..a5719b1a16 100644 --- a/packages/stream_chat_flutter/example/lib/debug/channel_page.dart +++ b/packages/stream_chat_flutter/example/lib/debug/channel_page.dart @@ -39,8 +39,7 @@ class _DebugChannelPageState extends State { _channelSubscription = _channel.state!.channelStateStream.listen((state) { setState(() => _channelState = state); }); - _ownUserSubscription = - _channel.client.state.currentUserStream.listen((ownUser) { + _ownUserSubscription = _channel.client.state.currentUserStream.listen((ownUser) { setState(() => _ownUser = ownUser); }); } @@ -54,10 +53,8 @@ class _DebugChannelPageState extends State { @override Widget build(BuildContext context) { - final members = - _channelState?.members ?? _channel.state?.members ?? const []; - final mutes = - _ownUser?.mutes ?? _channel.client.state.currentUser?.mutes ?? const []; + final members = _channelState?.members ?? _channel.state?.members ?? const []; + final mutes = _ownUser?.mutes ?? _channel.client.state.currentUser?.mutes ?? const []; //SingleChildScrollView return Scaffold( appBar: AppBar( diff --git a/packages/stream_chat_flutter/example/lib/main.dart b/packages/stream_chat_flutter/example/lib/main.dart index 3518d0056f..5d814dfa7b 100644 --- a/packages/stream_chat_flutter/example/lib/main.dart +++ b/packages/stream_chat_flutter/example/lib/main.dart @@ -195,16 +195,16 @@ class _ChannelListPageState extends State { @override Widget build(BuildContext context) => Scaffold( - body: StreamChannelListView( - onChannelTap: widget.onTap, - controller: _listController, - itemBuilder: (context, channels, index, defaultWidget) { - return defaultWidget.copyWith( - selected: channels[index] == widget.selectedChannel, - ); - }, - ), - ); + body: StreamChannelListView( + onChannelTap: widget.onTap, + controller: _listController, + itemBuilder: (context, channels, index, defaultWidget) { + return defaultWidget.copyWith( + selected: channels[index] == widget.selectedChannel, + ); + }, + ), + ); } class ChannelPage extends StatefulWidget { @@ -254,37 +254,28 @@ class _ChannelPageState extends State { Expanded( child: StreamMessageListView( threadBuilder: (_, parent) => ThreadPage(parent: parent!), - messageBuilder: ( - context, - messageDetails, - messages, - defaultWidget, - ) { + messageBuilder: (context, message, defaultProps) { // The threshold after which the message is considered // swiped. const threshold = 0.2; - final isMyMessage = messageDetails.isMyMessage; + final currentUser = StreamChat.of(context).currentUser; + final isMyMessage = message.user?.id == currentUser?.id; // The direction in which the message can be swiped. - final swipeDirection = isMyMessage - ? SwipeDirection.endToStart // - : SwipeDirection.startToEnd; + final swipeDirection = isMyMessage ? SwipeDirection.endToStart : SwipeDirection.startToEnd; return Swipeable( - key: ValueKey(messageDetails.message.id), + key: ValueKey(message.id), direction: swipeDirection, swipeThreshold: threshold, - onSwiped: (details) => reply(messageDetails.message), + onSwiped: (details) => reply(message), backgroundBuilder: (context, details) { // The alignment of the swipe action. - final alignment = isMyMessage - ? Alignment.centerRight // - : Alignment.centerLeft; + final alignment = isMyMessage ? Alignment.centerRight : Alignment.centerLeft; // The progress of the swipe action. - final progress = - math.min(details.progress, threshold) / threshold; + final progress = math.min(details.progress, threshold) / threshold; // The offset for the reply icon. var offset = Offset.lerp( @@ -314,8 +305,8 @@ class _ChannelPageState extends State { color: _streamTheme.colorTheme.borders, ), child: Center( - child: StreamSvgIcon( - icon: StreamSvgIcons.reply, + child: Icon( + context.streamIcons.arrowShareLeft, size: lerpDouble(0, 18, progress), color: _streamTheme.colorTheme.accentPrimary, ), @@ -326,7 +317,9 @@ class _ChannelPageState extends State { ), ); }, - child: defaultWidget.copyWith(onReplyTap: reply), + child: DefaultStreamMessage( + props: defaultProps.copyWith(onReplyTap: reply), + ), ); }, ), diff --git a/packages/stream_chat_flutter/example/lib/split_view.dart b/packages/stream_chat_flutter/example/lib/split_view.dart index 6f75b71a69..a873f92265 100644 --- a/packages/stream_chat_flutter/example/lib/split_view.dart +++ b/packages/stream_chat_flutter/example/lib/split_view.dart @@ -113,11 +113,11 @@ class _ChannelListPageState extends State { @override Widget build(BuildContext context) => Scaffold( - body: StreamChannelListView( - onChannelTap: widget.onTap, - controller: _listController, - ), - ); + body: StreamChannelListView( + onChannelTap: widget.onTap, + controller: _listController, + ), + ); } class ChannelPage extends StatelessWidget { @@ -127,20 +127,20 @@ class ChannelPage extends StatelessWidget { @override Widget build(BuildContext context) => Navigator( - onGenerateRoute: (settings) => MaterialPageRoute( - builder: (context) => const Scaffold( - appBar: StreamChannelHeader( - showBackButton: false, - ), - body: Column( - children: [ - Expanded( - child: StreamMessageListView(), - ), - StreamMessageInput(), - ], + onGenerateRoute: (settings) => MaterialPageRoute( + builder: (context) => const Scaffold( + appBar: StreamChannelHeader( + showBackButton: false, + ), + body: Column( + children: [ + Expanded( + child: StreamMessageListView(), ), - ), + StreamMessageInput(), + ], ), - ); + ), + ), + ); } diff --git a/packages/stream_chat_flutter/example/lib/tutorial_part_3.dart b/packages/stream_chat_flutter/example/lib/tutorial_part_3.dart index 8e5c52142f..d6cbd0d3a2 100644 --- a/packages/stream_chat_flutter/example/lib/tutorial_part_3.dart +++ b/packages/stream_chat_flutter/example/lib/tutorial_part_3.dart @@ -17,7 +17,7 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// /// We're passing a custom widget /// to [StreamChannelListView.itemBuilder]; -/// this will override the default [StreamChannelListTile] and allows you +/// this will override the default [StreamChannelListItem] and allows you /// to create one yourself. /// /// There are a couple interesting things we do in this widget: @@ -96,27 +96,27 @@ class _ChannelListPageState extends State { @override Widget build(BuildContext context) => Scaffold( - body: StreamChannelListView( - controller: _listController, - itemBuilder: _channelPreviewBuilder, - onChannelTap: (channel) { - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => StreamChannel( - channel: channel, - child: const ChannelPage(), - ), - ), - ); - }, - ), - ); + body: StreamChannelListView( + controller: _listController, + itemBuilder: _channelPreviewBuilder, + onChannelTap: (channel) { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => StreamChannel( + channel: channel, + child: const ChannelPage(), + ), + ), + ); + }, + ), + ); Widget _channelPreviewBuilder( BuildContext context, List channels, int index, - StreamChannelListTile defaultTile, + StreamChannelListItem defaultTile, ) { final channel = channels[index]; final lastMessage = channel.state?.messages.reversed.firstWhereOrNull( @@ -142,13 +142,11 @@ class _ChannelListPageState extends State { channel: channel, ), title: StreamChannelName( - textStyle: StreamChannelPreviewTheme.of(context).titleStyle!.copyWith( - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - // ignore: deprecated_member_use - .withOpacity(opacity), - ), + textStyle: StreamChannelListItemTheme.of(context).titleStyle!.copyWith( + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis + // ignore: deprecated_member_use + .withOpacity(opacity), + ), channel: channel, ), subtitle: Text(subtitle), diff --git a/packages/stream_chat_flutter/example/lib/tutorial_part_4.dart b/packages/stream_chat_flutter/example/lib/tutorial_part_4.dart index 905753f7e1..f271dae858 100644 --- a/packages/stream_chat_flutter/example/lib/tutorial_part_4.dart +++ b/packages/stream_chat_flutter/example/lib/tutorial_part_4.dart @@ -82,20 +82,20 @@ class _ChannelListPageState extends State { @override Widget build(BuildContext context) => Scaffold( - body: StreamChannelListView( - controller: _listController, - onChannelTap: (channel) { - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => StreamChannel( - channel: channel, - child: const ChannelPage(), - ), - ), - ); - }, - ), - ); + body: StreamChannelListView( + controller: _listController, + onChannelTap: (channel) { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => StreamChannel( + channel: channel, + child: const ChannelPage(), + ), + ), + ); + }, + ), + ); } class ChannelPage extends StatelessWidget { diff --git a/packages/stream_chat_flutter/example/lib/tutorial_part_5.dart b/packages/stream_chat_flutter/example/lib/tutorial_part_5.dart index ef205abfe5..d667748af8 100644 --- a/packages/stream_chat_flutter/example/lib/tutorial_part_5.dart +++ b/packages/stream_chat_flutter/example/lib/tutorial_part_5.dart @@ -128,13 +128,10 @@ class ChannelPage extends StatelessWidget { Widget _messageBuilder( BuildContext context, - MessageDetails details, - List messages, - StreamMessageWidget _, + Message message, + StreamMessageWidgetProps defaultProps, ) { - final message = details.message; - final isCurrentUser = - StreamChat.of(context).currentUser!.id == message.user!.id; + final isCurrentUser = StreamChat.of(context).currentUser!.id == message.user!.id; final textAlign = isCurrentUser ? TextAlign.right : TextAlign.left; final color = isCurrentUser ? Colors.blueGrey : Colors.blue; diff --git a/packages/stream_chat_flutter/example/pubspec.yaml b/packages/stream_chat_flutter/example/pubspec.yaml index 515df5f429..ed569474dc 100644 --- a/packages/stream_chat_flutter/example/pubspec.yaml +++ b/packages/stream_chat_flutter/example/pubspec.yaml @@ -16,8 +16,8 @@ version: 1.0.0+1 # 2. Add it to the melos.yaml file for future updates. environment: - sdk: ^3.6.2 - flutter: ">=3.27.4" + sdk: ^3.10.0 + flutter: ">=3.38.1" dependencies: collection: ^1.17.2 diff --git a/packages/stream_chat_flutter/lib/conditional_parent_builder/conditional_parent_builder.dart b/packages/stream_chat_flutter/lib/conditional_parent_builder/conditional_parent_builder.dart index 8314757de3..757be609e2 100644 --- a/packages/stream_chat_flutter/lib/conditional_parent_builder/conditional_parent_builder.dart +++ b/packages/stream_chat_flutter/lib/conditional_parent_builder/conditional_parent_builder.dart @@ -5,10 +5,11 @@ import 'package:flutter/material.dart'; /// {@template parentBuilder} /// A function that provides the [BuildContext] and the [child] widget. /// {@endtemplate} -typedef ParentBuilder = Widget Function( - BuildContext context, - Widget child, -); +typedef ParentBuilder = + Widget Function( + BuildContext context, + Widget child, + ); /// {@template conditionalParentBuilder} /// A widget that allows developers to conditionally wrap the [child] widget diff --git a/packages/stream_chat_flutter/lib/platform_widget_builder/src/desktop_widget.dart b/packages/stream_chat_flutter/lib/platform_widget_builder/src/desktop_widget.dart index f820eb2c13..5bc8129188 100644 --- a/packages/stream_chat_flutter/lib/platform_widget_builder/src/desktop_widget.dart +++ b/packages/stream_chat_flutter/lib/platform_widget_builder/src/desktop_widget.dart @@ -26,14 +26,11 @@ class DesktopWidget extends DesktopWidgetBase { final PlatformBuilder? linux; @override - Widget createMacosWidget(BuildContext context) => - macOS?.call(context) ?? const Empty(); + Widget createMacosWidget(BuildContext context) => macOS?.call(context) ?? const Empty(); @override - Widget createWindowsWidget(BuildContext context) => - windows?.call(context) ?? const Empty(); + Widget createWindowsWidget(BuildContext context) => windows?.call(context) ?? const Empty(); @override - Widget createLinuxWidget(BuildContext context) => - linux?.call(context) ?? const Empty(); + Widget createLinuxWidget(BuildContext context) => linux?.call(context) ?? const Empty(); } diff --git a/packages/stream_chat_flutter/lib/platform_widget_builder/src/desktop_widget_base.dart b/packages/stream_chat_flutter/lib/platform_widget_builder/src/desktop_widget_base.dart index f5a42a7bd7..9a1a822dfc 100644 --- a/packages/stream_chat_flutter/lib/platform_widget_builder/src/desktop_widget_base.dart +++ b/packages/stream_chat_flutter/lib/platform_widget_builder/src/desktop_widget_base.dart @@ -3,9 +3,10 @@ import 'package:flutter/material.dart' show Theme; import 'package:flutter/widgets.dart'; /// A generic widget builder function. -typedef PlatformBuilder = T Function( - BuildContext context, -); +typedef PlatformBuilder = + T Function( + BuildContext context, + ); /// An abstract class used as a building block for creating /// [DesktopPlatformWidget]s. @@ -21,8 +22,7 @@ typedef PlatformBuilder = T Function( /// * M = macOS /// * W = Windows /// * L = Linux -abstract class DesktopWidgetBase extends StatelessWidget { +abstract class DesktopWidgetBase extends StatelessWidget { /// Builds a [DesktopWidgetBase]. const DesktopWidgetBase({super.key}); diff --git a/packages/stream_chat_flutter/lib/platform_widget_builder/src/desktop_widget_builder.dart b/packages/stream_chat_flutter/lib/platform_widget_builder/src/desktop_widget_builder.dart index 923678617d..5aafeee047 100644 --- a/packages/stream_chat_flutter/lib/platform_widget_builder/src/desktop_widget_builder.dart +++ b/packages/stream_chat_flutter/lib/platform_widget_builder/src/desktop_widget_builder.dart @@ -2,10 +2,11 @@ import 'package:flutter/widgets.dart'; import 'package:stream_chat_flutter/platform_widget_builder/src/desktop_widget.dart'; /// A widget-building function that includes the child widget. -typedef DesktopTargetBuilder = Widget? Function( - BuildContext context, - Widget? child, -)?; +typedef DesktopTargetBuilder = + Widget? Function( + BuildContext context, + Widget? child, + )?; /// A widget that utilizes [DesktopWidgetBuilder]s to build different widgets /// for each specified desktop platform. diff --git a/packages/stream_chat_flutter/lib/platform_widget_builder/src/platform_widget.dart b/packages/stream_chat_flutter/lib/platform_widget_builder/src/platform_widget.dart index ff558ef1e7..426556b08c 100644 --- a/packages/stream_chat_flutter/lib/platform_widget_builder/src/platform_widget.dart +++ b/packages/stream_chat_flutter/lib/platform_widget_builder/src/platform_widget.dart @@ -26,14 +26,11 @@ class PlatformWidget extends PlatformWidgetBase { final PlatformBuilder? web; @override - Widget createDesktopWidget(BuildContext context) => - desktop?.call(context) ?? const Empty(); + Widget createDesktopWidget(BuildContext context) => desktop?.call(context) ?? const Empty(); @override - Widget createMobileWidget(BuildContext context) => - mobile?.call(context) ?? const Empty(); + Widget createMobileWidget(BuildContext context) => mobile?.call(context) ?? const Empty(); @override - Widget createWebWidget(BuildContext context) => - web?.call(context) ?? const Empty(); + Widget createWebWidget(BuildContext context) => web?.call(context) ?? const Empty(); } diff --git a/packages/stream_chat_flutter/lib/platform_widget_builder/src/platform_widget_base.dart b/packages/stream_chat_flutter/lib/platform_widget_builder/src/platform_widget_base.dart index 0487bd4396..8bf30cc361 100644 --- a/packages/stream_chat_flutter/lib/platform_widget_builder/src/platform_widget_base.dart +++ b/packages/stream_chat_flutter/lib/platform_widget_builder/src/platform_widget_base.dart @@ -2,9 +2,10 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; /// A generic widget builder function. -typedef PlatformBuilder = T Function( - BuildContext context, -); +typedef PlatformBuilder = + T Function( + BuildContext context, + ); /// An abstract class used as a building block for creating [PlatformWidget]s. /// @@ -21,8 +22,7 @@ typedef PlatformBuilder = T Function( /// * M = Mobile /// * D = Desktop /// * W = Web -abstract class PlatformWidgetBase extends StatelessWidget { +abstract class PlatformWidgetBase extends StatelessWidget { /// Builds a [PlatformWidgetBase]. const PlatformWidgetBase({ super.key, diff --git a/packages/stream_chat_flutter/lib/platform_widget_builder/src/platform_widget_builder.dart b/packages/stream_chat_flutter/lib/platform_widget_builder/src/platform_widget_builder.dart index b3950d2f0c..ef5e4db01c 100644 --- a/packages/stream_chat_flutter/lib/platform_widget_builder/src/platform_widget_builder.dart +++ b/packages/stream_chat_flutter/lib/platform_widget_builder/src/platform_widget_builder.dart @@ -2,10 +2,11 @@ import 'package:flutter/widgets.dart'; import 'package:stream_chat_flutter/platform_widget_builder/src/platform_widget.dart'; /// A widget-building function that includes the child widget. -typedef PlatformTargetBuilder = Widget? Function( - BuildContext context, - Widget? child, -)?; +typedef PlatformTargetBuilder = + Widget? Function( + BuildContext context, + Widget? child, + )?; /// A widget that utilizes [PlatformTargetBuilder]s to build different widgets /// for each specified platform. diff --git a/packages/stream_chat_flutter/lib/scrollable_positioned_list/src/element_registry.dart b/packages/stream_chat_flutter/lib/scrollable_positioned_list/src/element_registry.dart index 03f274f387..21d3b59446 100644 --- a/packages/stream_chat_flutter/lib/scrollable_positioned_list/src/element_registry.dart +++ b/packages/stream_chat_flutter/lib/scrollable_positioned_list/src/element_registry.dart @@ -38,9 +38,9 @@ class _RegistryWidgetState extends State { @override Widget build(BuildContext context) => _InheritedRegistryWidget( - state: this, - child: widget.child, - ); + state: this, + child: widget.child, + ); } class _InheritedRegistryWidget extends InheritedWidget { @@ -66,30 +66,25 @@ class _RegisteredElement extends ProxyElement { @override void mount(Element? parent, dynamic newSlot) { super.mount(parent, newSlot); - final _inheritedRegistryWidget = - dependOnInheritedWidgetOfExactType<_InheritedRegistryWidget>()!; + final _inheritedRegistryWidget = dependOnInheritedWidgetOfExactType<_InheritedRegistryWidget>()!; _registryWidgetState = _inheritedRegistryWidget.state; _registryWidgetState.registeredElements.add(this); - _registryWidgetState.widget.elementNotifier?.value = - _registryWidgetState.registeredElements; + _registryWidgetState.widget.elementNotifier?.value = _registryWidgetState.registeredElements; } @override void didChangeDependencies() { super.didChangeDependencies(); - final _inheritedRegistryWidget = - dependOnInheritedWidgetOfExactType<_InheritedRegistryWidget>()!; + final _inheritedRegistryWidget = dependOnInheritedWidgetOfExactType<_InheritedRegistryWidget>()!; _registryWidgetState = _inheritedRegistryWidget.state; _registryWidgetState.registeredElements.add(this); - _registryWidgetState.widget.elementNotifier?.value = - _registryWidgetState.registeredElements; + _registryWidgetState.widget.elementNotifier?.value = _registryWidgetState.registeredElements; } @override void unmount() { _registryWidgetState.registeredElements.remove(this); - _registryWidgetState.widget.elementNotifier?.value = - _registryWidgetState.registeredElements; + _registryWidgetState.widget.elementNotifier?.value = _registryWidgetState.registeredElements; super.unmount(); } } diff --git a/packages/stream_chat_flutter/lib/scrollable_positioned_list/src/item_positions_listener.dart b/packages/stream_chat_flutter/lib/scrollable_positioned_list/src/item_positions_listener.dart index d2752a00b2..808a2906fd 100644 --- a/packages/stream_chat_flutter/lib/scrollable_positioned_list/src/item_positions_listener.dart +++ b/packages/stream_chat_flutter/lib/scrollable_positioned_list/src/item_positions_listener.dart @@ -51,9 +51,7 @@ class ItemPosition { itemTrailingEdge == other.itemTrailingEdge; @override - int get hashCode => - 31 * (31 * (index.hashCode + 7) + itemLeadingEdge.hashCode) + - itemTrailingEdge.hashCode; + int get hashCode => 31 * (31 * (index.hashCode + 7) + itemLeadingEdge.hashCode) + itemTrailingEdge.hashCode; @override String toString() => diff --git a/packages/stream_chat_flutter/lib/scrollable_positioned_list/src/positioned_list.dart b/packages/stream_chat_flutter/lib/scrollable_positioned_list/src/positioned_list.dart index df08329cf8..a04f7c3397 100644 --- a/packages/stream_chat_flutter/lib/scrollable_positioned_list/src/positioned_list.dart +++ b/packages/stream_chat_flutter/lib/scrollable_positioned_list/src/positioned_list.dart @@ -47,9 +47,9 @@ class PositionedList extends StatefulWidget { this.findChildIndexCallback, this.keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual, }) : assert( - (positionedIndex == 0) || (positionedIndex < itemCount), - 'positionedIndex must be 0 or a value less than itemCount', - ); + (positionedIndex == 0) || (positionedIndex < itemCount), + 'positionedIndex must be 0 or a value less than itemCount', + ); /// Number of items the [itemBuilder] can produce. final int itemCount; @@ -186,81 +186,78 @@ class _PositionedListState extends State { @override Widget build(BuildContext context) => RegistryWidget( - elementNotifier: registeredElements, - child: UnboundedCustomScrollView( - anchor: widget.alignment, - center: _centerKey, - controller: scrollController, - scrollDirection: widget.scrollDirection, - reverse: widget.reverse, - cacheExtent: widget.cacheExtent, - physics: widget.physics, - shrinkWrap: widget.shrinkWrap, - semanticChildCount: widget.semanticChildCount ?? widget.itemCount, - keyboardDismissBehavior: widget.keyboardDismissBehavior, - slivers: [ - if (widget.positionedIndex > 0) - SliverPadding( - padding: _leadingSliverPadding, - sliver: SliverList( - delegate: SliverChildBuilderDelegate( - (context, index) => widget.separatorBuilder == null - ? _buildItem(widget.positionedIndex - (index + 1)) - : _buildSeparatedListElement( - widget.positionedIndex * 2 - (index + 1), - ), - childCount: widget.separatorBuilder == null - ? widget.positionedIndex - : widget.positionedIndex * 2, - addSemanticIndexes: false, - addRepaintBoundaries: widget.addRepaintBoundaries, - addAutomaticKeepAlives: widget.addAutomaticKeepAlives, - findChildIndexCallback: widget.findChildIndexCallback, - ), - ), - ), - SliverPadding( - key: _centerKey, - padding: _centerSliverPadding, - sliver: SliverList( - delegate: SliverChildBuilderDelegate( - (context, index) => widget.separatorBuilder == null - ? _buildItem(index + widget.positionedIndex) - : _buildSeparatedListElement( - index + widget.positionedIndex * 2, - ), - childCount: widget.itemCount != 0 ? 1 : 0, - addSemanticIndexes: false, - addRepaintBoundaries: widget.addRepaintBoundaries, - addAutomaticKeepAlives: widget.addAutomaticKeepAlives, - findChildIndexCallback: widget.findChildIndexCallback, - ), + elementNotifier: registeredElements, + child: UnboundedCustomScrollView( + anchor: widget.alignment, + center: _centerKey, + controller: scrollController, + scrollDirection: widget.scrollDirection, + reverse: widget.reverse, + cacheExtent: widget.cacheExtent, + physics: widget.physics, + shrinkWrap: widget.shrinkWrap, + semanticChildCount: widget.semanticChildCount ?? widget.itemCount, + keyboardDismissBehavior: widget.keyboardDismissBehavior, + slivers: [ + if (widget.positionedIndex > 0) + SliverPadding( + padding: _leadingSliverPadding, + sliver: SliverList( + delegate: SliverChildBuilderDelegate( + (context, index) => widget.separatorBuilder == null + ? _buildItem(widget.positionedIndex - (index + 1)) + : _buildSeparatedListElement( + widget.positionedIndex * 2 - (index + 1), + ), + childCount: widget.separatorBuilder == null ? widget.positionedIndex : widget.positionedIndex * 2, + addSemanticIndexes: false, + addRepaintBoundaries: widget.addRepaintBoundaries, + addAutomaticKeepAlives: widget.addAutomaticKeepAlives, + findChildIndexCallback: widget.findChildIndexCallback, ), ), - if (widget.positionedIndex >= 0 && - widget.positionedIndex < widget.itemCount - 1) - SliverPadding( - padding: _trailingSliverPadding, - sliver: SliverList( - delegate: SliverChildBuilderDelegate( - (context, index) => widget.separatorBuilder == null - ? _buildItem(index + widget.positionedIndex + 1) - : _buildSeparatedListElement( - index + widget.positionedIndex * 2 + 1, - ), - childCount: widget.separatorBuilder == null - ? widget.itemCount - widget.positionedIndex - 1 - : 2 * (widget.itemCount - widget.positionedIndex - 1), - addSemanticIndexes: false, - addRepaintBoundaries: widget.addRepaintBoundaries, - addAutomaticKeepAlives: widget.addAutomaticKeepAlives, - findChildIndexCallback: widget.findChildIndexCallback, - ), - ), - ), - ], + ), + SliverPadding( + key: _centerKey, + padding: _centerSliverPadding, + sliver: SliverList( + delegate: SliverChildBuilderDelegate( + (context, index) => widget.separatorBuilder == null + ? _buildItem(index + widget.positionedIndex) + : _buildSeparatedListElement( + index + widget.positionedIndex * 2, + ), + childCount: widget.itemCount != 0 ? 1 : 0, + addSemanticIndexes: false, + addRepaintBoundaries: widget.addRepaintBoundaries, + addAutomaticKeepAlives: widget.addAutomaticKeepAlives, + findChildIndexCallback: widget.findChildIndexCallback, + ), + ), ), - ); + if (widget.positionedIndex >= 0 && widget.positionedIndex < widget.itemCount - 1) + SliverPadding( + padding: _trailingSliverPadding, + sliver: SliverList( + delegate: SliverChildBuilderDelegate( + (context, index) => widget.separatorBuilder == null + ? _buildItem(index + widget.positionedIndex + 1) + : _buildSeparatedListElement( + index + widget.positionedIndex * 2 + 1, + ), + childCount: widget.separatorBuilder == null + ? widget.itemCount - widget.positionedIndex - 1 + : 2 * (widget.itemCount - widget.positionedIndex - 1), + addSemanticIndexes: false, + addRepaintBoundaries: widget.addRepaintBoundaries, + addAutomaticKeepAlives: widget.addAutomaticKeepAlives, + findChildIndexCallback: widget.findChildIndexCallback, + ), + ), + ), + ], + ), + ); Widget _buildSeparatedListElement(int index) { if (index.isEven) { @@ -274,63 +271,51 @@ class _PositionedListState extends State { final child = widget.itemBuilder(context, index); return RegisteredElementWidget( key: IndexedKey(child.key, index), - child: widget.addSemanticIndexes - ? IndexedSemantics(index: index, child: child) - : child, + child: widget.addSemanticIndexes ? IndexedSemantics(index: index, child: child) : child, ); } EdgeInsets get _leadingSliverPadding => (widget.scrollDirection == Axis.vertical ? widget.reverse - ? widget.padding?.copyWith(top: 0) - : widget.padding?.copyWith(bottom: 0) + ? widget.padding?.copyWith(top: 0) + : widget.padding?.copyWith(bottom: 0) : widget.reverse - ? widget.padding?.copyWith(left: 0) - : widget.padding?.copyWith(right: 0)) ?? + ? widget.padding?.copyWith(left: 0) + : widget.padding?.copyWith(right: 0)) ?? EdgeInsets.zero; EdgeInsets get _centerSliverPadding => widget.scrollDirection == Axis.vertical ? widget.reverse - ? widget.padding?.copyWith( - top: widget.positionedIndex == widget.itemCount - 1 - ? widget.padding!.top - : 0, - bottom: - widget.positionedIndex == 0 ? widget.padding!.bottom : 0, - ) ?? - EdgeInsets.zero - : widget.padding?.copyWith( - top: widget.positionedIndex == 0 ? widget.padding!.top : 0, - bottom: widget.positionedIndex == widget.itemCount - 1 - ? widget.padding!.bottom - : 0, - ) ?? - EdgeInsets.zero + ? widget.padding?.copyWith( + top: widget.positionedIndex == widget.itemCount - 1 ? widget.padding!.top : 0, + bottom: widget.positionedIndex == 0 ? widget.padding!.bottom : 0, + ) ?? + EdgeInsets.zero + : widget.padding?.copyWith( + top: widget.positionedIndex == 0 ? widget.padding!.top : 0, + bottom: widget.positionedIndex == widget.itemCount - 1 ? widget.padding!.bottom : 0, + ) ?? + EdgeInsets.zero : widget.reverse - ? widget.padding?.copyWith( - left: widget.positionedIndex == widget.itemCount - 1 - ? widget.padding!.left - : 0, - right: widget.positionedIndex == 0 ? widget.padding!.right : 0, - ) ?? - EdgeInsets.zero - : widget.padding?.copyWith( - left: widget.positionedIndex == 0 ? widget.padding!.left : 0, - right: widget.positionedIndex == widget.itemCount - 1 - ? widget.padding!.right - : 0, - ) ?? - EdgeInsets.zero; - - EdgeInsets get _trailingSliverPadding => - widget.scrollDirection == Axis.vertical - ? widget.reverse - ? widget.padding?.copyWith(bottom: 0) ?? EdgeInsets.zero - : widget.padding?.copyWith(top: 0) ?? EdgeInsets.zero - : widget.reverse - ? widget.padding?.copyWith(right: 0) ?? EdgeInsets.zero - : widget.padding?.copyWith(left: 0) ?? EdgeInsets.zero; + ? widget.padding?.copyWith( + left: widget.positionedIndex == widget.itemCount - 1 ? widget.padding!.left : 0, + right: widget.positionedIndex == 0 ? widget.padding!.right : 0, + ) ?? + EdgeInsets.zero + : widget.padding?.copyWith( + left: widget.positionedIndex == 0 ? widget.padding!.left : 0, + right: widget.positionedIndex == widget.itemCount - 1 ? widget.padding!.right : 0, + ) ?? + EdgeInsets.zero; + + EdgeInsets get _trailingSliverPadding => widget.scrollDirection == Axis.vertical + ? widget.reverse + ? widget.padding?.copyWith(bottom: 0) ?? EdgeInsets.zero + : widget.padding?.copyWith(top: 0) ?? EdgeInsets.zero + : widget.reverse + ? widget.padding?.copyWith(right: 0) ?? EdgeInsets.zero + : widget.padding?.copyWith(left: 0) ?? EdgeInsets.zero; void _schedulePositionNotificationUpdate() { if (!updateScheduled) { @@ -361,34 +346,34 @@ class _PositionedListState extends State { if (widget.scrollDirection == Axis.vertical) { final reveal = viewport!.getOffsetToReveal(box, 0).offset; if (!reveal.isFinite) continue; - final itemOffset = - reveal - viewport.offset.pixels + anchor * viewport.size.height; - positions.add(ItemPosition( - index: key.index, - itemLeadingEdge: itemOffset.round() / - scrollController.position.viewportDimension, - itemTrailingEdge: (itemOffset + box.size.height).round() / - scrollController.position.viewportDimension, - )); + final itemOffset = reveal - viewport.offset.pixels + anchor * viewport.size.height; + positions.add( + ItemPosition( + index: key.index, + itemLeadingEdge: itemOffset.round() / scrollController.position.viewportDimension, + itemTrailingEdge: (itemOffset + box.size.height).round() / scrollController.position.viewportDimension, + ), + ); } else { - final itemOffset = - box.localToGlobal(Offset.zero, ancestor: viewport).dx; + final itemOffset = box.localToGlobal(Offset.zero, ancestor: viewport).dx; if (!itemOffset.isFinite) continue; - positions.add(ItemPosition( - index: key.index, - itemLeadingEdge: (widget.reverse - ? scrollController.position.viewportDimension - - (itemOffset + box.size.width) - : itemOffset) - .round() / - scrollController.position.viewportDimension, - itemTrailingEdge: (widget.reverse - ? scrollController.position.viewportDimension - - itemOffset - : (itemOffset + box.size.width)) - .round() / - scrollController.position.viewportDimension, - )); + positions.add( + ItemPosition( + index: key.index, + itemLeadingEdge: + (widget.reverse + ? scrollController.position.viewportDimension - (itemOffset + box.size.width) + : itemOffset) + .round() / + scrollController.position.viewportDimension, + itemTrailingEdge: + (widget.reverse + ? scrollController.position.viewportDimension - itemOffset + : (itemOffset + box.size.width)) + .round() / + scrollController.position.viewportDimension, + ), + ); } } widget.itemPositionsNotifier?.itemPositions.value = positions; diff --git a/packages/stream_chat_flutter/lib/scrollable_positioned_list/src/scroll_view.dart b/packages/stream_chat_flutter/lib/scrollable_positioned_list/src/scroll_view.dart index 911512495b..160e8aa240 100644 --- a/packages/stream_chat_flutter/lib/scrollable_positioned_list/src/scroll_view.dart +++ b/packages/stream_chat_flutter/lib/scrollable_positioned_list/src/scroll_view.dart @@ -28,9 +28,9 @@ class UnboundedCustomScrollView extends CustomScrollView { super.semanticChildCount, super.dragStartBehavior, super.keyboardDismissBehavior, - }) : _shrinkWrap = shrinkWrap, - _anchor = anchor, - super(shrinkWrap: false); + }) : _shrinkWrap = shrinkWrap, + _anchor = anchor, + super(shrinkWrap: false); final bool _shrinkWrap; diff --git a/packages/stream_chat_flutter/lib/scrollable_positioned_list/src/scrollable_positioned_list.dart b/packages/stream_chat_flutter/lib/scrollable_positioned_list/src/scrollable_positioned_list.dart index af44d52562..e160dc6148 100644 --- a/packages/stream_chat_flutter/lib/scrollable_positioned_list/src/scrollable_positioned_list.dart +++ b/packages/stream_chat_flutter/lib/scrollable_positioned_list/src/scrollable_positioned_list.dart @@ -52,8 +52,8 @@ class ScrollablePositionedList extends StatefulWidget { this.minCacheExtent, this.findChildIndexCallback, this.keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual, - }) : itemPositionsNotifier = itemPositionsListener as ItemPositionsNotifier?, - separatorBuilder = null; + }) : itemPositionsNotifier = itemPositionsListener as ItemPositionsNotifier?, + separatorBuilder = null; /// Create a [ScrollablePositionedList] whose items are provided by /// [itemBuilder] and separators provided by [separatorBuilder]. @@ -278,8 +278,7 @@ class ItemScrollController { } } -class _ScrollablePositionedListState extends State - with TickerProviderStateMixin { +class _ScrollablePositionedListState extends State with TickerProviderStateMixin { /// Details for the primary (active) [ListView]. _ListDisplayDetails primary = _ListDisplayDetails(const ValueKey('Ping')); @@ -318,10 +317,8 @@ class _ScrollablePositionedListState extends State @override void dispose() { - primary.itemPositionsNotifier.itemPositions - .removeListener(_updatePositions); - secondary.itemPositionsNotifier.itemPositions - .removeListener(_updatePositions); + primary.itemPositionsNotifier.itemPositions.removeListener(_updatePositions); + secondary.itemPositionsNotifier.itemPositions.removeListener(_updatePositions); _animationController?.dispose(); super.dispose(); } @@ -431,12 +428,9 @@ class _ScrollablePositionedListState extends State } double _cacheExtent(BoxConstraints constraints) => max( - (widget.scrollDirection == Axis.vertical - ? constraints.maxHeight - : constraints.maxWidth) * - _screenScrollCount, - widget.minCacheExtent ?? 0, - ); + (widget.scrollDirection == Axis.vertical ? constraints.maxHeight : constraints.maxWidth) * _screenScrollCount, + widget.minCacheExtent ?? 0, + ); void _jumpTo({required int index, required double alignment}) { _stopScroll(canceled: true); @@ -494,14 +488,12 @@ class _ScrollablePositionedListState extends State required List opacityAnimationWeights, }) async { final direction = index > primary.target ? 1 : -1; - final itemPosition = - primary.itemPositionsNotifier.itemPositions.value.firstWhereOrNull( + final itemPosition = primary.itemPositionsNotifier.itemPositions.value.firstWhereOrNull( (ItemPosition itemPosition) => itemPosition.index == index, ); if (itemPosition != null) { // Scroll directly. - final localScrollAmount = itemPosition.itemLeadingEdge * - primary.scrollController.position.viewportDimension; + final localScrollAmount = itemPosition.itemLeadingEdge * primary.scrollController.position.viewportDimension; await primary.scrollController.animateTo( primary.scrollController.offset + localScrollAmount - @@ -510,31 +502,29 @@ class _ScrollablePositionedListState extends State curve: curve, ); } else { - final scrollAmount = _screenScrollCount * - primary.scrollController.position.viewportDimension; + final scrollAmount = _screenScrollCount * primary.scrollController.position.viewportDimension; final startCompleter = Completer(); final endCompleter = Completer(); startAnimationCallback = () { SchedulerBinding.instance.addPostFrameCallback((_) { startAnimationCallback = () {}; _animationController?.dispose(); - _animationController = - AnimationController(vsync: this, duration: duration)..forward(); - opacity.parent = _opacityAnimation(opacityAnimationWeights) - .animate(_animationController!); - secondary.scrollController.jumpTo(-direction * - (_screenScrollCount * - primary.scrollController.position.viewportDimension - - alignment * - secondary.scrollController.position.viewportDimension)); - - startCompleter.complete(primary.scrollController.animateTo( - primary.scrollController.offset + direction * scrollAmount, - duration: duration, - curve: curve, - )); - endCompleter.complete(secondary.scrollController - .animateTo(0, duration: duration, curve: curve)); + _animationController = AnimationController(vsync: this, duration: duration)..forward(); + opacity.parent = _opacityAnimation(opacityAnimationWeights).animate(_animationController!); + secondary.scrollController.jumpTo( + -direction * + (_screenScrollCount * primary.scrollController.position.viewportDimension - + alignment * secondary.scrollController.position.viewportDimension), + ); + + startCompleter.complete( + primary.scrollController.animateTo( + primary.scrollController.offset + direction * scrollAmount, + duration: duration, + curve: curve, + ), + ); + endCompleter.complete(secondary.scrollController.animateTo(0, duration: duration, curve: curve)); }); }; setState(() { @@ -599,14 +589,13 @@ class _ScrollablePositionedListState extends State } void _updatePositions() { - final itemPositions = primary.itemPositionsNotifier.itemPositions.value - .where((ItemPosition position) => - position.itemLeadingEdge < 1 && position.itemTrailingEdge > 0); + final itemPositions = primary.itemPositionsNotifier.itemPositions.value.where( + (ItemPosition position) => position.itemLeadingEdge < 1 && position.itemTrailingEdge > 0, + ); if (itemPositions.isNotEmpty) { PageStorage.of(context).writeState( context, - itemPositions.reduce((value, element) => - value.itemLeadingEdge < element.itemLeadingEdge ? value : element), + itemPositions.reduce((value, element) => value.itemLeadingEdge < element.itemLeadingEdge ? value : element), ); } widget.itemPositionsNotifier?.itemPositions.value = itemPositions; diff --git a/packages/stream_chat_flutter/lib/scrollable_positioned_list/src/viewport.dart b/packages/stream_chat_flutter/lib/scrollable_positioned_list/src/viewport.dart index aac9acc9e0..dac5e20f19 100644 --- a/packages/stream_chat_flutter/lib/scrollable_positioned_list/src/viewport.dart +++ b/packages/stream_chat_flutter/lib/scrollable_positioned_list/src/viewport.dart @@ -38,8 +38,7 @@ class UnboundedViewport extends Viewport { RenderViewport createRenderObject(BuildContext context) { return UnboundedRenderViewport( axisDirection: axisDirection, - crossAxisDirection: crossAxisDirection ?? - Viewport.getDefaultCrossAxisDirection(context, axisDirection), + crossAxisDirection: crossAxisDirection ?? Viewport.getDefaultCrossAxisDirection(context, axisDirection), anchor: anchor, offset: offset, cacheExtent: cacheExtent, @@ -233,10 +232,8 @@ class UnboundedRenderViewport extends RenderViewport { // to the zero scroll offset (the line between the forward slivers and the // reverse slivers). final centerOffset = mainAxisExtent * anchor - correctedOffset; - final reverseDirectionRemainingPaintExtent = - centerOffset.clamp(0.0, mainAxisExtent); - final forwardDirectionRemainingPaintExtent = - (mainAxisExtent - centerOffset).clamp(0.0, mainAxisExtent); + final reverseDirectionRemainingPaintExtent = centerOffset.clamp(0.0, mainAxisExtent); + final forwardDirectionRemainingPaintExtent = (mainAxisExtent - centerOffset).clamp(0.0, mainAxisExtent); switch (cacheExtentStyle) { case CacheExtentStyle.pixel: @@ -249,10 +246,8 @@ class UnboundedRenderViewport extends RenderViewport { final fullCacheExtent = mainAxisExtent + 2 * _calculatedCacheExtent!; final centerCacheOffset = centerOffset + _calculatedCacheExtent!; - final reverseDirectionRemainingCacheExtent = - centerCacheOffset.clamp(0.0, fullCacheExtent); - final forwardDirectionRemainingCacheExtent = - (fullCacheExtent - centerCacheOffset).clamp(0.0, fullCacheExtent); + final reverseDirectionRemainingCacheExtent = centerCacheOffset.clamp(0.0, fullCacheExtent); + final forwardDirectionRemainingCacheExtent = (fullCacheExtent - centerCacheOffset).clamp(0.0, fullCacheExtent); final leadingNegativeChild = childBefore(center!); @@ -269,8 +264,7 @@ class UnboundedRenderViewport extends RenderViewport { growthDirection: GrowthDirection.reverse, advance: childBefore, remainingCacheExtent: reverseDirectionRemainingCacheExtent, - cacheOrigin: (mainAxisExtent - centerOffset) - .clamp(-_calculatedCacheExtent!, 0.0), + cacheOrigin: (mainAxisExtent - centerOffset).clamp(-_calculatedCacheExtent!, 0.0), ); if (result != 0.0) return -result; } @@ -280,9 +274,7 @@ class UnboundedRenderViewport extends RenderViewport { child: center, scrollOffset: math.max(0, -centerOffset), overlap: leadingNegativeChild == null ? math.min(0, -centerOffset) : 0.0, - layoutOffset: centerOffset >= mainAxisExtent - ? centerOffset - : reverseDirectionRemainingPaintExtent, + layoutOffset: centerOffset >= mainAxisExtent ? centerOffset : reverseDirectionRemainingPaintExtent, remainingPaintExtent: forwardDirectionRemainingPaintExtent, mainAxisExtent: mainAxisExtent, crossAxisExtent: crossAxisExtent, diff --git a/packages/stream_chat_flutter/lib/scrollable_positioned_list/src/wrapping.dart b/packages/stream_chat_flutter/lib/scrollable_positioned_list/src/wrapping.dart index c3a8129ce7..e746813cae 100644 --- a/packages/stream_chat_flutter/lib/scrollable_positioned_list/src/wrapping.dart +++ b/packages/stream_chat_flutter/lib/scrollable_positioned_list/src/wrapping.dart @@ -59,8 +59,7 @@ class CustomShrinkWrappingViewport extends CustomViewport { CustomRenderShrinkWrappingViewport createRenderObject(BuildContext context) { return CustomRenderShrinkWrappingViewport( axisDirection: axisDirection, - crossAxisDirection: crossAxisDirection ?? - Viewport.getDefaultCrossAxisDirection(context, axisDirection), + crossAxisDirection: crossAxisDirection ?? Viewport.getDefaultCrossAxisDirection(context, axisDirection), offset: offset, anchor: anchor, cacheExtent: cacheExtent, @@ -74,8 +73,7 @@ class CustomShrinkWrappingViewport extends CustomViewport { ) { renderObject ..axisDirection = axisDirection - ..crossAxisDirection = crossAxisDirection ?? - Viewport.getDefaultCrossAxisDirection(context, axisDirection) + ..crossAxisDirection = crossAxisDirection ?? Viewport.getDefaultCrossAxisDirection(context, axisDirection) ..anchor = anchor ..offset = offset ..cacheExtent = cacheExtent @@ -267,10 +265,8 @@ class CustomRenderShrinkWrappingViewport extends CustomRenderViewport { final maxScrollOffset = math.max(math.min(0, top), bottom); final minScrollOffset = math.min(top, maxScrollOffset); - final didAcceptViewportDimension = - offset.applyViewportDimension(effectiveExtent); - final didAcceptContentDimension = - offset.applyContentDimensions(minScrollOffset, maxScrollOffset); + final didAcceptViewportDimension = offset.applyViewportDimension(effectiveExtent); + final didAcceptContentDimension = offset.applyContentDimensions(minScrollOffset, maxScrollOffset); if (didAcceptViewportDimension && didAcceptContentDimension) { break; } @@ -278,12 +274,10 @@ class CustomRenderShrinkWrappingViewport extends CustomRenderViewport { } while (true); switch (axis) { case Axis.vertical: - size = - constraints.constrainDimensions(crossAxisExtent, effectiveExtent); + size = constraints.constrainDimensions(crossAxisExtent, effectiveExtent); break; case Axis.horizontal: - size = - constraints.constrainDimensions(effectiveExtent, crossAxisExtent); + size = constraints.constrainDimensions(effectiveExtent, crossAxisExtent); break; } } @@ -313,10 +307,8 @@ class CustomRenderShrinkWrappingViewport extends CustomRenderViewport { // to the zero scroll offset (the line between the forward slivers and the // reverse slivers). final centerOffset = mainAxisExtent * anchor - correctedOffset; - final reverseDirectionRemainingPaintExtent = - centerOffset.clamp(0.0, mainAxisExtent); - final forwardDirectionRemainingPaintExtent = - (mainAxisExtent - centerOffset).clamp(0.0, mainAxisExtent); + final reverseDirectionRemainingPaintExtent = centerOffset.clamp(0.0, mainAxisExtent); + final forwardDirectionRemainingPaintExtent = (mainAxisExtent - centerOffset).clamp(0.0, mainAxisExtent); switch (cacheExtentStyle) { case CacheExtentStyle.pixel: @@ -329,10 +321,8 @@ class CustomRenderShrinkWrappingViewport extends CustomRenderViewport { final fullCacheExtent = mainAxisExtent + 2 * _calculatedCacheExtent!; final centerCacheOffset = centerOffset + _calculatedCacheExtent!; - final reverseDirectionRemainingCacheExtent = - centerCacheOffset.clamp(0.0, fullCacheExtent); - final forwardDirectionRemainingCacheExtent = - (fullCacheExtent - centerCacheOffset).clamp(0.0, fullCacheExtent); + final reverseDirectionRemainingCacheExtent = centerCacheOffset.clamp(0.0, fullCacheExtent); + final forwardDirectionRemainingCacheExtent = (fullCacheExtent - centerCacheOffset).clamp(0.0, fullCacheExtent); final leadingNegativeChild = childBefore(center!); @@ -349,8 +339,7 @@ class CustomRenderShrinkWrappingViewport extends CustomRenderViewport { growthDirection: GrowthDirection.reverse, advance: childBefore, remainingCacheExtent: reverseDirectionRemainingCacheExtent, - cacheOrigin: (mainAxisExtent - centerOffset) - .clamp(-_calculatedCacheExtent!, 0.0), + cacheOrigin: (mainAxisExtent - centerOffset).clamp(-_calculatedCacheExtent!, 0.0), ); if (result != 0.0) return -result; } @@ -360,9 +349,7 @@ class CustomRenderShrinkWrappingViewport extends CustomRenderViewport { child: center, scrollOffset: math.max(0, -centerOffset), overlap: leadingNegativeChild == null ? math.min(0, -centerOffset) : 0.0, - layoutOffset: centerOffset >= mainAxisExtent - ? centerOffset - : reverseDirectionRemainingPaintExtent, + layoutOffset: centerOffset >= mainAxisExtent ? centerOffset : reverseDirectionRemainingPaintExtent, remainingPaintExtent: forwardDirectionRemainingPaintExtent, mainAxisExtent: mainAxisExtent, crossAxisExtent: crossAxisExtent, @@ -450,16 +437,15 @@ abstract class CustomViewport extends MultiChildRenderObjectWidget { this.cacheExtentStyle = CacheExtentStyle.pixel, this.clipBehavior = Clip.hardEdge, List slivers = const [], - }) : assert( - center == null || - slivers.where((Widget child) => child.key == center).length == 1, - 'There should be at most one child with the same key as the center child: $center', - ), - assert( - cacheExtentStyle != CacheExtentStyle.viewport || cacheExtent != null, - 'A cacheExtent is required when using cacheExtentStyle.viewport', - ), - super(children: slivers); + }) : assert( + center == null || slivers.where((Widget child) => child.key == center).length == 1, + 'There should be at most one child with the same key as the center child: $center', + ), + assert( + cacheExtentStyle != CacheExtentStyle.viewport || cacheExtent != null, + 'A cacheExtent is required when using cacheExtentStyle.viewport', + ), + super(children: slivers); /// The direction in which the [offset]'s [ViewportOffset.pixels] increases. /// @@ -533,24 +519,24 @@ abstract class CustomViewport extends MultiChildRenderObjectWidget { ) { switch (axisDirection) { case AxisDirection.up: - assert(debugCheckHasDirectionality( - context, - why: - "to determine the cross-axis direction when the viewport has an 'up' axisDirection", - alternative: - "Alternatively, consider specifying the 'crossAxisDirection' argument on the Viewport.", - )); + assert( + debugCheckHasDirectionality( + context, + why: "to determine the cross-axis direction when the viewport has an 'up' axisDirection", + alternative: "Alternatively, consider specifying the 'crossAxisDirection' argument on the Viewport.", + ), + ); return textDirectionToAxisDirection(Directionality.of(context)); case AxisDirection.right: return AxisDirection.down; case AxisDirection.down: - assert(debugCheckHasDirectionality( - context, - why: - "to determine the cross-axis direction when the viewport has a 'down' axisDirection", - alternative: - "Alternatively, consider specifying the 'crossAxisDirection' argument on the Viewport.", - )); + assert( + debugCheckHasDirectionality( + context, + why: "to determine the cross-axis direction when the viewport has a 'down' axisDirection", + alternative: "Alternatively, consider specifying the 'crossAxisDirection' argument on the Viewport.", + ), + ); return textDirectionToAxisDirection(Directionality.of(context)); case AxisDirection.left: return AxisDirection.down; @@ -568,28 +554,34 @@ abstract class CustomViewport extends MultiChildRenderObjectWidget { super.debugFillProperties(properties); properties ..add(EnumProperty('axisDirection', axisDirection)) - ..add(EnumProperty( - 'crossAxisDirection', - crossAxisDirection, - defaultValue: null, - )) + ..add( + EnumProperty( + 'crossAxisDirection', + crossAxisDirection, + defaultValue: null, + ), + ) ..add(DoubleProperty('anchor', anchor)) ..add(DiagnosticsProperty('offset', offset)); if (center != null) { properties.add(DiagnosticsProperty('center', center)); } else if (children.isNotEmpty && children.first.key != null) { - properties.add(DiagnosticsProperty( - 'center', - children.first.key, - tooltip: 'implicit', - )); + properties.add( + DiagnosticsProperty( + 'center', + children.first.key, + tooltip: 'implicit', + ), + ); } properties ..add(DiagnosticsProperty('cacheExtent', cacheExtent)) - ..add(DiagnosticsProperty( - 'cacheExtentStyle', - cacheExtentStyle, - )); + ..add( + DiagnosticsProperty( + 'cacheExtentStyle', + cacheExtentStyle, + ), + ); } } @@ -601,8 +593,7 @@ class _ViewportElement extends MultiChildRenderObjectElement { CustomViewport get widget => super.widget as CustomViewport; @override - CustomRenderViewport get renderObject => - super.renderObject as CustomRenderViewport; + CustomRenderViewport get renderObject => super.renderObject as CustomRenderViewport; @override void mount(Element? parent, dynamic newSlot) { @@ -618,9 +609,8 @@ class _ViewportElement extends MultiChildRenderObjectElement { void _updateCenter() { if (widget.center != null) { - renderObject.center = children - .singleWhere((Element element) => element.widget.key == widget.center) - .renderObject as RenderSliver?; + renderObject.center = + children.singleWhere((Element element) => element.widget.key == widget.center).renderObject as RenderSliver?; } else if (children.isNotEmpty) { renderObject.center = children.first.renderObject as RenderSliver?; } else { @@ -630,15 +620,16 @@ class _ViewportElement extends MultiChildRenderObjectElement { @override void debugVisitOnstageChildren(ElementVisitor visitor) { - children.where((Element e) { - final renderSliver = e.renderObject! as RenderSliver; - return renderSliver.geometry!.visible; - }).forEach(visitor); + children + .where((Element e) { + final renderSliver = e.renderObject! as RenderSliver; + return renderSliver.geometry!.visible; + }) + .forEach(visitor); } } -class CustomSliverPhysicalContainerParentData - extends SliverPhysicalContainerParentData { +class CustomSliverPhysicalContainerParentData extends SliverPhysicalContainerParentData { /// The position of the child relative to the zero scroll offset. /// /// The number of pixels from from the zero scroll offset of the parent sliver @@ -685,8 +676,7 @@ class CustomSliverPhysicalContainerParentData /// placed inside a [RenderSliver] (the opposite of this class). /// * [RenderShrinkWrappingViewport], a variant of [RenderViewport] that /// shrink-wraps its contents along the main axis. -abstract class CustomRenderViewport - extends RenderViewportBase { +abstract class CustomRenderViewport extends RenderViewportBase { /// Creates a viewport for [RenderSliver] objects. /// /// If the [center] is not specified, then the first child in the `children` @@ -704,15 +694,15 @@ abstract class CustomRenderViewport super.cacheExtent, super.cacheExtentStyle, super.clipBehavior, - }) : assert( - anchor >= 0.0 && anchor <= 1.0, - 'Anchor must be between 0.0 and 1.0.', - ), - assert( - cacheExtentStyle != CacheExtentStyle.viewport || cacheExtent != null, - 'A cacheExtent is required when using CacheExtentStyle.viewport.', - ), - _center = center { + }) : assert( + anchor >= 0.0 && anchor <= 1.0, + 'Anchor must be between 0.0 and 1.0.', + ), + assert( + cacheExtentStyle != CacheExtentStyle.viewport || cacheExtent != null, + 'A cacheExtent is required when using CacheExtentStyle.viewport.', + ), + _center = center { addAll(children); if (center == null && firstChild != null) _center = firstChild; } @@ -735,8 +725,7 @@ abstract class CustomRenderViewport /// /// * [RenderViewportBase.describeSemanticsConfiguration], which adds this /// tag to its [SemanticsConfiguration]. - static const SemanticsTag useTwoPaneSemantics = - SemanticsTag('RenderViewport.twoPane'); + static const SemanticsTag useTwoPaneSemantics = SemanticsTag('RenderViewport.twoPane'); /// When a top-level [SemanticsNode] below a [RenderAbstractViewport] is /// tagged with [excludeFromScrolling] it will not be part of the scrolling @@ -751,8 +740,7 @@ abstract class CustomRenderViewport /// bar) can tag its [SemanticsNode] with [excludeFromScrolling] to indicate /// that it should no longer be considered for semantic actions related to /// scrolling. - static const SemanticsTag excludeFromScrolling = - SemanticsTag('RenderViewport.excludeFromScrolling'); + static const SemanticsTag excludeFromScrolling = SemanticsTag('RenderViewport.excludeFromScrolling'); @override void setupParentData(RenderObject child) { @@ -900,8 +888,7 @@ abstract class CustomRenderViewport double layoutOffset, GrowthDirection growthDirection, ) { - final childParentData = - child.parentData! as CustomSliverPhysicalContainerParentData; + final childParentData = child.parentData! as CustomSliverPhysicalContainerParentData; childParentData ..layoutOffset = layoutOffset ..growthDirection = growthDirection; @@ -909,8 +896,7 @@ abstract class CustomRenderViewport @override Offset paintOffsetOf(RenderSliver child) { - final childParentData = - child.parentData! as CustomSliverPhysicalContainerParentData; + final childParentData = child.parentData! as CustomSliverPhysicalContainerParentData; return computeAbsolutePaintOffset( child, childParentData.layoutOffset!, @@ -983,8 +969,7 @@ abstract class CustomRenderViewport RenderSliver child, double parentMainAxisPosition, ) { - final childParentData = - child.parentData! as CustomSliverPhysicalContainerParentData; + final childParentData = child.parentData! as CustomSliverPhysicalContainerParentData; switch (applyGrowthDirectionToAxisDirection( child.constraints.axisDirection, child.constraints.growthDirection, @@ -993,11 +978,9 @@ abstract class CustomRenderViewport case AxisDirection.right: return parentMainAxisPosition - childParentData.layoutOffset!; case AxisDirection.up: - return (size.height - parentMainAxisPosition) - - childParentData.layoutOffset!; + return (size.height - parentMainAxisPosition) - childParentData.layoutOffset!; case AxisDirection.left: - return (size.width - parentMainAxisPosition) - - childParentData.layoutOffset!; + return (size.width - parentMainAxisPosition) - childParentData.layoutOffset!; } } diff --git a/packages/stream_chat_flutter/lib/src/ai_assistant/ai_typing_indicator_view.dart b/packages/stream_chat_flutter/lib/src/ai_assistant/ai_typing_indicator_view.dart index ebafb3ef2a..288cedf09b 100644 --- a/packages/stream_chat_flutter/lib/src/ai_assistant/ai_typing_indicator_view.dart +++ b/packages/stream_chat_flutter/lib/src/ai_assistant/ai_typing_indicator_view.dart @@ -145,25 +145,25 @@ class _AnimatedDot extends StatefulWidget { State<_AnimatedDot> createState() => _AnimatedDotState(); } -class _AnimatedDotState extends State<_AnimatedDot> - with SingleTickerProviderStateMixin<_AnimatedDot> { +class _AnimatedDotState extends State<_AnimatedDot> with SingleTickerProviderStateMixin<_AnimatedDot> { late final AnimationController _repeatingController; @override void initState() { super.initState(); - _repeatingController = AnimationController( - vsync: this, - duration: const Duration(milliseconds: 800), - )..addStatusListener( - (status) { - if (status == AnimationStatus.completed) { - if (mounted) _repeatingController.reverse(); - } else if (status == AnimationStatus.dismissed) { - if (mounted) _repeatingController.forward(); - } - }, - ); + _repeatingController = + AnimationController( + vsync: this, + duration: const Duration(milliseconds: 800), + )..addStatusListener( + (status) { + if (status == AnimationStatus.completed) { + if (mounted) _repeatingController.reverse(); + } else if (status == AnimationStatus.dismissed) { + if (mounted) _repeatingController.forward(); + } + }, + ); Future.delayed( Duration(milliseconds: 200 * widget.index), diff --git a/packages/stream_chat_flutter/lib/src/ai_assistant/stream_typewriter_builder.dart b/packages/stream_chat_flutter/lib/src/ai_assistant/stream_typewriter_builder.dart index e004784bf6..f686766004 100644 --- a/packages/stream_chat_flutter/lib/src/ai_assistant/stream_typewriter_builder.dart +++ b/packages/stream_chat_flutter/lib/src/ai_assistant/stream_typewriter_builder.dart @@ -54,9 +54,7 @@ class TypewriterValue { @override bool operator ==(Object other) { if (identical(this, other)) return true; - return other is TypewriterValue && - other.text == text && - other.state == state; + return other is TypewriterValue && other.text == text && other.state == state; } @override @@ -205,11 +203,12 @@ class TypewriterController extends ValueNotifier { /// A widget builder for a [StreamTypewriterBuilder]. It allows you to build a /// widget depending on the [TypewriterValue]'s value. /// {@endtemplate} -typedef TypewriterWidgetBuilder = Widget Function( - BuildContext context, - TypewriterValue value, - Widget? child, -); +typedef TypewriterWidgetBuilder = + Widget Function( + BuildContext context, + TypewriterValue value, + Widget? child, + ); /// {@template streamTypewriterBuilder} /// A widget that listens to a [TypewriterController] and rebuilds whenever the diff --git a/packages/stream_chat_flutter/lib/src/ai_assistant/streaming_message_view.dart b/packages/stream_chat_flutter/lib/src/ai_assistant/streaming_message_view.dart index 4422b5d86a..0a81884920 100644 --- a/packages/stream_chat_flutter/lib/src/ai_assistant/streaming_message_view.dart +++ b/packages/stream_chat_flutter/lib/src/ai_assistant/streaming_message_view.dart @@ -82,8 +82,8 @@ class _StreamingMessageViewState extends State { onTapLink: switch (widget.onTapLink) { final onTapLink? => onTapLink, _ => (String link, String? href, String title) { - if (href != null) launchURL(context, href); - }, + if (href != null) launchURL(context, href); + }, }, ); } diff --git a/packages/stream_chat_flutter/lib/src/attachment/attachment_upload_state_builder.dart b/packages/stream_chat_flutter/lib/src/attachment/attachment_upload_state_builder.dart index dcb6a47a08..09548679f7 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/attachment_upload_state_builder.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/attachment_upload_state_builder.dart @@ -44,7 +44,8 @@ class StreamAttachmentUploadStateBuilder extends StatelessWidget { final messageId = message.id; final attachmentId = attachment.id; - final inProgress = inProgressBuilder ?? + final inProgress = + inProgressBuilder ?? (context, int sent, int total) { return _InProgressState( sent: sent, @@ -53,7 +54,8 @@ class StreamAttachmentUploadStateBuilder extends StatelessWidget { ); }; - final failed = failedBuilder ?? + final failed = + failedBuilder ?? (context, error) { return _FailedState( error: error, @@ -64,8 +66,7 @@ class StreamAttachmentUploadStateBuilder extends StatelessWidget { final success = successBuilder ?? (context) => _SuccessState(); - final preparing = preparingBuilder ?? - (context) => _PreparingState(attachmentId: attachmentId); + final preparing = preparingBuilder ?? (context) => _PreparingState(attachmentId: attachmentId); return attachment.uploadState.when( preparing: () => preparing(context), @@ -87,20 +88,22 @@ class _IconButton extends StatelessWidget { @override Widget build(BuildContext context) { - return SizedBox( - height: 24, - width: 24, - child: RawMaterialButton( - elevation: 0, - highlightElevation: 0, - focusElevation: 0, - hoverElevation: 0, - onPressed: onPressed, - fillColor: StreamChatTheme.of(context).colorTheme.overlayDark, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), + return SizedBox.square( + dimension: 20, + child: IconTheme.merge( + data: const IconThemeData(size: 16), + child: RawMaterialButton( + elevation: 0, + highlightElevation: 0, + focusElevation: 0, + hoverElevation: 0, + onPressed: onPressed, + fillColor: StreamChatTheme.of(context).colorTheme.overlayDark, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + child: icon, ), - child: icon, ), ); } @@ -121,8 +124,8 @@ class _PreparingState extends StatelessWidget { Align( alignment: Alignment.topRight, child: _IconButton( - icon: StreamSvgIcon( - icon: StreamSvgIcons.close, + icon: Icon( + context.streamIcons.crossMedium, color: StreamChatTheme.of(context).colorTheme.barsBg, ), onPressed: () => channel.cancelAttachmentUpload(attachmentId), @@ -161,8 +164,8 @@ class _InProgressState extends StatelessWidget { Align( alignment: Alignment.topRight, child: _IconButton( - icon: StreamSvgIcon( - icon: StreamSvgIcons.close, + icon: Icon( + context.streamIcons.crossMedium, color: StreamChatTheme.of(context).colorTheme.barsBg, ), onPressed: () => channel.cancelAttachmentUpload(attachmentId), @@ -200,9 +203,8 @@ class _FailedState extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ _IconButton( - icon: StreamSvgIcon( - size: 14, - icon: StreamSvgIcons.retry, + icon: Icon( + context.streamIcons.arrowRotateClockwise, color: theme.colorTheme.barsBg, ), onPressed: () { @@ -243,9 +245,10 @@ class _SuccessState extends StatelessWidget { alignment: Alignment.topRight, child: CircleAvatar( backgroundColor: StreamChatTheme.of(context).colorTheme.overlayDark, - maxRadius: 12, - child: StreamSvgIcon( - icon: StreamSvgIcons.check, + maxRadius: 10, + child: Icon( + size: 16, + context.streamIcons.checkmark2, color: StreamChatTheme.of(context).colorTheme.barsBg, ), ), diff --git a/packages/stream_chat_flutter/lib/src/attachment/attachment_widget_catalog.dart b/packages/stream_chat_flutter/lib/src/attachment/attachment_widget_catalog.dart index 78c0ba1f52..abd9f0e441 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/attachment_widget_catalog.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/attachment_widget_catalog.dart @@ -52,8 +52,11 @@ class AttachmentWidgetCatalog { extension on List { /// Groups the attachments by their type. Map> get grouped { - return groupBy(where((it) { - return it.type != null; - }), (attachment) => attachment.type!); + return groupBy( + where((it) { + return it.type != null; + }), + (attachment) => attachment.type!, + ); } } diff --git a/packages/stream_chat_flutter/lib/src/attachment/builder/attachment_widget_builder.dart b/packages/stream_chat_flutter/lib/src/attachment/builder/attachment_widget_builder.dart index 24c1df2a5e..63e08c83d4 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/builder/attachment_widget_builder.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/builder/attachment_widget_builder.dart @@ -16,10 +16,11 @@ part 'poll_attachment_builder.dart'; /// {@template streamAttachmentWidgetTapCallback} /// Signature for a function that's called when the user taps on an attachment. /// {@endtemplate} -typedef StreamAttachmentWidgetTapCallback = void Function( - Message message, - Attachment attachment, -); +typedef StreamAttachmentWidgetTapCallback = + void Function( + Message message, + Attachment attachment, + ); /// {@template attachmentWidgetBuilder} /// A builder which is used to build a widget for a given [Message] and @@ -67,67 +68,69 @@ abstract class StreamAttachmentWidgetBuilder { static List defaultBuilders({ required Message message, ShapeBorder? shape, - EdgeInsetsGeometry padding = const EdgeInsets.all(4), + EdgeInsetsGeometry? padding, StreamAttachmentWidgetTapCallback? onAttachmentTap, List? customAttachmentBuilders, }) { + final effectivePadding = padding ?? const EdgeInsets.symmetric(horizontal: 8); + return [ ...?customAttachmentBuilders, // Handles poll attachments. PollAttachmentBuilder( shape: shape, - padding: padding, + padding: effectivePadding, ), // Handles a mix of image, gif, video, url, file and voice recording // attachments. MixedAttachmentBuilder( - padding: padding, + padding: effectivePadding, onAttachmentTap: onAttachmentTap, ), // Handles a mix of image, gif, and video attachments. GalleryAttachmentBuilder( shape: shape, - padding: padding, - runSpacing: padding.vertical / 2, - spacing: padding.horizontal / 2, + padding: effectivePadding, + runSpacing: effectivePadding.vertical / 2, + spacing: effectivePadding.horizontal / 2, onAttachmentTap: onAttachmentTap, ), // Handles file attachments. FileAttachmentBuilder( shape: shape, - padding: padding, + padding: effectivePadding, onAttachmentTap: onAttachmentTap, ), // Handles giphy attachments. GiphyAttachmentBuilder( shape: shape, - padding: padding, + padding: effectivePadding, onAttachmentTap: onAttachmentTap, ), // Handles image attachments. ImageAttachmentBuilder( shape: shape, - padding: padding, + padding: effectivePadding, onAttachmentTap: onAttachmentTap, ), // Handles video attachments. VideoAttachmentBuilder( shape: shape, - padding: padding, + padding: effectivePadding, onAttachmentTap: onAttachmentTap, ), // Handles voice recording attachments. VoiceRecordingAttachmentPlaylistBuilder( shape: shape, - padding: padding, + padding: effectivePadding, onAttachmentTap: onAttachmentTap, ), @@ -135,7 +138,7 @@ abstract class StreamAttachmentWidgetBuilder { if (message.quotedMessage == null) UrlAttachmentBuilder( shape: shape, - padding: padding, + padding: effectivePadding, onAttachmentTap: onAttachmentTap, ), diff --git a/packages/stream_chat_flutter/lib/src/attachment/builder/mixed_attachment_builder.dart b/packages/stream_chat_flutter/lib/src/attachment/builder/mixed_attachment_builder.dart index 121c78e707..9b0fe31c14 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/builder/mixed_attachment_builder.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/builder/mixed_attachment_builder.dart @@ -14,35 +14,34 @@ class MixedAttachmentBuilder extends StreamAttachmentWidgetBuilder { MixedAttachmentBuilder({ this.padding = const EdgeInsets.all(4), StreamAttachmentWidgetTapCallback? onAttachmentTap, - }) : _imageAttachmentBuilder = ImageAttachmentBuilder( - padding: EdgeInsets.zero, - onAttachmentTap: onAttachmentTap, - ), - _videoAttachmentBuilder = VideoAttachmentBuilder( - padding: EdgeInsets.zero, - onAttachmentTap: onAttachmentTap, - ), - _giphyAttachmentBuilder = GiphyAttachmentBuilder( - padding: EdgeInsets.zero, - onAttachmentTap: onAttachmentTap, - ), - _galleryAttachmentBuilder = GalleryAttachmentBuilder( - padding: EdgeInsets.zero, - onAttachmentTap: onAttachmentTap, - ), - _fileAttachmentBuilder = FileAttachmentBuilder( - padding: EdgeInsets.zero, - onAttachmentTap: onAttachmentTap, - ), - _urlAttachmentBuilder = UrlAttachmentBuilder( - padding: EdgeInsets.zero, - onAttachmentTap: onAttachmentTap, - ), - _voiceRecordingAttachmentPlaylistBuilder = - VoiceRecordingAttachmentPlaylistBuilder( - padding: EdgeInsets.zero, - onAttachmentTap: onAttachmentTap, - ); + }) : _imageAttachmentBuilder = ImageAttachmentBuilder( + padding: EdgeInsets.zero, + onAttachmentTap: onAttachmentTap, + ), + _videoAttachmentBuilder = VideoAttachmentBuilder( + padding: EdgeInsets.zero, + onAttachmentTap: onAttachmentTap, + ), + _giphyAttachmentBuilder = GiphyAttachmentBuilder( + padding: EdgeInsets.zero, + onAttachmentTap: onAttachmentTap, + ), + _galleryAttachmentBuilder = GalleryAttachmentBuilder( + padding: EdgeInsets.zero, + onAttachmentTap: onAttachmentTap, + ), + _fileAttachmentBuilder = FileAttachmentBuilder( + padding: EdgeInsets.zero, + onAttachmentTap: onAttachmentTap, + ), + _urlAttachmentBuilder = UrlAttachmentBuilder( + padding: EdgeInsets.zero, + onAttachmentTap: onAttachmentTap, + ), + _voiceRecordingAttachmentPlaylistBuilder = VoiceRecordingAttachmentPlaylistBuilder( + padding: EdgeInsets.zero, + onAttachmentTap: onAttachmentTap, + ); /// The padding to apply to the mixed attachment widget. final EdgeInsetsGeometry padding; @@ -53,8 +52,7 @@ class MixedAttachmentBuilder extends StreamAttachmentWidgetBuilder { late final StreamAttachmentWidgetBuilder _galleryAttachmentBuilder; late final StreamAttachmentWidgetBuilder _fileAttachmentBuilder; late final StreamAttachmentWidgetBuilder _urlAttachmentBuilder; - late final StreamAttachmentWidgetBuilder - _voiceRecordingAttachmentPlaylistBuilder; + late final StreamAttachmentWidgetBuilder _voiceRecordingAttachmentPlaylistBuilder; @override bool canHandle( @@ -99,10 +97,12 @@ class MixedAttachmentBuilder extends StreamAttachmentWidgetBuilder { final shouldBuildGallery = [...?images, ...?videos, ...?giphys].length > 1; + final spacing = context.streamSpacing; + return Padding( padding: padding, child: Column( - spacing: padding.vertical / 2, + spacing: spacing.xs, mainAxisSize: MainAxisSize.min, children: [ if (urls != null) diff --git a/packages/stream_chat_flutter/lib/src/attachment/builder/url_attachment_builder.dart b/packages/stream_chat_flutter/lib/src/attachment/builder/url_attachment_builder.dart index 58c85fe15b..b8f9c40f7a 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/builder/url_attachment_builder.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/builder/url_attachment_builder.dart @@ -52,9 +52,7 @@ class UrlAttachmentBuilder extends StreamAttachmentWidgetBuilder { final isMyMessage = message.user?.id == client.state.currentUser?.id; final streamChatTheme = StreamChatTheme.of(context); - final messageTheme = isMyMessage - ? streamChatTheme.ownMessageTheme - : streamChatTheme.otherMessageTheme; + final messageTheme = isMyMessage ? streamChatTheme.ownMessageTheme : streamChatTheme.otherMessageTheme; Widget _buildUrlPreview(Attachment urlPreview) { VoidCallback? onTap; @@ -65,9 +63,8 @@ class UrlAttachmentBuilder extends StreamAttachmentWidgetBuilder { final host = Uri.parse(urlPreview.titleLink!).host; final splitList = host.split('.'); final hostName = splitList.length == 3 ? splitList[1] : splitList[0]; - final hostDisplayName = urlPreview.authorName?.sentenceCase ?? - getWebsiteName(hostName.toLowerCase()) ?? - hostName.sentenceCase; + final hostDisplayName = + urlPreview.authorName?.sentenceCase ?? getWebsiteName(hostName.toLowerCase()) ?? hostName.sentenceCase; return InkWell( onTap: onTap, diff --git a/packages/stream_chat_flutter/lib/src/attachment/builder/voice_recording_attachment_playlist_builder.dart b/packages/stream_chat_flutter/lib/src/attachment/builder/voice_recording_attachment_playlist_builder.dart index f53fea8642..220b3d7ed6 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/builder/voice_recording_attachment_playlist_builder.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/builder/voice_recording_attachment_playlist_builder.dart @@ -9,8 +9,7 @@ part of 'attachment_widget_builder.dart'; /// The widget is built when the message has at least one voice recording /// attachment. /// {@endtemplate} -class VoiceRecordingAttachmentPlaylistBuilder - extends StreamAttachmentWidgetBuilder { +class VoiceRecordingAttachmentPlaylistBuilder extends StreamAttachmentWidgetBuilder { /// {@macro voiceRecordingAttachmentPlaylistBuilder} const VoiceRecordingAttachmentPlaylistBuilder({ this.shape, diff --git a/packages/stream_chat_flutter/lib/src/attachment/file_attachment.dart b/packages/stream_chat_flutter/lib/src/attachment/file_attachment.dart index a6cf3a04a0..4bf4d4227c 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/file_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/file_attachment.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/src/attachment/handler/stream_attachment_handler.dart'; import 'package:stream_chat_flutter/src/attachment/thumbnail/file_attachment_thumbnail.dart'; -import 'package:stream_chat_flutter/src/icons/stream_svg_icon.dart'; import 'package:stream_chat_flutter/src/indicators/upload_progress_indicator.dart'; import 'package:stream_chat_flutter/src/theme/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/utils/utils.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; /// {@template streamFileAttachment} /// Displays file attachments that have been sent in a chat. @@ -59,7 +59,8 @@ class StreamFileAttachment extends StatelessWidget { final colorTheme = chatTheme.colorTheme; final backgroundColor = this.backgroundColor ?? colorTheme.barsBg; - final shape = this.shape ?? + final shape = + this.shape ?? RoundedRectangleBorder( side: BorderSide( color: colorTheme.borders, @@ -103,7 +104,8 @@ class StreamFileAttachment extends StatelessWidget { const SizedBox(width: 8), Material( type: MaterialType.transparency, - child: trailing ?? + child: + trailing ?? _Trailing( attachment: file, message: message, @@ -171,8 +173,8 @@ class _Trailing extends StatelessWidget { if (message.state.isCompleted) { return IconButton( - icon: StreamSvgIcon( - icon: StreamSvgIcons.cloudDownload, + icon: Icon( + context.streamIcons.arrowDown, color: theme.colorTheme.textHighEmphasis, ), visualDensity: VisualDensity.compact, @@ -194,8 +196,8 @@ class _Trailing extends StatelessWidget { preparing: () => Padding( padding: const EdgeInsets.all(8), child: _TrailingButton( - icon: StreamSvgIcon( - icon: StreamSvgIcons.close, + icon: Icon( + context.streamIcons.crossMedium, color: theme.colorTheme.barsBg, ), fillColor: theme.colorTheme.overlayDark, @@ -205,8 +207,8 @@ class _Trailing extends StatelessWidget { inProgress: (_, __) => Padding( padding: const EdgeInsets.all(8), child: _TrailingButton( - icon: StreamSvgIcon( - icon: StreamSvgIcons.close, + icon: Icon( + context.streamIcons.crossMedium, color: theme.colorTheme.barsBg, ), fillColor: theme.colorTheme.overlayDark, @@ -218,8 +220,8 @@ class _Trailing extends StatelessWidget { child: CircleAvatar( backgroundColor: theme.colorTheme.accentPrimary, maxRadius: 12, - child: StreamSvgIcon( - icon: StreamSvgIcons.check, + child: Icon( + context.streamIcons.checkmark2, color: theme.colorTheme.barsBg, ), ), @@ -227,8 +229,8 @@ class _Trailing extends StatelessWidget { failed: (_) => Padding( padding: const EdgeInsets.all(8), child: _TrailingButton( - icon: StreamSvgIcon( - icon: StreamSvgIcons.retry, + icon: Icon( + context.streamIcons.arrowRotateClockwise, color: theme.colorTheme.barsBg, ), fillColor: theme.colorTheme.overlayDark, diff --git a/packages/stream_chat_flutter/lib/src/attachment/gallery_attachment.dart b/packages/stream_chat_flutter/lib/src/attachment/gallery_attachment.dart index 22cbf84265..51d457acbd 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/gallery_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/gallery_attachment.dart @@ -72,7 +72,8 @@ class StreamGalleryAttachment extends StatelessWidget { final chatTheme = StreamChatTheme.of(context); final colorTheme = chatTheme.colorTheme; - final shape = this.shape ?? + final shape = + this.shape ?? RoundedRectangleBorder( side: BorderSide( color: colorTheme.borders, @@ -218,8 +219,7 @@ class StreamGalleryAttachment extends StatelessWidget { ); } - Widget _buildForFourOrMore( - BuildContext context, List attachments) { + Widget _buildForFourOrMore(BuildContext context, List attachments) { final pattern = >[]; final children = []; diff --git a/packages/stream_chat_flutter/lib/src/attachment/giphy_attachment.dart b/packages/stream_chat_flutter/lib/src/attachment/giphy_attachment.dart index e370cd00bf..8c1690b812 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/giphy_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/giphy_attachment.dart @@ -58,7 +58,8 @@ class StreamGiphyAttachment extends StatelessWidget { final chatTheme = StreamChatTheme.of(context); final colorTheme = chatTheme.colorTheme; - final shape = this.shape ?? + final shape = + this.shape ?? RoundedRectangleBorder( side: BorderSide( color: colorTheme.borders, diff --git a/packages/stream_chat_flutter/lib/src/attachment/handler/common.dart b/packages/stream_chat_flutter/lib/src/attachment/handler/common.dart index 8c58fe2dc3..256c8ac6da 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/handler/common.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/handler/common.dart @@ -78,8 +78,7 @@ Future downloadAttachmentData( queryParameters: queryParameters, cancelToken: cancelToken, // set responseType to `bytes` - options: options?.copyWith(responseType: ResponseType.bytes) ?? - Options(responseType: ResponseType.bytes), + options: options?.copyWith(responseType: ResponseType.bytes) ?? Options(responseType: ResponseType.bytes), ); final bytes = Uint8List.fromList(response.data!); diff --git a/packages/stream_chat_flutter/lib/src/attachment/handler/stream_attachment_handler_html.dart b/packages/stream_chat_flutter/lib/src/attachment/handler/stream_attachment_handler_html.dart index 3a9de9dde3..ef3568b165 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/handler/stream_attachment_handler_html.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/handler/stream_attachment_handler_html.dart @@ -11,8 +11,7 @@ class StreamAttachmentHandler extends StreamAttachmentHandlerBase { /// Returns the singleton instance of [StreamAttachmentHandler]. // ignore: prefer_constructors_over_static_methods - static StreamAttachmentHandler get instance => - _instance ??= StreamAttachmentHandler._(); + static StreamAttachmentHandler get instance => _instance ??= StreamAttachmentHandler._(); late final _filePicker = FilePicker.platform; diff --git a/packages/stream_chat_flutter/lib/src/attachment/handler/stream_attachment_handler_io.dart b/packages/stream_chat_flutter/lib/src/attachment/handler/stream_attachment_handler_io.dart index 8e209ea8a2..8ad12ddd3e 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/handler/stream_attachment_handler_io.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/handler/stream_attachment_handler_io.dart @@ -64,8 +64,7 @@ class StreamAttachmentHandler extends StreamAttachmentHandlerBase { /// Returns the singleton instance of [StreamAttachmentHandler]. // ignore: prefer_constructors_over_static_methods - static StreamAttachmentHandler get instance => - _instance ??= StreamAttachmentHandler._(); + static StreamAttachmentHandler get instance => _instance ??= StreamAttachmentHandler._(); late final _imagePicker = ImagePicker(); late final _filePicker = FilePicker.platform; @@ -140,9 +139,7 @@ class StreamAttachmentHandler extends StreamAttachmentHandlerBase { final tempDir = await getTemporaryDirectory(); final tempPath = Uri.file(tempDir.path, windows: CurrentPlatform.isWindows); - final tempFilePath = tempPath - .resolve(fileName!) - .toFilePath(windows: CurrentPlatform.isWindows); + final tempFilePath = tempPath.resolve(fileName!).toFilePath(windows: CurrentPlatform.isWindows); final attachmentFileBytes = attachmentFile.bytes; if (attachmentFileBytes == null) { diff --git a/packages/stream_chat_flutter/lib/src/attachment/image_attachment.dart b/packages/stream_chat_flutter/lib/src/attachment/image_attachment.dart index 5a3837b487..f213264fe6 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/image_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/image_attachment.dart @@ -12,9 +12,7 @@ class StreamImageAttachment extends StatelessWidget { required this.image, this.shape, this.constraints = const BoxConstraints(), - this.imageThumbnailSize, - this.imageThumbnailResizeType = 'clip', - this.imageThumbnailCropType = 'center', + this.resize, }); /// The [Message] that the image is attached to. @@ -31,18 +29,14 @@ class StreamImageAttachment extends StatelessWidget { /// The constraints to use when displaying the image. final BoxConstraints constraints; - /// Size of the attachment image thumbnail. - final Size? imageThumbnailSize; - - /// Resize type of the image attachment thumbnail. + /// The resize configuration for the image attachment thumbnail. /// - /// Defaults to [crop] - final String /*clip|crop|scale|fill*/ imageThumbnailResizeType; - - /// Crop type of the image attachment thumbnail. + /// When provided, its [ImageResize.width] and [ImageResize.height] are used + /// directly as the CDN resize dimensions. /// - /// Defaults to [center] - final String /*center|top|bottom|left|right*/ imageThumbnailCropType; + /// When null, the size is auto-calculated from the layout constraints + /// and defaults to [ResizeMode.clip] and [CropMode.center]. + final ImageResize? resize; @override Widget build(BuildContext context) { @@ -62,7 +56,8 @@ class StreamImageAttachment extends StatelessWidget { final chatTheme = StreamChatTheme.of(context); final colorTheme = chatTheme.colorTheme; - final shape = this.shape ?? + final shape = + this.shape ?? RoundedRectangleBorder( side: BorderSide( color: colorTheme.borders, @@ -85,9 +80,7 @@ class StreamImageAttachment extends StatelessWidget { fit: fit, width: double.infinity, height: double.infinity, - thumbnailSize: imageThumbnailSize, - thumbnailResizeType: imageThumbnailResizeType, - thumbnailCropType: imageThumbnailCropType, + resize: resize, ), Padding( padding: const EdgeInsets.all(8), diff --git a/packages/stream_chat_flutter/lib/src/attachment/poll_attachment.dart b/packages/stream_chat_flutter/lib/src/attachment/poll_attachment.dart index 231d4ad455..a176bcfa64 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/poll_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/poll_attachment.dart @@ -62,7 +62,8 @@ class _PollAttachmentState extends State { Widget build(BuildContext context) { final theme = StreamChatTheme.of(context); - final shape = widget.shape ?? + final shape = + widget.shape ?? RoundedRectangleBorder( side: BorderSide( color: theme.colorTheme.borders, diff --git a/packages/stream_chat_flutter/lib/src/attachment/thumbnail/file_attachment_thumbnail.dart b/packages/stream_chat_flutter/lib/src/attachment/thumbnail/file_attachment_thumbnail.dart index b6f14b291c..a3b8472cb8 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/thumbnail/file_attachment_thumbnail.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/thumbnail/file_attachment_thumbnail.dart @@ -54,17 +54,17 @@ class StreamFileAttachmentThumbnail extends StatelessWidget { return switch (mediaType?.type) { AttachmentType.image => StreamImageAttachmentThumbnail( - image: file, - width: width, - height: height, - fit: fit, - ), + image: file, + width: width, + height: height, + fit: fit, + ), AttachmentType.video => StreamVideoAttachmentThumbnail( - video: file, - width: width, - height: height, - fit: fit, - ), + video: file, + width: width, + height: height, + fit: fit, + ), // Return a generic file type icon. _ => getFileTypeImage(mediaType?.mimeType), }; diff --git a/packages/stream_chat_flutter/lib/src/attachment/thumbnail/image_attachment_thumbnail.dart b/packages/stream_chat_flutter/lib/src/attachment/thumbnail/image_attachment_thumbnail.dart index 55474beae4..9c05cb70dc 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/thumbnail/image_attachment_thumbnail.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/thumbnail/image_attachment_thumbnail.dart @@ -5,7 +5,9 @@ import 'package:flutter/material.dart'; import 'package:shimmer/shimmer.dart'; import 'package:stream_chat_flutter/src/attachment/thumbnail/thumbnail_error.dart'; import 'package:stream_chat_flutter/src/attachment/thumbnail/thumbnail_size_calculator.dart'; +import 'package:stream_chat_flutter/src/stream_chat_configuration.dart'; import 'package:stream_chat_flutter/src/theme/stream_chat_theme.dart'; +import 'package:stream_chat_flutter/src/utils/stream_image_cdn.dart'; import 'package:stream_chat_flutter/src/utils/utils.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; @@ -22,9 +24,7 @@ class StreamImageAttachmentThumbnail extends StatelessWidget { this.width, this.height, this.fit, - this.thumbnailSize, - this.thumbnailResizeType = 'clip', - this.thumbnailCropType = 'center', + this.resize, this.errorBuilder = _defaultErrorBuilder, }); @@ -40,18 +40,14 @@ class StreamImageAttachmentThumbnail extends StatelessWidget { /// Fit of the attachment image thumbnail. final BoxFit? fit; - /// Size of the attachment image thumbnail. - final Size? thumbnailSize; - - /// Resize type of the image attachment thumbnail. + /// The resize configuration for the image attachment thumbnail. /// - /// Defaults to [crop] - final String /*clip|crop|scale|fill*/ thumbnailResizeType; - - /// Crop type of the image attachment thumbnail. + /// When provided, its [ImageResize.width] and [ImageResize.height] are used + /// directly as the CDN resize dimensions. /// - /// Defaults to [center] - final String /*center|top|bottom|left|right*/ thumbnailCropType; + /// When null, the size is auto-calculated from the layout constraints + /// and defaults to [ResizeMode.clip] and [CropMode.center]. + final ImageResize? resize; /// Builder used when the thumbnail fails to load. final ThumbnailErrorBuilder errorBuilder; @@ -75,35 +71,31 @@ class StreamImageAttachmentThumbnail extends StatelessWidget { Widget build(BuildContext context) { return LayoutBuilder( builder: (context, constraints) { - // Calculate optimal thumbnail size once for all paths - final effectiveThumbnailSize = switch (thumbnailSize) { - final thumbnailSize? => thumbnailSize, - _ => ThumbnailSizeCalculator.calculate( - targetSize: constraints.biggest, - originalSize: image.originalSize, - pixelRatio: MediaQuery.devicePixelRatioOf(context), - ), - }; - - final cacheWidth = effectiveThumbnailSize?.width.round(); - final cacheHeight = effectiveThumbnailSize?.height.round(); + var effectiveResize = resize; + if (effectiveResize == null) { + final size = ThumbnailSizeCalculator.calculate( + targetSize: constraints.biggest, + originalSize: image.originalSize, + pixelRatio: MediaQuery.devicePixelRatioOf(context), + ); + + if (size != null) effectiveResize = .new(width: size.width, height: size.height); + } + + final cacheWidth = effectiveResize?.width.round(); + final cacheHeight = effectiveResize?.height.round(); // If the remote image URL is available, we can directly show it using // the _RemoteImageAttachment widget. final imageUrl = image.thumbUrl ?? image.imageUrl ?? image.assetUrl; if (imageUrl case final imageUrl?) { - var resizedImageUrl = imageUrl; - if (effectiveThumbnailSize case final thumbnailSize?) { - resizedImageUrl = imageUrl.getResizedImageUrl( - crop: thumbnailCropType, - resize: thumbnailResizeType, - width: thumbnailSize.width, - height: thumbnailSize.height, - ); - } + final imageCDN = StreamChatConfiguration.maybeOf(context)?.imageCDN ?? const StreamImageCDN(); + final resolvedUrl = imageCDN.resolveUrl(imageUrl, resize: effectiveResize); + final resolvedCacheKey = imageCDN.cacheKey(resolvedUrl); return _RemoteImageAttachment( - url: resizedImageUrl, + url: resolvedUrl, + cacheKey: resolvedCacheKey, width: width, height: height, fit: fit, @@ -195,6 +187,7 @@ class _LocalImageAttachment extends StatelessWidget { class _RemoteImageAttachment extends StatelessWidget { const _RemoteImageAttachment({ required this.url, + this.cacheKey, required this.errorBuilder, this.width, this.height, @@ -204,6 +197,7 @@ class _RemoteImageAttachment extends StatelessWidget { }); final String url; + final String? cacheKey; final double? width; final double? height; final int? cacheWidth; @@ -215,6 +209,7 @@ class _RemoteImageAttachment extends StatelessWidget { Widget build(BuildContext context) { return CachedNetworkImage( imageUrl: url, + cacheKey: cacheKey, width: width, height: height, memCacheWidth: cacheWidth, diff --git a/packages/stream_chat_flutter/lib/src/attachment/thumbnail/media_attachment_thumbnail.dart b/packages/stream_chat_flutter/lib/src/attachment/thumbnail/media_attachment_thumbnail.dart index ab8c60c571..b2fb2dab5f 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/thumbnail/media_attachment_thumbnail.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/thumbnail/media_attachment_thumbnail.dart @@ -3,6 +3,7 @@ import 'package:stream_chat_flutter/src/attachment/thumbnail/giphy_attachment_th import 'package:stream_chat_flutter/src/attachment/thumbnail/image_attachment_thumbnail.dart'; import 'package:stream_chat_flutter/src/attachment/thumbnail/thumbnail_error.dart'; import 'package:stream_chat_flutter/src/attachment/thumbnail/video_attachment_thumbnail.dart'; +import 'package:stream_chat_flutter/src/utils/stream_image_cdn.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; /// {@template mediaAttachmentThumbnail} @@ -24,9 +25,7 @@ class StreamMediaAttachmentThumbnail extends StatelessWidget { this.width, this.height, this.fit, - this.thumbnailSize, - this.thumbnailResizeType = 'clip', - this.thumbnailCropType = 'center', + this.resize, this.gifInfoType = GiphyInfoType.original, this.errorBuilder = _defaultErrorBuilder, }); @@ -46,24 +45,16 @@ class StreamMediaAttachmentThumbnail extends StatelessWidget { /// Builder used when the thumbnail fails to load. final ThumbnailErrorBuilder errorBuilder; - /// Size of the attachment image thumbnail. + /// The resize configuration for the image attachment thumbnail. /// - /// Ignored if the [Attachment.type] is not [AttachmentType.image]. - final Size? thumbnailSize; - - /// Resize type of the image attachment thumbnail. - /// - /// Defaults to [crop] - /// - /// Ignored if the [Attachment.type] is not [AttachmentType.image]. - final String /*clip|crop|scale|fill*/ thumbnailResizeType; - - /// Crop type of the image attachment thumbnail. + /// When provided, its [ImageResize.width] and [ImageResize.height] are used + /// directly as the CDN resize dimensions. /// - /// Defaults to [center] + /// When null, the size is auto-calculated from the layout constraints + /// and defaults to [ResizeMode.clip] and [CropMode.center]. /// /// Ignored if the [Attachment.type] is not [AttachmentType.image]. - final String /*center|top|bottom|left|right*/ thumbnailCropType; + final ImageResize? resize; /// The type of giphy thumbnail to build. /// @@ -94,9 +85,7 @@ class StreamMediaAttachmentThumbnail extends StatelessWidget { width: width, height: height, fit: fit, - thumbnailSize: thumbnailSize, - thumbnailResizeType: thumbnailResizeType, - thumbnailCropType: thumbnailCropType, + resize: resize, errorBuilder: errorBuilder, ); } diff --git a/packages/stream_chat_flutter/lib/src/attachment/thumbnail/thumbnail_error.dart b/packages/stream_chat_flutter/lib/src/attachment/thumbnail/thumbnail_error.dart index d5f5a4ae56..4ed0b4ed39 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/thumbnail/thumbnail_error.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/thumbnail/thumbnail_error.dart @@ -6,11 +6,12 @@ import 'package:flutter/material.dart'; /// The parameters represent the [BuildContext], [error] and [stackTrace] of the /// error that triggered this callback. /// {@endtemplate} -typedef ThumbnailErrorBuilder = Widget Function( - BuildContext context, - Object error, - StackTrace? stackTrace, -); +typedef ThumbnailErrorBuilder = + Widget Function( + BuildContext context, + Object error, + StackTrace? stackTrace, + ); /// {@template thumbnailError} /// A widget that shows an error state when a thumbnail fails to load. diff --git a/packages/stream_chat_flutter/lib/src/attachment/url_attachment.dart b/packages/stream_chat_flutter/lib/src/attachment/url_attachment.dart index 6d8629ea4c..31a018a316 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/url_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/url_attachment.dart @@ -40,7 +40,8 @@ class StreamUrlAttachment extends StatelessWidget { Widget build(BuildContext context) { final chatTheme = StreamChatTheme.of(context); final colorTheme = chatTheme.colorTheme; - final shape = this.shape ?? + final shape = + this.shape ?? RoundedRectangleBorder( side: BorderSide( color: colorTheme.borders, @@ -105,37 +106,41 @@ class StreamUrlAttachment extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.stretch, children: [ if (urlAttachment.title != null) - Builder(builder: (context) { - final maxLines = messageTheme.urlAttachmentTitleMaxLine; + Builder( + builder: (context) { + final maxLines = messageTheme.urlAttachmentTitleMaxLine; - TextOverflow? overflow; - if (maxLines != null && maxLines > 0) { - overflow = TextOverflow.ellipsis; - } + TextOverflow? overflow; + if (maxLines != null && maxLines > 0) { + overflow = TextOverflow.ellipsis; + } - return Text( - urlAttachment.title!.trim(), - maxLines: maxLines, - overflow: overflow, - style: messageTheme.urlAttachmentTitleStyle, - ); - }), + return Text( + urlAttachment.title!.trim(), + maxLines: maxLines, + overflow: overflow, + style: messageTheme.urlAttachmentTitleStyle, + ); + }, + ), if (urlAttachment.text != null) - Builder(builder: (context) { - final maxLines = messageTheme.urlAttachmentTextMaxLine; + Builder( + builder: (context) { + final maxLines = messageTheme.urlAttachmentTextMaxLine; - TextOverflow? overflow; - if (maxLines != null && maxLines > 0) { - overflow = TextOverflow.ellipsis; - } + TextOverflow? overflow; + if (maxLines != null && maxLines > 0) { + overflow = TextOverflow.ellipsis; + } - return Text( - urlAttachment.text!, - maxLines: maxLines, - overflow: overflow, - style: messageTheme.urlAttachmentTextStyle, - ); - }), + return Text( + urlAttachment.text!, + maxLines: maxLines, + overflow: overflow, + style: messageTheme.urlAttachmentTextStyle, + ); + }, + ), ], ), ), diff --git a/packages/stream_chat_flutter/lib/src/attachment/video_attachment.dart b/packages/stream_chat_flutter/lib/src/attachment/video_attachment.dart index a79d44c77f..b36d49ef0e 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/video_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/video_attachment.dart @@ -32,7 +32,8 @@ class StreamVideoAttachment extends StatelessWidget { Widget build(BuildContext context) { final chatTheme = StreamChatTheme.of(context); final colorTheme = chatTheme.colorTheme; - final shape = this.shape ?? + final shape = + this.shape ?? RoundedRectangleBorder( side: BorderSide( color: colorTheme.borders, diff --git a/packages/stream_chat_flutter/lib/src/attachment/voice_recording_attachment.dart b/packages/stream_chat_flutter/lib/src/attachment/voice_recording_attachment.dart index 3681d30939..f4805549c2 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/voice_recording_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/voice_recording_attachment.dart @@ -1,22 +1,23 @@ import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/src/audio/audio_playlist_state.dart'; import 'package:stream_chat_flutter/src/audio/audio_sampling.dart' as sampling; -import 'package:stream_chat_flutter/src/misc/audio_waveform.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; const _kDefaultWaveformLimit = 35; -const _kDefaultWaveformHeight = 28.0; +const _kDefaultWaveformHeight = 20.0; /// Signature for building trailing widgets in voice recording attachments. /// /// Provides a flexible way to customize the trailing section of the /// voice recording player based on the current track and playback state. -typedef StreamVoiceRecordingAttachmentTrailingWidgetBuilder = Widget Function( - BuildContext context, - PlaylistTrack track, - PlaybackSpeed speed, - ValueChanged? onChangeSpeed, -); +typedef StreamVoiceRecordingAttachmentTrailingWidgetBuilder = + Widget Function( + BuildContext context, + PlaylistTrack track, + PlaybackSpeed speed, + ValueChanged? onChangeSpeed, + ); /// {@template streamVoiceRecordingAttachment} /// An embedded audio player for voice recordings with comprehensive playback @@ -46,7 +47,9 @@ class StreamVoiceRecordingAttachment extends StatelessWidget { this.shape, this.constraints = const BoxConstraints(), this.showTitle = false, + this.title, this.trailingBuilder = _defaultTrailingBuilder, + this.onRemovePressed, }); /// The audio track to display. @@ -84,6 +87,10 @@ class StreamVoiceRecordingAttachment extends StatelessWidget { /// The constraints to use when displaying the voice recording. final BoxConstraints constraints; + /// The title of the audio message to display when [showTitle] is `true`. + /// If not provided, the [track.title] will be used. + final String? title; + /// Whether to show the title of the audio message. /// /// Defaults to `false`. @@ -92,48 +99,36 @@ class StreamVoiceRecordingAttachment extends StatelessWidget { /// The builder to use for the trailing widget. final StreamVoiceRecordingAttachmentTrailingWidgetBuilder trailingBuilder; + /// Callback called when the remove button is pressed. + /// If not provided, the remove button will not be shown. + final VoidCallback? onRemovePressed; + static Widget _defaultTrailingBuilder( BuildContext context, PlaylistTrack track, PlaybackSpeed speed, ValueChanged? onChangeSpeed, ) { - return AnimatedSwitcher( - duration: const Duration(milliseconds: 200), - child: switch (track.state.isPlaying) { - true => SpeedControlButton( - speed: speed, - onChangeSpeed: onChangeSpeed, - ), - false => getFileTypeImage(track.title?.mediaType?.mimeType), - }, + return SpeedControlButton( + speed: speed, + onChangeSpeed: onChangeSpeed, ); } @override Widget build(BuildContext context) { + final colorScheme = context.streamColorScheme; final theme = StreamVoiceRecordingAttachmentTheme.of(context); - final waveformSliderTheme = theme.audioWaveformSliderTheme; - final waveformTheme = waveformSliderTheme?.audioWaveformTheme; - - final shape = this.shape ?? - RoundedRectangleBorder( - side: BorderSide( - color: StreamChatTheme.of(context).colorTheme.borders, - strokeAlign: BorderSide.strokeAlignOutside, - ), - borderRadius: BorderRadius.circular(14), - ); - - return Container( - constraints: constraints, - clipBehavior: Clip.hardEdge, - padding: const EdgeInsets.all(8), - decoration: ShapeDecoration( - shape: shape, - color: theme.backgroundColor, - ), + final textTheme = context.streamTextTheme; + final spacing = context.streamSpacing; + + return StreamMessageComposerAttachmentContainer( + borderColor: colorScheme.borderDefault, + onRemovePressed: onRemovePressed, + backgroundColor: theme.backgroundColor, + padding: EdgeInsets.all(spacing.sm), child: Row( + crossAxisAlignment: .center, children: [ AudioControlButton( state: track.state, @@ -147,21 +142,22 @@ class StreamVoiceRecordingAttachment extends StatelessWidget { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (track.title case final title? when showTitle) ...[ + if (title ?? track.title case final title? when showTitle) AudioTitleText( title: title, - style: theme.titleTextStyle, + style: theme.titleTextStyle ?? textTheme.metadataEmphasis, ), - const SizedBox(height: 6), - ], + Row( children: [ AudioDurationText( duration: track.duration, position: track.position, - style: theme.durationTextStyle, + style: + theme.durationTextStyle ?? + textTheme.metadataEmphasis.copyWith(color: colorScheme.textSecondary), ), - const SizedBox(width: 8), + SizedBox(width: spacing.xs), Expanded( child: SizedBox( height: _kDefaultWaveformHeight, @@ -175,14 +171,7 @@ class StreamVoiceRecordingAttachment extends StatelessWidget { onChangeStart: onTrackSeekStart, onChanged: onTrackSeekChanged, onChangeEnd: onTrackSeekEnd, - color: waveformTheme?.color, - progressColor: waveformTheme?.progressColor, - minBarHeight: waveformTheme?.minBarHeight, - spacingRatio: waveformTheme?.spacingRatio, - heightScale: waveformTheme?.heightScale, - thumbColor: waveformSliderTheme?.thumbColor, - thumbBorderColor: - waveformSliderTheme?.thumbBorderColor, + isActive: track.state != TrackState.idle, ), ), ), @@ -284,6 +273,9 @@ class AudioControlButton extends StatelessWidget { this.onPlay, this.onPause, this.onReplay, + this.style = .secondary, + this.type = .outline, + this.size = .medium, }); /// The current state of the audio track. @@ -298,24 +290,35 @@ class AudioControlButton extends StatelessWidget { /// Callback when the track is replayed. final VoidCallback? onReplay; + /// The style of the button. + final StreamButtonStyle style; + + /// The type of the button. + final StreamButtonType type; + + /// The size of the button. + final StreamButtonSize size; + @override Widget build(BuildContext context) { - final theme = StreamVoiceRecordingAttachmentTheme.of(context); + final icons = context.streamIcons; - return ElevatedButton( - style: theme.audioControlButtonStyle, - onPressed: switch (state) { + return StreamButton.icon( + style: style, + type: type, + size: size, + icon: switch (state) { + TrackState.loading => icons.playSolid, + TrackState.idle => icons.playSolid, + TrackState.playing => icons.pause, + TrackState.paused => icons.playSolid, + }, + onTap: switch (state) { TrackState.loading => null, TrackState.idle => onPlay, TrackState.playing => onPause, TrackState.paused => onPlay, }, - child: switch (state) { - TrackState.loading => theme.loadingIndicator, - TrackState.idle => theme.playIcon, - TrackState.playing => theme.pauseIcon, - TrackState.paused => theme.playIcon, - }, ); } } @@ -342,14 +345,35 @@ class SpeedControlButton extends StatelessWidget { @override Widget build(BuildContext context) { final theme = StreamVoiceRecordingAttachmentTheme.of(context); + final colorScheme = context.streamColorScheme; + final textTheme = context.streamTextTheme; + + final buttonStyle = + theme.speedControlButtonStyle ?? + ElevatedButton.styleFrom( + elevation: 2, + textStyle: textTheme.metadataEmphasis, + foregroundColor: colorScheme.textPrimary, + padding: const EdgeInsets.symmetric(horizontal: 8), + shape: StadiumBorder(side: BorderSide(color: colorScheme.borderDefault)), + tapTargetSize: MaterialTapTargetSize.shrinkWrap, + minimumSize: const Size(40, 28), + ); - return ElevatedButton( - style: theme.speedControlButtonStyle, + return TextButton( + style: buttonStyle, onPressed: switch (onChangeSpeed) { final it? => () => it(speed.next), _ => null, }, - child: Text('x${speed.speed}'), + child: ConstrainedBox( + constraints: const BoxConstraints(minWidth: 28), + child: Text( + 'x${speed.speed.toString().replaceFirst('.0', '')}', + style: textTheme.metadataEmphasis.copyWith(height: 1), + textAlign: TextAlign.center, + ), + ), ); } } diff --git a/packages/stream_chat_flutter/lib/src/attachment/voice_recording_attachment_playlist.dart b/packages/stream_chat_flutter/lib/src/attachment/voice_recording_attachment_playlist.dart index 2bd55a8023..9fede75283 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/voice_recording_attachment_playlist.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/voice_recording_attachment_playlist.dart @@ -19,6 +19,8 @@ class StreamVoiceRecordingAttachmentPlaylist extends StatefulWidget { this.itemBuilder, this.separatorBuilder = _defaultVoiceRecordingPlaylistSeparatorBuilder, this.constraints = const BoxConstraints(), + this.onRemovePressed, + this.voiceRecordingTitle, }); /// The shape of the attachment. @@ -47,6 +49,12 @@ class StreamVoiceRecordingAttachmentPlaylist extends StatefulWidget { /// The separator to use between the voice recordings. final IndexedWidgetBuilder separatorBuilder; + /// Callback called when the remove button is pressed. + final ValueSetter? onRemovePressed; + + /// The title to use for the voice recording. + final String? voiceRecordingTitle; + // Default separator builder for the voice recording playlist. static Widget _defaultVoiceRecordingPlaylistSeparatorBuilder( BuildContext context, @@ -56,12 +64,10 @@ class StreamVoiceRecordingAttachmentPlaylist extends StatefulWidget { } @override - State createState() => - _StreamVoiceRecordingAttachmentPlaylistState(); + State createState() => _StreamVoiceRecordingAttachmentPlaylistState(); } -class _StreamVoiceRecordingAttachmentPlaylistState - extends State { +class _StreamVoiceRecordingAttachmentPlaylistState extends State { late final _controller = StreamAudioPlaylistController( widget.voiceRecordings.toPlaylist(), ); @@ -113,10 +119,18 @@ class _StreamVoiceRecordingAttachmentPlaylistState } final track = state.tracks[index]; + Attachment? attachment; + if (track.key is Attachment) { + attachment = track.key as Attachment?; + } return StreamVoiceRecordingAttachment( track: track, speed: state.speed, showTitle: true, + title: widget.voiceRecordingTitle, + onRemovePressed: attachment != null && widget.onRemovePressed != null + ? () => widget.onRemovePressed?.call(attachment!) + : null, shape: widget.shape, constraints: widget.constraints, onTrackPause: _controller.pause, diff --git a/packages/stream_chat_flutter/lib/src/attachment_actions_modal/attachment_actions_modal.dart b/packages/stream_chat_flutter/lib/src/attachment_actions_modal/attachment_actions_modal.dart index c36963a7aa..61b5bf44b7 100644 --- a/packages/stream_chat_flutter/lib/src/attachment_actions_modal/attachment_actions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/attachment_actions_modal/attachment_actions_modal.dart @@ -105,148 +105,149 @@ class AttachmentActionsModal extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.end, mainAxisSize: MainAxisSize.min, - children: [ - if (showReply) - _buildButton( - context, - context.translations.replyLabel, - StreamSvgIcon( - size: 24, - icon: StreamSvgIcons.reply, - color: theme.colorTheme.textLowEmphasis, - ), - onReply, - ), - if (showShowInChat) - _buildButton( - context, - context.translations.showInChatLabel, - StreamSvgIcon( - size: 24, - icon: StreamSvgIcons.eye, - color: theme.colorTheme.textHighEmphasis, - ), - onShowMessage, - ), - if (showSave) - _buildButton( - context, - attachment.type == AttachmentType.video - ? context.translations.saveVideoLabel - : context.translations.saveImageLabel, - StreamSvgIcon( - size: 24, - icon: StreamSvgIcons.save, - color: theme.colorTheme.textLowEmphasis, - ), - () { - // Closing attachment actions modal before opening - // attachment download dialog - Navigator.of(context).pop(); - - final downloader = attachmentDownloader ?? - StreamAttachmentHandler.instance.downloadAttachment; - - // No need to show progress dialog in case of - // web or desktop. - if (isDesktopDeviceOrWeb) { - downloader(attachment); - return; - } - - final progressNotifier = - ValueNotifier<_DownloadProgress?>( - _DownloadProgress.initial(), - ); - - final downloadedPathNotifier = - ValueNotifier(null); - - downloader( - attachment, - onReceiveProgress: (received, total) { - progressNotifier.value = _DownloadProgress( - total, - received, - ); - }, - ).then((path) { - downloadedPathNotifier.value = path; - }).catchError((e, stk) { - print(e); - print(stk); - progressNotifier.value = null; - }); - - showDialog( - barrierDismissible: false, - context: context, - barrierColor: theme.colorTheme.overlay, - builder: (context) => _buildDownloadProgressDialog( - context, - progressNotifier, - downloadedPathNotifier, + children: + [ + if (showReply) + _buildButton( + context, + context.translations.replyLabel, + Icon( + context.streamIcons.arrowShareLeft, + size: 24, + color: theme.colorTheme.textLowEmphasis, + ), + onReply, + ), + if (showShowInChat) + _buildButton( + context, + context.translations.showInChatLabel, + Icon( + context.streamIcons.eyeOpen, + size: 24, + color: theme.colorTheme.textHighEmphasis, + ), + onShowMessage, + ), + if (showSave) + _buildButton( + context, + attachment.type == AttachmentType.video + ? context.translations.saveVideoLabel + : context.translations.saveImageLabel, + Icon( + context.streamIcons.bookmark, + size: 24, + color: theme.colorTheme.textLowEmphasis, + ), + () { + // Closing attachment actions modal before opening + // attachment download dialog + Navigator.of(context).pop(); + + final downloader = + attachmentDownloader ?? StreamAttachmentHandler.instance.downloadAttachment; + + // No need to show progress dialog in case of + // web or desktop. + if (isDesktopDeviceOrWeb) { + downloader(attachment); + return; + } + + final progressNotifier = ValueNotifier<_DownloadProgress?>( + _DownloadProgress.initial(), + ); + + final downloadedPathNotifier = ValueNotifier(null); + + downloader( + attachment, + onReceiveProgress: (received, total) { + progressNotifier.value = _DownloadProgress( + total, + received, + ); + }, + ) + .then((path) { + downloadedPathNotifier.value = path; + }) + .catchError((e, stk) { + print(e); + print(stk); + progressNotifier.value = null; + }); + + showDialog( + barrierDismissible: false, + context: context, + barrierColor: theme.colorTheme.overlay, + builder: (context) => _buildDownloadProgressDialog( + context, + progressNotifier, + downloadedPathNotifier, + ), + ); + }, + ), + if (StreamChat.of(context).currentUser?.id == message.user?.id && showDelete) + _buildButton( + context, + context.translations.deleteLabel.sentenceCase, + Icon( + context.streamIcons.trashBin, + size: 24, + color: theme.colorTheme.accentError, + ), + () { + final channel = StreamChannel.of(context).channel; + if (message.attachments.length > 1 || message.text?.isNotEmpty == true) { + final currentAttachmentIndex = message.attachments.indexWhere( + (element) => element.id == attachment.id, + ); + final remainingAttachments = [...message.attachments] + ..removeAt(currentAttachmentIndex); + channel.updateMessage( + message.copyWith( + attachments: remainingAttachments, + ), + ); + Navigator.of(context) + ..pop() + ..maybePop(); + } else { + channel.deleteMessage(message); + Navigator.of(context) + ..pop() + ..maybePop(); + } + }, + color: theme.colorTheme.accentError, + ), + ...customActions + .map( + (e) => _buildButton( + context, + e.actionTitle, + e.icon, + e.onTap, + ), + ) + .toList(), + ] + .map( + (e) => Align( + alignment: Alignment.centerRight, + child: e, + ), + ) + .insertBetween( + Container( + height: 1, + color: theme.colorTheme.borders, ), - ); - }, - ), - if (StreamChat.of(context).currentUser?.id == - message.user?.id && - showDelete) - _buildButton( - context, - context.translations.deleteLabel.sentenceCase, - StreamSvgIcon( - size: 24, - icon: StreamSvgIcons.delete, - color: theme.colorTheme.accentError, - ), - () { - final channel = StreamChannel.of(context).channel; - if (message.attachments.length > 1 || - message.text?.isNotEmpty == true) { - final currentAttachmentIndex = - message.attachments.indexWhere( - (element) => element.id == attachment.id, - ); - final remainingAttachments = [...message.attachments] - ..removeAt(currentAttachmentIndex); - channel.updateMessage(message.copyWith( - attachments: remainingAttachments, - )); - Navigator.of(context) - ..pop() - ..maybePop(); - } else { - channel.deleteMessage(message); - Navigator.of(context) - ..pop() - ..maybePop(); - } - }, - color: theme.colorTheme.accentError, - ), - ...customActions - .map( - (e) => _buildButton( - context, - e.actionTitle, - e.icon, - e.onTap, ), - ) - .toList(), - ] - .map((e) => Align( - alignment: Alignment.centerRight, - child: e, - )) - .insertBetween( - Container( - height: 1, - color: theme.colorTheme.borders, - ), - ), ), ), ), @@ -276,10 +277,7 @@ class AttachmentActionsModal extends StatelessWidget { const SizedBox(width: 16), Text( title, - style: StreamChatTheme.of(context) - .textTheme - .body - .copyWith(color: color), + style: StreamChatTheme.of(context).textTheme.body.copyWith(color: color), ), ], ), @@ -330,46 +328,44 @@ class AttachmentActionsModal extends StatelessWidget { ? SizedBox( height: 100, width: 100, - child: StreamSvgIcon( - icon: StreamSvgIcons.error, + child: Icon( + context.streamIcons.exclamationCircle1, color: theme.colorTheme.disabled, ), ) : _downloadComplete - ? SizedBox( - key: const Key('completedIcon'), - height: 160, - width: 160, - child: StreamSvgIcon( - icon: StreamSvgIcons.check, - color: theme.colorTheme.disabled, + ? SizedBox( + key: const Key('completedIcon'), + height: 160, + width: 160, + child: Icon( + context.streamIcons.checkmark2, + color: theme.colorTheme.disabled, + ), + ) + : SizedBox( + height: 100, + width: 100, + child: Stack( + fit: StackFit.expand, + children: [ + CircularProgressIndicator.adaptive( + strokeWidth: 8, + valueColor: AlwaysStoppedAnimation( + theme.colorTheme.accentPrimary, + ), ), - ) - : SizedBox( - height: 100, - width: 100, - child: Stack( - fit: StackFit.expand, - children: [ - CircularProgressIndicator.adaptive( - strokeWidth: 8, - valueColor: AlwaysStoppedAnimation( - theme.colorTheme.accentPrimary, - ), - ), - Center( - child: Text( - '${progress.receivedValueInMB} MB', - style: - theme.textTheme.headline.copyWith( - color: - theme.colorTheme.textLowEmphasis, - ), - ), + Center( + child: Text( + '${progress.receivedValueInMB} MB', + style: theme.textTheme.headline.copyWith( + color: theme.colorTheme.textLowEmphasis, ), - ], + ), ), - ), + ], + ), + ), ), ), ), @@ -384,8 +380,7 @@ class AttachmentActionsModal extends StatelessWidget { class _DownloadProgress { const _DownloadProgress(this.total, this.received); - factory _DownloadProgress.initial() => - _DownloadProgress(double.maxFinite.toInt(), 0); + factory _DownloadProgress.initial() => _DownloadProgress(double.maxFinite.toInt(), 0); final int total; final int received; diff --git a/packages/stream_chat_flutter/lib/src/audio/audio_playlist_controller.dart b/packages/stream_chat_flutter/lib/src/audio/audio_playlist_controller.dart index 013aadde1e..1f226972cb 100644 --- a/packages/stream_chat_flutter/lib/src/audio/audio_playlist_controller.dart +++ b/packages/stream_chat_flutter/lib/src/audio/audio_playlist_controller.dart @@ -21,8 +21,8 @@ class StreamAudioPlaylistController extends ValueNotifier { StreamAudioPlaylistController.raw({ AudioPlayer? player, AudioPlaylistState state = const AudioPlaylistState(tracks: []), - }) : _player = player ?? AudioPlayer(), - super(state); + }) : _player = player ?? AudioPlayer(), + super(state); final AudioPlayer _player; @@ -45,21 +45,22 @@ class StreamAudioPlaylistController extends ValueNotifier { final tracks = [ ...value.tracks.mapIndexed((index, track) { final trackState = switch (index == currentIndex) { - true => state.playing - ? TrackState.playing - : switch (state.processingState) { - ProcessingState.idle => TrackState.idle, - ProcessingState.loading => TrackState.loading, - _ => TrackState.paused, - }, + true => + state.playing + ? TrackState.playing + : switch (state.processingState) { + ProcessingState.idle => TrackState.idle, + ProcessingState.loading => TrackState.loading, + _ => TrackState.paused, + }, false => switch (track.state) { - TrackState.idle => TrackState.idle, - _ => TrackState.paused, - }, + TrackState.idle => TrackState.idle, + _ => TrackState.paused, + }, }; return track.copyWith(state: trackState); - }) + }), ]; value = value.copyWith(tracks: tracks); @@ -74,7 +75,7 @@ class StreamAudioPlaylistController extends ValueNotifier { ...value.tracks.mapIndexed((index, track) { if (index != currentIndex) return track; return track.copyWith(position: position); - }) + }), ]; value = value.copyWith(tracks: tracks); diff --git a/packages/stream_chat_flutter/lib/src/audio/audio_playlist_state.dart b/packages/stream_chat_flutter/lib/src/audio/audio_playlist_state.dart index 36a3078037..69b40caa83 100644 --- a/packages/stream_chat_flutter/lib/src/audio/audio_playlist_state.dart +++ b/packages/stream_chat_flutter/lib/src/audio/audio_playlist_state.dart @@ -86,7 +86,8 @@ enum TrackState { playing, /// The track is currently paused. - paused; + paused + ; /// Returns `true` if the track is currently idle. bool get isIdle => this == TrackState.idle; @@ -112,7 +113,8 @@ enum PlaybackSpeed { faster._(1.5), /// The fastest speed of the playback (2x). - fastest._(2); + fastest._(2) + ; const PlaybackSpeed._(this.speed); @@ -147,6 +149,7 @@ class PlaylistTrack { /// {@macro playlistTrack} const PlaylistTrack({ required this.uri, + this.key, this.title, this.waveform = const [], this.duration = Duration.zero, @@ -154,6 +157,9 @@ class PlaylistTrack { this.state = TrackState.idle, }); + /// The key to identify the track. + final Object? key; + /// The uri of the track. final Uri uri; @@ -200,6 +206,7 @@ class PlaylistTrack { TrackState? state, }) { return PlaylistTrack( + key: key, uri: uri ?? this.uri, title: title ?? this.title, duration: duration ?? this.duration, diff --git a/packages/stream_chat_flutter/lib/src/audio/audio_sampling.dart b/packages/stream_chat_flutter/lib/src/audio/audio_sampling.dart index 0381d273eb..943032510d 100644 --- a/packages/stream_chat_flutter/lib/src/audio/audio_sampling.dart +++ b/packages/stream_chat_flutter/lib/src/audio/audio_sampling.dart @@ -36,23 +36,21 @@ List downSample(List data, int targetOutputSize) { final previousBucketRefPoint = data[lastSelectedPointIndex]; final nextBucketMean = _getNextBucketMean(data, bucketIndex, bucketSize); - final currentBucketStartIndex = - ((bucketIndex - 1) * bucketSize).floor() + 1; + final currentBucketStartIndex = ((bucketIndex - 1) * bucketSize).floor() + 1; final nextBucketStartIndex = (bucketIndex * bucketSize).floor() + 1; - final countUnitsBetweenAtoC = - 1 + nextBucketStartIndex - currentBucketStartIndex; + final countUnitsBetweenAtoC = 1 + nextBucketStartIndex - currentBucketStartIndex; var maxArea = -1.0; var triangleArea = -1.0; double? maxAreaPoint; - for (var currentPointIndex = currentBucketStartIndex; - currentPointIndex < nextBucketStartIndex; - currentPointIndex++) { - final countUnitsBetweenAtoB = - (currentPointIndex - currentBucketStartIndex).abs() + 1; - final countUnitsBetweenBtoC = - countUnitsBetweenAtoC - countUnitsBetweenAtoB; + for ( + var currentPointIndex = currentBucketStartIndex; + currentPointIndex < nextBucketStartIndex; + currentPointIndex++ + ) { + final countUnitsBetweenAtoB = (currentPointIndex - currentBucketStartIndex).abs() + 1; + final countUnitsBetweenBtoC = countUnitsBetweenAtoC - countUnitsBetweenAtoB; final currentPointValue = data[currentPointIndex]; triangleArea = _triangleAreaHeron( @@ -107,11 +105,8 @@ double _getNextBucketMean( double bucketSize, ) { final nextBucketStartIndex = (currentBucketIndex * bucketSize).floor() + 1; - var nextNextBucketStartIndex = - ((currentBucketIndex + 1) * bucketSize).floor() + 1; - nextNextBucketStartIndex = nextNextBucketStartIndex < data.length - ? nextNextBucketStartIndex - : data.length; + var nextNextBucketStartIndex = ((currentBucketIndex + 1) * bucketSize).floor() + 1; + nextNextBucketStartIndex = nextNextBucketStartIndex < data.length ? nextNextBucketStartIndex : data.length; return _mean(data.sublist(nextBucketStartIndex, nextNextBucketStartIndex)); } diff --git a/packages/stream_chat_flutter/lib/src/autocomplete/stream_autocomplete.dart b/packages/stream_chat_flutter/lib/src/autocomplete/stream_autocomplete.dart index 98487690c2..9cbcc3d662 100644 --- a/packages/stream_chat_flutter/lib/src/autocomplete/stream_autocomplete.dart +++ b/packages/stream_chat_flutter/lib/src/autocomplete/stream_autocomplete.dart @@ -19,7 +19,8 @@ enum OptionsAlignment { /// The options are displayed above the field. /// /// This is the default. - above; + above + ; Anchor _toAnchor() { switch (this) { @@ -45,11 +46,12 @@ enum OptionsAlignment { /// See also: /// /// * [StreamAutocomplete.fieldViewBuilder], which is of this type. -typedef StreamAutocompleteFieldViewBuilder = Widget Function( - BuildContext context, - StreamMessageEditingController messageEditingController, - FocusNode focusNode, -); +typedef StreamAutocompleteFieldViewBuilder = + Widget Function( + BuildContext context, + StreamMessageEditingController messageEditingController, + FocusNode focusNode, + ); /// The type of the [StreamAutocompleteTrigger] callback which returns a /// [Widget] that displays the specified [options]. @@ -57,11 +59,12 @@ typedef StreamAutocompleteFieldViewBuilder = Widget Function( /// See also: /// /// * [StreamAutocompleteTrigger.optionsViewBuilder], which is of this type. -typedef StreamAutocompleteOptionsViewBuilder = Widget Function( - BuildContext context, - StreamAutocompleteQuery autocompleteQuery, - StreamMessageEditingController messageEditingController, -); +typedef StreamAutocompleteOptionsViewBuilder = + Widget Function( + BuildContext context, + StreamAutocompleteQuery autocompleteQuery, + StreamMessageEditingController messageEditingController, + ); /// The query to determine the autocomplete options. class StreamAutocompleteQuery { @@ -148,8 +151,7 @@ class StreamAutocompleteTrigger { final cursorPosition = textEditingValue.selection.baseOffset; // Find the first [trigger] location before the input cursor. - final firstTriggerIndexBeforeCursor = - text.substring(0, cursorPosition).lastIndexOf(trigger); + final firstTriggerIndexBeforeCursor = text.substring(0, cursorPosition).lastIndexOf(trigger); // If the [trigger] is not found before the cursor, then it's not a trigger. if (firstTriggerIndexBeforeCursor == -1) return null; @@ -164,9 +166,7 @@ class StreamAutocompleteTrigger { // valid examples: "@user", "Hello @user" // invalid examples: "Hello@user" final textBeforeTrigger = text.substring(0, firstTriggerIndexBeforeCursor); - if (triggerOnlyAfterSpace && - textBeforeTrigger.isNotEmpty && - !textBeforeTrigger.endsWith(' ')) { + if (triggerOnlyAfterSpace && textBeforeTrigger.isNotEmpty && !textBeforeTrigger.endsWith(' ')) { return null; } @@ -287,10 +287,7 @@ class _StreamAutocompleteState extends State { // True if the state indicates that the options should be visible. bool get _shouldShowOptions { - return !_hideOptions && - _focusNode.hasFocus && - _currentQuery != null && - _currentTrigger != null; + return !_hideOptions && _focusNode.hasFocus && _currentQuery != null && _currentTrigger != null; } /// Accepts and replaces the current query with the given [option] and closes @@ -467,8 +464,7 @@ class _StreamAutocompleteState extends State { @override void initState() { super.initState(); - _messageEditingController = - widget.messageEditingController ?? StreamMessageEditingController(); + _messageEditingController = widget.messageEditingController ?? StreamMessageEditingController(); _messageEditingController.addListener(_onChangedField); _focusNode = widget.focusNode ?? FocusNode(); _focusNode.addListener(_onChangedFocus); diff --git a/packages/stream_chat_flutter/lib/src/autocomplete/stream_command_autocomplete_options.dart b/packages/stream_chat_flutter/lib/src/autocomplete/stream_command_autocomplete_options.dart index d2556eb55a..ce8c22c145 100644 --- a/packages/stream_chat_flutter/lib/src/autocomplete/stream_command_autocomplete_options.dart +++ b/packages/stream_chat_flutter/lib/src/autocomplete/stream_command_autocomplete_options.dart @@ -44,8 +44,8 @@ class StreamCommandAutocompleteOptions extends StatelessWidget { return ListTile( dense: true, horizontalTitleGap: 0, - leading: StreamSvgIcon( - icon: StreamSvgIcons.lightning, + leading: Icon( + context.streamIcons.thunder, color: colorTheme.accentPrimary, size: 28, ), @@ -79,9 +79,7 @@ class StreamCommandAutocompleteOptions extends StatelessWidget { ), ], ), - onTap: onCommandSelected == null - ? null - : () => onCommandSelected!(command), + onTap: onCommandSelected == null ? null : () => onCommandSelected!(command), ); }, ); @@ -109,20 +107,20 @@ class _CommandIcon extends StatelessWidget { return CircleAvatar( backgroundColor: _streamChatTheme.colorTheme.accentPrimary, radius: 12, - child: const StreamSvgIcon( + child: Icon( + context.streamIcons.peopleRemove, size: 16, color: Colors.white, - icon: StreamSvgIcons.userRemove, ), ); case 'flag': return CircleAvatar( backgroundColor: _streamChatTheme.colorTheme.accentPrimary, radius: 12, - child: const StreamSvgIcon( + child: Icon( + context.streamIcons.flag2, size: 14, color: Colors.white, - icon: StreamSvgIcons.flag, ), ); case 'imgur': @@ -140,40 +138,40 @@ class _CommandIcon extends StatelessWidget { return CircleAvatar( backgroundColor: _streamChatTheme.colorTheme.accentPrimary, radius: 12, - child: const StreamSvgIcon( + child: Icon( + context.streamIcons.mute, size: 16, color: Colors.white, - icon: StreamSvgIcons.mute, ), ); case 'unban': return CircleAvatar( backgroundColor: _streamChatTheme.colorTheme.accentPrimary, radius: 12, - child: const StreamSvgIcon( + child: Icon( + context.streamIcons.peopleAdd, size: 16, color: Colors.white, - icon: StreamSvgIcons.userAdd, ), ); case 'unmute': return CircleAvatar( backgroundColor: _streamChatTheme.colorTheme.accentPrimary, radius: 12, - child: const StreamSvgIcon( + child: Icon( + context.streamIcons.volumeFull, size: 16, color: Colors.white, - icon: StreamSvgIcons.volumeUp, ), ); default: return CircleAvatar( backgroundColor: _streamChatTheme.colorTheme.accentPrimary, radius: 12, - child: const StreamSvgIcon( + child: Icon( + context.streamIcons.thunder, size: 16, color: Colors.white, - icon: StreamSvgIcons.lightning, ), ); } diff --git a/packages/stream_chat_flutter/lib/src/autocomplete/stream_mention_autocomplete_options.dart b/packages/stream_chat_flutter/lib/src/autocomplete/stream_mention_autocomplete_options.dart index 955e02ae34..e528ab4832 100644 --- a/packages/stream_chat_flutter/lib/src/autocomplete/stream_mention_autocomplete_options.dart +++ b/packages/stream_chat_flutter/lib/src/autocomplete/stream_mention_autocomplete_options.dart @@ -16,14 +16,14 @@ class StreamMentionAutocompleteOptions extends StatefulWidget { this.mentionAllAppUsers = false, this.mentionsTileBuilder, this.onMentionUserTap, - }) : assert( - channel.state != null, - 'Channel ${channel.cid} is not yet initialized', - ), - assert( - !mentionAllAppUsers || (mentionAllAppUsers && client != null), - 'StreamChatClient is required in order to use mentionAllAppUsers', - ); + }) : assert( + channel.state != null, + 'Channel ${channel.cid} is not yet initialized', + ), + assert( + !mentionAllAppUsers || (mentionAllAppUsers && client != null), + 'StreamChatClient is required in order to use mentionAllAppUsers', + ); /// Query for searching users. final String query; @@ -49,12 +49,10 @@ class StreamMentionAutocompleteOptions extends StatefulWidget { final ValueSetter? onMentionUserTap; @override - _StreamMentionAutocompleteOptionsState createState() => - _StreamMentionAutocompleteOptionsState(); + _StreamMentionAutocompleteOptionsState createState() => _StreamMentionAutocompleteOptionsState(); } -class _StreamMentionAutocompleteOptionsState - extends State { +class _StreamMentionAutocompleteOptionsState extends State { late Future> userMentionsFuture; @override @@ -90,11 +88,8 @@ class _StreamMentionAutocompleteOptionsState return Material( color: colorTheme.barsBg, child: InkWell( - onTap: widget.onMentionUserTap == null - ? null - : () => widget.onMentionUserTap!(user), - child: widget.mentionsTileBuilder?.call(context, user) ?? - StreamUserMentionTile(user), + onTap: widget.onMentionUserTap == null ? null : () => widget.onMentionUserTap!(user), + child: widget.mentionsTileBuilder?.call(context, user) ?? StreamUserMentionTile(user), ), ); }, @@ -131,18 +126,13 @@ class _StreamMentionAutocompleteOptionsState } final result = await _queryMembers(query); - return result - .map((it) => it.user) - .whereType() - .toList(growable: false); + return result.map((it) => it.user).whereType().toList(growable: false); } Future> _queryMembers(String query) async { final response = await widget.channel.queryMembers( pagination: PaginationParams(limit: widget.limit), - filter: query.isEmpty - ? const Filter.empty() - : Filter.autoComplete('name', query), + filter: query.isEmpty ? const Filter.empty() : Filter.autoComplete('name', query), ); return response.members; } diff --git a/packages/stream_chat_flutter/lib/src/avatars/group_avatar.dart b/packages/stream_chat_flutter/lib/src/avatars/group_avatar.dart deleted file mode 100644 index 146a664318..0000000000 --- a/packages/stream_chat_flutter/lib/src/avatars/group_avatar.dart +++ /dev/null @@ -1,176 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; - -/// WidgetBuilder for [StreamGroupAvatar]. -typedef StreamGroupAvatarBuilder = Widget Function( - BuildContext context, - List members, - // ignore: avoid_positional_boolean_parameters - bool isSelected, -); - -/// {@template streamGroupAvatar} -/// Widget for constructing a group of images -/// {@endtemplate} -class StreamGroupAvatar extends StatelessWidget { - /// {@macro streamGroupAvatar} - const StreamGroupAvatar({ - super.key, - this.channel, - required this.members, - this.constraints, - this.onTap, - this.borderRadius, - this.selected = false, - this.selectionColor, - this.selectionThickness = 4, - }); - - /// The channel of the avatar - final Channel? channel; - - /// The list of members in the group whose avatars should be displayed. - final List members; - - /// Constraints on the widget - final BoxConstraints? constraints; - - /// The action to perform when the widget is tapped - final VoidCallback? onTap; - - /// If `true`, this widget should be highlighted. - /// - /// Defaults to `false`. - final bool selected; - - /// [BorderRadius] to pass to the widget - final BorderRadius? borderRadius; - - /// The color to highlight the widget with if [selected] is `true` - final Color? selectionColor; - - /// The value to use for the border thickness and padding of the - /// selected image - final double selectionThickness; - - @override - Widget build(BuildContext context) { - final channel = this.channel ?? StreamChannel.of(context).channel; - - assert(channel.state != null, 'Channel ${channel.id} is not initialized'); - - final streamChatTheme = StreamChatTheme.of(context); - final colorTheme = streamChatTheme.colorTheme; - final previewTheme = streamChatTheme.channelPreviewTheme.avatarTheme; - - Widget avatar = GestureDetector( - onTap: onTap, - child: ClipRRect( - borderRadius: - borderRadius ?? previewTheme?.borderRadius ?? BorderRadius.zero, - child: Container( - constraints: constraints ?? previewTheme?.constraints, - decoration: BoxDecoration(color: colorTheme.accentPrimary), - child: Flex( - direction: Axis.vertical, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Flexible( - fit: FlexFit.tight, - child: Flex( - direction: Axis.horizontal, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: members - .take(2) - .map( - (member) => Flexible( - fit: FlexFit.tight, - child: FittedBox( - fit: BoxFit.cover, - clipBehavior: Clip.antiAlias, - child: Transform.scale( - scale: 1.2, - child: BetterStreamBuilder( - stream: channel.state!.membersStream.map( - (members) => members.firstWhere( - (it) => it.userId == member.userId, - orElse: () => member, - ), - ), - initialData: member, - builder: (context, member) => StreamUserAvatar( - showOnlineStatus: false, - user: member.user!, - borderRadius: BorderRadius.zero, - ), - ), - ), - ), - ), - ) - .toList(), - ), - ), - if (members.length > 2) - Flexible( - fit: FlexFit.tight, - child: Flex( - direction: Axis.horizontal, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: members - .skip(2) - .take(2) - .map( - (member) => Flexible( - fit: FlexFit.tight, - child: FittedBox( - fit: BoxFit.cover, - clipBehavior: Clip.antiAlias, - child: Transform.scale( - scale: 1.2, - child: BetterStreamBuilder( - stream: channel.state!.membersStream.map( - (members) => members.firstWhere( - (it) => it.userId == member.userId, - orElse: () => member, - ), - ), - initialData: member, - builder: (context, member) => - StreamUserAvatar( - showOnlineStatus: false, - user: member.user!, - borderRadius: BorderRadius.zero, - ), - ), - ), - ), - ), - ) - .toList(), - ), - ), - ], - ), - ), - ), - ); - - if (selected) { - avatar = ClipRRect( - borderRadius: BorderRadius.circular(selectionThickness) + - (borderRadius ?? previewTheme?.borderRadius ?? BorderRadius.zero), - child: Container( - constraints: constraints ?? previewTheme?.constraints, - color: selectionColor ?? colorTheme.accentPrimary, - child: Padding( - padding: EdgeInsets.all(selectionThickness), - child: avatar, - ), - ), - ); - } - - return avatar; - } -} diff --git a/packages/stream_chat_flutter/lib/src/avatars/user_avatar.dart b/packages/stream_chat_flutter/lib/src/avatars/user_avatar.dart deleted file mode 100644 index f62e40bcff..0000000000 --- a/packages/stream_chat_flutter/lib/src/avatars/user_avatar.dart +++ /dev/null @@ -1,192 +0,0 @@ -import 'package:cached_network_image/cached_network_image.dart'; -import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; - -/// WidgetBuilder for [StreamUserAvatar]. -typedef StreamUserAvatarBuilder = Widget Function( - BuildContext context, - User user, - // ignore: avoid_positional_boolean_parameters - bool isSelected, -); - -/// {@template streamUserAvatar} -/// Displays a user's avatar. -/// {@endtemplate} -class StreamUserAvatar extends StatelessWidget { - /// {@macro streamUserAvatar} - const StreamUserAvatar({ - super.key, - required this.user, - this.constraints, - this.onlineIndicatorConstraints, - this.onTap, - this.onLongPress, - this.showOnlineStatus = true, - this.borderRadius, - this.onlineIndicatorAlignment = Alignment.topRight, - this.selected = false, - this.selectionColor, - this.selectionThickness = 4, - this.placeholder, - }); - - /// User whose avatar is to be displayed - final User user; - - /// Alignment of the online indicator - /// - /// Defaults to `Alignment.topRight` - final Alignment onlineIndicatorAlignment; - - /// Sizing constraints of the avatar - final BoxConstraints? constraints; - - /// [BorderRadius] of the image - final BorderRadius? borderRadius; - - /// Sizing constraints of the online indicator - final BoxConstraints? onlineIndicatorConstraints; - - /// {@macro onUserAvatarTap} - final OnUserAvatarPress? onTap; - - /// {@macro onUserAvatarTap} - final OnUserAvatarPress? onLongPress; - - /// Flag for showing online status - /// - /// Defaults to `true` - final bool showOnlineStatus; - - /// Flag for if avatar is selected - /// - /// Defaults to `false` - final bool selected; - - /// Color of selection - final Color? selectionColor; - - /// Selection thickness around the avatar - /// - /// Defaults to `4` - final double selectionThickness; - - /// {@macro placeholderUserImage} - final PlaceholderUserImage? placeholder; - - @override - Widget build(BuildContext context) { - final streamChatTheme = StreamChatTheme.of(context); - final colorTheme = streamChatTheme.colorTheme; - final avatarTheme = streamChatTheme.ownMessageTheme.avatarTheme; - final streamChatConfig = StreamChatConfiguration.of(context); - - final effectivePlaceholder = switch (placeholder) { - final placeholder? => placeholder, - _ => streamChatConfig.placeholderUserImage, - }; - - final effectiveBorderRadius = borderRadius ?? avatarTheme?.borderRadius; - - final backupGradientAvatar = ClipRRect( - borderRadius: effectiveBorderRadius ?? BorderRadius.zero, - child: streamChatConfig.defaultUserImage(context, user), - ); - - Widget avatar = FittedBox( - fit: BoxFit.cover, - child: Container( - constraints: constraints ?? avatarTheme?.constraints, - child: LayoutBuilder( - builder: (context, constraints) { - final imageUrl = user.image; - if (imageUrl == null || imageUrl.isEmpty) { - return backupGradientAvatar; - } - - // Calculate optimal thumbnail size for the avatar - final devicePixelRatio = MediaQuery.devicePixelRatioOf(context); - final thumbnailSize = constraints.biggest * devicePixelRatio; - - int? cacheWidth, cacheHeight; - if (thumbnailSize.isFinite && !thumbnailSize.isEmpty) { - cacheWidth = thumbnailSize.width.round(); - cacheHeight = thumbnailSize.height.round(); - } - - return CachedNetworkImage( - fit: BoxFit.cover, - filterQuality: FilterQuality.high, - imageUrl: imageUrl, - errorWidget: (_, __, ___) => backupGradientAvatar, - placeholder: switch (effectivePlaceholder) { - final holder? => (context, __) => holder(context, user), - _ => null, - }, - imageBuilder: (context, imageProvider) => DecoratedBox( - decoration: BoxDecoration( - borderRadius: effectiveBorderRadius, - image: DecorationImage( - fit: BoxFit.cover, - image: ResizeImage( - imageProvider, - width: cacheWidth, - height: cacheHeight, - ), - ), - ), - ), - ); - }, - ), - ), - ); - - if (selected) { - avatar = ClipRRect( - borderRadius: (effectiveBorderRadius ?? BorderRadius.zero) + - BorderRadius.circular(selectionThickness), - child: Container( - constraints: constraints ?? avatarTheme?.constraints, - color: selectionColor ?? colorTheme.accentPrimary, - child: Padding( - padding: EdgeInsets.all(selectionThickness), - child: avatar, - ), - ), - ); - } - return GestureDetector( - onTap: onTap != null ? () => onTap!(user) : null, - onLongPress: onLongPress != null ? () => onLongPress!(user) : null, - child: Stack( - children: [ - avatar, - if (showOnlineStatus && user.online) - Positioned.fill( - child: Align( - alignment: onlineIndicatorAlignment, - child: Material( - type: MaterialType.circle, - color: colorTheme.barsBg, - child: Container( - margin: const EdgeInsets.all(2), - constraints: onlineIndicatorConstraints ?? - const BoxConstraints.tightFor( - width: 8, - height: 8, - ), - child: Material( - shape: const CircleBorder(), - color: colorTheme.accentInfo, - ), - ), - ), - ), - ), - ], - ), - ); - } -} diff --git a/packages/stream_chat_flutter/lib/src/bottom_sheets/edit_message_sheet.dart b/packages/stream_chat_flutter/lib/src/bottom_sheets/edit_message_sheet.dart index ced57b6ecc..1e428db1f4 100644 --- a/packages/stream_chat_flutter/lib/src/bottom_sheets/edit_message_sheet.dart +++ b/packages/stream_chat_flutter/lib/src/bottom_sheets/edit_message_sheet.dart @@ -89,8 +89,8 @@ class _EditMessageSheetState extends State { children: [ Padding( padding: const EdgeInsets.all(8), - child: StreamSvgIcon( - icon: StreamSvgIcons.edit, + child: Icon( + context.streamIcons.editBig, color: streamChatThemeData.colorTheme.disabled, ), ), @@ -100,8 +100,8 @@ class _EditMessageSheetState extends State { ), IconButton( visualDensity: VisualDensity.compact, - icon: StreamSvgIcon( - icon: StreamSvgIcons.closeSmall, + icon: Icon( + context.streamIcons.crossSmall, color: streamChatThemeData.colorTheme.textLowEmphasis, ), onPressed: Navigator.of(context).pop, diff --git a/packages/stream_chat_flutter/lib/src/bottom_sheets/error_alert_sheet.dart b/packages/stream_chat_flutter/lib/src/bottom_sheets/error_alert_sheet.dart index 744664dd8f..0d7871c4d4 100644 --- a/packages/stream_chat_flutter/lib/src/bottom_sheets/error_alert_sheet.dart +++ b/packages/stream_chat_flutter/lib/src/bottom_sheets/error_alert_sheet.dart @@ -25,8 +25,8 @@ class ErrorAlertSheet extends StatelessWidget { const SizedBox( height: 26, ), - StreamSvgIcon( - icon: StreamSvgIcons.error, + Icon( + context.streamIcons.exclamationCircle1, color: _streamChatTheme.colorTheme.accentError, size: 24, ), diff --git a/packages/stream_chat_flutter/lib/src/bottom_sheets/stream_channel_info_bottom_sheet.dart b/packages/stream_chat_flutter/lib/src/bottom_sheets/stream_channel_info_bottom_sheet.dart index c1cb4ad060..042f0f8408 100644 --- a/packages/stream_chat_flutter/lib/src/bottom_sheets/stream_channel_info_bottom_sheet.dart +++ b/packages/stream_chat_flutter/lib/src/bottom_sheets/stream_channel_info_bottom_sheet.dart @@ -13,9 +13,9 @@ class StreamChannelInfoBottomSheet extends StatelessWidget { this.onDeleteConversationTap, this.onCancelTap, }) : assert( - channel.state != null, - 'Channel ${channel.id} is not initialized', - ); + channel.state != null, + 'Channel ${channel.id} is not initialized', + ); /// The [Channel] to show information about. final Channel channel; @@ -94,19 +94,15 @@ class StreamChannelInfoBottomSheet extends StatelessWidget { mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, children: [ - StreamUserAvatar( - user: user, - constraints: const BoxConstraints.tightFor( - height: 64, - width: 64, + GestureDetector( + onTap: switch (onMemberTap) { + final onTap? => () => onTap(member), + _ => null, + }, + child: StreamUserAvatar( + size: .xl, + user: user, ), - borderRadius: BorderRadius.circular(32), - onlineIndicatorConstraints: BoxConstraints.tight( - const Size(12, 12), - ), - onTap: onMemberTap != null - ? (_) => onMemberTap!(member) - : null, ), const SizedBox(height: 6), Text( @@ -124,8 +120,8 @@ class StreamChannelInfoBottomSheet extends StatelessWidget { StreamOptionListTile( leading: Padding( padding: const EdgeInsets.symmetric(horizontal: 16), - child: StreamSvgIcon( - icon: StreamSvgIcons.user, + child: Icon( + context.streamIcons.people, color: colorTheme.textLowEmphasis, ), ), @@ -137,8 +133,8 @@ class StreamChannelInfoBottomSheet extends StatelessWidget { title: context.translations.leaveGroupLabel, leading: Padding( padding: const EdgeInsets.symmetric(horizontal: 16), - child: StreamSvgIcon( - icon: StreamSvgIcons.userRemove, + child: Icon( + context.streamIcons.peopleRemove, color: colorTheme.textLowEmphasis, ), ), @@ -148,8 +144,8 @@ class StreamChannelInfoBottomSheet extends StatelessWidget { StreamOptionListTile( leading: Padding( padding: const EdgeInsets.symmetric(horizontal: 16), - child: StreamSvgIcon( - icon: StreamSvgIcons.delete, + child: Icon( + context.streamIcons.trashBin, color: colorTheme.accentError, ), ), @@ -160,8 +156,8 @@ class StreamChannelInfoBottomSheet extends StatelessWidget { StreamOptionListTile( leading: Padding( padding: const EdgeInsets.symmetric(horizontal: 16), - child: StreamSvgIcon( - icon: StreamSvgIcons.closeSmall, + child: Icon( + context.streamIcons.crossMedium, color: colorTheme.textLowEmphasis, ), ), @@ -259,30 +255,29 @@ Future showChannelInfoModalBottomSheet({ VoidCallback? onLeaveChannelTap, VoidCallback? onDeleteConversationTap, VoidCallback? onCancelTap, -}) => - showModalBottomSheet( - context: context, - backgroundColor: backgroundColor, - elevation: elevation, - shape: shape, - clipBehavior: clipBehavior, - constraints: constraints, - barrierColor: barrierColor, - isScrollControlled: isScrollControlled, - useRootNavigator: useRootNavigator, - isDismissible: isDismissible, - enableDrag: enableDrag, - routeSettings: routeSettings, - transitionAnimationController: transitionAnimationController, - builder: (BuildContext context) => StreamChannelInfoBottomSheet( - channel: channel, - onMemberTap: onMemberTap, - onViewInfoTap: onViewInfoTap, - onLeaveChannelTap: onLeaveChannelTap, - onDeleteConversationTap: onDeleteConversationTap, - onCancelTap: onCancelTap, - ), - ); +}) => showModalBottomSheet( + context: context, + backgroundColor: backgroundColor, + elevation: elevation, + shape: shape, + clipBehavior: clipBehavior, + constraints: constraints, + barrierColor: barrierColor, + isScrollControlled: isScrollControlled, + useRootNavigator: useRootNavigator, + isDismissible: isDismissible, + enableDrag: enableDrag, + routeSettings: routeSettings, + transitionAnimationController: transitionAnimationController, + builder: (BuildContext context) => StreamChannelInfoBottomSheet( + channel: channel, + onMemberTap: onMemberTap, + onViewInfoTap: onViewInfoTap, + onLeaveChannelTap: onLeaveChannelTap, + onDeleteConversationTap: onDeleteConversationTap, + onCancelTap: onCancelTap, + ), +); /// Shows a material design bottom sheet in the nearest [Scaffold] ancestor. If /// you wish to show a persistent bottom sheet, use [Scaffold.bottomSheet]. @@ -339,21 +334,20 @@ PersistentBottomSheetController showChannelInfoBottomSheet({ VoidCallback? onLeaveChannelTap, VoidCallback? onDeleteConversationTap, VoidCallback? onCancelTap, -}) => - showBottomSheet( - context: context, - backgroundColor: backgroundColor, - elevation: elevation, - shape: shape, - clipBehavior: clipBehavior, - constraints: constraints, - transitionAnimationController: transitionAnimationController, - builder: (BuildContext context) => StreamChannelInfoBottomSheet( - channel: channel, - onMemberTap: onMemberTap, - onViewInfoTap: onViewInfoTap, - onLeaveChannelTap: onLeaveChannelTap, - onDeleteConversationTap: onDeleteConversationTap, - onCancelTap: onCancelTap, - ), - ); +}) => showBottomSheet( + context: context, + backgroundColor: backgroundColor, + elevation: elevation, + shape: shape, + clipBehavior: clipBehavior, + constraints: constraints, + transitionAnimationController: transitionAnimationController, + builder: (BuildContext context) => StreamChannelInfoBottomSheet( + channel: channel, + onMemberTap: onMemberTap, + onViewInfoTap: onViewInfoTap, + onLeaveChannelTap: onLeaveChannelTap, + onDeleteConversationTap: onDeleteConversationTap, + onCancelTap: onCancelTap, + ), +); diff --git a/packages/stream_chat_flutter/lib/src/channel/channel_header.dart b/packages/stream_chat_flutter/lib/src/channel/channel_header.dart index 20dd2d5ae3..00d7727290 100644 --- a/packages/stream_chat_flutter/lib/src/channel/channel_header.dart +++ b/packages/stream_chat_flutter/lib/src/channel/channel_header.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_portal/flutter_portal.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// {@template streamChannelHeader} @@ -50,8 +51,7 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// and the [StreamChatThemeData.channelHeaderTheme] property. Modify it to /// change the widget's appearance. /// {@endtemplate} -class StreamChannelHeader extends StatelessWidget - implements PreferredSizeWidget { +class StreamChannelHeader extends StatelessWidget implements PreferredSizeWidget { /// {@macro streamChannelHeader} const StreamChannelHeader({ super.key, @@ -68,7 +68,8 @@ class StreamChannelHeader extends StatelessWidget this.actions, this.bottom, this.backgroundColor, - this.elevation = 1, + this.elevation = 0, + this.scrolledUnderElevation = 0, this.bottomOpacity = 1, }); @@ -122,6 +123,9 @@ class StreamChannelHeader extends StatelessWidget /// The elevation for this [StreamChannelHeader]. final double elevation; + /// The scrolled under elevation for this [StreamChannelHeader]. + final double scrolledUnderElevation; + /// The opacity of the bottom widget. final double bottomOpacity; @@ -141,7 +145,8 @@ class StreamChannelHeader extends StatelessWidget final channel = StreamChannel.of(context).channel; final channelHeaderTheme = StreamChannelHeaderTheme.of(context); - final leadingWidget = leading ?? + final leadingWidget = + leading ?? (showBackButton ? StreamBackButton( onPressed: onBackPressed, @@ -149,86 +154,99 @@ class StreamChannelHeader extends StatelessWidget ) : const SizedBox()); - return StreamConnectionStatusBuilder( - statusBuilder: (context, status) { - var statusString = ''; - var showStatus = true; - - switch (status) { - case ConnectionStatus.connected: - statusString = context.translations.connectedLabel; - showStatus = false; - break; - case ConnectionStatus.connecting: - statusString = context.translations.reconnectingLabel; - break; - case ConnectionStatus.disconnected: - statusString = context.translations.disconnectedLabel; - break; - } - - final theme = Theme.of(context); - - return StreamInfoTile( - showMessage: showConnectionStateTile && showStatus, - message: statusString, - child: AppBar( - toolbarTextStyle: theme.textTheme.bodyMedium, - titleTextStyle: theme.textTheme.titleLarge, - systemOverlayStyle: theme.brightness == Brightness.dark - ? SystemUiOverlayStyle.light - : SystemUiOverlayStyle.dark, - elevation: elevation, - leading: leadingWidget, - bottom: bottom, - bottomOpacity: bottomOpacity, - backgroundColor: backgroundColor ?? channelHeaderTheme.color, - actions: actions ?? - [ - Padding( - padding: const EdgeInsets.only(right: 10), - child: Center( - child: StreamChannelAvatar( - channel: channel, - borderRadius: - channelHeaderTheme.avatarTheme?.borderRadius, - constraints: - channelHeaderTheme.avatarTheme?.constraints, - onTap: onImageTap, + return Portal( + child: StreamConnectionStatusBuilder( + statusBuilder: (context, status) { + var statusString = ''; + var showStatus = true; + + switch (status) { + case ConnectionStatus.connected: + statusString = context.translations.connectedLabel; + showStatus = false; + break; + case ConnectionStatus.connecting: + statusString = context.translations.reconnectingLabel; + break; + case ConnectionStatus.disconnected: + statusString = context.translations.disconnectedLabel; + break; + } + + final theme = Theme.of(context); + final colorScheme = context.streamColorScheme; + + return StreamInfoTile( + showMessage: showConnectionStateTile && showStatus, + message: statusString, + child: AppBar( + toolbarTextStyle: theme.textTheme.bodyMedium, + titleTextStyle: theme.textTheme.titleLarge, + systemOverlayStyle: theme.brightness == Brightness.dark + ? SystemUiOverlayStyle.light + : SystemUiOverlayStyle.dark, + elevation: elevation, + scrolledUnderElevation: scrolledUnderElevation, + leading: leadingWidget, + bottom: bottom, + bottomOpacity: bottomOpacity, + shape: LinearBorder( + side: BorderSide( + color: colorScheme.borderDefault, + width: 1, + ), + bottom: const LinearBorderEdge(), + ), + backgroundColor: backgroundColor ?? channelHeaderTheme.color, + actions: + actions ?? + [ + Padding( + padding: const EdgeInsets.only(right: 10), + child: Center( + child: GestureDetector( + onTap: onImageTap, + child: StreamChannelAvatar( + size: .lg, + channel: channel, + ), + ), ), ), - ), - ], - centerTitle: centerTitle, - title: InkWell( - onTap: onTitleTap, - child: SizedBox( - height: preferredSize.height, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: effectiveCenterTitle - ? CrossAxisAlignment.center - : CrossAxisAlignment.stretch, - children: [ - title ?? - StreamChannelName( - channel: channel, - textStyle: channelHeaderTheme.titleStyle, - ), - const SizedBox(height: 2), - subtitle ?? - StreamChannelInfo( - showTypingIndicator: showTypingIndicator, - channel: channel, - textStyle: channelHeaderTheme.subtitleStyle, - ), ], + centerTitle: centerTitle, + title: InkWell( + onTap: onTitleTap, + child: SizedBox( + height: preferredSize.height, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: effectiveCenterTitle ? CrossAxisAlignment.center : CrossAxisAlignment.stretch, + children: [ + title ?? + StreamChannelName( + channel: channel, + textStyle: + channelHeaderTheme.titleStyle ?? + context.streamTextTheme.headingSm.copyWith( + color: context.streamColorScheme.textPrimary, + ), + ), + const SizedBox(height: 2), + subtitle ?? + StreamChannelInfo( + showTypingIndicator: showTypingIndicator, + channel: channel, + textStyle: channelHeaderTheme.subtitleStyle, + ), + ], + ), ), ), ), - ), - ); - }, + ); + }, + ), ); } } diff --git a/packages/stream_chat_flutter/lib/src/channel/channel_info.dart b/packages/stream_chat_flutter/lib/src/channel/channel_info.dart index d74d26673b..a5c973d1a0 100644 --- a/packages/stream_chat_flutter/lib/src/channel/channel_info.dart +++ b/packages/stream_chat_flutter/lib/src/channel/channel_info.dart @@ -33,6 +33,12 @@ class StreamChannelInfo extends StatelessWidget { @override Widget build(BuildContext context) { final client = StreamChat.of(context).client; + final effectiveTextStyle = + textStyle ?? + context.streamTextTheme.captionDefault.copyWith( + color: context.streamColorScheme.textSecondary, + ); + return BetterStreamBuilder>( stream: channel.state!.membersStream, initialData: channel.state!.members, @@ -43,16 +49,16 @@ class StreamChannelInfo extends StatelessWidget { return _ConnectedTitleState( channel: channel, showTypingIndicator: showTypingIndicator, - textStyle: textStyle, + textStyle: effectiveTextStyle, members: data, parentId: parentId, ); case ConnectionStatus.connecting: - return _ConnectingTitleState(textStyle: textStyle); + return _ConnectingTitleState(textStyle: effectiveTextStyle); case ConnectionStatus.disconnected: return _DisconnectedTitleState( client: client, - textStyle: textStyle, + textStyle: effectiveTextStyle, ); } }, @@ -83,15 +89,11 @@ class _ConnectedTitleState extends StatelessWidget { final memberCount = channel.memberCount; if (memberCount != null && memberCount > 2) { var text = context.translations.membersCountText(memberCount); - final onlineCount = - members?.where((m) => m.user?.online == true).length ?? 0; + final onlineCount = members?.where((m) => m.user?.online == true).length ?? 0; if (onlineCount > 0) { text += ', ${context.translations.watchersCountText(onlineCount)}'; } - alternativeWidget = Text( - text, - style: StreamChannelHeaderTheme.of(context).subtitleStyle, - ); + alternativeWidget = Text(text, style: textStyle); } else { final userId = StreamChat.of(context).currentUser?.id; final otherMember = members?.firstWhereOrNull( diff --git a/packages/stream_chat_flutter/lib/src/channel/channel_list_header.dart b/packages/stream_chat_flutter/lib/src/channel/channel_list_header.dart index 2c3338425e..bf7c50ca69 100644 --- a/packages/stream_chat_flutter/lib/src/channel/channel_list_header.dart +++ b/packages/stream_chat_flutter/lib/src/channel/channel_list_header.dart @@ -1,7 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_portal/flutter_portal.dart'; import 'package:stream_chat_flutter/src/misc/empty_widget.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; /// {@template streamChannelListHeader} /// Shows the current [StreamChatClient] status. @@ -37,8 +39,7 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// the [StreamChannelListHeaderThemeData] property. Modify it to change the /// widget's appearance. /// {@endtemplate} -class StreamChannelListHeader extends StatelessWidget - implements PreferredSizeWidget { +class StreamChannelListHeader extends StatelessWidget implements PreferredSizeWidget { /// {@macro streamChannelListHeader} const StreamChannelListHeader({ super.key, @@ -53,7 +54,8 @@ class StreamChannelListHeader extends StatelessWidget this.leading, this.actions, this.backgroundColor, - this.elevation = 1, + this.elevation = 0, + this.scrolledUnderElevation = 0, }); /// Use this if you don't have a [StreamChatClient] in your widget tree. @@ -98,6 +100,9 @@ class StreamChannelListHeader extends StatelessWidget /// The elevation for this [StreamChannelListHeader]. final double elevation; + /// The scrolled under elevation for this [StreamChannelListHeader]. + final double scrolledUnderElevation; + @override Size get preferredSize => const Size.fromHeight(kToolbarHeight); @@ -105,106 +110,111 @@ class StreamChannelListHeader extends StatelessWidget Widget build(BuildContext context) { final _client = client ?? StreamChat.of(context).client; final user = _client.state.currentUser; - return StreamConnectionStatusBuilder( - statusBuilder: (context, status) { - var statusString = ''; - var showStatus = true; + return Portal( + child: StreamConnectionStatusBuilder( + statusBuilder: (context, status) { + var statusString = ''; + var showStatus = true; + + switch (status) { + case ConnectionStatus.connected: + statusString = context.translations.connectedLabel; + showStatus = false; + break; + case ConnectionStatus.connecting: + statusString = context.translations.reconnectingLabel; + break; + case ConnectionStatus.disconnected: + statusString = context.translations.disconnectedLabel; + break; + } - switch (status) { - case ConnectionStatus.connected: - statusString = context.translations.connectedLabel; - showStatus = false; - break; - case ConnectionStatus.connecting: - statusString = context.translations.reconnectingLabel; - break; - case ConnectionStatus.disconnected: - statusString = context.translations.disconnectedLabel; - break; - } + final channelListHeaderThemeData = StreamChannelListHeaderTheme.of(context); + final theme = Theme.of(context); + final colorScheme = context.streamColorScheme; - final chatThemeData = StreamChatTheme.of(context); - final channelListHeaderThemeData = - StreamChannelListHeaderTheme.of(context); - final theme = Theme.of(context); - return StreamInfoTile( - showMessage: showConnectionStateTile && showStatus, - message: statusString, - child: AppBar( - toolbarTextStyle: theme.textTheme.bodyMedium, - titleTextStyle: theme.textTheme.titleLarge, - systemOverlayStyle: theme.brightness == Brightness.dark - ? SystemUiOverlayStyle.light - : SystemUiOverlayStyle.dark, - elevation: elevation, - backgroundColor: - backgroundColor ?? channelListHeaderThemeData.color, - centerTitle: centerTitle, - leading: leading ?? - Center( - child: user != null - ? StreamUserAvatar( - user: user, - showOnlineStatus: false, - onTap: onUserAvatarTap ?? - (_) { - preNavigationCallback?.call(); - Scaffold.of(context).openDrawer(); - }, - borderRadius: channelListHeaderThemeData - .avatarTheme?.borderRadius, - constraints: channelListHeaderThemeData - .avatarTheme?.constraints, - ) - : const Empty(), + return StreamInfoTile( + showMessage: showConnectionStateTile && showStatus, + message: statusString, + child: AppBar( + toolbarTextStyle: theme.textTheme.bodyMedium, + titleTextStyle: theme.textTheme.titleLarge, + systemOverlayStyle: theme.brightness == Brightness.dark + ? SystemUiOverlayStyle.light + : SystemUiOverlayStyle.dark, + elevation: elevation, + scrolledUnderElevation: scrolledUnderElevation, + backgroundColor: backgroundColor ?? channelListHeaderThemeData.color, + centerTitle: centerTitle, + shape: LinearBorder( + side: BorderSide( + color: colorScheme.borderDefault, + width: 1, ), - actions: actions ?? - [ - StreamNeumorphicButton( - child: IconButton( - icon: StreamConnectionStatusBuilder( - statusBuilder: (context, status) { - final color = switch (status) { - ConnectionStatus.connected => - chatThemeData.colorTheme.accentPrimary, - ConnectionStatus.connecting => Colors.grey, - ConnectionStatus.disconnected => Colors.grey, - }; + bottom: const LinearBorderEdge(), + ), + leading: switch ((leading, user)) { + (final leading?, _) => leading, + (_, final user?) => Center( + child: GestureDetector( + onTap: switch (onUserAvatarTap) { + final onTap? => () => onTap(user), + _ => () { + preNavigationCallback?.call(); + Scaffold.of(context).openDrawer(); + }, + }, + child: StreamUserAvatar( + size: .lg, + user: user, + showOnlineIndicator: false, + ), + ), + ), + _ => const Empty(), + }, + actions: + actions ?? + [ + StreamConnectionStatusBuilder( + statusBuilder: (context, status) { + final callback = switch (status) { + ConnectionStatus.connected => onNewChatButtonTap, + ConnectionStatus.connecting => null, + ConnectionStatus.disconnected => null, + }; - return StreamSvgIcon( - size: 24, - color: color, - icon: StreamSvgIcons.penWrite, - ); - }, - ), - onPressed: onNewChatButtonTap, + return StreamButton.icon( + icon: context.streamIcons.plusLarge, + onTap: callback, + ); + }, ), + ], + title: Column( + children: [ + Builder( + builder: (context) { + if (titleBuilder != null) { + return titleBuilder!(context, status, _client); + } + switch (status) { + case ConnectionStatus.connected: + return _ConnectedTitleState(); + case ConnectionStatus.connecting: + return _ConnectingTitleState(); + case ConnectionStatus.disconnected: + return _DisconnectedTitleState(client: _client); + } + }, ), + subtitle ?? const Empty(), ], - title: Column( - children: [ - Builder( - builder: (context) { - if (titleBuilder != null) { - return titleBuilder!(context, status, _client); - } - switch (status) { - case ConnectionStatus.connected: - return _ConnectedTitleState(); - case ConnectionStatus.connecting: - return _ConnectingTitleState(); - case ConnectionStatus.disconnected: - return _DisconnectedTitleState(client: _client); - } - }, - ), - subtitle ?? const Empty(), - ], + ), ), - ), - ); - }, + ); + }, + ), ); } } @@ -239,9 +249,9 @@ class _ConnectingTitleState extends StatelessWidget { Text( context.translations.searchingForNetworkText, style: StreamChannelListHeaderTheme.of(context).titleStyle?.copyWith( - fontSize: 16, - fontWeight: FontWeight.bold, - ), + fontSize: 16, + fontWeight: FontWeight.bold, + ), ), ], ); diff --git a/packages/stream_chat_flutter/lib/src/channel/channel_name.dart b/packages/stream_chat_flutter/lib/src/channel/channel_name.dart index ddccdb7200..79aedaddbd 100644 --- a/packages/stream_chat_flutter/lib/src/channel/channel_name.dart +++ b/packages/stream_chat_flutter/lib/src/channel/channel_name.dart @@ -87,8 +87,7 @@ class _NameGenerator extends StatelessWidget { } }); - final exceedingMembers = - otherMembers.length - currentMembers.length; + final exceedingMembers = otherMembers.length - currentMembers.length; channelName = '${currentMembers.map((e) => e.user?.name).join(', ')} ' '${exceedingMembers > 0 ? '+ $exceedingMembers' : ''}'; diff --git a/packages/stream_chat_flutter/lib/src/channel/stream_channel_avatar.dart b/packages/stream_chat_flutter/lib/src/channel/stream_channel_avatar.dart deleted file mode 100644 index 2f7665939f..0000000000 --- a/packages/stream_chat_flutter/lib/src/channel/stream_channel_avatar.dart +++ /dev/null @@ -1,256 +0,0 @@ -import 'package:cached_network_image/cached_network_image.dart'; -import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; - -/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/packages/stream_chat_flutter/screenshots/channel_image.png) -/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/packages/stream_chat_flutter/screenshots/channel_image_paint.png) -/// -/// It shows the current [Channel] image. -/// -/// ```dart -/// class MyApp extends StatelessWidget { -/// final StreamChatClient client; -/// final Channel channel; -/// -/// MyApp(this.client, this.channel); -/// -/// @override -/// Widget build(BuildContext context) { -/// return MaterialApp( -/// debugShowCheckedModeBanner: false, -/// home: StreamChat( -/// client: client, -/// child: StreamChannel( -/// channel: channel, -/// child: Center( -/// child: StreamChannelAvatar( -/// channel: channel, -/// ), -/// ), -/// ), -/// ), -/// ); -/// } -/// } -/// ``` -/// -/// The widget uses a [StreamBuilder] to render the channel information -/// image as soon as it updates. -/// -/// By default the widget radius size is 40x40 pixels. -/// Set the property [constraints] to set a custom dimension. -/// -/// The widget renders the ui based on the first ancestor of type -/// [StreamChatTheme]. -/// Modify it to change the widget appearance. -class StreamChannelAvatar extends StatelessWidget { - /// Instantiate a new ChannelImage - StreamChannelAvatar({ - super.key, - required this.channel, - this.constraints, - this.onTap, - this.borderRadius, - this.selected = false, - this.selectionColor, - this.selectionThickness = 4, - this.ownSpaceAvatarBuilder, - this.oneToOneAvatarBuilder, - this.groupAvatarBuilder, - }) : assert( - channel.state != null, - 'Channel ${channel.id} is not initialized', - ); - - /// [BorderRadius] to display the widget - final BorderRadius? borderRadius; - - /// The channel to show the image of - final Channel channel; - - /// The diameter of the image - final BoxConstraints? constraints; - - /// The function called when the image is tapped - final VoidCallback? onTap; - - /// If image is selected - final bool selected; - - /// Selection color for image - final Color? selectionColor; - - /// Thickness of selection image - final double selectionThickness; - - /// Builder to create avatar for own space channel. - /// - /// Defaults to [StreamUserAvatar]. - final StreamUserAvatarBuilder? ownSpaceAvatarBuilder; - - /// Builder to create avatar for one to one channel. - /// - /// Defaults to [StreamUserAvatar]. - final StreamUserAvatarBuilder? oneToOneAvatarBuilder; - - /// Builder to create avatar for group channel. - /// - /// Defaults to [StreamGroupAvatar]. - final StreamGroupAvatarBuilder? groupAvatarBuilder; - - @override - Widget build(BuildContext context) { - final client = channel.client.state; - - final chatThemeData = StreamChatTheme.of(context); - final colorTheme = chatThemeData.colorTheme; - final previewTheme = chatThemeData.channelPreviewTheme.avatarTheme; - - final fallbackWidget = Center( - child: Text( - channel.name?.characters.firstOrNull ?? '', - style: TextStyle( - color: colorTheme.barsBg, - fontWeight: FontWeight.bold, - ), - ), - ); - - return BetterStreamBuilder( - stream: channel.imageStream, - initialData: channel.image, - builder: (context, channelImage) { - Widget child = ClipRRect( - borderRadius: - borderRadius ?? previewTheme?.borderRadius ?? BorderRadius.zero, - child: Container( - constraints: constraints ?? previewTheme?.constraints, - decoration: BoxDecoration(color: colorTheme.accentPrimary), - child: InkWell( - onTap: onTap, - child: LayoutBuilder( - builder: (context, constraints) { - if (channelImage.isEmpty) return fallbackWidget; - - // Calculate optimal thumbnail size for the avatar - final devicePixel = MediaQuery.devicePixelRatioOf(context); - final thumbnailSize = constraints.biggest * devicePixel; - - int? cacheWidth, cacheHeight; - if (thumbnailSize.isFinite && !thumbnailSize.isEmpty) { - cacheWidth = thumbnailSize.width.round(); - cacheHeight = thumbnailSize.height.round(); - } - - return CachedNetworkImage( - imageUrl: channelImage, - memCacheWidth: cacheWidth, - memCacheHeight: cacheHeight, - errorWidget: (_, __, ___) => fallbackWidget, - fit: BoxFit.cover, - ); - }, - ), - ), - ), - ); - - if (selected) { - child = ClipRRect( - key: const Key('selectedImage'), - borderRadius: BorderRadius.circular(selectionThickness) + - (borderRadius ?? - previewTheme?.borderRadius ?? - BorderRadius.zero), - child: Container( - constraints: constraints ?? previewTheme?.constraints, - color: selectionColor ?? colorTheme.accentPrimary, - child: Padding( - padding: EdgeInsets.all(selectionThickness), - child: child, - ), - ), - ); - } - return child; - }, - noDataBuilder: (context) { - final currentUser = client.currentUser!; - final otherMembers = channel.state!.members - .where((it) => it.userId != currentUser.id) - .toList(growable: false); - - // our own space, no other members - if (otherMembers.isEmpty) { - return BetterStreamBuilder( - stream: client.currentUserStream.map((it) => it!), - initialData: currentUser, - builder: (context, user) { - final ownSpaceBuilder = ownSpaceAvatarBuilder; - if (ownSpaceBuilder != null) { - return ownSpaceBuilder(context, user, selected); - } - - return StreamUserAvatar( - borderRadius: borderRadius ?? previewTheme?.borderRadius, - user: user, - constraints: constraints ?? previewTheme?.constraints, - onTap: onTap != null ? (_) => onTap!() : null, - selected: selected, - selectionColor: selectionColor ?? colorTheme.accentPrimary, - selectionThickness: selectionThickness, - ); - }, - ); - } - - // 1-1 Conversation - if (otherMembers.length == 1) { - final member = otherMembers.first; - return BetterStreamBuilder( - stream: channel.state!.membersStream.map( - (members) => members.firstWhere( - (it) => it.userId == member.userId, - orElse: () => member, - ), - ), - initialData: member, - builder: (context, member) { - final oneToOneBuilder = oneToOneAvatarBuilder; - if (oneToOneBuilder != null) { - return oneToOneBuilder(context, member.user!, selected); - } - - return StreamUserAvatar( - borderRadius: borderRadius ?? previewTheme?.borderRadius, - user: member.user!, - constraints: constraints ?? previewTheme?.constraints, - onTap: onTap != null ? (_) => onTap!() : null, - selected: selected, - selectionColor: selectionColor ?? colorTheme.accentPrimary, - selectionThickness: selectionThickness, - ); - }, - ); - } - - final groupBuilder = groupAvatarBuilder; - if (groupBuilder != null) { - return groupBuilder(context, otherMembers, selected); - } - - // Group conversation - return StreamGroupAvatar( - channel: channel, - members: otherMembers, - borderRadius: borderRadius ?? previewTheme?.borderRadius, - constraints: constraints ?? previewTheme?.constraints, - onTap: onTap, - selected: selected, - selectionColor: selectionColor ?? colorTheme.accentPrimary, - selectionThickness: selectionThickness, - ); - }, - ); - } -} diff --git a/packages/stream_chat_flutter/lib/src/channel/stream_channel_name.dart b/packages/stream_chat_flutter/lib/src/channel/stream_channel_name.dart index 273b5b37e8..4dc226be84 100644 --- a/packages/stream_chat_flutter/lib/src/channel/stream_channel_name.dart +++ b/packages/stream_chat_flutter/lib/src/channel/stream_channel_name.dart @@ -13,9 +13,9 @@ class StreamChannelName extends StatelessWidget { this.textStyle, this.textOverflow = TextOverflow.ellipsis, }) : assert( - channel.state != null, - 'Channel ${channel.id} is not initialized', - ); + channel.state != null, + 'Channel ${channel.id} is not initialized', + ); /// The [Channel] to show the name for. final Channel channel; @@ -28,63 +28,69 @@ class StreamChannelName extends StatelessWidget { @override Widget build(BuildContext context) => BetterStreamBuilder( - stream: channel.nameStream, - initialData: channel.name, - builder: (context, channelName) => Text( - channelName, - style: textStyle, - overflow: textOverflow, - ), - noDataBuilder: (context) => _generateName( - channel.client.state.currentUser!, - channel.state!.members, - ), - ); + stream: channel.nameStream, + initialData: channel.name, + builder: (context, channelName) => Text( + channelName, + style: textStyle, + overflow: textOverflow, + ), + noDataBuilder: (context) => _generateName( + channel.client.state.currentUser!, + channel.state!.members, + ), + ); Widget _generateName( User currentUser, List members, - ) => - LayoutBuilder( - builder: (context, constraints) { - var channelName = context.translations.noTitleText; - final otherMembers = members.where( - (member) => member.userId != currentUser.id, - ); - - if (otherMembers.isNotEmpty) { - if (otherMembers.length == 1) { - final user = otherMembers.first.user; - if (user != null) { - channelName = user.name; - } - } else { - final maxWidth = constraints.maxWidth; - final maxChars = maxWidth / (textStyle?.fontSize ?? 1); - var currentChars = 0; - final currentMembers = []; - otherMembers.forEach((element) { - final newLength = - currentChars + (element.user?.name.length ?? 0); - if (newLength < maxChars) { - currentChars = newLength; - currentMembers.add(element); - } - }); + ) => LayoutBuilder( + builder: (context, constraints) { + var channelName = context.translations.noTitleText; + final otherMembers = members.where( + (member) => member.userId != currentUser.id, + ); - final exceedingMembers = - otherMembers.length - currentMembers.length; - channelName = - '${currentMembers.map((e) => e.user?.name).join(', ')} ' - '${exceedingMembers > 0 ? '+ $exceedingMembers' : ''}'; - } + if (otherMembers.isNotEmpty) { + if (otherMembers.length == 1) { + final user = otherMembers.first.user; + if (user != null) { + channelName = user.name; } + } else { + final maxWidth = constraints.maxWidth; + channelName = ''; + final currentMembers = []; + otherMembers.forEach((element) { + final newTitle = _getChannelName(currentMembers: [...currentMembers, element], members: members); + if (_calculateTextSize(newTitle).width < maxWidth) { + currentMembers.add(element); + channelName = newTitle; + } + }); + } + } - return Text( - channelName, - style: textStyle, - overflow: textOverflow, - ); - }, + return Text( + channelName, + style: textStyle, + overflow: textOverflow, ); + }, + ); + + String _getChannelName({required List currentMembers, required List members}) { + final exceedingMembers = members.length - currentMembers.length; + return '${currentMembers.map((e) => e.user?.name).join(', ')} ' + '${exceedingMembers > 0 ? '+ $exceedingMembers' : ''}'; + } + + Size _calculateTextSize(String text) { + final textPainter = TextPainter( + text: TextSpan(text: text, style: textStyle), + maxLines: 1, + textDirection: TextDirection.ltr, + )..layout(minWidth: 0, maxWidth: double.infinity); + return textPainter.size; + } } diff --git a/packages/stream_chat_flutter/lib/src/components/avatar/stream_channel_avatar.dart b/packages/stream_chat_flutter/lib/src/components/avatar/stream_channel_avatar.dart new file mode 100644 index 0000000000..6d47fd2dd1 --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/components/avatar/stream_channel_avatar.dart @@ -0,0 +1,130 @@ +import 'package:collection/collection.dart'; +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/src/components/avatar/stream_user_avatar.dart'; +import 'package:stream_chat_flutter/src/components/avatar/stream_user_avatar_group.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; + +/// A circular avatar component for displaying a channel's image. +/// +/// [StreamChannelAvatar] displays a channel's image or an appropriate fallback +/// based on the channel type. It supports channel images, user avatars for +/// 1-1 conversations, and group avatars for multi-member channels. +/// +/// The avatar automatically handles: +/// - Reactive updates when channel image changes via [Channel.imageStream] +/// - Fallback to member avatars when no channel image is set +/// - Deterministic color assignment for member avatars +/// +/// {@tool snippet} +/// +/// Basic usage with a channel: +/// +/// ```dart +/// StreamChannelAvatar(channel: channel) +/// ``` +/// {@end-tool} +/// +/// {@tool snippet} +/// +/// With custom size: +/// +/// ```dart +/// StreamChannelAvatar( +/// channel: channel, +/// size: StreamAvatarGroupSize.xl, +/// ) +/// ``` +/// {@end-tool} +/// +/// ## Theming +/// +/// [StreamChannelAvatar] uses [StreamAvatarThemeData] for default styling. +/// Member avatars within the channel avatar use deterministic colors from +/// [StreamColorScheme.avatarPalette]. +/// +/// See also: +/// +/// * [StreamAvatarGroupSize], which defines the available size variants. +/// * [StreamAvatarThemeData], which provides theme-level customization. +/// * [StreamUserAvatar], which is used to display individual member avatars. +class StreamChannelAvatar extends StatelessWidget { + /// Creates a Stream channel avatar. + const StreamChannelAvatar({ + super.key, + this.size, + required this.channel, + }); + + /// The channel whose avatar is displayed. + final Channel channel; + + /// The size of the channel avatar. + /// + /// If null, defaults to [StreamAvatarGroupSize.lg]. + final StreamAvatarGroupSize? size; + + @override + Widget build(BuildContext context) { + assert(channel.state != null, 'Channel ${channel.id} is not initialized'); + + final effectiveSize = size ?? StreamAvatarGroupSize.lg; + + return BetterStreamBuilder( + stream: channel.imageStream, + initialData: channel.image, + builder: (context, channelImage) => StreamAvatar( + imageUrl: channelImage, + size: _avatarSizeForAvatarGroupSize(effectiveSize), + placeholder: (_) => const _StreamChannelAvatarPlaceholder(), + ), + noDataBuilder: (context) => BetterStreamBuilder( + stream: channel.state!.membersStream, + initialData: channel.state!.members, + builder: (context, members) { + final users = members.map((it) => it.user!).toList(); + final currentUserId = channel.client.state.currentUser?.id; + + if (channel.isDistinct && users.length == 2) { + final otherUser = users.firstWhere( + (u) => u.id != currentUserId, + orElse: () => users.first, + ); + return StreamUserAvatar( + user: otherUser, + size: _avatarSizeForAvatarGroupSize(effectiveSize), + // TODO: make this configurable when the online state is shown. + showOnlineIndicator: otherUser.online, + ); + } + + return StreamUserAvatarGroup( + size: effectiveSize, + users: users.sortedBy((it) => it.id == currentUserId ? 1 : 0), + ); + }, + ), + ); + } + + // Maps [StreamAvatarGroupSize] to corresponding [StreamAvatarSize]. + // + // Used when displaying a single channel image avatar. + StreamAvatarSize _avatarSizeForAvatarGroupSize( + StreamAvatarGroupSize size, + ) => switch (size) { + .lg => StreamAvatarSize.lg, + .xl => StreamAvatarSize.xl, + .xxl => StreamAvatarSize.xxl, + }; +} + +// Placeholder widget for [StreamChannelAvatar]. +// +// Displays a team icon as a fallback when the channel image fails to load. +class _StreamChannelAvatarPlaceholder extends StatelessWidget { + const _StreamChannelAvatarPlaceholder(); + + @override + Widget build(BuildContext context) => Icon(context.streamIcons.team); +} diff --git a/packages/stream_chat_flutter/lib/src/components/avatar/stream_user_avatar.dart b/packages/stream_chat_flutter/lib/src/components/avatar/stream_user_avatar.dart new file mode 100644 index 0000000000..cccfa2b3d1 --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/components/avatar/stream_user_avatar.dart @@ -0,0 +1,168 @@ +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/src/utils/extensions.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; + +/// A circular avatar component for displaying a user's profile. +/// +/// [StreamUserAvatar] displays a user's profile image or initials placeholder +/// when no image is available. It supports multiple sizes, deterministic +/// color assignment, and an optional online status indicator. +/// +/// The avatar automatically handles: +/// - Displaying the user's profile image when available +/// - Showing user initials as a placeholder when no image exists +/// - Deterministic color assignment based on the user's ID +/// - Online status indicator positioning +/// +/// {@tool snippet} +/// +/// Basic usage with a user: +/// +/// ```dart +/// StreamUserAvatar(user: currentUser) +/// ``` +/// {@end-tool} +/// +/// {@tool snippet} +/// +/// With online indicator and custom size: +/// +/// ```dart +/// StreamUserAvatar( +/// user: currentUser, +/// size: StreamAvatarSize.lg, +/// showOnlineIndicator: true, +/// ) +/// ``` +/// {@end-tool} +/// +/// {@tool snippet} +/// +/// With border for selection states: +/// +/// ```dart +/// StreamUserAvatar( +/// user: selectedUser, +/// showBorder: true, +/// ) +/// ``` +/// {@end-tool} +/// +/// ## Theming +/// +/// [StreamUserAvatar] uses [StreamAvatarThemeData] for default styling. Colors +/// are automatically assigned based on the user's ID hash, selecting from +/// [StreamColorScheme.avatarPalette] for consistent user-specific colors. +/// +/// See also: +/// +/// * [StreamAvatarSize], which defines the available size variants. +/// * [StreamAvatarThemeData], which provides theme-level customization. +/// * [StreamColorScheme.avatarPalette], which provides colors for user avatars. +class StreamUserAvatar extends StatelessWidget { + /// Creates a Stream user avatar. + const StreamUserAvatar({ + super.key, + this.size, + required this.user, + this.showBorder = true, + this.showOnlineIndicator = true, + }); + + /// The user whose avatar is displayed. + final User user; + + /// Whether to show a border around the avatar. + /// + /// Defaults to true. The border style is determined by + /// [StreamAvatarThemeData.border]. + final bool showBorder; + + /// Whether to show the online status indicator. + /// + /// Defaults to true. + final bool showOnlineIndicator; + + /// The size of the avatar. + /// + /// If null, uses [StreamAvatarThemeData.size], or falls back to + /// [StreamAvatarSize.lg]. + final StreamAvatarSize? size; + + @override + Widget build(BuildContext context) { + final avatarTheme = context.streamAvatarTheme; + final colorScheme = context.streamColorScheme; + final avatarPalette = colorScheme.avatarPalette; + + final userHash = user.id.hashCode; // Ensure deterministic colors. + final colorPair = avatarPalette[userHash % avatarPalette.length]; + + final effectiveSize = size ?? avatarTheme.size ?? .lg; + final effectiveBackgroundColor = avatarTheme.backgroundColor ?? colorPair.backgroundColor; + final effectiveForegroundColor = avatarTheme.foregroundColor ?? colorPair.foregroundColor; + + final userAvatar = StreamAvatar( + size: effectiveSize, + imageUrl: user.image, + showBorder: showBorder, + backgroundColor: effectiveBackgroundColor, + foregroundColor: effectiveForegroundColor, + placeholder: (_) => _StreamUserAvatarPlaceholder(user: user, size: effectiveSize), + ); + + if (showOnlineIndicator) { + final indicatorSize = _indicatorSizeForAvatarSize(effectiveSize); + + return StreamOnlineIndicator( + size: indicatorSize, + isOnline: user.online, + alignment: AlignmentDirectional.topEnd, + child: userAvatar, + ); + } + + return userAvatar; + } + + // Maps [StreamAvatarSize] to corresponding [StreamOnlineIndicatorSize]. + // + // Ensures the online indicator scales appropriately with the avatar size. + StreamOnlineIndicatorSize _indicatorSizeForAvatarSize( + StreamAvatarSize size, + ) => switch (size) { + .xs || .sm => StreamOnlineIndicatorSize.sm, + .md => StreamOnlineIndicatorSize.md, + .lg => StreamOnlineIndicatorSize.lg, + .xl || .xxl => StreamOnlineIndicatorSize.xl, + }; +} + +// Placeholder widget for [StreamUserAvatar]. +// +// Displays user initials or a fallback person icon when no name is available. +// Shows full initials (up to 2 characters) for medium and larger sizes, +// and only the first initial for extra-small and small sizes. +class _StreamUserAvatarPlaceholder extends StatelessWidget { + const _StreamUserAvatarPlaceholder({ + required this.user, + required this.size, + }); + + final User user; + final StreamAvatarSize size; + + @override + Widget build(BuildContext context) { + final userInitials = user.name.initials; + if (userInitials != null && userInitials.isNotEmpty) { + return switch (size) { + .md || .lg || .xl || .xxl => Text(userInitials), + .xs || .sm => Text(userInitials.characters.first), + }; + } + + return Icon(context.streamIcons.people); + } +} diff --git a/packages/stream_chat_flutter/lib/src/components/avatar/stream_user_avatar_group.dart b/packages/stream_chat_flutter/lib/src/components/avatar/stream_user_avatar_group.dart new file mode 100644 index 0000000000..5b557f4e6b --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/components/avatar/stream_user_avatar_group.dart @@ -0,0 +1,80 @@ +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/src/components/avatar/stream_user_avatar.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; + +/// A widget that displays multiple user avatars in a grid layout. +/// +/// [StreamUserAvatarGroup] arranges user avatars in a 2x2 grid pattern, +/// typically used for displaying group channel participants. It supports +/// two sizes and automatically handles overflow with a badge indicator. +/// +/// The group automatically handles: +/// - Grid layout for up to 4 user avatars +/// - Overflow indicator showing remaining count for additional users +/// - Deterministic color assignment for each user avatar +/// - Consistent sizing across all child avatars +/// +/// {@tool snippet} +/// +/// Basic usage with a list of users: +/// +/// ```dart +/// StreamUserAvatarGroup(users: groupMembers) +/// ``` +/// {@end-tool} +/// +/// {@tool snippet} +/// +/// With custom size: +/// +/// ```dart +/// StreamUserAvatarGroup( +/// users: groupMembers, +/// size: StreamAvatarGroupSize.xl, +/// ) +/// ``` +/// {@end-tool} +/// +/// ## Theming +/// +/// [StreamUserAvatarGroup] uses [StreamAvatarThemeData] for styling the child +/// avatars. Individual user avatars use deterministic colors from +/// [StreamColorScheme.avatarPalette]. +/// +/// See also: +/// +/// * [StreamAvatarGroupSize], which defines the available size variants. +/// * [StreamUserAvatar], which is used to display individual user avatars. +/// * [StreamAvatarGroup], the underlying group component. +class StreamUserAvatarGroup extends StatelessWidget { + /// Creates a Stream user avatar group. + /// + /// If [users] is empty, returns an empty [SizedBox]. + const StreamUserAvatarGroup({ + super.key, + required this.users, + this.size, + }); + + /// The list of users whose avatars are displayed. + final Iterable users; + + /// The size of the avatar group. + /// + /// If null, defaults to [StreamAvatarGroupSize.lg]. + final StreamAvatarGroupSize? size; + + @override + Widget build(BuildContext context) { + return StreamAvatarGroup( + size: size, + children: users.map( + (user) => StreamUserAvatar( + user: user, + showOnlineIndicator: false, + ), + ), + ); + } +} diff --git a/packages/stream_chat_flutter/lib/src/components/avatar/stream_user_avatar_stack.dart b/packages/stream_chat_flutter/lib/src/components/avatar/stream_user_avatar_stack.dart new file mode 100644 index 0000000000..12b8ee48f9 --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/components/avatar/stream_user_avatar_stack.dart @@ -0,0 +1,113 @@ +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/src/components/avatar/stream_user_avatar.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; + +/// A widget that displays a stack of user avatars with overlap. +/// +/// [StreamUserAvatarStack] displays multiple user avatars in a horizontal +/// stack, with each avatar partially overlapping the previous one. This is +/// useful for showing participants in a conversation, group members, or +/// any collection of users in a compact space. +/// +/// The stack automatically handles: +/// - Displaying user avatars with deterministic colors +/// - Overflow handling with a badge showing remaining count +/// - Customizable overlap and maximum visible avatars +/// +/// {@tool snippet} +/// +/// Basic usage with a list of users: +/// +/// ```dart +/// StreamUserAvatarStack(users: participants) +/// ``` +/// {@end-tool} +/// +/// {@tool snippet} +/// +/// With custom max and size: +/// +/// ```dart +/// StreamUserAvatarStack( +/// users: participants, +/// max: 3, +/// size: StreamAvatarStackSize.xs, +/// ) +/// ``` +/// {@end-tool} +/// +/// {@tool snippet} +/// +/// With custom overlap: +/// +/// ```dart +/// StreamUserAvatarStack( +/// users: participants, +/// overlap: 0.5, // 50% overlap +/// ) +/// ``` +/// {@end-tool} +/// +/// ## Theming +/// +/// [StreamUserAvatarStack] uses [StreamAvatarThemeData] for default styling. +/// Individual user avatars use deterministic colors from +/// [StreamColorScheme.avatarPalette]. +/// +/// See also: +/// +/// * [StreamAvatarStackSize], which defines the available size variants. +/// * [StreamUserAvatar], which is used to display individual user avatars. +/// * [StreamAvatarStack], the underlying stack component. +class StreamUserAvatarStack extends StatelessWidget { + /// Creates a Stream user avatar stack. + /// + /// If [users] is empty, returns an empty [SizedBox]. + /// The [max] must be at least 2. + const StreamUserAvatarStack({ + super.key, + required this.users, + this.size, + this.overlap = 0.33, + this.max = 5, + }) : assert(max >= 2, 'max must be at least 2'); + + /// The list of users whose avatars are displayed. + final Iterable users; + + /// The size of the avatar stack. + /// + /// If null, defaults to [StreamAvatarStackSize.sm]. + final StreamAvatarStackSize? size; + + /// How much each avatar overlaps the previous one, as a fraction of size. + /// + /// - `0.0`: No overlap (side by side) + /// - `0.33`: 33% overlap (default) + /// - `1.0`: Fully stacked + final double overlap; + + /// Maximum number of avatars to display before showing overflow badge. + /// + /// When [users] exceeds this value, displays [max] avatars followed + /// by a badge showing the overflow count (e.g., "+2"). + /// + /// Must be at least 2. Defaults to 5. + final int max; + + @override + Widget build(BuildContext context) { + return StreamAvatarStack( + max: max, + size: size, + overlap: overlap, + children: users.map( + (user) => StreamUserAvatar( + user: user, + showOnlineIndicator: false, + ), + ), + ); + } +} diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer.dart new file mode 100644 index 0000000000..d7370d4d6e --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer.dart @@ -0,0 +1,2 @@ +export 'message_composer_component_props.dart'; +export 'stream_chat_message_composer.dart'; diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_component_props.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_component_props.dart new file mode 100644 index 0000000000..9d450d0615 --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_component_props.dart @@ -0,0 +1,261 @@ +import 'package:flutter/widgets.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart' as core; + +/// Properties to build any of the sub-components. +/// These properties are all the same, so features such as 'add attachment', +/// can be added to any of the sub-components. +class MessageComposerComponentProps { + /// Creates a new instance of [MessageComposerComponentProps]. + /// [controller] is the controller for the message composer component. + /// [isFloating] is whether the message composer is floating. + /// [message] is the message for the message composer component. + /// [onSendPressed] is the callback for when the send button is pressed. + /// [onMicrophonePressed] is the callback for when the microphone button is pressed. + /// [onAttachmentButtonPressed] is the callback for when the attachment button is pressed. + /// [focusNode] is the focus node for the message composer component. + /// [currentUserId] is the current user id. + const MessageComposerComponentProps({ + required this.controller, + this.isFloating = false, + this.message, + required this.onSendPressed, + this.voiceRecordingCallback, + this.onAttachmentButtonPressed, + this.isPickerOpen = false, + this.focusNode, + this.currentUserId, + required this.audioRecorderState, + }); + + /// The controller for the message composer component. + final StreamMessageInputController controller; + + /// Whether the message composer is floating. + final bool isFloating; + + /// The message for the message composer component. + final Message? message; + + /// The callback for when the send button is pressed. + final VoidCallback onSendPressed; + + /// The callback for when the microphone button is pressed. + final core.VoiceRecordingCallback? voiceRecordingCallback; + + /// The callback for when the attachment button is pressed. + final VoidCallback? onAttachmentButtonPressed; + + /// Whether the inline attachment picker is currently open. + final bool isPickerOpen; + + /// The focus node for the message composer component. + final FocusNode? focusNode; + + /// The current user id. + final String? currentUserId; + + /// Whether the audio recording flow is active. + final AudioRecorderState audioRecorderState; + + /// Whether the audio recording flow is active. + bool get isAudioRecordingFlowActive => audioRecorderState is RecordStateRecording || isAudioRecordingFlowStopped; + + /// Whether the audio recording flow is locked. + bool get isAudioRecordingFlowLocked => audioRecorderState is RecordStateRecordingLocked; + + /// Whether the audio recording flow is stopped. + bool get isAudioRecordingFlowStopped => audioRecorderState is RecordStateStopped; +} + +/// Properties for building the leading component of the message composer. +class MessageComposerLeadingProps extends MessageComposerComponentProps { + const MessageComposerLeadingProps._({ + required super.controller, + required super.isFloating, + required super.message, + required super.onSendPressed, + required super.voiceRecordingCallback, + required super.onAttachmentButtonPressed, + required super.isPickerOpen, + required super.focusNode, + required super.currentUserId, + required super.audioRecorderState, + }) : super(); + + /// Creates a new instance of [MessageComposerLeadingProps] from a [MessageComposerComponentProps]. + factory MessageComposerLeadingProps.from(MessageComposerComponentProps props) { + return MessageComposerLeadingProps._( + controller: props.controller, + isFloating: props.isFloating, + message: props.message, + onSendPressed: props.onSendPressed, + voiceRecordingCallback: props.voiceRecordingCallback, + onAttachmentButtonPressed: props.onAttachmentButtonPressed, + isPickerOpen: props.isPickerOpen, + focusNode: props.focusNode, + currentUserId: props.currentUserId, + audioRecorderState: props.audioRecorderState, + ); + } +} + +/// Properties for building the trailing component of the message composer. +class MessageComposerTrailingProps extends MessageComposerComponentProps { + const MessageComposerTrailingProps._({ + required super.controller, + required super.isFloating, + required super.message, + required super.onSendPressed, + required super.voiceRecordingCallback, + required super.onAttachmentButtonPressed, + required super.isPickerOpen, + required super.focusNode, + required super.currentUserId, + required super.audioRecorderState, + }) : super(); + + /// Creates a new instance of [MessageComposerTrailingProps] from a [MessageComposerComponentProps]. + factory MessageComposerTrailingProps.from(MessageComposerComponentProps props) { + return MessageComposerTrailingProps._( + controller: props.controller, + isFloating: props.isFloating, + message: props.message, + onSendPressed: props.onSendPressed, + voiceRecordingCallback: props.voiceRecordingCallback, + onAttachmentButtonPressed: props.onAttachmentButtonPressed, + isPickerOpen: props.isPickerOpen, + focusNode: props.focusNode, + currentUserId: props.currentUserId, + audioRecorderState: props.audioRecorderState, + ); + } +} + +/// Properties for building the input component of the message composer. +class MessageComposerInputProps extends MessageComposerComponentProps { + const MessageComposerInputProps._({ + required super.controller, + required super.isFloating, + required super.message, + required super.onSendPressed, + required super.voiceRecordingCallback, + required super.onAttachmentButtonPressed, + required super.isPickerOpen, + required super.focusNode, + required super.currentUserId, + required super.audioRecorderState, + }) : super(); + + /// Creates a new instance of [MessageComposerInputProps] from a [MessageComposerComponentProps]. + factory MessageComposerInputProps.from(MessageComposerComponentProps props) { + return MessageComposerInputProps._( + controller: props.controller, + isFloating: props.isFloating, + message: props.message, + onSendPressed: props.onSendPressed, + voiceRecordingCallback: props.voiceRecordingCallback, + onAttachmentButtonPressed: props.onAttachmentButtonPressed, + isPickerOpen: props.isPickerOpen, + focusNode: props.focusNode, + currentUserId: props.currentUserId, + audioRecorderState: props.audioRecorderState, + ); + } +} + +/// Properties for building the input leading component of the message composer. +class MessageComposerInputLeadingProps extends MessageComposerComponentProps { + const MessageComposerInputLeadingProps._({ + required super.controller, + required super.isFloating, + required super.message, + required super.onSendPressed, + required super.voiceRecordingCallback, + required super.onAttachmentButtonPressed, + required super.isPickerOpen, + required super.focusNode, + required super.currentUserId, + required super.audioRecorderState, + }) : super(); + + /// Creates a new instance of [MessageComposerInputLeadingProps] from a [MessageComposerComponentProps]. + factory MessageComposerInputLeadingProps.from(MessageComposerComponentProps props) { + return MessageComposerInputLeadingProps._( + controller: props.controller, + isFloating: props.isFloating, + message: props.message, + onSendPressed: props.onSendPressed, + voiceRecordingCallback: props.voiceRecordingCallback, + onAttachmentButtonPressed: props.onAttachmentButtonPressed, + isPickerOpen: props.isPickerOpen, + focusNode: props.focusNode, + currentUserId: props.currentUserId, + audioRecorderState: props.audioRecorderState, + ); + } +} + +/// Properties for building the input header component of the message composer. +class MessageComposerInputHeaderProps extends MessageComposerComponentProps { + const MessageComposerInputHeaderProps._({ + required super.controller, + required super.isFloating, + required super.message, + required super.onSendPressed, + required super.voiceRecordingCallback, + required super.onAttachmentButtonPressed, + required super.isPickerOpen, + required super.focusNode, + required super.currentUserId, + required super.audioRecorderState, + }) : super(); + + /// Creates a new instance of [MessageComposerInputHeaderProps] from a [MessageComposerComponentProps]. + factory MessageComposerInputHeaderProps.from(MessageComposerComponentProps props) { + return MessageComposerInputHeaderProps._( + controller: props.controller, + isFloating: props.isFloating, + message: props.message, + onSendPressed: props.onSendPressed, + voiceRecordingCallback: props.voiceRecordingCallback, + onAttachmentButtonPressed: props.onAttachmentButtonPressed, + isPickerOpen: props.isPickerOpen, + focusNode: props.focusNode, + currentUserId: props.currentUserId, + audioRecorderState: props.audioRecorderState, + ); + } +} + +/// Properties for building the input trailing component of the message composer. +class MessageComposerInputTrailingProps extends MessageComposerComponentProps { + const MessageComposerInputTrailingProps._({ + required super.controller, + required super.isFloating, + required super.message, + required super.onSendPressed, + required super.voiceRecordingCallback, + required super.onAttachmentButtonPressed, + required super.isPickerOpen, + required super.focusNode, + required super.currentUserId, + required super.audioRecorderState, + }) : super(); + + /// Creates a new instance of [MessageComposerInputTrailingProps] from a [MessageComposerComponentProps]. + factory MessageComposerInputTrailingProps.from(MessageComposerComponentProps props) { + return MessageComposerInputTrailingProps._( + controller: props.controller, + isFloating: props.isFloating, + message: props.message, + onSendPressed: props.onSendPressed, + voiceRecordingCallback: props.voiceRecordingCallback, + onAttachmentButtonPressed: props.onAttachmentButtonPressed, + isPickerOpen: props.isPickerOpen, + focusNode: props.focusNode, + currentUserId: props.currentUserId, + audioRecorderState: props.audioRecorderState, + ); + } +} diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_header.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_header.dart new file mode 100644 index 0000000000..257985c7df --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_header.dart @@ -0,0 +1,230 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; + +/// A widget that shows the input header of the message composer. +/// Uses the factory to show custom components or used the default implementation. +class StreamMessageComposerInputHeader extends StatelessWidget { + /// Creates a new instance of [StreamMessageComposerInputHeader]. + /// [props] contains the properties for the message composer component. + const StreamMessageComposerInputHeader({super.key, required this.props}); + + /// The properties for the message composer component. + final MessageComposerComponentProps props; + + @override + Widget build(BuildContext context) { + return context.chatComponentBuilder()?.call( + context, + MessageComposerInputHeaderProps.from(props), + ) ?? + _DefaultStreamMessageComposerInputHeader(props: props); + } +} + +class _DefaultStreamMessageComposerInputHeader extends StatelessWidget { + const _DefaultStreamMessageComposerInputHeader({required this.props}); + + final MessageComposerComponentProps props; + StreamMessageInputController get controller => props.controller; + + @override + Widget build(BuildContext context) { + return ValueListenableBuilder( + valueListenable: props.controller, + builder: (context, _, __) => _buildContent(context), + ); + } + + Widget _buildContent(BuildContext context) { + final isEditing = !controller.message.state.isInitial; + final quotedMessage = !isEditing ? controller.message.quotedMessage : null; + final ogAttachment = controller.ogAttachment; + final nonOGAttachments = controller.attachments + .where((it) { + return it.titleLink == null && it.type != AttachmentType.voiceRecording; + }) + .toList(growable: false); + final voiceRecordings = controller.attachments + .where((it) { + return it.type == AttachmentType.voiceRecording; + }) + .toList(growable: false); + + final hasAttachments = nonOGAttachments.isNotEmpty; + final hasContent = + isEditing || quotedMessage != null || hasAttachments || ogAttachment != null || voiceRecordings.isNotEmpty; + + final spacing = context.streamSpacing; + final contentPadding = EdgeInsets.only( + left: spacing.xs, + right: spacing.xs, + ); + + return AnimatedSize( + duration: const Duration(milliseconds: 200), + alignment: Alignment.topCenter, + child: Padding( + padding: EdgeInsets.only( + top: hasContent ? spacing.xs : 0, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + if (isEditing) + Padding( + padding: contentPadding, + child: _EditMessageInHeader( + message: controller.editingOriginalMessage ?? controller.message, + onRemovePressed: controller.cancelEditMessage, + ), + ) + else if (quotedMessage != null) + Padding( + padding: contentPadding, + child: _QuotedMessageInHeader( + quotedMessage: quotedMessage, + onRemovePressed: controller.clearQuotedMessage, + currentUserId: props.currentUserId, + ), + ), + if (voiceRecordings.isNotEmpty) + Padding( + padding: contentPadding, + child: StreamVoiceRecordingAttachmentPlaylist( + voiceRecordings: voiceRecordings, + onRemovePressed: _onAttachmentRemovePressed, + voiceRecordingTitle: 'Voice Message', + message: props.controller.message, + ), + ), + if (hasAttachments) + StreamMessageInputAttachmentList( + attachments: nonOGAttachments, + onRemovePressed: _onAttachmentRemovePressed, + ), + if (ogAttachment != null) + Padding( + padding: contentPadding, + child: MessageComposerLinkPreviewAttachment( + title: ogAttachment.title, + subtitle: ogAttachment.text, + image: ogAttachment.imageUrl != null ? CachedNetworkImageProvider(ogAttachment.imageUrl!) : null, + url: ogAttachment.titleLink, + onRemovePressed: () { + controller.clearOGAttachment(); + props.focusNode?.unfocus(); + }, + ), + ), + ], + ), + ), + ); + } + + // Default callback for removing an attachment. + Future _onAttachmentRemovePressed(Attachment attachment) async { + final file = attachment.file; + final uploadState = attachment.uploadState; + + if (file != null && !uploadState.isSuccess && !isWeb) { + await StreamAttachmentHandler.instance.deleteAttachmentFile( + attachmentFile: file, + ); + } + + controller.removeAttachmentById(attachment.id); + } +} + +class _EditMessageInHeader extends StatelessWidget { + const _EditMessageInHeader({ + required this.message, + required this.onRemovePressed, + }); + + final Message message; + final VoidCallback onRemovePressed; + + @override + Widget build(BuildContext context) { + return MessageComposerReplyAttachment( + title: Text(context.translations.editMessageLabel), + subtitle: StreamMessagePreviewText(message: message), + onRemovePressed: onRemovePressed, + style: ReplyStyle.outgoing, + ); + } +} + +class _QuotedMessageInHeader extends StatelessWidget { + const _QuotedMessageInHeader({ + required this.quotedMessage, + required this.onRemovePressed, + required this.currentUserId, + }); + + final Message quotedMessage; + final VoidCallback onRemovePressed; + final String? currentUserId; + + ImageProvider? _imageProvider(Message message) { + final attachments = message.attachments; + if (attachments.isEmpty || attachments.length > 1) return null; + + final attachment = attachments.first; + if (attachment.type == AttachmentType.file) return null; + final imageUrl = attachment.imageUrl ?? attachment.thumbUrl ?? attachment.assetUrl; + + if (imageUrl == null) return null; + return CachedNetworkImageProvider(imageUrl); + } + + String? _mimeTypeAttachment(Message message) { + final attachments = message.attachments; + if (attachments.isEmpty) return null; + final attachment = attachments.first; + + if (attachment.type != AttachmentType.file) return null; + if (attachments.any((it) => it.mimeType != attachment.mimeType)) return null; + + return attachment.mimeType; + } + + @override + Widget build(BuildContext context) { + final isIncoming = currentUserId != quotedMessage.user?.id; + + final image = _imageProvider(quotedMessage); + final mimeType = _mimeTypeAttachment(quotedMessage); + + Widget? trailing; + if (image != null) { + Container( + width: 40, + height: 40, + decoration: BoxDecoration( + borderRadius: BorderRadius.all(context.streamRadius.md), + image: DecorationImage(image: image, fit: BoxFit.cover), + ), + ); + } else if (mimeType != null) { + trailing = StreamFileTypeIcon.fromMimeType(mimeType: mimeType); + } else { + trailing = null; + } + + return + // TODO: localize strings + MessageComposerReplyAttachment( + title: Text(isIncoming ? 'Reply to ${quotedMessage.user?.name}' : 'You'), + subtitle: StreamMessagePreviewText(message: quotedMessage), + onRemovePressed: onRemovePressed, + trailing: trailing, + style: isIncoming ? .incoming : .outgoing, + ); + } +} diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_leading.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_leading.dart new file mode 100644 index 0000000000..2399796aa3 --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_leading.dart @@ -0,0 +1,22 @@ +import 'package:flutter/widgets.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +/// A widget that shows the input leading of the message composer. +/// Uses the factory to show custom components, but is empty by default. +class StreamMessageComposerInputLeading extends StatelessWidget { + /// Creates a new instance of [StreamMessageComposerInputLeading]. + /// [props] contains the properties for the message composer component. + const StreamMessageComposerInputLeading({super.key, required this.props}); + + /// The properties for the message composer component. + final MessageComposerComponentProps props; + + @override + Widget build(BuildContext context) { + return context.chatComponentBuilder()?.call( + context, + MessageComposerInputLeadingProps.from(props), + ) ?? + const SizedBox.shrink(); + } +} diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_trailing.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_trailing.dart new file mode 100644 index 0000000000..3c9d3284af --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_trailing.dart @@ -0,0 +1,72 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; + +/// A widget that shows the input trailing of the message composer. +/// Uses the factory to show custom components or the default implementation. +/// By default it shows the send button and the microphone button. +class StreamMessageComposerInputTrailing extends StatelessWidget { + /// Creates a new instance of [StreamMessageComposerInputTrailing]. + /// [props] contains the properties for the message composer component. + const StreamMessageComposerInputTrailing({super.key, required this.props}); + + /// The properties for the message composer component. + final MessageComposerComponentProps props; + + @override + Widget build(BuildContext context) { + return context.chatComponentBuilder()?.call( + context, + MessageComposerInputTrailingProps.from(props), + ) ?? + DefaultStreamMessageComposerInputTrailing(props: props); + } +} + +/// Default implementation of the input trailing of the message composer. +/// Shows the send button or the microphone button based on the state of the message composer. +/// It shows no button when the audio recording flow is locked or stopped. +class DefaultStreamMessageComposerInputTrailing extends StatelessWidget { + /// Creates a new instance of [DefaultStreamMessageComposerInputTrailing]. + /// [props] contains the properties for the message composer component. + const DefaultStreamMessageComposerInputTrailing({super.key, required this.props}); + + /// The properties for the message composer component. + final MessageComposerComponentProps props; + + StreamMessageInputController get _controller => props.controller; + + @override + Widget build(BuildContext context) { + return ValueListenableBuilder( + valueListenable: _controller, + builder: (context, value, child) { + final hasText = _controller.text.isNotEmpty; + final hasContent = hasText || _controller.attachments.isNotEmpty; + final isEditing = !_controller.message.state.isInitial; + var buttonState = StreamMessageComposerInputTrailingState.microphone; + if (props.isAudioRecordingFlowActive) { + buttonState = StreamMessageComposerInputTrailingState.voiceRecordingActive; + } + + if (isEditing) { + buttonState = StreamMessageComposerInputTrailingState.edit; + } else if (hasContent) { + buttonState = StreamMessageComposerInputTrailingState.send; + } + + final isEnabled = !isEditing || hasContent; + + return props.isAudioRecordingFlowLocked || props.isAudioRecordingFlowStopped + ? const SizedBox.shrink() + : StreamCoreMessageComposerInputTrailing( + controller: _controller.textFieldController, + onSendPressed: isEnabled ? props.onSendPressed : null, + voiceRecordingCallback: props.voiceRecordingCallback, + buttonState: buttonState, + ); + }, + ); + } +} diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_leading.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_leading.dart new file mode 100644 index 0000000000..26114de383 --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_leading.dart @@ -0,0 +1,69 @@ +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; + +/// A widget that shows the leading of the message composer. +/// Uses the factory to show custom components or the default implementation. +/// By default this contains a button to add attachments. +class StreamMessageComposerLeading extends StatelessWidget { + /// Creates a new instance of [StreamMessageComposerLeading]. + /// [props] contains the properties for the message composer component. + const StreamMessageComposerLeading({super.key, required this.props}); + + /// The properties for the message composer component. + final MessageComposerComponentProps props; + + @override + Widget build(BuildContext context) { + return context.chatComponentBuilder()?.call( + context, + MessageComposerLeadingProps.from(props), + ) ?? + _DefaultStreamMessageComposerLeading(props: props); + } +} + +class _DefaultStreamMessageComposerLeading extends StatelessWidget { + const _DefaultStreamMessageComposerLeading({required this.props}); + + final MessageComposerComponentProps props; + + @override + Widget build(BuildContext context) { + // 45 degrees = 0.125 turns + const closedRotation = 0.125; + final showButton = !props.isAudioRecordingFlowActive && props.controller.message.command == null; + + return AnimatedOpacity( + opacity: showButton ? 1.0 : 0.0, + duration: showButton ? const Duration(milliseconds: 200) : Duration.zero, + curve: Curves.easeInQuint, + child: AnimatedSize( + duration: const Duration(milliseconds: 200), + alignment: Alignment.bottomCenter, + child: Row( + children: [ + if (showButton) ...[ + AnimatedRotation( + turns: props.isPickerOpen ? closedRotation : 0, + duration: const Duration(milliseconds: 150), + curve: Curves.easeOut, + child: StreamButton.icon( + icon: context.streamIcons.plusLarge, + style: StreamButtonStyle.secondary, + type: StreamButtonType.outline, + size: StreamButtonSize.large, + isFloating: props.isFloating, + onTap: () { + props.onAttachmentButtonPressed?.call(); + }, + ), + ), + SizedBox(width: context.streamSpacing.xs), + ], + ], + ), + ), + ); + } +} diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_recording_locked.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_recording_locked.dart new file mode 100644 index 0000000000..220f80a289 --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_recording_locked.dart @@ -0,0 +1,328 @@ +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/src/audio/audio_playlist_controller.dart'; +import 'package:stream_chat_flutter/src/audio/audio_playlist_state.dart'; +import 'package:stream_chat_flutter/src/audio/audio_sampling.dart' as sampling; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; + +const _kDefaultWaveformHeight = 20.0; +const _kDefaultWaveformLimit = 35; + +/// Widget to display the recording locked state. +/// This widget can be used inside of the [StreamBaseMessageComposer] instead of the default `inputBody`. +class MessageComposerRecordingLocked extends StatelessWidget { + /// Creates a new instance of [MessageComposerRecordingLocked]. + /// [audioRecorderController] is the controller for the audio recorder. + /// [feedback] is the feedback for the audio recorder. + /// [messageInputController] is the controller for the message input. + /// [sendMessageCallback] is the callback for when the message is sent automatically. + const MessageComposerRecordingLocked({ + super.key, + required this.audioRecorderController, + required this.feedback, + required this.messageInputController, + required this.sendMessageCallback, + required this.state, + }); + + /// The controller for the audio recorder. + final StreamAudioRecorderController audioRecorderController; + + /// The feedback for the audio recorder. + final AudioRecorderFeedback feedback; + + /// The controller for the message input. + final StreamMessageInputController messageInputController; + + /// The callback for when the message is sent automatically. + /// This callback should be null when the message is not supposed to be sent automatically. + final VoidCallback? sendMessageCallback; + + /// The state of the recording. + final RecordStateRecording state; + + @override + Widget build(BuildContext context) { + final icons = context.streamIcons; + final textTheme = context.streamTextTheme; + final colorScheme = context.streamColorScheme; + final spacing = context.streamSpacing; + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + height: 48, + width: 48, + alignment: Alignment.center, + child: Icon( + icons.microphone, + color: context.streamColorScheme.accentError, + size: 20, + ), + ), + Text( + state.duration.toMinutesAndSeconds(), + style: textTheme.captionEmphasis.copyWith( + color: colorScheme.textPrimary, + fontFeatures: [const FontFeature.tabularFigures()], + ), + ), + Expanded( + child: Container( + height: _kDefaultWaveformHeight, + padding: EdgeInsets.symmetric(horizontal: spacing.md), + child: StreamAudioWaveform(waveform: state.waveform, limit: 50), + ), + ), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + StreamButton.icon( + key: const ValueKey('cancel-record-button'), + style: StreamButtonStyle.secondary, + type: StreamButtonType.outline, + size: StreamButtonSize.small, + icon: icons.trashBin, + onTap: audioRecorderController.cancelRecord, + ), + if (audioRecorderController.value is RecordStateRecording) + StreamButton.icon( + key: const ValueKey('stop-record-button'), + style: StreamButtonStyle.destructive, + type: StreamButtonType.outline, + size: StreamButtonSize.small, + icon: icons.stop, + onTap: audioRecorderController.stopRecord, + ), + StreamButton.icon( + key: const ValueKey('finish-record-button'), + style: StreamButtonStyle.primary, + type: StreamButtonType.solid, + size: StreamButtonSize.small, + icon: icons.checkmark2Small, + onTap: () async { + await feedback.onRecordFinish(context); + final audio = await audioRecorderController.finishRecord(); + if (audio != null) { + messageInputController.addAttachment(audio); + } + + // Once the recording is finished, cancel the recorder. + audioRecorderController.cancelRecord(discardTrack: false); + + // Send the message if the user has enabled the option to + // send the voice recording automatically. + sendMessageCallback?.call(); + }, + ), + ], + ), + ], + ); + } +} + +/// Widget to display the recording stopped state. +/// This widget can be used inside of the [StreamBaseMessageComposer] instead of the default `inputBody`. +class MessageComposerRecordingStopped extends StatefulWidget { + /// Creates a new instance of [MessageComposerRecordingStopped]. + /// [audioRecorderController] is the controller for the audio recorder. + /// [feedback] is the feedback for the audio recorder. + /// [messageInputController] is the controller for the message input. + /// [sendMessageCallback] is the callback for when the message is sent automatically. + const MessageComposerRecordingStopped({ + super.key, + required this.audioRecorderController, + required this.feedback, + required this.messageInputController, + required this.sendMessageCallback, + required this.recordingState, + }); + + /// The controller for the audio recorder. + final StreamAudioRecorderController audioRecorderController; + + /// The feedback for the audio recorder. + final AudioRecorderFeedback feedback; + + /// The controller for the message input. + final StreamMessageInputController messageInputController; + + /// The callback for when the message is sent automatically. + /// This callback should be null when the message is not supposed to be sent automatically. + final VoidCallback? sendMessageCallback; + + /// The state of the recording. + final RecordStateStopped recordingState; + + Attachment get _audioRecording => recordingState.audioRecording; + + @override + State createState() => _MessageComposerRecordingStoppedState(); +} + +class _MessageComposerRecordingStoppedState extends State { + late final _controller = StreamAudioPlaylistController( + widget._audioRecording.toPlaylist(), + ); + + @override + void initState() { + super.initState(); + _controller.initialize(); + } + + @override + void didUpdateWidget( + covariant MessageComposerRecordingStopped oldWidget, + ) { + super.didUpdateWidget(oldWidget); + if (widget._audioRecording != widget._audioRecording) { + // If the playlist have changed, update the playlist. + _controller.updatePlaylist(widget._audioRecording.toPlaylist()); + } + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final icons = context.streamIcons; + final textTheme = context.streamTextTheme; + final colorScheme = context.streamColorScheme; + const index = 0; + + final spacing = context.streamSpacing; + + return ValueListenableBuilder( + valueListenable: _controller, + builder: (context, state, child) { + final track = state.tracks.firstOrNull; + if (track == null) return const SizedBox.shrink(); + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + AudioControlButton( + state: track.state, + style: StreamButtonStyle.secondary, + type: StreamButtonType.ghost, + size: StreamButtonSize.small, + onPlay: () { + if (state.currentIndex == index) { + // Play the track directly if it is already loaded. + _controller.play(); + } else { + // Otherwise, load the track first and then play it. + _controller.skipToItem(index); + } + }, + onPause: _controller.pause, + ), + AudioDurationText( + duration: track.duration, + position: track.position, + style: textTheme.captionEmphasis.copyWith( + color: colorScheme.textPrimary, + fontFeatures: [const FontFeature.tabularFigures()], + ), + ), + + Expanded( + child: Container( + padding: EdgeInsets.symmetric(horizontal: spacing.md), + height: _kDefaultWaveformHeight, + child: StreamAudioWaveformSlider( + limit: _kDefaultWaveformLimit, + waveform: sampling.resampleWaveformData( + track.waveform, + _kDefaultWaveformLimit, + ), + progress: track.progress, + // Only allow seeking if the current track is the one being + // interacted with. + onChangeStart: (_) async { + if (state.currentIndex != index) return; + return _controller.pause(); + }, + onChangeEnd: (_) async { + if (state.currentIndex != index) return; + return _controller.play(); + }, + onChanged: (progress) async { + if (state.currentIndex != index) return; + + final duration = track.duration.inMicroseconds; + final seekPosition = (duration * progress).toInt(); + final seekDuration = Duration(microseconds: seekPosition); + + return _controller.seek(seekDuration); + }, + isActive: track.state != TrackState.idle, + ), + ), + ), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + StreamButton.icon( + key: const ValueKey('cancel-record-button'), + style: StreamButtonStyle.secondary, + type: StreamButtonType.outline, + size: StreamButtonSize.small, + icon: icons.trashBin, + onTap: widget.audioRecorderController.cancelRecord, + ), + if (widget.audioRecorderController.value is RecordStateRecording) + StreamButton.icon( + key: const ValueKey('stop-record-button'), + style: StreamButtonStyle.destructive, + type: StreamButtonType.outline, + size: StreamButtonSize.small, + icon: icons.stop, + onTap: widget.audioRecorderController.stopRecord, + ), + StreamButton.icon( + key: const ValueKey('finish-record-button'), + style: StreamButtonStyle.primary, + type: StreamButtonType.solid, + size: StreamButtonSize.small, + icon: icons.checkmark2Small, + onTap: () async { + await widget.feedback.onRecordFinish(context); + final audio = await widget.audioRecorderController.finishRecord(); + if (audio != null) { + widget.messageInputController.addAttachment(audio); + } + + // Once the recording is finished, cancel the recorder. + widget.audioRecorderController.cancelRecord(discardTrack: false); + + // Send the message if the user has enabled the option to + // send the voice recording automatically. + widget.sendMessageCallback?.call(); + }, + ), + ], + ), + ], + ); + }, + ); + } +} diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_recording_ongoing.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_recording_ongoing.dart new file mode 100644 index 0000000000..38bdfcb26d --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_recording_ongoing.dart @@ -0,0 +1,88 @@ +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +/// Widget to display the recording ongoing state. +/// This widget can be used inside of the [StreamBaseMessageComposer] instead of the default `inputBody`. +/// It shows a hint to slide to cancel the recording. +class StreamMessageComposerRecordingOngoing extends StatelessWidget { + /// Creates a new instance of [StreamMessageComposerRecordingOngoing]. + /// [audioRecorderController] is the controller for the audio recorder. + const StreamMessageComposerRecordingOngoing({super.key, required this.audioRecorderController}); + + /// The controller for the audio recorder. + final StreamAudioRecorderController audioRecorderController; + + @override + Widget build(BuildContext context) { + final textTheme = context.streamTextTheme; + final colorScheme = context.streamColorScheme; + final icons = context.streamIcons; + + return ConstrainedBox( + constraints: const BoxConstraints( + minHeight: 48, + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + height: 48, + width: 48, + alignment: Alignment.center, + child: Icon( + icons.microphone, + color: context.streamColorScheme.accentError, + size: 20, + ), + ), + ValueListenableBuilder( + valueListenable: audioRecorderController, + builder: (context, state, child) { + final duration = state is RecordStateRecording ? state.duration : Duration.zero; + return Text( + duration.toMinutesAndSeconds(), + style: textTheme.captionEmphasis.copyWith( + color: colorScheme.textPrimary, + fontFeatures: [const FontFeature.tabularFigures()], + ), + ); + }, + ), + const SizedBox(width: 23), + _GradientText( + 'Slide to cancel', + style: context.streamTextTheme.bodyDefault, + gradient: LinearGradient( + colors: [colorScheme.textPrimary, colorScheme.textTertiary], + ), + ), + SizedBox(width: context.streamSpacing.xxs), + Icon(icons.chevronLeft, color: colorScheme.textTertiary, size: 20), + ], + ), + ); + } +} + +class _GradientText extends StatelessWidget { + const _GradientText( + this.text, { + required this.gradient, + this.style, + }); + + final String text; + final TextStyle? style; + final Gradient gradient; + + @override + Widget build(BuildContext context) { + return ShaderMask( + blendMode: BlendMode.srcIn, + shaderCallback: (bounds) => gradient.createShader( + Rect.fromLTWH(0, 0, bounds.width, bounds.height), + ), + child: Text(text, style: style), + ); + } +} diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_trailing.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_trailing.dart new file mode 100644 index 0000000000..f5399ebc34 --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_trailing.dart @@ -0,0 +1,23 @@ +import 'package:flutter/widgets.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +/// A widget that shows the trailing of the message composer. +/// Uses the factory to show custom components or the default implementation. +/// By default this area is empty. +class StreamMessageComposerTrailing extends StatelessWidget { + /// Creates a new instance of [StreamMessageComposerTrailing]. + /// [props] contains the properties for the message composer component. + const StreamMessageComposerTrailing({super.key, required this.props}); + + /// The properties for the message composer component. + final MessageComposerComponentProps props; + + @override + Widget build(BuildContext context) { + return context.chatComponentBuilder()?.call( + context, + MessageComposerTrailingProps.from(props), + ) ?? + const SizedBox.shrink(); + } +} diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/stream_chat_message_composer.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/stream_chat_message_composer.dart new file mode 100644 index 0000000000..cb976d2df3 --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/stream_chat_message_composer.dart @@ -0,0 +1,392 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_portal/flutter_portal.dart'; +import 'package:stream_chat_flutter/src/components/message_composer/message_composer_input_header.dart'; +import 'package:stream_chat_flutter/src/components/message_composer/message_composer_input_leading.dart'; +import 'package:stream_chat_flutter/src/components/message_composer/message_composer_input_trailing.dart'; +import 'package:stream_chat_flutter/src/components/message_composer/message_composer_leading.dart'; +import 'package:stream_chat_flutter/src/components/message_composer/message_composer_recording_locked.dart'; +import 'package:stream_chat_flutter/src/components/message_composer/message_composer_recording_ongoing.dart'; +import 'package:stream_chat_flutter/src/components/message_composer/message_composer_trailing.dart'; +import 'package:stream_chat_flutter/src/message_input/dm_checkbox_list_tile.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart' as core; + +/// A widget that shows the message composer. +/// Uses the factory to show custom components or the default implementation. +class StreamChatMessageComposer extends StatefulWidget { + /// Creates a new instance of [StreamChatMessageComposer]. + /// [controller] is the controller for the message composer. + /// [onSendPressed] is the callback for when the send button is pressed. + /// [onMicrophonePressed] is the callback for when the microphone button is pressed. + /// [onAttachmentButtonPressed] is the callback for when the attachment button is pressed. + /// [focusNode] is the focus node for the message composer. + /// [currentUserId] is the current user id. + /// [placeholder] is the placeholder text of the message composer. + StreamChatMessageComposer({ + super.key, + StreamMessageInputController? controller, + required VoidCallback onSendPressed, + VoidCallback? onAttachmentButtonPressed, + bool isPickerOpen = false, + FocusNode? focusNode, + String? currentUserId, + String placeholder = '', + StreamAudioRecorderController? audioRecorderController, + bool sendVoiceRecordingAutomatically = false, + AudioRecorderFeedback feedback = const AudioRecorderFeedback(), + bool canAlsoSendToChannel = false, + }) : props = MessageComposerProps( + controller: controller, + isFloating: false, + message: null, + onSendPressed: onSendPressed, + onAttachmentButtonPressed: onAttachmentButtonPressed, + isPickerOpen: isPickerOpen, + focusNode: focusNode, + currentUserId: currentUserId, + placeholder: placeholder, + audioRecorderController: audioRecorderController, + sendVoiceRecordingAutomatically: sendVoiceRecordingAutomatically, + feedback: feedback, + canAlsoSendToChannel: canAlsoSendToChannel, + ); + + /// The controller for the message composer. + StreamMessageInputController? get controller => props.controller; + + /// The properties for the message composer. + final MessageComposerProps props; + + @override + State createState() => _StreamChatMessageComposerState(); +} + +class _StreamChatMessageComposerState extends State { + late StreamMessageInputController _controller; + + @override + void initState() { + super.initState(); + _initController(); + } + + @override + void didUpdateWidget(StreamChatMessageComposer oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.controller != oldWidget.controller) { + _disposeController(oldWidget); + _initController(); + } + } + + @override + void dispose() { + _disposeController(widget); + super.dispose(); + } + + void _initController() { + _controller = widget.controller ?? StreamMessageInputController(); + } + + void _disposeController(StreamChatMessageComposer widget) { + if (widget.controller == null) { + _controller.dispose(); + } + } + + @override + Widget build(BuildContext context) { + if (context.chatComponentBuilder()?.call(context, widget.props) case final messageComposer?) { + return messageComposer; + } + + final audioRecorderController = widget.props.audioRecorderController; + if (audioRecorderController == null) { + return DefaultStreamChatMessageComposer( + props: widget.props, + inputController: _controller, + ); + } + + return ValueListenableBuilder( + valueListenable: audioRecorderController, + builder: (context, state, _) { + final body = switch (state) { + RecordStateRecordingLocked() => MessageComposerRecordingLocked( + audioRecorderController: audioRecorderController, + feedback: widget.props.feedback, + messageInputController: _controller, + sendMessageCallback: widget.props.sendVoiceRecordingAutomatically ? widget.props.onSendPressed : null, + state: state, + ), + RecordStateStopped() => MessageComposerRecordingStopped( + audioRecorderController: audioRecorderController, + feedback: widget.props.feedback, + messageInputController: _controller, + sendMessageCallback: widget.props.sendVoiceRecordingAutomatically ? widget.props.onSendPressed : null, + recordingState: state, + ), + RecordStateRecording() => StreamMessageComposerRecordingOngoing( + audioRecorderController: audioRecorderController, + ), + _ => null, + }; + + final streamSpacing = context.streamSpacing; + + return PortalTarget( + anchor: Aligned( + offset: Offset(-streamSpacing.md, -streamSpacing.md), + target: Alignment.topRight, + follower: Alignment.bottomRight, + ), + visible: state is RecordStateRecording, + portalFollower: SwipeToLockButton(isLocked: state is RecordStateRecordingLocked), + child: DefaultStreamChatMessageComposer( + props: widget.props, + inputController: _controller, + audioRecorderState: state, + body: body, + ), + ); + }, + ); + } +} + +/// Properties to build the main message composer component +class MessageComposerProps { + /// Creates a new instance of [MessageComposerProps]. + /// [isFloating] is whether the message composer is floating. + /// [message] is the message for the message composer. + /// [placeholder] is the placeholder text of the message composer. + /// [onSendPressed] is the callback for when the send button is pressed. + /// [onMicrophonePressed] is the callback for when the microphone button is pressed. + /// [onAttachmentButtonPressed] is the callback for when the attachment button is pressed. + /// [focusNode] is the focus node for the message composer. + /// [currentUserId] is the current user id. + const MessageComposerProps({ + this.controller, + this.isFloating = false, + this.message, + this.placeholder = '', + required this.onSendPressed, + this.onAttachmentButtonPressed, + this.isPickerOpen = false, + this.focusNode, + this.currentUserId, + this.audioRecorderController, + this.sendVoiceRecordingAutomatically = false, + this.feedback = const AudioRecorderFeedback(), + this.canAlsoSendToChannel = false, + }); + + /// The controller for the message composer. + final StreamMessageInputController? controller; + + /// Whether the message composer is floating. + final bool isFloating; + + /// The message for the message composer. + final Message? message; + + /// The placeholder text of the message composer. + final String placeholder; + + /// The callback for when the send button is pressed. + final VoidCallback onSendPressed; + + /// The callback for when the attachment button is pressed. + final VoidCallback? onAttachmentButtonPressed; + + /// Whether the inline attachment picker is currently open. + final bool isPickerOpen; + + /// The focus node for the message composer. + final FocusNode? focusNode; + + /// The current user id. + final String? currentUserId; + + /// The audio recorder controller. + final StreamAudioRecorderController? audioRecorderController; + + /// Whether the voice recording should be sent automatically. + /// If enabled, the voice recording will be sent automatically when the recording is finished. + /// If disabled, the voice recording will be added as an attachment to the message + /// and the user will need to send the message manually. + final bool sendVoiceRecordingAutomatically; + + /// The feedback for the audio recorder. + final AudioRecorderFeedback feedback; + + /// Whether the user can also send the message as a direct message. + /// Usually used in threads. + final bool canAlsoSendToChannel; +} + +extension on StreamAudioRecorderController { + bool get isRecording => value is RecordStateRecording; + bool get isLocked => isRecording && value is! RecordStateRecordingHold; +} + +/// Default implementation of the message composer. +/// Shows the message composer with the default components. +/// Does not include the audio recording flow in the body. +class DefaultStreamChatMessageComposer extends StatelessWidget { + /// Creates a new instance of [DefaultStreamChatMessageComposer]. + /// [props] contains the properties for the message composer. + /// [inputController] is the controller for the message input. + /// [audioRecorderState] is the state of the audio recorder. + /// [body] is the body of the message composer. + const DefaultStreamChatMessageComposer({ + super.key, + required this.props, + required this.inputController, + this.audioRecorderState = const RecordStateIdle(), + this.body, + }); + + /// The properties for the message composer. + final MessageComposerProps props; + + /// The controller for the message input. + final StreamMessageInputController inputController; + + /// The state of the audio recorder. + /// Used for the microphone button state. + final AudioRecorderState audioRecorderState; + + /// The body of the message composer. + final Widget? body; + + /// The threshold to lock the recording. + static const double _lockRecordThreshold = 50; + + /// The threshold to cancel the recording. + static const double _cancelRecordThreshold = 75; + + @override + Widget build(BuildContext context) { + final componentProps = MessageComposerComponentProps( + controller: inputController, + isFloating: props.isFloating, + message: props.message, + currentUserId: props.currentUserId, + onSendPressed: props.onSendPressed, + voiceRecordingCallback: _createVoiceRecordingCallback(context), + onAttachmentButtonPressed: props.onAttachmentButtonPressed, + isPickerOpen: props.isPickerOpen, + audioRecorderState: audioRecorderState, + focusNode: props.focusNode, + ); + + return core.StreamCoreMessageComposer( + placeholder: props.placeholder, + controller: inputController.textFieldController, + isFloating: props.isFloating, + focusNode: props.focusNode, + composerLeading: StreamMessageComposerLeading(props: componentProps), + composerTrailing: StreamMessageComposerTrailing( + props: componentProps, + ), + inputHeader: StreamMessageComposerInputHeader(props: componentProps), + inputTrailing: StreamMessageComposerInputTrailing( + props: componentProps, + ), + inputLeading: StreamMessageComposerInputLeading( + props: componentProps, + ), + inputBody: + body ?? + Column( + mainAxisSize: MainAxisSize.min, + children: [ + core.StreamMessageComposerInputField( + controller: inputController.textFieldController, + placeholder: props.placeholder, + focusNode: props.focusNode, + command: inputController.message.command?.toUpperCase(), + onDismissCommand: inputController.clear, + ), + if (props.canAlsoSendToChannel) + DmCheckboxListTile( + value: props.controller?.showInChannel ?? false, + // height of list tile is 34px, height of checkbox is 16px, so we need to subtract 8px to make the spacing correct. + contentPadding: EdgeInsets.only( + right: context.streamSpacing.md, + left: context.streamSpacing.md, + bottom: context.streamSpacing.md - 8, + ), + onChanged: (value) => props.controller?.showInChannel = value, + ), + ], + ), + ); + } + + core.VoiceRecordingCallback? _createVoiceRecordingCallback(BuildContext context) { + if (props.audioRecorderController case final audioRecorderController?) { + return core.VoiceRecordingCallback( + onLongPressStart: () async { + // Return if the recording is already started. + if (audioRecorderController.isRecording) return; + + await props.feedback.onRecordStart(context); + return audioRecorderController.startRecord(); + }, + onLongPressEnd: (_) async { + // Return if the recording not yet started or already locked. + if (!audioRecorderController.isRecording || audioRecorderController.isLocked) return; + + await props.feedback.onRecordFinish(context); + final audio = await audioRecorderController.finishRecord(); + if (audio != null) { + inputController.addAttachment(audio); + } + + // Once the recording is finished, cancel the recorder. + audioRecorderController.cancelRecord(discardTrack: false); + + // Send the message if the user has enabled the option to + // send the voice recording automatically. + if (props.sendVoiceRecordingAutomatically) { + return props.onSendPressed.call(); + } + }, + onLongPressCancel: () async { + // Return if the recording is already started. + if (audioRecorderController.isRecording) return; + + // Notify the parent that the recorder is canceled before it starts. + await props.feedback.onRecordStartCancel(context); + // Show a message to the user to hold to record. + audioRecorderController.showInfo( + context.translations.holdToRecordLabel, + ); + }, + onLongPressMoveUpdate: (details) async { + // Return if the recording not yet started or already locked. + if (!audioRecorderController.isRecording || audioRecorderController.isLocked) return; + final dragOffset = details.offsetFromOrigin; + + // Lock recording if the drag offset is greater than the threshold. + if (dragOffset.dy <= -_lockRecordThreshold) { + await props.feedback.onRecordLock(context); + return audioRecorderController.lockRecord(); + } + // Cancel recording if the drag offset is greater than the threshold. + if (dragOffset.dx <= -_cancelRecordThreshold) { + await props.feedback.onRecordCancel(context); + return audioRecorderController.cancelRecord(); + } + + // Update the drag offset. + return audioRecorderController.dragRecord(dragOffset); + }, + ); + } + return null; + } +} diff --git a/packages/stream_chat_flutter/lib/src/components/stream_chat_component_builders.dart b/packages/stream_chat_flutter/lib/src/components/stream_chat_component_builders.dart new file mode 100644 index 0000000000..08c065e4e3 --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/components/stream_chat_component_builders.dart @@ -0,0 +1,37 @@ +import 'package:flutter/widgets.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +/// Builds the list of component builders for the stream chat components. +Iterable> streamChatComponentBuilders({ + StreamComponentBuilder? channelListItem, + StreamComponentBuilder? messageComposer, + StreamComponentBuilder? messageComposerLeading, + StreamComponentBuilder? messageComposerTrailing, + StreamComponentBuilder? messageComposerInput, + StreamComponentBuilder? messageComposerInputLeading, + StreamComponentBuilder? messageComposerInputHeader, + StreamComponentBuilder? messageComposerInputTrailing, + StreamComponentBuilder? messageWidget, + StreamComponentBuilder? unreadIndicator, +}) { + final builders = [ + if (channelListItem != null) StreamComponentBuilderExtension(builder: channelListItem), + if (messageComposer != null) StreamComponentBuilderExtension(builder: messageComposer), + if (messageComposerLeading != null) StreamComponentBuilderExtension(builder: messageComposerLeading), + if (messageComposerTrailing != null) StreamComponentBuilderExtension(builder: messageComposerTrailing), + if (messageComposerInput != null) StreamComponentBuilderExtension(builder: messageComposerInput), + if (messageComposerInputLeading != null) StreamComponentBuilderExtension(builder: messageComposerInputLeading), + if (messageComposerInputHeader != null) StreamComponentBuilderExtension(builder: messageComposerInputHeader), + if (messageComposerInputTrailing != null) StreamComponentBuilderExtension(builder: messageComposerInputTrailing), + if (messageWidget != null) StreamComponentBuilderExtension(builder: messageWidget), + if (unreadIndicator != null) StreamComponentBuilderExtension(builder: unreadIndicator), + ]; + + return builders; +} + +/// Helper extensions for the factory builders. +extension StreamChatComponentBuildersExtension on BuildContext { + /// The builder for the given component type. + StreamComponentBuilder? chatComponentBuilder() => StreamComponentFactory.of(this).extension(); +} diff --git a/packages/stream_chat_flutter/lib/src/context_menu/context_menu.dart b/packages/stream_chat_flutter/lib/src/context_menu/context_menu.dart index 12b9e68e25..2158b77e31 100644 --- a/packages/stream_chat_flutter/lib/src/context_menu/context_menu.dart +++ b/packages/stream_chat_flutter/lib/src/context_menu/context_menu.dart @@ -1,16 +1,17 @@ import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; const double _kContextMenuScreenPadding = 8; -const double _kContextMenuWidth = 222; -/// Signature for a builder function that wraps the context menu widget. +/// Signature for a builder function that wraps the context menu items. /// /// This builder can be used to customize the appearance of the menu -/// container by wrapping [child] in additional UI elements. -typedef ContextMenuBuilder = Widget Function( - BuildContext context, - Widget child, -); +/// container by wrapping [menuItems] in additional UI elements. +typedef ContextMenuContainerBuilder = + Widget Function( + BuildContext context, + List menuItems, + ); /// A widget that displays a context menu anchored to a specific [Offset]. /// @@ -41,35 +42,20 @@ class ContextMenu extends StatelessWidget { /// Builds the outer container for the menu. /// - /// The [menuBuilder] receives the current context and a [child] widget - /// containing all the [menuItems]. + /// The [menuBuilder] receives the current context and the [menuItems] list, + /// and should return a widget wrapping all items. /// - /// Defaults to a card-style scrollable container with fixed width. - final ContextMenuBuilder menuBuilder; + /// Defaults to a [StreamContextMenu] wrapping all items. + final ContextMenuContainerBuilder menuBuilder; /// Default menu container with standard styling. /// /// Wraps the menu content in a card-like [Material] with scroll support, /// applying max width and height constraints. - static Widget _defaultMenuBuilder(BuildContext context, Widget child) { - final availableHeight = MediaQuery.of(context).size.height; - final maxHeight = availableHeight - _kContextMenuScreenPadding * 2; - - return ConstrainedBox( - constraints: BoxConstraints( - minWidth: _kContextMenuWidth, - maxWidth: _kContextMenuWidth, - maxHeight: maxHeight, - ), - child: Material( - elevation: 1, - type: MaterialType.card, - clipBehavior: Clip.antiAlias, - borderRadius: const BorderRadius.all(Radius.circular(7)), - child: SingleChildScrollView(child: child), - ), - ); - } + static Widget _defaultMenuBuilder( + BuildContext context, + List menuItems, + ) => StreamContextMenu(children: menuItems); @override Widget build(BuildContext context) { @@ -90,14 +76,7 @@ class ContextMenu extends StatelessWidget { delegate: DesktopTextSelectionToolbarLayoutDelegate( anchor: anchor - localAdjustment, ), - child: menuBuilder.call( - context, - Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: menuItems, - ), - ), + child: menuBuilder.call(context, menuItems), ), ); } diff --git a/packages/stream_chat_flutter/lib/src/context_menu/context_menu_region.dart b/packages/stream_chat_flutter/lib/src/context_menu/context_menu_region.dart index d3554889dc..9a30e03e3a 100644 --- a/packages/stream_chat_flutter/lib/src/context_menu/context_menu_region.dart +++ b/packages/stream_chat_flutter/lib/src/context_menu/context_menu_region.dart @@ -6,14 +6,15 @@ import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; /// /// The function receives the [BuildContext] and the [Offset] where /// the menu should appear. -typedef ContextMenuBuilder = Widget Function( - BuildContext context, - Offset offset, -); +typedef ContextMenuBuilder = + Widget Function( + BuildContext context, + Offset offset, + ); /// Displays a custom context menu as a general dialog. /// -/// The [contextMenuBuilder] is used to construct the contents of the +/// The [menuBuilder] is used to construct the contents of the /// context menu, typically positioned based on the triggering gesture. /// /// The dialog can be customized using parameters such as [barrierColor], @@ -22,7 +23,7 @@ typedef ContextMenuBuilder = Widget Function( /// Returns a [Future] that resolves when the menu is dismissed. Future showContextMenu({ required BuildContext context, - required WidgetBuilder contextMenuBuilder, + required WidgetBuilder menuBuilder, String? barrierLabel, Color? barrierColor, Duration transitionDuration = const Duration(milliseconds: 150), @@ -59,7 +60,7 @@ Future showContextMenu({ ); }, pageBuilder: (context, animation, secondaryAnimation) { - final pageChild = Builder(builder: contextMenuBuilder); + final pageChild = Builder(builder: menuBuilder); return capturedThemes.wrap(pageChild); }, ); @@ -73,11 +74,12 @@ class ContextMenuRegion extends StatefulWidget { /// Creates a [ContextMenuRegion]. /// /// The [child] is the widget wrapped by this region. When a gesture is - /// detected on it, the [contextMenuBuilder] is used to construct the menu. + /// detected on it, the [menuBuilder] is used to construct the menu. const ContextMenuRegion({ super.key, required this.child, - required this.contextMenuBuilder, + required this.menuBuilder, + this.onSelected, }); /// The widget below this widget in the tree. @@ -86,7 +88,14 @@ class ContextMenuRegion extends StatefulWidget { /// Called to build the context menu when the gesture is triggered. /// /// The builder is given the [BuildContext] and the [Offset] of the gesture. - final ContextMenuBuilder contextMenuBuilder; + final ContextMenuBuilder menuBuilder; + + /// Called with the value returned when the context menu is dismissed. + /// + /// When a menu item pops the route with a value (e.g. via + /// [Navigator.pop]), that value is forwarded here. If the menu is dismissed + /// without a selection the value will be `null`. + final ValueChanged? onSelected; @override State createState() => _ContextMenuRegionState(); @@ -107,14 +116,16 @@ class _ContextMenuRegionState extends State { super.dispose(); } - Future _showContextMenu(BuildContext context, Offset position) async { - print('ContextMenuRegion: Showing context menu at $position'); - await showContextMenu( + Future _showContextMenu( + BuildContext context, + Offset position, + ) async { + final result = await showContextMenu( context: context, - contextMenuBuilder: (context) { - return widget.contextMenuBuilder(context, position); - }, + menuBuilder: (context) => widget.menuBuilder(context, position), ); + + return widget.onSelected?.call(result); } @override diff --git a/packages/stream_chat_flutter/lib/src/dialogs/channel_info_dialog.dart b/packages/stream_chat_flutter/lib/src/dialogs/channel_info_dialog.dart index 974a1c2fc6..dc1991b74c 100644 --- a/packages/stream_chat_flutter/lib/src/dialogs/channel_info_dialog.dart +++ b/packages/stream_chat_flutter/lib/src/dialogs/channel_info_dialog.dart @@ -39,9 +39,7 @@ class ChannelInfoDialog extends StatelessWidget { children: [ StreamChannelInfo( channel: channel, - textStyle: StreamChatTheme.of(context) - .channelPreviewTheme - .subtitleStyle, + textStyle: StreamChatTheme.of(context).channelPreviewTheme.subtitleStyle, ), ], ), @@ -50,18 +48,12 @@ class ChannelInfoDialog extends StatelessWidget { Column( children: [ StreamUserAvatar( + size: .xl, user: members .firstWhere( (e) => e.user?.id != userAsMember.user?.id, ) .user!, - constraints: const BoxConstraints.tightFor( - height: 64, - width: 64, - ), - borderRadius: BorderRadius.circular(32), - onlineIndicatorConstraints: - BoxConstraints.tight(const Size(12, 12)), ), const SizedBox(height: 6), Text( diff --git a/packages/stream_chat_flutter/lib/src/dialogs/message_dialog.dart b/packages/stream_chat_flutter/lib/src/dialogs/message_dialog.dart index f8b9930c43..24daa59d48 100644 --- a/packages/stream_chat_flutter/lib/src/dialogs/message_dialog.dart +++ b/packages/stream_chat_flutter/lib/src/dialogs/message_dialog.dart @@ -33,8 +33,7 @@ class MessageDialog extends StatelessWidget { title: Text(titleText ?? context.translations.somethingWentWrongError), content: messageText != null ? Text( - messageText ?? - context.translations.operationCouldNotBeCompletedText, + messageText ?? context.translations.operationCouldNotBeCompletedText, ) : null, actions: [ diff --git a/packages/stream_chat_flutter/lib/src/fullscreen_media/fsm_stub.dart b/packages/stream_chat_flutter/lib/src/fullscreen_media/fsm_stub.dart index 1bf7c435b3..1dd6ac10c3 100644 --- a/packages/stream_chat_flutter/lib/src/fullscreen_media/fsm_stub.dart +++ b/packages/stream_chat_flutter/lib/src/fullscreen_media/fsm_stub.dart @@ -15,5 +15,4 @@ FullScreenMediaWidget getFsm({ ReplyMessageCallback? onReplyMessage, AttachmentActionsBuilder? attachmentActionsModalBuilder, bool? autoplayVideos, -}) => - throw UnsupportedError('Cannot create FullScreenMedia'); +}) => throw UnsupportedError('Cannot create FullScreenMedia'); diff --git a/packages/stream_chat_flutter/lib/src/fullscreen_media/full_screen_media.dart b/packages/stream_chat_flutter/lib/src/fullscreen_media/full_screen_media.dart index eb95489c24..b9c72ceb45 100644 --- a/packages/stream_chat_flutter/lib/src/fullscreen_media/full_screen_media.dart +++ b/packages/stream_chat_flutter/lib/src/fullscreen_media/full_screen_media.dart @@ -81,17 +81,16 @@ class _FullScreenMediaState extends State { return; } - final currentAttachment = - widget.mediaAttachmentPackages[widget.startIndex].attachment; + final currentAttachment = widget.mediaAttachmentPackages[widget.startIndex].attachment; - await Future.wait(videoPackages.values.map( - (it) => it.initialize(), - )); + await Future.wait( + videoPackages.values.map( + (it) => it.initialize(), + ), + ); - if (widget.autoplayVideos && - currentAttachment.type == AttachmentType.video) { - final package = videoPackages.values - .firstWhere((e) => e._attachment == currentAttachment); + if (widget.autoplayVideos && currentAttachment.type == AttachmentType.video) { + final package = videoPackages.values.firstWhere((e) => e._attachment == currentAttachment); package._chewieController?.play(); } setState(() {}); // ignore: no-empty-block @@ -115,8 +114,7 @@ class _FullScreenMediaState extends State { body: ValueListenableBuilder( valueListenable: _currentPage, builder: (context, currentPage, child) { - final _currentAttachmentPackage = - widget.mediaAttachmentPackages[currentPage]; + final _currentAttachmentPackage = widget.mediaAttachmentPackages[currentPage]; final _currentMessage = _currentAttachmentPackage.message; final _currentAttachment = _currentAttachmentPackage.attachment; return Stack( @@ -130,8 +128,7 @@ class _FullScreenMediaState extends State { return AnimatedPositionedDirectional( duration: kThemeAnimationDuration, curve: Curves.easeInOut, - top: - isDisplayingDetail ? 0 : -(topPadding + kToolbarHeight), + top: isDisplayingDetail ? 0 : -(topPadding + kToolbarHeight), start: 0, end: 0, height: topPadding + kToolbarHeight, @@ -163,8 +160,7 @@ class _FullScreenMediaState extends State { ); } : null, - attachmentActionsModalBuilder: - widget.attachmentActionsModalBuilder, + attachmentActionsModalBuilder: widget.attachmentActionsModalBuilder, ), ); }, @@ -178,9 +174,7 @@ class _FullScreenMediaState extends State { return AnimatedPositionedDirectional( duration: kThemeAnimationDuration, curve: Curves.easeInOut, - bottom: isDisplayingDetail - ? 0 - : -(bottomPadding + kToolbarHeight), + bottom: isDisplayingDetail ? 0 : -(bottomPadding + kToolbarHeight), start: 0, end: 0, height: bottomPadding + kToolbarHeight, @@ -246,8 +240,7 @@ class _FullScreenMediaState extends State { } }, onRightArrowKeypress: () { - if (_currentPage.value < - widget.mediaAttachmentPackages.length - 1) { + if (_currentPage.value < widget.mediaAttachmentPackages.length - 1) { _currentPage.value++; _pageController.nextPage( duration: const Duration(milliseconds: 300), @@ -261,22 +254,19 @@ class _FullScreenMediaState extends State { onPageChanged: (val) { _currentPage.value = val; if (videoPackages.isEmpty) return; - final currentAttachment = - widget.mediaAttachmentPackages[val].attachment; + final currentAttachment = widget.mediaAttachmentPackages[val].attachment; for (final e in videoPackages.values) { if (e._attachment != currentAttachment) { e._chewieController?.pause(); } } - if (widget.autoplayVideos && - currentAttachment.type == AttachmentType.video) { + if (widget.autoplayVideos && currentAttachment.type == AttachmentType.video) { final controller = videoPackages[currentAttachment.id]!; controller._chewieController?.play(); } }, itemBuilder: (context, index) { - final currentAttachmentPackage = - widget.mediaAttachmentPackages[index]; + final currentAttachmentPackage = widget.mediaAttachmentPackages[index]; final attachment = currentAttachmentPackage.attachment; return ValueListenableBuilder( valueListenable: _isDisplayingDetail, @@ -298,8 +288,7 @@ class _FullScreenMediaState extends State { }, child: Builder( builder: (context) { - if (attachment.type == AttachmentType.image || - attachment.type == AttachmentType.giphy) { + if (attachment.type == AttachmentType.image || attachment.type == AttachmentType.giphy) { return PhotoView.customChild( maxScale: PhotoViewComputedScale.covered, minScale: PhotoViewComputedScale.contained, @@ -345,15 +334,15 @@ class VideoPackage { this._attachment, { bool showControls = false, bool autoInitialize = true, - }) : _showControls = showControls, - _autoInitialize = autoInitialize, - _videoPlayerController = _attachment.localUri != null - ? VideoPlayerController.file( - File.fromUri(_attachment.localUri!), - ) - : VideoPlayerController.networkUrl( - Uri.parse(_attachment.assetUrl!), - ); + }) : _showControls = showControls, + _autoInitialize = autoInitialize, + _videoPlayerController = _attachment.localUri != null + ? VideoPlayerController.file( + File.fromUri(_attachment.localUri!), + ) + : VideoPlayerController.networkUrl( + Uri.parse(_attachment.assetUrl!), + ); final Attachment _attachment; final bool _showControls; @@ -384,12 +373,10 @@ class VideoPackage { } /// Add a listener to video player controller - void addListener(VoidCallback listener) => - _videoPlayerController.addListener(listener); + void addListener(VoidCallback listener) => _videoPlayerController.addListener(listener); /// Remove a listener to video player controller - void removeListener(VoidCallback listener) => - _videoPlayerController.removeListener(listener); + void removeListener(VoidCallback listener) => _videoPlayerController.removeListener(listener); /// Dispose controllers Future dispose() { diff --git a/packages/stream_chat_flutter/lib/src/fullscreen_media/full_screen_media_builder.dart b/packages/stream_chat_flutter/lib/src/fullscreen_media/full_screen_media_builder.dart index f1919db192..1609445cd8 100644 --- a/packages/stream_chat_flutter/lib/src/fullscreen_media/full_screen_media_builder.dart +++ b/packages/stream_chat_flutter/lib/src/fullscreen_media/full_screen_media_builder.dart @@ -1,7 +1,8 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/src/fullscreen_media/fsm_stub.dart' - if (dart.library.io) 'full_screen_media_desktop.dart' as desktop_fsm; + if (dart.library.io) 'full_screen_media_desktop.dart' + as desktop_fsm; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// {@template fsmBuilder} diff --git a/packages/stream_chat_flutter/lib/src/fullscreen_media/full_screen_media_desktop.dart b/packages/stream_chat_flutter/lib/src/fullscreen_media/full_screen_media_desktop.dart index 53a5dac471..884adcbcc5 100644 --- a/packages/stream_chat_flutter/lib/src/fullscreen_media/full_screen_media_desktop.dart +++ b/packages/stream_chat_flutter/lib/src/fullscreen_media/full_screen_media_desktop.dart @@ -109,8 +109,7 @@ class _FullScreenMediaDesktopState extends State { @override Widget build(BuildContext context) { - final containsOnlyVideos = - widget.mediaAttachmentPackages.length == videoPackages.length; + final containsOnlyVideos = widget.mediaAttachmentPackages.length == videoPackages.length; return Scaffold( resizeToAvoidBottomInset: false, @@ -122,12 +121,16 @@ class _FullScreenMediaDesktopState extends State { return Stack( children: [ ContextMenuRegion( - contextMenuBuilder: (context, anchor) { + menuBuilder: (context, anchor) { final index = _currentPage.value; final mediaAttachment = widget.mediaAttachmentPackages[index]; return ContextMenu( anchor: anchor, - menuItems: [_DownloadMenuItem(mediaAttachment: mediaAttachment)], + menuItems: [ + _DownloadMenuItem( + mediaAttachment: mediaAttachment.attachment, + ), + ], ); }, child: _PlaylistPlayer( @@ -145,9 +148,9 @@ class _FullScreenMediaDesktopState extends State { videoPackages.values.first.player.stop(); Navigator.of(context).pop(); }, - child: const StreamSvgIcon( + child: Icon( + context.streamIcons.crossMedium, size: 30, - icon: StreamSvgIcons.close, ), ), ), @@ -160,8 +163,7 @@ class _FullScreenMediaDesktopState extends State { return ValueListenableBuilder( valueListenable: _currentPage, builder: (context, currentPage, child) { - final _currentAttachmentPackage = - widget.mediaAttachmentPackages[currentPage]; + final _currentAttachmentPackage = widget.mediaAttachmentPackages[currentPage]; final _currentMessage = _currentAttachmentPackage.message; final _currentAttachment = _currentAttachmentPackage.attachment; return Stack( @@ -194,8 +196,7 @@ class _FullScreenMediaDesktopState extends State { StreamChannel.of(context).channel, ); }, - attachmentActionsModalBuilder: - widget.attachmentActionsModalBuilder, + attachmentActionsModalBuilder: widget.attachmentActionsModalBuilder, ), ); }, @@ -209,9 +210,7 @@ class _FullScreenMediaDesktopState extends State { return AnimatedPositionedDirectional( duration: kThemeAnimationDuration, curve: Curves.easeInOut, - bottom: isDisplayingDetail - ? 0 - : -(bottomPadding + kToolbarHeight), + bottom: isDisplayingDetail ? 0 : -(bottomPadding + kToolbarHeight), start: 0, end: 0, height: bottomPadding + kToolbarHeight, @@ -277,8 +276,7 @@ class _FullScreenMediaDesktopState extends State { } }, onRightArrowKeypress: () { - if (_currentPage.value < - widget.mediaAttachmentPackages.length - 1) { + if (_currentPage.value < widget.mediaAttachmentPackages.length - 1) { _currentPage.value++; _pageController.nextPage( duration: const Duration(milliseconds: 300), @@ -292,22 +290,19 @@ class _FullScreenMediaDesktopState extends State { onPageChanged: (val) { _currentPage.value = val; if (videoPackages.isEmpty) return; - final currentAttachment = - widget.mediaAttachmentPackages[val].attachment; + final currentAttachment = widget.mediaAttachmentPackages[val].attachment; for (final p in videoPackages.values) { if (p.attachment != currentAttachment) { p.player.pause(); } } - if (widget.autoplayVideos && - currentAttachment.type == AttachmentType.video) { + if (widget.autoplayVideos && currentAttachment.type == AttachmentType.video) { final package = videoPackages[currentAttachment.id]!; package.player.play(); } }, itemBuilder: (context, index) { - final currentAttachmentPackage = - widget.mediaAttachmentPackages[index]; + final currentAttachmentPackage = widget.mediaAttachmentPackages[index]; final attachment = currentAttachmentPackage.attachment; return ValueListenableBuilder( @@ -330,8 +325,7 @@ class _FullScreenMediaDesktopState extends State { }, child: Builder( builder: (context) { - if (attachment.type == AttachmentType.image || - attachment.type == AttachmentType.giphy) { + if (attachment.type == AttachmentType.image || attachment.type == AttachmentType.giphy) { return PhotoView.customChild( maxScale: PhotoViewComputedScale.covered, minScale: PhotoViewComputedScale.contained, @@ -358,16 +352,14 @@ class _FullScreenMediaDesktopState extends State { } return ContextMenuRegion( - contextMenuBuilder: (_, anchor) { - return ContextMenu( - anchor: anchor, - menuItems: [ - _DownloadMenuItem( - mediaAttachment: currentAttachmentPackage, - ), - ], - ); - }, + menuBuilder: (_, anchor) => ContextMenu( + anchor: anchor, + menuItems: [ + _DownloadMenuItem( + mediaAttachment: currentAttachmentPackage.attachment, + ), + ], + ), child: Video( controller: package.controller, ), @@ -392,8 +384,7 @@ class _FullScreenMediaDesktopState extends State { /// This widget displays a download option in a context menu, allowing users to /// download the attachment associated with a message. /// -/// It uses [StreamMessageActionItem] and [StreamMessageAction] to create a -/// consistent UI with other message actions. +/// It uses [StreamContextMenuAction] to stay consistent with message actions. /// {@endtemplate} class _DownloadMenuItem extends StatelessWidget { /// {@macro streamDownloadMenuItem} @@ -402,31 +393,17 @@ class _DownloadMenuItem extends StatelessWidget { }); /// The attachment package containing the message and attachment to download. - final StreamAttachmentPackage mediaAttachment; - static const String _attachmentKey = 'attachment'; + final Attachment mediaAttachment; @override Widget build(BuildContext context) { - return StreamMessageActionItem( - action: StreamMessageAction( - leading: const StreamSvgIcon(icon: StreamSvgIcons.download), - title: Text(context.translations.downloadLabel), - action: CustomMessageAction( - message: mediaAttachment.message, - extraData: {_attachmentKey: mediaAttachment.attachment}, - ), - ), - // TODO: Use a callback to handle the action instead of onTap. - onTap: (action) async { - if (action is! CustomMessageAction) return; - final attachment = action.extraData[_attachmentKey] as Attachment?; - if (attachment == null) return; - - final popped = await Navigator.of(context).maybePop(); - if (popped) { - final handler = StreamAttachmentHandler.instance; - return handler.downloadAttachment(attachment).ignore(); - } + final icons = context.streamIcons; + return StreamContextMenuAction( + leading: Icon(icons.arrowDown), + label: Text(context.translations.downloadLabel), + onTap: () { + final handler = StreamAttachmentHandler.instance; + return handler.downloadAttachment(mediaAttachment).ignore(); }, ); } diff --git a/packages/stream_chat_flutter/lib/src/gallery/gallery_footer.dart b/packages/stream_chat_flutter/lib/src/gallery/gallery_footer.dart index ffcfef6cd2..0e1ef333b0 100644 --- a/packages/stream_chat_flutter/lib/src/gallery/gallery_footer.dart +++ b/packages/stream_chat_flutter/lib/src/gallery/gallery_footer.dart @@ -9,8 +9,7 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// {@template streamGalleryFooter} /// Footer widget for media display /// {@endtemplate} -class StreamGalleryFooter extends StatefulWidget - implements PreferredSizeWidget { +class StreamGalleryFooter extends StatefulWidget implements PreferredSizeWidget { /// {@macro streamGalleryFooter} const StreamGalleryFooter({ super.key, @@ -73,10 +72,8 @@ class _StreamGalleryFooterState extends State { context: context, removeTop: true, child: BottomAppBar( - surfaceTintColor: - widget.backgroundColor ?? galleryFooterThemeData.backgroundColor, - color: - widget.backgroundColor ?? galleryFooterThemeData.backgroundColor, + surfaceTintColor: widget.backgroundColor ?? galleryFooterThemeData.backgroundColor, + color: widget.backgroundColor ?? galleryFooterThemeData.backgroundColor, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -85,34 +82,26 @@ class _StreamGalleryFooterState extends State { else IconButton( key: shareButtonKey, - icon: StreamSvgIcon( + icon: Icon( + context.streamIcons.shareOs, size: 24, - icon: StreamSvgIcons.share, color: galleryFooterThemeData.shareIconColor, ), onPressed: () async { - final attachment = widget - .mediaAttachmentPackages[widget.currentPage].attachment; - final url = attachment.imageUrl ?? - attachment.assetUrl ?? - attachment.thumbUrl!; - final type = attachment.type == AttachmentType.image - ? 'jpg' - : url.split('?').first.split('.').last; + final attachment = widget.mediaAttachmentPackages[widget.currentPage].attachment; + final url = attachment.imageUrl ?? attachment.assetUrl ?? attachment.thumbUrl!; + final type = attachment.type == AttachmentType.image ? 'jpg' : url.split('?').first.split('.').last; final request = await HttpClient().getUrl(Uri.parse(url)); final response = await request.close(); - final bytes = - await consolidateHttpClientResponseBytes(response); + final bytes = await consolidateHttpClientResponseBytes(response); final tmpPath = await getTemporaryDirectory(); final filePath = '${tmpPath.path}/${attachment.id}.$type'; final file = File(filePath); await file.writeAsBytes(bytes); - final box = - shareButtonKey.currentContext?.findRenderObject(); + final box = shareButtonKey.currentContext?.findRenderObject(); final size = shareButtonKey.currentContext?.size; - final position = - (box! as RenderBox).localToGlobal(Offset.zero); + final position = (box! as RenderBox).localToGlobal(Offset.zero); await SharePlus.instance.share( ShareParams( @@ -146,8 +135,8 @@ class _StreamGalleryFooterState extends State { ), ), IconButton( - icon: StreamSvgIcon( - icon: StreamSvgIcons.grid, + icon: Icon( + context.streamIcons.layoutGrid1, color: galleryFooterThemeData.gridIconButtonColor, ), onPressed: () => _showPhotosModal(context), @@ -176,8 +165,7 @@ class _StreamGalleryFooterState extends State { builder: (context) { return DraggableScrollableSheet( expand: false, - initialChildSize: - (CurrentPlatform.isAndroid || CurrentPlatform.isIos) ? 0.3 : 0.5, + initialChildSize: (CurrentPlatform.isAndroid || CurrentPlatform.isIos) ? 0.3 : 0.5, minChildSize: 0.3, maxChildSize: 0.7, builder: (context, scrollController) => Column( @@ -189,16 +177,15 @@ class _StreamGalleryFooterState extends State { padding: const EdgeInsets.all(16), child: Text( context.translations.photosLabel, - style: - galleryFooterThemeData.bottomSheetPhotosTextStyle, + style: galleryFooterThemeData.bottomSheetPhotosTextStyle, ), ), ), Align( alignment: Alignment.centerRight, child: IconButton( - icon: StreamSvgIcon( - icon: StreamSvgIcons.close, + icon: Icon( + context.streamIcons.crossMedium, color: galleryFooterThemeData.bottomSheetCloseIconColor, ), onPressed: () => Navigator.of(context).maybePop(), @@ -219,8 +206,7 @@ class _StreamGalleryFooterState extends State { ), itemBuilder: (context, index) { Widget media; - final attachmentPackage = - widget.mediaAttachmentPackages[index]; + final attachmentPackage = widget.mediaAttachmentPackages[index]; final attachment = attachmentPackage.attachment; final message = attachmentPackage.message; if (attachment.type == AttachmentType.video) { @@ -268,18 +254,16 @@ class _StreamGalleryFooterState extends State { boxShadow: [ BoxShadow( blurRadius: 8, - color: chatThemeData - .colorTheme.textHighEmphasis + color: chatThemeData.colorTheme.textHighEmphasis // ignore: deprecated_member_use .withOpacity(0.3), ), ], ), child: StreamUserAvatar( + size: .sm, user: message.user!, - constraints: - BoxConstraints.tight(const Size(24, 24)), - showOnlineStatus: false, + showOnlineIndicator: false, ), ), ), diff --git a/packages/stream_chat_flutter/lib/src/gallery/gallery_header.dart b/packages/stream_chat_flutter/lib/src/gallery/gallery_header.dart index 679c1c3d72..3291f4ac72 100644 --- a/packages/stream_chat_flutter/lib/src/gallery/gallery_header.dart +++ b/packages/stream_chat_flutter/lib/src/gallery/gallery_header.dart @@ -1,18 +1,17 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:stream_chat_flutter/src/attachment_actions_modal/attachment_actions_modal.dart'; -import 'package:stream_chat_flutter/src/icons/stream_svg_icon.dart'; import 'package:stream_chat_flutter/src/misc/empty_widget.dart'; import 'package:stream_chat_flutter/src/theme/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/theme/themes.dart'; import 'package:stream_chat_flutter/src/utils/utils.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; /// {@template streamGalleryHeader} /// Header/AppBar widget for media display screen /// {@endtemplate} -class StreamGalleryHeader extends StatelessWidget - implements PreferredSizeWidget { +class StreamGalleryHeader extends StatelessWidget implements PreferredSizeWidget { /// {@macro streamGalleryHeader} const StreamGalleryHeader({ super.key, @@ -83,29 +82,25 @@ class StreamGalleryHeader extends StatelessWidget return AppBar( toolbarTextStyle: theme.textTheme.bodyMedium, titleTextStyle: theme.textTheme.titleLarge, - systemOverlayStyle: theme.brightness == Brightness.dark - ? SystemUiOverlayStyle.light - : SystemUiOverlayStyle.dark, + systemOverlayStyle: theme.brightness == Brightness.dark ? SystemUiOverlayStyle.light : SystemUiOverlayStyle.dark, elevation: elevation, leading: showBackButton ? IconButton( - icon: StreamSvgIcon( - icon: StreamSvgIcons.close, + icon: Icon( + context.streamIcons.crossMedium, color: galleryHeaderThemeData.closeButtonColor, size: 24, ), onPressed: onBackPressed, ) : const Empty(), - surfaceTintColor: - backgroundColor ?? galleryHeaderThemeData.backgroundColor, - backgroundColor: - backgroundColor ?? galleryHeaderThemeData.backgroundColor, + surfaceTintColor: backgroundColor ?? galleryHeaderThemeData.backgroundColor, + backgroundColor: backgroundColor ?? galleryHeaderThemeData.backgroundColor, actions: [ if (!message.isEphemeral) IconButton( - icon: StreamSvgIcon( - icon: StreamSvgIcons.menuPoint, + icon: Icon( + context.streamIcons.dotsGrid1x3Vertical, color: galleryHeaderThemeData.iconMenuPointColor, ), onPressed: () => _showMessageActionModalBottomSheet(context), @@ -143,8 +138,7 @@ class StreamGalleryHeader extends StatelessWidget Future _showMessageActionModalBottomSheet(BuildContext context) async { final channel = StreamChannel.of(context).channel; - final galleryHeaderThemeData = - StreamChatTheme.of(context).galleryHeaderTheme; + final galleryHeaderThemeData = StreamChatTheme.of(context).galleryHeaderTheme; final defaultModal = AttachmentActionsModal( attachment: attachment, @@ -153,7 +147,8 @@ class StreamGalleryHeader extends StatelessWidget onReply: onReplyMessage, ); - final effectiveModal = attachmentActionsModalBuilder?.call( + final effectiveModal = + attachmentActionsModalBuilder?.call( context, attachment, defaultModal, diff --git a/packages/stream_chat_flutter/lib/src/icons/stream_svg_icon.dart b/packages/stream_chat_flutter/lib/src/icons/stream_svg_icon.dart index 46acd43a8d..cc7f45e9cd 100644 --- a/packages/stream_chat_flutter/lib/src/icons/stream_svg_icon.dart +++ b/packages/stream_chat_flutter/lib/src/icons/stream_svg_icon.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; import 'package:svg_icon_widget/svg_icon_widget.dart'; part 'stream_svg_icon.g.dart'; @@ -11,8 +12,104 @@ typedef StreamSvgIconData = SvgIconData; /// {@template StreamSvgIcon} /// Icon set of stream chat /// {@endtemplate} +@Deprecated('Use Icon(context.streamIcons.*) instead') class StreamSvgIcon extends StatelessWidget { /// Creates a [StreamSvgIcon]. + /// + /// Depcreated in favor of regular [Icon] widgets. + /// New icons can be replaced using the [StreamIcons] theme data. + /// + /// Previously: + /// + /// ```dart + /// StreamSvgIcon(icon: StreamSvgIcons.arrowRight) + /// ``` + /// + /// Replacement: + /// + /// ```dart + /// Icon(context.streamIcons.arrowRight) + /// ``` + /// + /// List of replacement icons: + /// + /// Exact / close matches: + /// - StreamSvgIcons.arrowRight -> context.streamIcons.arrowRight + /// - StreamSvgIcons.attach -> context.streamIcons.paperclip1 + /// - StreamSvgIcons.camera -> context.streamIcons.camera1 + /// - StreamSvgIcons.check -> context.streamIcons.checkmark2 + /// - StreamSvgIcons.checkAll -> context.streamIcons.doupleCheckmark1Small + /// - StreamSvgIcons.checkSend -> context.streamIcons.circleCheck + /// - StreamSvgIcons.close -> context.streamIcons.crossMedium + /// - StreamSvgIcons.closeSmall -> context.streamIcons.crossSmall + /// - StreamSvgIcons.copy -> context.streamIcons.squareBehindSquare2Copy + /// - StreamSvgIcons.delete -> context.streamIcons.trashBin + /// - StreamSvgIcons.down -> context.streamIcons.chevronDown + /// - StreamSvgIcons.edit -> context.streamIcons.editBig + /// - StreamSvgIcons.error -> context.streamIcons.exclamationCircle1 + /// - StreamSvgIcons.eye -> context.streamIcons.eyeOpen + /// - StreamSvgIcons.flag -> context.streamIcons.flag2 + /// - StreamSvgIcons.left -> context.streamIcons.chevronLeft + /// - StreamSvgIcons.lightning -> context.streamIcons.thunder + /// - StreamSvgIcons.link -> context.streamIcons.chainLink3 + /// - StreamSvgIcons.lock -> context.streamIcons.lock + /// - StreamSvgIcons.mentions -> context.streamIcons.at + /// - StreamSvgIcons.mic -> context.streamIcons.microphone + /// - StreamSvgIcons.mute -> context.streamIcons.mute + /// - StreamSvgIcons.notification -> context.streamIcons.bellNotification + /// - StreamSvgIcons.pause -> context.streamIcons.pause + /// - StreamSvgIcons.penWrite -> context.streamIcons.pencil + /// - StreamSvgIcons.pin -> context.streamIcons.pin + /// - StreamSvgIcons.right -> context.streamIcons.chevronRight + /// - StreamSvgIcons.search -> context.streamIcons.magnifyingGlassSearch + /// - StreamSvgIcons.settings -> context.streamIcons.settingsGear2 + /// - StreamSvgIcons.smile -> context.streamIcons.emojiSmile + /// - StreamSvgIcons.stop -> context.streamIcons.stop + /// - StreamSvgIcons.time -> context.streamIcons.clock + /// - StreamSvgIcons.up -> context.streamIcons.chevronTop + /// - StreamSvgIcons.volumeUp -> context.streamIcons.volumeFull + /// + /// Conceptual matches (similar purpose, may differ visually): + /// - StreamSvgIcons.award -> context.streamIcons.trophy + /// - StreamSvgIcons.circleUp -> context.streamIcons.arrowUp (no circle) + /// - StreamSvgIcons.contacts -> context.streamIcons.users + /// - StreamSvgIcons.download -> context.streamIcons.arrowDown (no tray) + /// - StreamSvgIcons.emptyCircleRight -> context.streamIcons.chevronRight (no circle) + /// - StreamSvgIcons.files -> context.streamIcons.fileBend (folder vs document) + /// - StreamSvgIcons.grid -> context.streamIcons.layoutGrid1 (3x3 vs 2x2) + /// - StreamSvgIcons.group -> context.streamIcons.users + /// - StreamSvgIcons.loveReaction -> context.streamIcons.heart2 + /// - StreamSvgIcons.menuPoint -> context.streamIcons.dotsGrid1x3Vertical + /// - StreamSvgIcons.message -> context.streamIcons.bubble3ChatMessage + /// - StreamSvgIcons.messageUnread -> context.streamIcons.bubbleWideNotificationChatMessage + /// - StreamSvgIcons.pictures -> context.streamIcons.images1Alt + /// - StreamSvgIcons.play -> context.streamIcons.playSolid + /// - StreamSvgIcons.polls -> context.streamIcons.chart5 + /// - StreamSvgIcons.record -> context.streamIcons.video + /// - StreamSvgIcons.reload -> context.streamIcons.arrowRotateRightLeftRepeatRefresh + /// - StreamSvgIcons.reply -> context.streamIcons.arrowShareLeft + /// - StreamSvgIcons.retry -> context.streamIcons.arrowRotateClockwise + /// - StreamSvgIcons.save -> context.streamIcons.bookmark (arrow vs ribbon) + /// - StreamSvgIcons.send -> context.streamIcons.paperPlane + /// - StreamSvgIcons.sendMessage -> context.streamIcons.paperPlaneTopRight (no circle) + /// - StreamSvgIcons.share -> context.streamIcons.shareOs + /// - StreamSvgIcons.shareArrow -> context.streamIcons.shareRedirectLink + /// - StreamSvgIcons.threadReply -> context.streamIcons.bubbleAnnotation2ChatMessage + /// - StreamSvgIcons.user -> context.streamIcons.people + /// - StreamSvgIcons.userAdd -> context.streamIcons.peopleAdd + /// - StreamSvgIcons.userDelete -> context.streamIcons.peopleRemove + /// - StreamSvgIcons.userRemove -> context.streamIcons.peopleRemove + /// - StreamSvgIcons.userSettings -> context.streamIcons.peopleEditUserRights + /// - StreamSvgIcons.videoCall -> context.streamIcons.videoSolid + /// + /// Removed in new set (no equivalent): + /// - StreamSvgIcons.cloudDownload + /// - StreamSvgIcons.lolReaction + /// - StreamSvgIcons.moon + /// - StreamSvgIcons.thumbsDownReaction + /// - StreamSvgIcons.thumbsUpReaction + /// - StreamSvgIcons.wutReaction + @Deprecated('Use Icon(context.streamIcons.*) instead') const StreamSvgIcon({ super.key, this.icon, diff --git a/packages/stream_chat_flutter/lib/src/icons/stream_svg_icon.g.dart b/packages/stream_chat_flutter/lib/src/icons/stream_svg_icon.g.dart index c6a7fc2001..f9f154eb59 100644 --- a/packages/stream_chat_flutter/lib/src/icons/stream_svg_icon.g.dart +++ b/packages/stream_chat_flutter/lib/src/icons/stream_svg_icon.g.dart @@ -503,16 +503,14 @@ abstract final class StreamSvgIcons { ); /// Stream SVG icon named 'filetypePresentationStandard'. - static const StreamSvgIconData filetypePresentationStandard = - StreamSvgIconData( + static const StreamSvgIconData filetypePresentationStandard = StreamSvgIconData( 'lib/assets/icons/colored/icon_filetype_presentation_standard.svg', package: package, preserveColors: true, ); /// Stream SVG icon named 'filetypePresentationSpecial'. - static const StreamSvgIconData filetypePresentationSpecial = - StreamSvgIconData( + static const StreamSvgIconData filetypePresentationSpecial = StreamSvgIconData( 'lib/assets/icons/colored/icon_filetype_presentation_special.svg', package: package, preserveColors: true, @@ -652,8 +650,7 @@ abstract final class StreamSvgIcons { ); /// Stream SVG icon named 'filetypeSpreadsheetStandard'. - static const StreamSvgIconData filetypeSpreadsheetStandard = - StreamSvgIconData( + static const StreamSvgIconData filetypeSpreadsheetStandard = StreamSvgIconData( 'lib/assets/icons/colored/icon_filetype_spreadsheet_standard.svg', package: package, preserveColors: true, @@ -828,8 +825,7 @@ abstract final class StreamSvgIcons { ); /// Stream SVG icon named 'filetypeCompressionStandard'. - static const StreamSvgIconData filetypeCompressionStandard = - StreamSvgIconData( + static const StreamSvgIconData filetypeCompressionStandard = StreamSvgIconData( 'lib/assets/icons/colored/icon_filetype_compression_standard.svg', package: package, preserveColors: true, diff --git a/packages/stream_chat_flutter/lib/src/indicators/sending_indicator.dart b/packages/stream_chat_flutter/lib/src/indicators/sending_indicator.dart index 5951336615..b4fabfd62b 100644 --- a/packages/stream_chat_flutter/lib/src/indicators/sending_indicator.dart +++ b/packages/stream_chat_flutter/lib/src/indicators/sending_indicator.dart @@ -12,7 +12,7 @@ class StreamSendingIndicator extends StatelessWidget { required this.message, this.isMessageRead = false, this.isMessageDelivered = false, - this.size = 12, + this.size, }); /// The message whose sending status is to be shown. @@ -33,33 +33,33 @@ class StreamSendingIndicator extends StatelessWidget { final colorTheme = streamChatTheme.colorTheme; if (isMessageRead) { - return StreamSvgIcon( + return Icon( + context.streamIcons.doupleCheckmark1Small, size: size, - icon: StreamSvgIcons.checkAll, color: colorTheme.accentPrimary, ); } if (isMessageDelivered) { - return StreamSvgIcon( + return Icon( + context.streamIcons.doupleCheckmark1Small, size: size, - icon: StreamSvgIcons.checkAll, color: colorTheme.textLowEmphasis, ); } if (message.state.isCompleted) { - return StreamSvgIcon( + return Icon( + context.streamIcons.checkmark2, size: size, - icon: StreamSvgIcons.check, color: colorTheme.textLowEmphasis, ); } if (message.state.isOutgoing) { - return StreamSvgIcon( + return Icon( + context.streamIcons.clock, size: size, - icon: StreamSvgIcons.time, color: colorTheme.textLowEmphasis, ); } diff --git a/packages/stream_chat_flutter/lib/src/indicators/typing_indicator.dart b/packages/stream_chat_flutter/lib/src/indicators/typing_indicator.dart index fdf8e3a001..10a84b2c7f 100644 --- a/packages/stream_chat_flutter/lib/src/indicators/typing_indicator.dart +++ b/packages/stream_chat_flutter/lib/src/indicators/typing_indicator.dart @@ -34,17 +34,15 @@ class StreamTypingIndicator extends StatelessWidget { @override Widget build(BuildContext context) { - final channelState = - channel?.state ?? StreamChannel.of(context).channel.state!; + final channelState = channel?.state ?? StreamChannel.of(context).channel.state!; final altWidget = alternativeWidget ?? const Empty(); return BetterStreamBuilder>( initialData: channelState.typingEvents.keys, - stream: channelState.typingEventsStream.map((typingEvents) => typingEvents - .entries - .where((element) => element.value.parentId == parentId) - .map((e) => e.key)), + stream: channelState.typingEventsStream.map( + (typingEvents) => typingEvents.entries.where((element) => element.value.parentId == parentId).map((e) => e.key), + ), builder: (context, users) => AnimatedSwitcher( layoutBuilder: (currentChild, previousChildren) => Stack( children: [ @@ -60,11 +58,6 @@ class StreamTypingIndicator extends StatelessWidget { mainAxisSize: MainAxisSize.min, spacing: 4, children: [ - Lottie.asset( - 'lib/assets/animations/typing_dots.json', - package: 'stream_chat_flutter', - height: 4, - ), Flexible( child: Text( context.translations.userTypingText(users), @@ -72,6 +65,14 @@ class StreamTypingIndicator extends StatelessWidget { style: style, ), ), + Padding( + padding: const EdgeInsets.only(top: 2), + child: Lottie.asset( + 'lib/assets/animations/typing_dots.json', + package: 'stream_chat_flutter', + height: 5, + ), + ), ], ), ) diff --git a/packages/stream_chat_flutter/lib/src/indicators/unread_indicator.dart b/packages/stream_chat_flutter/lib/src/indicators/unread_indicator.dart index f035edb700..2ae46f73cc 100644 --- a/packages/stream_chat_flutter/lib/src/indicators/unread_indicator.dart +++ b/packages/stream_chat_flutter/lib/src/indicators/unread_indicator.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/src/misc/empty_widget.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; /// {@template streamUnreadIndicator} /// Shows different unread counts of the user. @@ -31,31 +32,30 @@ class StreamUnreadIndicator extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = StreamChatTheme.of(context); final client = StreamChat.of(context).client; final stream = switch (_unreadType) { _TotalUnreadCount() => client.state.totalUnreadCountStream, _UnreadChannels(cid: final cid) => switch (cid) { - final cid? => client.state.channels[cid]?.state?.unreadCountStream, - _ => client.state.unreadChannelsStream, - }, + final cid? => client.state.channels[cid]?.state?.unreadCountStream, + _ => client.state.unreadChannelsStream, + }, _UnreadThreads(id: final id) => switch (id) { - // TODO: Handle id once it's supported - _ => client.state.unreadThreadsStream, - } + // TODO: Handle id once it's supported + _ => client.state.unreadThreadsStream, + }, }; final initialData = switch (_unreadType) { _TotalUnreadCount() => client.state.totalUnreadCount, _UnreadChannels(cid: final cid) => switch (cid) { - final cid? => client.state.channels[cid]?.state?.unreadCount, - _ => client.state.unreadChannels, - }, + final cid? => client.state.channels[cid]?.state?.unreadCount, + _ => client.state.unreadChannels, + }, _UnreadThreads(id: final id) => switch (id) { - // TODO: Handle id once it's supported - _ => client.state.unreadThreads, - } + // TODO: Handle id once it's supported + _ => client.state.unreadThreads, + }, }; return IgnorePointer( @@ -65,16 +65,12 @@ class StreamUnreadIndicator extends StatelessWidget { builder: (context, unreadCount) { if (unreadCount == 0) return const Empty(); - return Badge( - textColor: Colors.white, - textStyle: theme.textTheme.footnoteBold, - backgroundColor: theme.channelPreviewTheme.unreadCounterColor, - label: Text( - switch (unreadCount) { - > 99 => '99+', - _ => '$unreadCount', - }, - ), + return StreamBadgeNotification( + size: StreamBadgeNotificationSize.xs, + label: switch (unreadCount) { + > 99 => '99+', + _ => '$unreadCount', + }, ); }, ), diff --git a/packages/stream_chat_flutter/lib/src/indicators/upload_progress_indicator.dart b/packages/stream_chat_flutter/lib/src/indicators/upload_progress_indicator.dart index 7a6b289814..c28dba83b6 100644 --- a/packages/stream_chat_flutter/lib/src/indicators/upload_progress_indicator.dart +++ b/packages/stream_chat_flutter/lib/src/indicators/upload_progress_indicator.dart @@ -59,7 +59,8 @@ class StreamUploadProgressIndicator extends StatelessWidget { const SizedBox(width: 8), Text( '${_percentage.toInt()}%', - style: textStyle ?? + style: + textStyle ?? theme.textTheme.footnote.copyWith( color: theme.colorTheme.barsBg, ), diff --git a/packages/stream_chat_flutter/lib/src/keyboard_shortcuts/keyboard_shortcut_runner.dart b/packages/stream_chat_flutter/lib/src/keyboard_shortcuts/keyboard_shortcut_runner.dart index 706adad454..fa2383a17b 100644 --- a/packages/stream_chat_flutter/lib/src/keyboard_shortcuts/keyboard_shortcut_runner.dart +++ b/packages/stream_chat_flutter/lib/src/keyboard_shortcuts/keyboard_shortcut_runner.dart @@ -37,8 +37,7 @@ class KeyboardShortcutRunner extends StatelessWidget { shortcuts: { if (onEnterKeypress != null) enterKeySet: EnterKeyIntent(), if (onEscapeKeypress != null) escapeKeySet: EscapeKeyIntent(), - if (onRightArrowKeypress != null) - rightArrowKeySet: RightArrowKeyIntent(), + if (onRightArrowKeypress != null) rightArrowKeySet: RightArrowKeyIntent(), if (onLeftArrowKeypress != null) leftArrowKeySet: LeftArrowKeyIntent(), }, actions: { diff --git a/packages/stream_chat_flutter/lib/src/localization/stream_chat_localizations.dart b/packages/stream_chat_flutter/lib/src/localization/stream_chat_localizations.dart index 66c09e8847..bc7da0a593 100644 --- a/packages/stream_chat_flutter/lib/src/localization/stream_chat_localizations.dart +++ b/packages/stream_chat_flutter/lib/src/localization/stream_chat_localizations.dart @@ -1,6 +1,5 @@ import 'package:flutter/widgets.dart'; -import 'package:stream_chat_flutter/src/localization/translations.dart' - show Translations; +import 'package:stream_chat_flutter/src/localization/translations.dart' show Translations; /// Defines the localized resource values used by the StreamChatFlutter widgets. /// diff --git a/packages/stream_chat_flutter/lib/src/localization/translations.dart b/packages/stream_chat_flutter/lib/src/localization/translations.dart index 21320fc960..33203c4439 100644 --- a/packages/stream_chat_flutter/lib/src/localization/translations.dart +++ b/packages/stream_chat_flutter/lib/src/localization/translations.dart @@ -537,6 +537,18 @@ abstract class Translations { /// The text for video attachment in channel list preview String get videoAttachmentText; + /// The text for file attachment in channel list preview + String get fileAttachmentText; + + /// The text for multiple files attachment in channel list preview + String filesAttachmentCountText(int count); + + /// The text for multiple photos attachment in channel list preview + String photosAttachmentCountText(int count); + + /// The text for multiple videos attachment in channel list preview + String videosAttachmentCountText(int count); + /// The text for poll when current user voted String get pollYouVotedText; @@ -597,20 +609,19 @@ class DefaultTranslations implements Translations { } @override - String get threadReplyLabel => 'Thread Reply'; + String get threadReplyLabel => 'Thread'; @override String get onlyVisibleToYouText => 'Only visible to you'; @override - String threadReplyCountText(int count) => '$count Thread Replies'; + String threadReplyCountText(int count) => count == 1 ? '1 reply' : '$count replies'; @override String attachmentsUploadProgressText({ required int remaining, required int total, - }) => - 'Uploading $remaining/$total ...'; + }) => 'Uploading $remaining/$total ...'; @override String pinnedByUserText({ @@ -623,8 +634,7 @@ class DefaultTranslations implements Translations { } @override - String get sendMessagePermissionError => - "You don't have permission to send messages"; + String get sendMessagePermissionError => "You don't have permission to send messages"; @override String get emptyMessagesText => 'There are no messages currently'; @@ -658,8 +668,8 @@ class DefaultTranslations implements Translations { @override String threadSeparatorText(int replyCount) { - if (replyCount == 1) return '1 Reply'; - return '$replyCount Replies'; + if (replyCount == 1) return '1 reply'; + return '$replyCount replies'; } @override @@ -672,7 +682,7 @@ class DefaultTranslations implements Translations { String get reconnectingLabel => 'Reconnecting...'; @override - String get alsoSendAsDirectMessageLabel => 'Also send as direct message'; + String get alsoSendAsDirectMessageLabel => 'Also send in Channel'; @override String get addACommentOrSendLabel => 'Add a comment or send'; @@ -697,8 +707,7 @@ class DefaultTranslations implements Translations { 'The file is too large to upload. The file size limit is $limitInMB MB.'; @override - String get couldNotReadBytesFromFileError => - 'Could not read bytes from file.'; + String get couldNotReadBytesFromFileError => 'Could not read bytes from file.'; @override String get addAFileLabel => 'Add a file'; @@ -725,7 +734,7 @@ class DefaultTranslations implements Translations { String get somethingWentWrongError => 'Something went wrong'; @override - String get addMoreFilesLabel => 'Add more files'; + String get addMoreFilesLabel => 'Add more'; @override String get enablePhotoAndVideoAccessMessage => @@ -752,8 +761,7 @@ class DefaultTranslations implements Translations { String get flagMessageSuccessfulLabel => 'Message flagged'; @override - String get flagMessageSuccessfulText => - 'The message has been reported to a moderator.'; + String get flagMessageSuccessfulText => 'The message has been reported to a moderator.'; @override String get deleteLabel => 'DELETE'; @@ -762,12 +770,10 @@ class DefaultTranslations implements Translations { String get deleteMessageLabel => 'Delete Message'; @override - String get deleteMessageQuestion => - 'Are you sure you want to permanently delete this message?'; + String get deleteMessageQuestion => 'Are you sure you want to permanently delete this message?'; @override - String get operationCouldNotBeCompletedText => - "The operation couldn't be completed."; + String get operationCouldNotBeCompletedText => "The operation couldn't be completed."; @override String get replyLabel => 'Reply'; @@ -845,8 +851,7 @@ class DefaultTranslations implements Translations { String get letsStartChattingLabel => 'Let’s start chatting!'; @override - String get sendingFirstMessageLabel => - 'How about sending your first message to a friend?'; + String get sendingFirstMessageLabel => 'How about sending your first message to a friend?'; @override String get startAChatLabel => 'Start a chat'; @@ -858,8 +863,7 @@ class DefaultTranslations implements Translations { String get deleteConversationLabel => 'Delete Conversation'; @override - String get deleteConversationQuestion => - 'Are you sure you want to delete this conversation?'; + String get deleteConversationQuestion => 'Are you sure you want to delete this conversation?'; @override String get streamChatLabel => 'Stream Chat'; @@ -898,8 +902,7 @@ class DefaultTranslations implements Translations { String get leaveConversationLabel => 'Leave conversation'; @override - String get leaveConversationQuestion => - 'Are you sure you want to leave this conversation?'; + String get leaveConversationQuestion => 'Are you sure you want to leave this conversation?'; @override String get showInChatLabel => 'Show in Chat'; @@ -935,8 +938,7 @@ class DefaultTranslations implements Translations { String galleryPaginationText({ required int currentPage, required int totalPages, - }) => - '${currentPage + 1} of $totalPages'; + }) => '${currentPage + 1} of $totalPages'; @override String get fileText => 'File'; @@ -1003,8 +1005,7 @@ Attachment limit exceeded: it's not possible to add more than $limit attachments } @override - String get linkDisabledDetails => - 'Sending links is not allowed in this conversation.'; + String get linkDisabledDetails => 'Sending links is not allowed in this conversation.'; @override String get linkDisabledError => 'Links are disabled'; @@ -1013,7 +1014,8 @@ Attachment limit exceeded: it's not possible to add more than $limit attachments String unreadMessagesSeparatorText() => 'New messages'; @override - String get enableFileAccessMessage => 'Please enable access to files' + String get enableFileAccessMessage => + 'Please enable access to files' '\nso you can share them with friends.'; @override @@ -1114,15 +1116,13 @@ Attachment limit exceeded: it's not possible to add more than $limit attachments String get enterYourCommentLabel => 'Enter your comment'; @override - String get endVoteConfirmationText => - 'Are you sure you want to end the vote?'; + String get endVoteConfirmationText => 'Are you sure you want to end the vote?'; @override String get deletePollOptionLabel => 'Delete Option'; @override - String get deletePollOptionQuestion => - 'Are you sure you want to delete this option?'; + String get deletePollOptionQuestion => 'Are you sure you want to delete this option?'; @override String get createLabel => 'Create'; @@ -1166,10 +1166,10 @@ Attachment limit exceeded: it's not possible to add more than $limit attachments @override String voteCountLabel({int? count}) => switch (count) { - null || < 1 => '0 votes', - 1 => '1 vote', - _ => '$count votes', - }; + null || < 1 => '0 votes', + 1 => '1 vote', + _ => '$count votes', + }; @override String get noPollVotesLabel => 'There are no poll votes currently'; @@ -1196,8 +1196,7 @@ Attachment limit exceeded: it's not possible to add more than $limit attachments String get sendAnywayLabel => 'Send Anyway'; @override - String get moderatedMessageBlockedText => - 'Message was blocked by moderation policies'; + String get moderatedMessageBlockedText => 'Message was blocked by moderation policies'; @override String get moderationReviewModalTitle => 'Are you sure?'; @@ -1221,6 +1220,18 @@ Attachment limit exceeded: it's not possible to add more than $limit attachments @override String get videoAttachmentText => 'Video'; + @override + String get fileAttachmentText => 'File'; + + @override + String filesAttachmentCountText(int count) => count == 1 ? 'File' : '$count files'; + + @override + String photosAttachmentCountText(int count) => count == 1 ? 'Photo' : '$count photos'; + + @override + String videosAttachmentCountText(int count) => count == 1 ? 'Video' : '$count videos'; + @override String get pollYouVotedText => 'You voted'; diff --git a/packages/stream_chat_flutter/lib/src/message_action/message_action.dart b/packages/stream_chat_flutter/lib/src/message_action/message_action.dart index 27e4fb00fa..27f950eae8 100644 --- a/packages/stream_chat_flutter/lib/src/message_action/message_action.dart +++ b/packages/stream_chat_flutter/lib/src/message_action/message_action.dart @@ -1,70 +1,138 @@ import 'package:flutter/widgets.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; -part 'message_action_type.dart'; - -/// {@template streamMessageAction} -/// A class that represents an action that can be performed on a message. +/// {@template messageActionsBuilder} +/// Signature for a builder that customizes the list of message actions shown +/// in the long-press context menu. +/// +/// [defaultActions] are the pre-built actions already filtered by the widget's +/// `show*` flags. Return a modified list to add, remove, or reorder actions. /// -/// This class is used to define actions that appear in message action menus -/// or option lists, providing a consistent structure for message-related -/// actions including their visual representation and behavior. +/// The return type is [List] so any widget — including +/// [StreamContextMenuSeparator] or fully custom items — can be mixed in +/// alongside the default [StreamContextMenuAction] items. /// {@endtemplate} -class StreamMessageAction { - /// {@macro streamMessageAction} - const StreamMessageAction({ - required this.action, - this.isDestructive = false, - this.leading, - this.iconColor, - this.title, - this.titleTextColor, - this.titleTextStyle, - this.backgroundColor, +typedef MessageActionsBuilder = + List Function( + BuildContext context, + List> defaultActions, + ); + +/// {@template messageAction} +/// A sealed class that represents different actions that can be performed on a +/// message. +/// {@endtemplate} +sealed class MessageAction { + /// {@macro messageAction} + const MessageAction({required this.message}); + + /// The message this action applies to. + final Message message; +} + +/// Action to show reaction selector for adding reactions to a message +final class SelectReaction extends MessageAction { + /// Create a new select reaction action + const SelectReaction({ + required super.message, + required this.reaction, + this.enforceUnique = false, + }); + + /// The reaction to be added or removed from the message. + final Reaction reaction; + + /// Whether to enforce unique reactions. + final bool enforceUnique; +} + +/// Action to copy message content to clipboard +final class CopyMessage extends MessageAction { + /// Create a new copy message action + const CopyMessage({required super.message}); +} + +/// Action to delete a message from the conversation +final class DeleteMessage extends MessageAction { + /// Create a new delete message action + const DeleteMessage({required super.message}); +} + +/// Action to hard delete a message permanently from the conversation +final class HardDeleteMessage extends MessageAction { + /// Create a new hard delete message action + const HardDeleteMessage({required super.message}); +} + +/// Action to modify content of an existing message +final class EditMessage extends MessageAction { + /// Create a new edit message action + const EditMessage({required super.message}); +} + +/// Action to flag a message for moderator review +final class FlagMessage extends MessageAction { + /// Create a new flag message action + const FlagMessage({required super.message}); +} + +/// Action to mark a message as unread for later viewing +final class MarkUnread extends MessageAction { + /// Create a new mark unread action + const MarkUnread({required super.message}); +} + +/// Action to mute a user to prevent notifications from their messages +final class MuteUser extends MessageAction { + /// Create a new mute user action + const MuteUser({ + required super.message, + required this.user, }); - /// The [MessageAction] that this item represents. - final T action; - - /// Whether the action is destructive. - /// - /// Destructive actions are typically displayed with a red color to indicate - /// that they will remove or delete content. - /// - /// Defaults to `false`. - final bool isDestructive; - - /// A widget to display before the title. - /// - /// Typically an [Icon] or a [CircleAvatar] widget. - final Widget? leading; - - /// The color for the [leading] icon. - /// - /// If this property is null, the icon will use the default color provided by - /// the theme or parent widget. - final Color? iconColor; - - /// The primary content of the action item. - /// - /// Typically a [Text] widget. - /// - /// This should not wrap. To enforce the single line limit, use - /// [Text.maxLines]. - final Widget? title; - - /// The color for the text in the [title]. - /// - /// If this property is null, the text will use the default color provided by - /// the theme or parent widget. - final Color? titleTextColor; - - /// The text style for the [title]. - /// - /// If this property is null, the title will use the default text style - /// provided by the theme or parent widget. - final TextStyle? titleTextStyle; - - /// Defines the background color of the action item. - final Color? backgroundColor; + /// The user to be muted. + final User user; +} + +/// Action to unmute a user to receive notifications from their messages +final class UnmuteUser extends MessageAction { + /// Create a new unmute user action + const UnmuteUser({ + required super.message, + required this.user, + }); + + /// The user to be unmuted. + final User user; +} + +/// Action to pin a message to make it prominently visible in the channel +final class PinMessage extends MessageAction { + /// Create a new pin message action + const PinMessage({required super.message}); +} + +/// Action to remove a previously pinned message +final class UnpinMessage extends MessageAction { + /// Create a new unpin message action + const UnpinMessage({required super.message}); +} + +/// Action to attempt to resend a message that failed to send +final class ResendMessage extends MessageAction { + /// Create a new resend message action + const ResendMessage({required super.message}); +} + +/// Action to create a reply with quoted original message content +final class QuotedReply extends MessageAction { + /// Create a new quoted reply action + const QuotedReply({required super.message}); +} + +/// Action to start a threaded conversation from a message +final class ThreadReply extends MessageAction { + /// Create a new thread reply action + const ThreadReply({required super.message}); } diff --git a/packages/stream_chat_flutter/lib/src/message_action/message_action_item.dart b/packages/stream_chat_flutter/lib/src/message_action/message_action_item.dart deleted file mode 100644 index fa4af9328c..0000000000 --- a/packages/stream_chat_flutter/lib/src/message_action/message_action_item.dart +++ /dev/null @@ -1,81 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/src/message_action/message_action.dart'; -import 'package:stream_chat_flutter/src/theme/stream_chat_theme.dart'; - -/// {@template streamMessageActionItem} -/// A widget that represents an action item within a message interface. -/// -/// This widget is typically used in action menus or option lists related to -/// messages, providing a consistent appearance for selectable actions with an -/// optional icon and title. -/// {@endtemplate} -class StreamMessageActionItem extends StatelessWidget { - /// {@macro streamMessageActionItem} - const StreamMessageActionItem({ - super.key, - required this.action, - this.onTap, - }); - - /// The underlying action that this item represents. - final StreamMessageAction action; - - /// Called when the user taps this action item. - /// - /// This callback provides the tap handling for the action item, and is - /// typically used to execute the associated action or dismiss menus. - final OnMessageActionTap? onTap; - - @override - Widget build(BuildContext context) { - final theme = StreamChatTheme.of(context); - final textTheme = theme.textTheme; - final colorTheme = theme.colorTheme; - - final iconColor = switch (action.isDestructive) { - true => action.iconColor ?? colorTheme.accentError, - false => action.iconColor ?? colorTheme.textLowEmphasis, - }; - - final titleTextColor = switch (action.isDestructive) { - true => action.titleTextColor ?? colorTheme.accentError, - false => action.titleTextColor ?? colorTheme.textHighEmphasis, - }; - - final titleTextStyle = action.titleTextStyle ?? textTheme.body; - final backgroundColor = action.backgroundColor ?? colorTheme.barsBg; - - return InkWell( - onTap: switch (onTap) { - final onTap? => () => onTap(action.action), - _ => null, - }, - child: Ink( - color: backgroundColor, - child: IconTheme.merge( - data: IconThemeData(color: iconColor), - child: Padding( - padding: const EdgeInsets.symmetric( - vertical: 9, - horizontal: 16, - ), - child: Row( - spacing: 16, - mainAxisSize: MainAxisSize.min, - children: [ - if (action.leading case final leading?) leading, - if (action.title case final title?) - DefaultTextStyle( - style: titleTextStyle.copyWith( - color: titleTextColor, - ), - child: title, - ), - ], - ), - ), - ), - ), - ); - } -} diff --git a/packages/stream_chat_flutter/lib/src/message_action/message_action_type.dart b/packages/stream_chat_flutter/lib/src/message_action/message_action_type.dart deleted file mode 100644 index 5aea4980b1..0000000000 --- a/packages/stream_chat_flutter/lib/src/message_action/message_action_type.dart +++ /dev/null @@ -1,137 +0,0 @@ -part of 'message_action.dart'; - -/// {@template onMessageActionTap} -/// Signature for a function that is called when a message action is tapped. -/// {@endtemplate} -typedef OnMessageActionTap = void Function(T action); - -/// {@template messageAction} -/// A sealed class that represents different actions that can be performed on a -/// message. -/// {@endtemplate} -sealed class MessageAction { - /// {@macro messageAction} - const MessageAction({required this.message}); - - /// The message this action applies to. - final Message message; -} - -/// Action to show reaction selector for adding reactions to a message -final class SelectReaction extends MessageAction { - /// Create a new select reaction action - const SelectReaction({ - required super.message, - required this.reaction, - this.enforceUnique = false, - }); - - /// The reaction to be added or removed from the message. - final Reaction reaction; - - /// Whether to enforce unique reactions. - final bool enforceUnique; -} - -/// Action to copy message content to clipboard -final class CopyMessage extends MessageAction { - /// Create a new copy message action - const CopyMessage({required super.message}); -} - -/// Action to delete a message from the conversation -final class DeleteMessage extends MessageAction { - /// Create a new delete message action - const DeleteMessage({required super.message}); -} - -/// Action to hard delete a message permanently from the conversation -final class HardDeleteMessage extends MessageAction { - /// Create a new hard delete message action - const HardDeleteMessage({required super.message}); -} - -/// Action to modify content of an existing message -final class EditMessage extends MessageAction { - /// Create a new edit message action - const EditMessage({required super.message}); -} - -/// Action to flag a message for moderator review -final class FlagMessage extends MessageAction { - /// Create a new flag message action - const FlagMessage({required super.message}); -} - -/// Action to mark a message as unread for later viewing -final class MarkUnread extends MessageAction { - /// Create a new mark unread action - const MarkUnread({required super.message}); -} - -/// Action to mute a user to prevent notifications from their messages -final class MuteUser extends MessageAction { - /// Create a new mute user action - const MuteUser({ - required super.message, - required this.user, - }); - - /// The user to be muted. - final User user; -} - -/// Action to unmute a user to receive notifications from their messages -final class UnmuteUser extends MessageAction { - /// Create a new unmute user action - const UnmuteUser({ - required super.message, - required this.user, - }); - - /// The user to be unmuted. - final User user; -} - -/// Action to pin a message to make it prominently visible in the channel -final class PinMessage extends MessageAction { - /// Create a new pin message action - const PinMessage({required super.message}); -} - -/// Action to remove a previously pinned message -final class UnpinMessage extends MessageAction { - /// Create a new unpin message action - const UnpinMessage({required super.message}); -} - -/// Action to attempt to resend a message that failed to send -final class ResendMessage extends MessageAction { - /// Create a new resend message action - const ResendMessage({required super.message}); -} - -/// Action to create a reply with quoted original message content -final class QuotedReply extends MessageAction { - /// Create a new quoted reply action - const QuotedReply({required super.message}); -} - -/// Action to start a threaded conversation from a message -final class ThreadReply extends MessageAction { - /// Create a new thread reply action - const ThreadReply({required super.message}); -} - -/// Custom message action that allows for additional data to be passed -/// along with the message. -class CustomMessageAction extends MessageAction { - /// Create a new custom message action - const CustomMessageAction({ - required super.message, - this.extraData = const {}, - }); - - /// Map of extra data associated with the action. - final Map extraData; -} diff --git a/packages/stream_chat_flutter/lib/src/message_action/message_actions_builder.dart b/packages/stream_chat_flutter/lib/src/message_action/message_actions_builder.dart index 18bad5a42e..1bae56bdc8 100644 --- a/packages/stream_chat_flutter/lib/src/message_action/message_actions_builder.dart +++ b/packages/stream_chat_flutter/lib/src/message_action/message_actions_builder.dart @@ -1,9 +1,9 @@ -import 'package:flutter/widgets.dart'; -import 'package:stream_chat_flutter/src/icons/stream_svg_icon.dart'; +import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/src/message_action/message_action.dart'; import 'package:stream_chat_flutter/src/theme/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/utils/extensions.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; /// {@template streamMessageActionsBuilder} /// A utility class that provides a builder for message actions @@ -15,35 +15,39 @@ class StreamMessageActionsBuilder { /// Returns a list of message actions for the "bounced with error" state. /// - /// This method builds a list of [StreamMessageAction]s that are applicable to + /// This method builds a list of [StreamContextMenuAction]s that are + /// applicable to /// the given [message] when it is in the "bounced with error" state. /// /// The actions include options to retry sending the message, edit or delete /// the message. - static List buildBouncedErrorActions({ + static List> buildBouncedErrorActions({ required BuildContext context, required Message message, }) { // If the message is not bounced with an error, we don't show any actions. if (!message.isBouncedWithError) return []; - return [ - StreamMessageAction( - action: ResendMessage(message: message), - iconColor: StreamChatTheme.of(context).colorTheme.accentPrimary, - title: Text(context.translations.sendAnywayLabel), - leading: const StreamSvgIcon(icon: StreamSvgIcons.circleUp), + final icons = context.streamIcons; + + return >[ + StreamContextMenuAction( + value: ResendMessage(message: message), + label: Text(context.translations.sendAnywayLabel), + leading: Icon( + icons.paperPlaneTopRight, + color: StreamChatTheme.of(context).colorTheme.accentPrimary, + ), ), - StreamMessageAction( - action: EditMessage(message: message), - title: Text(context.translations.editMessageLabel), - leading: const StreamSvgIcon(icon: StreamSvgIcons.edit), + StreamContextMenuAction( + value: EditMessage(message: message), + label: Text(context.translations.editMessageLabel), + leading: Icon(icons.editBig), ), - StreamMessageAction( - isDestructive: true, - action: HardDeleteMessage(message: message), - title: Text(context.translations.deleteMessageLabel), - leading: const StreamSvgIcon(icon: StreamSvgIcons.delete), + StreamContextMenuAction.destructive( + value: HardDeleteMessage(message: message), + label: Text(context.translations.deleteMessageLabel), + leading: Icon(icons.trashBin), ), ]; } @@ -51,40 +55,40 @@ class StreamMessageActionsBuilder { /// Returns a list of message actions based on the provided message and /// channel capabilities. /// - /// This method builds a list of [StreamMessageAction]s that are applicable to + /// This method builds a list of [StreamContextMenuAction]s that are + /// applicable to /// the given [message] in the [channel], considering the permissions of the /// [currentUser] and the current state of the message. - static List buildActions({ + static List> buildActions({ required BuildContext context, required Message message, required Channel channel, OwnUser? currentUser, - Iterable? customActions, }) { final messageState = message.state; // If the message is deleted, we don't show any actions. if (messageState.isDeleted) return []; + final icons = context.streamIcons; + if (messageState.isFailed) { return [ if (messageState.isSendingFailed || messageState.isUpdatingFailed) ...[ - StreamMessageAction( - action: ResendMessage(message: message), - leading: const StreamSvgIcon(icon: StreamSvgIcons.circleUp), - iconColor: StreamChatTheme.of(context).colorTheme.accentPrimary, - title: Text( + StreamContextMenuAction( + value: ResendMessage(message: message), + leading: Icon(icons.paperPlaneTopRight), + label: Text( context.translations.toggleResendOrResendEditedMessage( isUpdateFailed: messageState.isUpdatingFailed, ), ), ), if (messageState.isSendingFailed) - StreamMessageAction( - isDestructive: true, - action: HardDeleteMessage(message: message), - leading: const StreamSvgIcon(icon: StreamSvgIcons.delete), - title: Text( + StreamContextMenuAction.destructive( + value: HardDeleteMessage(message: message), + leading: Icon(icons.trashBin), + label: Text( context.translations.toggleDeleteRetryDeleteMessageText( isDeleteFailed: false, ), @@ -92,11 +96,10 @@ class StreamMessageActionsBuilder { ), ], if (message.state.isDeletingFailed) - StreamMessageAction( - isDestructive: true, - action: ResendMessage(message: message), - leading: const StreamSvgIcon(icon: StreamSvgIcons.delete), - title: Text( + StreamContextMenuAction.destructive( + value: ResendMessage(message: message), + leading: Icon(icons.trashBin), + label: Text( context.translations.toggleDeleteRetryDeleteMessageText( isDeleteFailed: true, ), @@ -123,34 +126,34 @@ class StreamMessageActionsBuilder { (attachment) => attachment.type == AttachmentType.giphy, ); - final messageActions = []; + final messageActions = >[]; if (canQuoteMessage) { messageActions.add( - StreamMessageAction( - action: QuotedReply(message: message), - title: Text(context.translations.replyLabel), - leading: const StreamSvgIcon(icon: StreamSvgIcons.reply), + StreamContextMenuAction( + value: QuotedReply(message: message), + label: Text(context.translations.replyLabel), + leading: Icon(icons.arrowShareLeft), ), ); } if (canSendReply && !isThreadMessage) { messageActions.add( - StreamMessageAction( - action: ThreadReply(message: message), - title: Text(context.translations.threadReplyLabel), - leading: const StreamSvgIcon(icon: StreamSvgIcons.threadReply), + StreamContextMenuAction( + value: ThreadReply(message: message), + label: Text(context.translations.threadReplyLabel), + leading: Icon(icons.bubbleText6ChatMessage), ), ); } if (canReceiveReadEvents) { - StreamMessageAction markUnreadAction() { - return StreamMessageAction( - action: MarkUnread(message: message), - title: Text(context.translations.markAsUnreadLabel), - leading: const StreamSvgIcon(icon: StreamSvgIcons.messageUnread), + StreamContextMenuAction markUnreadAction() { + return StreamContextMenuAction( + value: MarkUnread(message: message), + label: Text(context.translations.markAsUnreadLabel), + leading: Icon(icons.bubbleWideNotificationChatMessage), ); } @@ -168,10 +171,10 @@ class StreamMessageActionsBuilder { if (message.text case final text? when text.isNotEmpty) { messageActions.add( - StreamMessageAction( - action: CopyMessage(message: message), - title: Text(context.translations.copyMessageLabel), - leading: const StreamSvgIcon(icon: StreamSvgIcons.copy), + StreamContextMenuAction( + value: CopyMessage(message: message), + label: Text(context.translations.copyMessageLabel), + leading: Icon(icons.squareBehindSquare2Copy), ), ); } @@ -179,10 +182,10 @@ class StreamMessageActionsBuilder { if (!containsPoll && !containsGiphy) { if (canUpdateAnyMessage || (canUpdateOwnMessage && isSentByCurrentUser)) { messageActions.add( - StreamMessageAction( - action: EditMessage(message: message), - title: Text(context.translations.editMessageLabel), - leading: const StreamSvgIcon(icon: StreamSvgIcons.edit), + StreamContextMenuAction( + value: EditMessage(message: message), + label: Text(context.translations.editMessageLabel), + leading: Icon(icons.editBig), ), ); } @@ -197,14 +200,14 @@ class StreamMessageActionsBuilder { final action = switch (isPinned) { true => UnpinMessage(message: message), - false => PinMessage(message: message) + false => PinMessage(message: message), }; messageActions.add( - StreamMessageAction( - action: action, - title: Text(label.call(pinned: isPinned)), - leading: const StreamSvgIcon(icon: StreamSvgIcons.pin), + StreamContextMenuAction( + value: action, + label: Text(label.call(pinned: isPinned)), + leading: Icon(icons.pin), ), ); } @@ -213,27 +216,25 @@ class StreamMessageActionsBuilder { final label = context.translations.toggleDeleteRetryDeleteMessageText; messageActions.add( - StreamMessageAction( - isDestructive: true, - action: DeleteMessage(message: message), - leading: const StreamSvgIcon(icon: StreamSvgIcons.delete), - title: Text(label.call(isDeleteFailed: false)), + StreamContextMenuAction.destructive( + value: DeleteMessage(message: message), + leading: Icon(icons.trashBin), + label: Text(label.call(isDeleteFailed: false)), ), ); } if (!isSentByCurrentUser) { messageActions.add( - StreamMessageAction( - action: FlagMessage(message: message), - title: Text(context.translations.flagMessageLabel), - leading: const StreamSvgIcon(icon: StreamSvgIcons.flag), + StreamContextMenuAction( + value: FlagMessage(message: message), + label: Text(context.translations.flagMessageLabel), + leading: Icon(icons.flag2), ), ); } - if (message.user case final messageUser? - when channel.config?.mutes == true && !isSentByCurrentUser) { + if (message.user case final messageUser? when channel.config?.mutes == true && !isSentByCurrentUser) { final mutedUsers = currentUser?.mutes.map((mute) => mute.target.id); final isMuted = mutedUsers?.contains(messageUser.id) ?? false; final label = context.translations.toggleMuteUnmuteUserText; @@ -244,17 +245,14 @@ class StreamMessageActionsBuilder { }; messageActions.add( - StreamMessageAction( - action: action, - title: Text(label.call(isMuted: isMuted)), - leading: const StreamSvgIcon(icon: StreamSvgIcons.mute), + StreamContextMenuAction( + value: action, + label: Text(label.call(isMuted: isMuted)), + leading: Icon(icons.mute), ), ); } - // Add all the remaining custom actions if provided. - if (customActions case final actions?) messageActions.addAll(actions); - return messageActions; } } diff --git a/packages/stream_chat_flutter/lib/src/message_input/attachment_button.dart b/packages/stream_chat_flutter/lib/src/message_input/attachment_button.dart index e77465572c..5bdb6bc9e7 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/attachment_button.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/attachment_button.dart @@ -53,7 +53,7 @@ class AttachmentButton extends StatelessWidget { color: color, iconSize: size, onPressed: onPressed, - icon: icon ?? const StreamSvgIcon(icon: StreamSvgIcons.attach), + icon: icon ?? Icon(context.streamIcons.paperclip1), ); } } diff --git a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_file_picker.dart b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_file_picker.dart index 396af5ace3..69a0a3b60a 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_file_picker.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_file_picker.dart @@ -2,11 +2,10 @@ import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:photo_manager/photo_manager.dart'; import 'package:stream_chat_flutter/src/attachment/handler/stream_attachment_handler.dart'; -import 'package:stream_chat_flutter/src/icons/stream_svg_icon.dart'; import 'package:stream_chat_flutter/src/message_input/attachment_picker/stream_attachment_picker.dart'; -import 'package:stream_chat_flutter/src/theme/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/utils/utils.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; /// Widget used to pick files from the device class StreamFilePicker extends StatelessWidget { @@ -57,56 +56,79 @@ class StreamFilePicker extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = StreamChatTheme.of(context); + final spacing = context.streamSpacing; + final textTheme = context.streamTextTheme; + final colorScheme = context.streamColorScheme; + + Future onPickFile() async { + final pickedFile = await runInPermissionRequestLock(() { + return StreamAttachmentHandler.instance.pickFile( + dialogTitle: dialogTitle, + initialDirectory: initialDirectory, + type: type, + allowedExtensions: allowedExtensions, + onFileLoading: onFileLoading, + compressionQuality: compressionQuality, + withData: withData, + withReadStream: withReadStream, + lockParentWindow: lockParentWindow, + ); + }); + + return onFilePicked.call(pickedFile); + } + return OptionDrawer( child: EndOfFrameCallbackWidget( - child: StreamSvgIcon( - size: 240, - icon: StreamSvgIcons.files, - color: theme.colorTheme.disabled, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + size: 32, + context.streamIcons.fileBend, + color: colorScheme.textTertiary, + ), + SizedBox(height: spacing.xs), + Text( + 'Select files to share', + style: textTheme.bodyDefault.copyWith( + color: colorScheme.textSecondary, + ), + textAlign: TextAlign.center, + ), + SizedBox(height: spacing.md), + StreamButton( + type: .outline, + style: .secondary, + onTap: onPickFile, + label: 'Open files', + ), + ], ), - onEndOfFrame: (_) async { - final pickedFile = await runInPermissionRequestLock(() { - return StreamAttachmentHandler.instance.pickFile( - dialogTitle: dialogTitle, - initialDirectory: initialDirectory, - type: type, - allowedExtensions: allowedExtensions, - onFileLoading: onFileLoading, - compressionQuality: compressionQuality, - withData: withData, - withReadStream: withReadStream, - lockParentWindow: lockParentWindow, - ); - }); - - onFilePicked.call(pickedFile); - }, + onEndOfFrame: (_) => onPickFile(), errorBuilder: (context, error, stacktrace) { return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - StreamSvgIcon( - size: 240, - icon: StreamSvgIcons.files, - color: theme.colorTheme.disabled, + Icon( + size: 32, + context.streamIcons.fileBend, + color: colorScheme.textTertiary, ), + SizedBox(height: spacing.xs), Text( context.translations.enablePhotoAndVideoAccessMessage, - style: theme.textTheme.body.copyWith( - color: theme.colorTheme.textLowEmphasis, + style: textTheme.bodyDefault.copyWith( + color: colorScheme.textSecondary, ), textAlign: TextAlign.center, ), - const SizedBox(height: 8), - TextButton( - onPressed: PhotoManager.openSetting, - child: Text( - context.translations.allowGalleryAccessMessage, - style: theme.textTheme.bodyBold.copyWith( - color: theme.colorTheme.accentPrimary, - ), - ), + SizedBox(height: spacing.md), + StreamButton( + type: .outline, + style: .secondary, + onTap: PhotoManager.openSetting, + label: context.translations.allowGalleryAccessMessage, ), ], ); diff --git a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_gallery_picker.dart b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_gallery_picker.dart index c2914d2007..b5a4e3cd48 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_gallery_picker.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_gallery_picker.dart @@ -4,15 +4,14 @@ import 'dart:math' as math; import 'package:flutter/material.dart'; import 'package:path_provider/path_provider.dart'; import 'package:photo_manager/photo_manager.dart'; -import 'package:stream_chat_flutter/src/icons/stream_svg_icon.dart'; import 'package:stream_chat_flutter/src/message_input/attachment_picker/stream_attachment_picker.dart'; import 'package:stream_chat_flutter/src/message_input/attachment_picker/stream_attachment_picker_controller.dart'; import 'package:stream_chat_flutter/src/misc/empty_widget.dart'; import 'package:stream_chat_flutter/src/scroll_view/photo_gallery/stream_photo_gallery.dart'; import 'package:stream_chat_flutter/src/scroll_view/photo_gallery/stream_photo_gallery_controller.dart'; -import 'package:stream_chat_flutter/src/theme/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/utils/utils.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; /// Max image resolution which can be resized by the CDN. /// Taken from https://getstream.io/chat/docs/flutter-dart/file_uploads/?language=dart#image-resizing @@ -80,9 +79,9 @@ class _StreamGalleryPickerState extends State { builder: (context, snapshot) { if (!snapshot.hasData) return const Empty(); - final theme = StreamChatTheme.of(context); - final textTheme = theme.textTheme; - final colorTheme = theme.colorTheme; + final spacing = context.streamSpacing; + final textTheme = context.streamTextTheme; + final colorScheme = context.streamColorScheme; // Available on both Android and iOS. final isAuthorized = snapshot.data == PermissionState.authorized; @@ -92,44 +91,32 @@ class _StreamGalleryPickerState extends State { final isPermissionGranted = isAuthorized || isLimited; return OptionDrawer( - actions: [ - if (isLimited) - IconButton( - color: colorTheme.accentPrimary, - icon: const Icon(Icons.add_circle_outline_rounded), - onPressed: () async { - await PhotoManager.presentLimited(); - _controller.doInitialLoad(); - }, - ), - ], + margin: .zero, child: Builder( builder: (context) { if (!isPermissionGranted) { return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - StreamSvgIcon( - size: 240, - icon: StreamSvgIcons.pictures, - color: colorTheme.disabled, + Icon( + size: 32, + context.streamIcons.images1Alt, + color: colorScheme.textTertiary, ), + SizedBox(height: spacing.xs), Text( context.translations.enablePhotoAndVideoAccessMessage, - style: textTheme.body.copyWith( - color: colorTheme.textLowEmphasis, + style: textTheme.bodyDefault.copyWith( + color: colorScheme.textSecondary, ), textAlign: TextAlign.center, ), - const SizedBox(height: 8), - TextButton( - onPressed: PhotoManager.openSetting, - child: Text( - context.translations.allowGalleryAccessMessage, - style: textTheme.bodyBold.copyWith( - color: colorTheme.accentPrimary, - ), - ), + SizedBox(height: spacing.md), + StreamButton( + type: .outline, + style: .secondary, + onTap: PhotoManager.openSetting, + label: context.translations.allowGalleryAccessMessage, ), ], ); @@ -145,6 +132,14 @@ class _StreamGalleryPickerState extends State { thumbnailFormat: widget.config.mediaThumbnailFormat, thumbnailQuality: widget.config.mediaThumbnailQuality, thumbnailScale: widget.config.mediaThumbnailScale, + addMoreBuilder: isLimited + ? (context) => _AddMoreTile( + onTap: () async { + await PhotoManager.presentLimited(); + _controller.doInitialLoad(); + }, + ) + : null, itemBuilder: (context, mediaItems, index, defaultWidget) { final media = mediaItems[index]; return defaultWidget.copyWith( @@ -160,6 +155,50 @@ class _StreamGalleryPickerState extends State { } } +class _AddMoreTile extends StatelessWidget { + const _AddMoreTile({required this.onTap}); + + final VoidCallback onTap; + + @override + Widget build(BuildContext context) { + final colorScheme = context.streamColorScheme; + final textTheme = context.streamTextTheme; + final spacing = context.streamSpacing; + + return Material( + color: colorScheme.backgroundSurfaceCard, + child: InkWell( + onTap: onTap, + overlayColor: WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.pressed)) return colorScheme.statePressed; + if (states.contains(WidgetState.hovered)) return colorScheme.stateHover; + if (states.contains(WidgetState.focused)) return colorScheme.stateFocused; + return null; + }), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + context.streamIcons.plusLarge, + size: 20, + color: colorScheme.textTertiary, + ), + SizedBox(height: spacing.xs), + Text( + context.translations.addMoreFilesLabel, + style: textTheme.captionEmphasis.copyWith( + color: colorScheme.textTertiary, + ), + textAlign: TextAlign.center, + ), + ], + ), + ), + ); + } +} + /// Configuration for the [StreamGalleryPicker]. class GalleryPickerConfig { /// Creates a [GalleryPickerConfig] instance. @@ -248,8 +287,7 @@ extension StreamImagePickerX on StreamAttachmentPickerController { final image = await asset.originFile; if (image != null) { final tempDir = await getTemporaryDirectory(); - final cachedFile = - File('${tempDir.path}/${image.path.split('/').last}'); + final cachedFile = File('${tempDir.path}/${image.path.split('/').last}'); if (cachedFile.existsSync()) { cachedFile.deleteSync(); } diff --git a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_image_picker.dart b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_image_picker.dart index a9130ea73b..35968f2a3b 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_image_picker.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_image_picker.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:image_picker/image_picker.dart'; import 'package:photo_manager/photo_manager.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; /// Widget used to pick images from the device. class StreamImagePicker extends StatelessWidget { @@ -36,52 +37,71 @@ class StreamImagePicker extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = StreamChatTheme.of(context); + final spacing = context.streamSpacing; + final textTheme = context.streamTextTheme; + final colorScheme = context.streamColorScheme; + + Future onPickImage() async { + final pickedImage = await runInPermissionRequestLock(() { + return StreamAttachmentHandler.instance.pickImage( + source: source, + maxWidth: maxWidth, + maxHeight: maxHeight, + imageQuality: imageQuality, + preferredCameraDevice: preferredCameraDevice, + ); + }); + + return onImagePicked.call(pickedImage); + } + return OptionDrawer( child: EndOfFrameCallbackWidget( - child: StreamSvgIcon( - size: 240, - icon: StreamSvgIcons.camera, - color: theme.colorTheme.disabled, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + size: 32, + context.streamIcons.camera1, + color: colorScheme.textTertiary, + ), + SizedBox(height: spacing.xs), + Text( + 'Take a photo and share', + style: textTheme.bodyDefault.copyWith(color: colorScheme.textSecondary), + textAlign: TextAlign.center, + ), + SizedBox(height: spacing.md), + StreamButton( + type: .outline, + style: .secondary, + onTap: onPickImage, + label: 'Open camera', + ), + ], ), - onEndOfFrame: (_) async { - final pickedImage = await runInPermissionRequestLock(() { - return StreamAttachmentHandler.instance.pickImage( - source: source, - maxWidth: maxWidth, - maxHeight: maxHeight, - imageQuality: imageQuality, - preferredCameraDevice: preferredCameraDevice, - ); - }); - - onImagePicked.call(pickedImage); - }, + onEndOfFrame: (_) => onPickImage(), errorBuilder: (context, error, stacktrace) { return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - StreamSvgIcon( - size: 240, - icon: StreamSvgIcons.camera, - color: theme.colorTheme.disabled, + Icon( + size: 32, + context.streamIcons.camera1, + color: colorScheme.textTertiary, ), + SizedBox(height: spacing.xs), Text( context.translations.enablePhotoAndVideoAccessMessage, - style: theme.textTheme.body.copyWith( - color: theme.colorTheme.textLowEmphasis, - ), + style: textTheme.bodyDefault.copyWith(color: colorScheme.textSecondary), textAlign: TextAlign.center, ), - const SizedBox(height: 8), - TextButton( - onPressed: PhotoManager.openSetting, - child: Text( - context.translations.allowGalleryAccessMessage, - style: theme.textTheme.bodyBold.copyWith( - color: theme.colorTheme.accentPrimary, - ), - ), + SizedBox(height: spacing.md), + StreamButton( + type: .outline, + style: .secondary, + onTap: PhotoManager.openSetting, + label: context.translations.allowGalleryAccessMessage, ), ], ); diff --git a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_poll_creator.dart b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_poll_creator.dart index ecf95fe96d..4f1703e673 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_poll_creator.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_poll_creator.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; /// Widget used to create a poll. class StreamPollCreator extends StatelessWidget { @@ -22,7 +23,9 @@ class StreamPollCreator extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = StreamChatTheme.of(context); + final spacing = context.streamSpacing; + final textTheme = context.streamTextTheme; + final colorScheme = context.streamColorScheme; Future _openCreatePollFlow() async { final result = await showStreamPollCreatorDialog( @@ -31,35 +34,50 @@ class StreamPollCreator extends StatelessWidget { config: config, ); - onPollCreated?.call(result); + return onPollCreated?.call(result); } return OptionDrawer( child: EndOfFrameCallbackWidget( - child: StreamSvgIcon( - size: 180, - icon: StreamSvgIcons.polls, - color: theme.colorTheme.disabled, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + size: 32, + context.streamIcons.chart5, + color: colorScheme.textTertiary, + ), + SizedBox(height: spacing.xs), + Text( + 'Create a poll and let everyone vote!', + style: textTheme.bodyDefault.copyWith(color: colorScheme.textSecondary), + textAlign: TextAlign.center, + ), + SizedBox(height: spacing.md), + StreamButton( + type: .outline, + style: .secondary, + onTap: _openCreatePollFlow, + label: context.translations.createPollLabel(), + ), + ], ), onEndOfFrame: (_) => _openCreatePollFlow(), errorBuilder: (context, error, stacktrace) { return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - StreamSvgIcon( - size: 240, - icon: StreamSvgIcons.polls, - color: theme.colorTheme.disabled, + Icon( + size: 32, + context.streamIcons.chart5, + color: colorScheme.textTertiary, ), - const SizedBox(height: 8), - TextButton( - onPressed: _openCreatePollFlow, - child: Text( - context.translations.createPollLabel(isNew: true), - style: theme.textTheme.bodyBold.copyWith( - color: theme.colorTheme.accentPrimary, - ), - ), + SizedBox(height: spacing.md), + StreamButton( + type: .outline, + style: .secondary, + onTap: _openCreatePollFlow, + label: context.translations.createPollLabel(isNew: true), ), ], ); diff --git a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_video_picker.dart b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_video_picker.dart index 4ba4e8f7b6..ee67cadcce 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_video_picker.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_video_picker.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:image_picker/image_picker.dart'; import 'package:photo_manager/photo_manager.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; /// Widget used to capture video using the device camera. class StreamVideoPicker extends StatelessWidget { @@ -28,50 +29,71 @@ class StreamVideoPicker extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = StreamChatTheme.of(context); + final spacing = context.streamSpacing; + final textTheme = context.streamTextTheme; + final colorScheme = context.streamColorScheme; + + Future onPickVideo() async { + final pickedVideo = await runInPermissionRequestLock(() { + return StreamAttachmentHandler.instance.pickVideo( + source: source, + preferredCameraDevice: preferredCameraDevice, + maxDuration: maxDuration, + ); + }); + + return onVideoPicked.call(pickedVideo); + } + return OptionDrawer( child: EndOfFrameCallbackWidget( - child: StreamSvgIcon( - size: 240, - icon: StreamSvgIcons.record, - color: theme.colorTheme.disabled, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + size: 32, + context.streamIcons.video, + color: colorScheme.textTertiary, + ), + SizedBox(height: spacing.xs), + Text( + 'Take a video and share', + style: textTheme.bodyDefault.copyWith(color: colorScheme.textSecondary), + textAlign: TextAlign.center, + ), + SizedBox(height: spacing.md), + StreamButton( + type: .outline, + style: .secondary, + onTap: onPickVideo, + label: 'Open camera', + ), + ], ), - onEndOfFrame: (_) async { - final pickedVideo = await runInPermissionRequestLock(() { - return StreamAttachmentHandler.instance.pickVideo( - source: source, - preferredCameraDevice: preferredCameraDevice, - maxDuration: maxDuration, - ); - }); - - onVideoPicked.call(pickedVideo); - }, + onEndOfFrame: (_) => onPickVideo(), errorBuilder: (context, error, stacktrace) { return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - StreamSvgIcon( - size: 240, - icon: StreamSvgIcons.record, - color: theme.colorTheme.disabled, + Icon( + size: 32, + context.streamIcons.video, + color: colorScheme.textTertiary, ), + SizedBox(height: spacing.xs), Text( context.translations.enablePhotoAndVideoAccessMessage, - style: theme.textTheme.body.copyWith( - color: theme.colorTheme.textLowEmphasis, + style: textTheme.bodyDefault.copyWith( + color: colorScheme.textSecondary, ), textAlign: TextAlign.center, ), - const SizedBox(height: 8), - TextButton( - onPressed: PhotoManager.openSetting, - child: Text( - context.translations.allowGalleryAccessMessage, - style: theme.textTheme.bodyBold.copyWith( - color: theme.colorTheme.accentPrimary, - ), - ), + SizedBox(height: spacing.md), + StreamButton( + type: .outline, + style: .secondary, + onTap: PhotoManager.openSetting, + label: context.translations.allowGalleryAccessMessage, ), ], ); diff --git a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker.dart b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker.dart index 4c742b7a70..365fe48296 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker.dart @@ -5,13 +5,15 @@ import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/src/message_input/attachment_picker/options/options.dart'; import 'package:stream_chat_flutter/src/misc/empty_widget.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; -/// Bottom sheet widget for the system attachment picker interface. -/// This is used when the attachment picker uses system integration, -/// typically on web/desktop or when useSystemAttachmentPicker is true. -class StreamSystemAttachmentPickerBottomSheet extends StatelessWidget { - /// Creates a new instance of [StreamSystemAttachmentPickerBottomSheet]. - const StreamSystemAttachmentPickerBottomSheet({ +/// Inline widget for the system attachment picker interface. +/// +/// Shows a list of options that launch native platform dialogs. +/// Selections are applied in real-time via the [controller]. +class StreamSystemAttachmentPicker extends StatelessWidget { + /// Creates a new instance of [StreamSystemAttachmentPicker]. + const StreamSystemAttachmentPicker({ super.key, required this.options, required this.controller, @@ -40,7 +42,7 @@ class StreamSystemAttachmentPickerBottomSheet extends StatelessWidget { return ListTile( enabled: isEnabled, - leading: option.icon, + leading: Icon(option.icon), title: Text(option.title), onTap: () => option.onTap(context, controller), ); @@ -53,17 +55,21 @@ class StreamSystemAttachmentPickerBottomSheet extends StatelessWidget { } } -/// Bottom sheet widget for the tabbed attachment picker interface. -/// This is used when the attachment picker displays a tabbed interface, -/// typically on mobile when useSystemAttachmentPicker is false. -class StreamTabbedAttachmentPickerBottomSheet extends StatefulWidget { - /// Creates a new instance of [StreamTabbedAttachmentPickerBottomSheet]. - const StreamTabbedAttachmentPickerBottomSheet({ +/// Inline widget for the tabbed attachment picker interface. +/// +/// Displays a tabbed interface with horizontal tabs for different attachment +/// types (gallery, camera, files, etc.). Each tab shows a specialized +/// interface for selecting that type of attachment. +/// +/// Selections are applied in real-time via the [controller] rather than +/// through a modal result pattern. +class StreamTabbedAttachmentPicker extends StatefulWidget { + /// Creates a new instance of [StreamTabbedAttachmentPicker]. + const StreamTabbedAttachmentPicker({ super.key, required this.options, required this.controller, this.initialOption, - this.onSendValue, }); /// The list of options. @@ -75,17 +81,11 @@ class StreamTabbedAttachmentPickerBottomSheet extends StatefulWidget { /// The controller of the attachment picker. final StreamAttachmentPickerController controller; - /// The callback when the send button gets tapped. - final ValueSetter? onSendValue; - @override - State createState() => - _StreamTabbedAttachmentPickerBottomSheetState(); + State createState() => _StreamTabbedAttachmentPickerState(); } -class _StreamTabbedAttachmentPickerBottomSheetState - extends State { - // The current option selected in the tabbed attachment picker. +class _StreamTabbedAttachmentPickerState extends State { late var _currentOption = _calculateInitialOption(); TabbedAttachmentPickerOption _calculateInitialOption() { if (widget.initialOption case final option?) return option; @@ -107,13 +107,13 @@ class _StreamTabbedAttachmentPickerBottomSheetState valueListenable: widget.controller, builder: (context, value, _) { return Column( + crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ _TabbedAttachmentPickerOptions( controller: widget.controller, options: widget.options, currentOption: _currentOption, - onSendValue: widget.onSendValue, onOptionSelected: (option) async { setState(() => _currentOption = option); }, @@ -137,84 +137,51 @@ class _TabbedAttachmentPickerOptions extends StatelessWidget { required this.currentOption, required this.controller, this.onOptionSelected, - this.onSendValue, }); final Iterable options; final TabbedAttachmentPickerOption currentOption; final StreamAttachmentPickerController controller; final ValueSetter? onOptionSelected; - final ValueSetter? onSendValue; @override Widget build(BuildContext context) { - final colorTheme = StreamChatTheme.of(context).colorTheme; + final spacing = context.streamSpacing; + return ValueListenableBuilder( valueListenable: controller, builder: (context, value, __) { final enabledTypes = value.filterEnabledTypes(options: options); - return Row( - children: [ - Expanded( - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: Row( - children: [ - ...options.map( - (option) { - final supported = option.supportedTypes; - final isEnabled = enabledTypes.any(supported.contains); - final isSelected = option == currentOption; - - final color = switch (isSelected) { - true => colorTheme.accentPrimary, - _ => colorTheme.textLowEmphasis, - }; - - final onPressed = switch (isEnabled) { - true => () => onOptionSelected?.call(option), - _ => null, - }; - - return IconButton( - color: color, - disabledColor: colorTheme.disabled, - icon: option.icon, - onPressed: onPressed, - ); - }, - ), - ], - ), + return SingleChildScrollView( + scrollDirection: Axis.horizontal, + padding: EdgeInsets.symmetric(horizontal: spacing.md, vertical: spacing.sm), + child: Row( + spacing: spacing.xxxs, + children: [ + ...options.map( + (option) { + final supported = option.supportedTypes; + final isEnabled = enabledTypes.any(supported.contains); + final isSelected = option == currentOption; + + final onPressed = switch (isEnabled) { + true => () => onOptionSelected?.call(option), + _ => null, + }; + + return StreamButton.icon( + style: StreamButtonStyle.secondary, + type: StreamButtonType.ghost, + size: StreamButtonSize.large, + icon: option.icon, + onTap: onPressed, + isSelected: isSelected, + ); + }, ), - ), - Builder( - builder: (context) { - final initialValue = controller.initialValue; - final isValueChanged = value != initialValue; - - final onPressed = switch (onSendValue) { - final onSendValue? when isValueChanged => () { - final result = AttachmentsPicked( - attachments: value.attachments, - ); - return onSendValue(result); - }, - _ => null, - }; - - return IconButton( - color: colorTheme.accentPrimary, - disabledColor: colorTheme.disabled, - icon: const StreamSvgIcon( - icon: StreamSvgIcons.emptyCircleRight, - ), - onPressed: onPressed, - ); - }, - ), - ], + ], + ), ); }, ); @@ -223,11 +190,12 @@ class _TabbedAttachmentPickerOptions extends StatelessWidget { /// Signature used by [EndOfFrameCallbackWidget.errorBuilder] to create a /// replacement widget to render. -typedef EndOfFrameCallbackErrorWidgetBuilder = Widget Function( - BuildContext context, - Object error, - StackTrace? stackTrace, -); +typedef EndOfFrameCallbackErrorWidgetBuilder = + Widget Function( + BuildContext context, + Object error, + StackTrace? stackTrace, + ); /// Function signature for a callback that is called when the end of the frame /// is reached. @@ -247,7 +215,7 @@ class EndOfFrameCallbackWidget extends StatefulWidget { /// The widget below this widget in the tree. final Widget? child; - /// The callback that is called when the end of the frame is reached.x + /// The callback that is called when the end of the frame is reached. final EndOfFrameCallback onEndOfFrame; /// The callback that will be called if the [onEndOfFrame] callback throws an @@ -255,8 +223,7 @@ class EndOfFrameCallbackWidget extends StatefulWidget { final EndOfFrameCallbackErrorWidgetBuilder? errorBuilder; @override - State createState() => - _EndOfFrameCallbackWidgetState(); + State createState() => _EndOfFrameCallbackWidgetState(); } class _EndOfFrameCallbackWidgetState extends State { @@ -293,8 +260,6 @@ class _EndOfFrameCallbackWidgetState extends State { return const Text('An error occurred'); } - // Reset the error and stack trace so that we don't keep showing the same - // error over and over. _error = null; _stackTrace = null; @@ -302,13 +267,6 @@ class _EndOfFrameCallbackWidgetState extends State { } } -const _kDefaultOptionDrawerShape = RoundedRectangleBorder( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(16), - topRight: Radius.circular(16), - ), -); - /// A widget that will be shown in the attachment picker. /// It can be used to show a custom view for each attachment picker option. class OptionDrawer extends StatelessWidget { @@ -316,98 +274,23 @@ class OptionDrawer extends StatelessWidget { const OptionDrawer({ super.key, required this.child, - this.color, - this.elevation = 2, - this.margin = EdgeInsets.zero, - this.clipBehavior = Clip.hardEdge, - this.shape = _kDefaultOptionDrawerShape, - this.title, - this.actions = const [], + this.margin, }); /// The widget below this widget in the tree. final Widget child; - /// The background color of the options card. - /// - /// Defaults to [StreamColorTheme.barsBg]. - final Color? color; - - /// The elevation of the options card. - /// - /// The default value is 2. - final double elevation; - /// The margin of the options card. - /// - /// The default value is [EdgeInsets.zero]. - final EdgeInsetsGeometry margin; - - /// The clip behavior of the options card. - /// - /// The default value is [Clip.hardEdge]. - final Clip clipBehavior; - - /// The shape of the options card. - final ShapeBorder shape; - - /// The title of the options card. - final Widget? title; - - /// The actions available for the options card. - final List actions; + final EdgeInsetsGeometry? margin; @override Widget build(BuildContext context) { - final colorTheme = StreamChatTheme.of(context).colorTheme; - - var height = 20.0; - if (title != null || actions.isNotEmpty) { - height = 40.0; - } + final spacing = context.streamSpacing; + final effectiveMargin = margin ?? .symmetric(horizontal: spacing.md, vertical: spacing.xxxl); - final leading = title ?? const Empty(); - - Widget trailing; - if (actions.isNotEmpty) { - trailing = Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.end, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: actions, - ); - } else { - trailing = const Empty(); - } - - return Card( - elevation: elevation, - color: color ?? colorTheme.barsBg, - margin: margin, - shape: shape, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - SizedBox( - height: height, - child: Row( - children: [ - Expanded(child: leading), - Container( - height: 4, - width: 40, - decoration: BoxDecoration( - color: colorTheme.disabled, - borderRadius: BorderRadius.circular(6), - ), - ), - Expanded(child: trailing), - ], - ), - ), - Expanded(child: child), - ], - ), + return Container( + margin: effectiveMargin, + child: child, ); } } @@ -420,9 +303,13 @@ class OptionDrawer extends StatelessWidget { /// attachment. Tabs get enabled or disabled based on what you've already /// selected. /// -/// This is the default interface for mobile platforms. Configure with -/// [customOptions], [galleryPickerConfig], [pollConfig], and -/// [allowedTypes]. +/// Selections are applied in real-time via the [controller]. +/// +/// The [onError] callback is invoked when an error occurs during attachment +/// selection (e.g., file too large or attachment limit reached). +/// +/// The [onPollCreated] callback is invoked when a poll is created, allowing +/// the caller to handle poll-specific logic. Widget tabbedAttachmentPickerBuilder({ required BuildContext context, required StreamAttachmentPickerController controller, @@ -430,34 +317,17 @@ Widget tabbedAttachmentPickerBuilder({ GalleryPickerConfig? galleryPickerConfig, List allowedTypes = AttachmentPickerType.values, AttachmentPickerOptionsBuilder? optionsBuilder, + ValueSetter? onError, + ValueSetter? onPollCreated, }) { - Future _handleSingePick( - StreamAttachmentPickerController controller, - Attachment? attachment, - ) async { - try { - if (attachment != null) await controller.addAttachment(attachment); - return AttachmentsPicked(attachments: controller.value.attachments); - } catch (error, stk) { - return AttachmentPickerError(error: error, stackTrace: stk); - } - } - final defaultOptions = [ TabbedAttachmentPickerOption( key: 'gallery-picker', - icon: const StreamSvgIcon(icon: StreamSvgIcons.pictures), + icon: context.streamIcons.images1Alt, supportedTypes: [ AttachmentPickerType.images, AttachmentPickerType.videos, ], - isEnabled: (value) { - // Enable if nothing has been selected yet. - if (value.isEmpty) return true; - - // Otherwise, enable only if there is at least a image or a video. - return value.attachments.any((it) => it.isImage || it.isVideo); - }, optionViewBuilder: (context, controller) { final attachment = controller.value.attachments; final selectedIds = attachment.map((it) => it.id); @@ -471,8 +341,9 @@ Widget tabbedAttachmentPickerBuilder({ } return await controller.addAssetAttachment(media); } catch (e, stk) { - final err = AttachmentPickerError(error: e, stackTrace: stk); - return Navigator.pop(context, err); + onError?.call( + AttachmentPickerError(error: e, stackTrace: stk), + ); } }, ); @@ -480,80 +351,65 @@ Widget tabbedAttachmentPickerBuilder({ ), TabbedAttachmentPickerOption( key: 'file-picker', - icon: const StreamSvgIcon(icon: StreamSvgIcons.files), + icon: context.streamIcons.fileBend, supportedTypes: [AttachmentPickerType.files], - isEnabled: (value) { - // Enable if nothing has been selected yet. - if (value.isEmpty) return true; - - // Otherwise, enable only if there is at least a file. - return value.attachments.any((it) => it.isFile); - }, optionViewBuilder: (context, controller) => StreamFilePicker( onFilePicked: (file) async { - final result = await _handleSingePick(controller, file); - return Navigator.pop(context, result); + try { + if (file != null) await controller.addAttachment(file); + } catch (e, stk) { + onError?.call( + AttachmentPickerError(error: e, stackTrace: stk), + ); + } }, ), ), TabbedAttachmentPickerOption( key: 'image-picker', - icon: const StreamSvgIcon(icon: StreamSvgIcons.camera), + icon: context.streamIcons.camera1, supportedTypes: [AttachmentPickerType.images], - isEnabled: (value) { - // Enable if nothing has been selected yet. - if (value.isEmpty) return true; - - // Otherwise, enable only if there is at least a image. - return value.attachments.any((it) => it.isImage); - }, optionViewBuilder: (context, controller) => StreamImagePicker( onImagePicked: (image) async { - final result = await _handleSingePick(controller, image); - return Navigator.pop(context, result); + try { + if (image != null) await controller.addAttachment(image); + } catch (e, stk) { + onError?.call( + AttachmentPickerError(error: e, stackTrace: stk), + ); + } }, ), ), TabbedAttachmentPickerOption( key: 'video-picker', - icon: const StreamSvgIcon(icon: StreamSvgIcons.record), + icon: context.streamIcons.video, supportedTypes: [AttachmentPickerType.videos], - isEnabled: (value) { - // Enable if nothing has been selected yet. - if (value.isEmpty) return true; - - // Otherwise, enable only if there is at least a video. - return value.attachments.any((it) => it.isVideo); - }, optionViewBuilder: (context, controller) => StreamVideoPicker( onVideoPicked: (video) async { - final result = await _handleSingePick(controller, video); - return Navigator.pop(context, result); + try { + if (video != null) await controller.addAttachment(video); + } catch (e, stk) { + onError?.call( + AttachmentPickerError(error: e, stackTrace: stk), + ); + } }, ), ), TabbedAttachmentPickerOption( key: 'poll-creator', - icon: const StreamSvgIcon(icon: StreamSvgIcons.polls), + icon: context.streamIcons.chart5, supportedTypes: [AttachmentPickerType.poll], - isEnabled: (value) { - // Enable if nothing has been selected yet. - if (value.isEmpty) return true; - - // Otherwise, enable only if there is a poll. - return value.poll != null; - }, optionViewBuilder: (context, controller) { final initialPoll = controller.value.poll; return StreamPollCreator( poll: initialPoll, config: pollConfig, onPollCreated: (poll) { - if (poll == null) return Navigator.pop(context); + if (poll == null) return; controller.poll = poll; - - final result = PollCreated(poll: poll); - return Navigator.pop(context, result); + onPollCreated?.call(poll); }, ); }, @@ -574,9 +430,8 @@ Widget tabbedAttachmentPickerBuilder({ ); } - return StreamTabbedAttachmentPickerBottomSheet( + return StreamTabbedAttachmentPicker( controller: controller, - onSendValue: Navigator.of(context).pop, options: { ...validOptions.where( (option) => option.supportedTypes.every(allowedTypes.contains), @@ -591,9 +446,12 @@ Widget tabbedAttachmentPickerBuilder({ /// built-in file browser, camera app, or other native tools instead of /// custom interfaces. /// -/// This is the default for web and desktop platforms, and can be enabled on -/// mobile with `useSystemAttachmentPicker`. Configure with [customOptions], -/// [pollConfig], and [allowedTypes]. +/// Selections are applied in real-time via the [controller]. +/// +/// The [onError] callback is invoked when an error occurs during attachment +/// selection. +/// +/// The [onPollCreated] callback is invoked when a poll is created. Widget systemAttachmentPickerBuilder({ required BuildContext context, required StreamAttachmentPickerController controller, @@ -601,18 +459,18 @@ Widget systemAttachmentPickerBuilder({ GalleryPickerConfig? galleryPickerConfig = const GalleryPickerConfig(), List allowedTypes = AttachmentPickerType.values, AttachmentPickerOptionsBuilder? optionsBuilder, + ValueSetter? onError, + ValueSetter? onPollCreated, }) { - Future _pickSystemFile( + Future pickSystemFile( StreamAttachmentPickerController controller, FileType type, ) async { try { final file = await StreamAttachmentHandler.instance.pickFile(type: type); if (file != null) await controller.addAttachment(file); - - return AttachmentsPicked(attachments: controller.value.attachments); - } catch (error, stk) { - return AttachmentPickerError(error: error, stackTrace: stk); + } catch (e, stk) { + onError?.call(AttachmentPickerError(error: e, stackTrace: stk)); } } @@ -620,37 +478,34 @@ Widget systemAttachmentPickerBuilder({ SystemAttachmentPickerOption( key: 'image-picker', supportedTypes: [AttachmentPickerType.images], - icon: const StreamSvgIcon(icon: StreamSvgIcons.pictures), + icon: context.streamIcons.images1Alt, title: context.translations.uploadAPhotoLabel, onTap: (context, controller) async { - final result = await _pickSystemFile(controller, FileType.image); - return Navigator.pop(context, result); + await pickSystemFile(controller, FileType.image); }, ), SystemAttachmentPickerOption( key: 'video-picker', supportedTypes: [AttachmentPickerType.videos], - icon: const StreamSvgIcon(icon: StreamSvgIcons.record), + icon: context.streamIcons.video, title: context.translations.uploadAVideoLabel, onTap: (context, controller) async { - final result = await _pickSystemFile(controller, FileType.video); - return Navigator.pop(context, result); + await pickSystemFile(controller, FileType.video); }, ), SystemAttachmentPickerOption( key: 'file-picker', supportedTypes: [AttachmentPickerType.files], - icon: const StreamSvgIcon(icon: StreamSvgIcons.files), + icon: context.streamIcons.fileBend, title: context.translations.uploadAFileLabel, onTap: (context, controller) async { - final result = await _pickSystemFile(controller, FileType.any); - return Navigator.pop(context, result); + await pickSystemFile(controller, FileType.any); }, ), SystemAttachmentPickerOption( key: 'poll-creator', supportedTypes: [AttachmentPickerType.poll], - icon: const StreamSvgIcon(icon: StreamSvgIcons.polls), + icon: context.streamIcons.chart5, title: context.translations.createPollLabel(isNew: true), onTap: (context, controller) async { final initialPoll = controller.value.poll; @@ -660,11 +515,9 @@ Widget systemAttachmentPickerBuilder({ config: pollConfig, ); - if (poll == null) return Navigator.pop(context); + if (poll == null) return; controller.poll = poll; - - final result = PollCreated(poll: poll); - return Navigator.pop(context, result); + onPollCreated?.call(poll); }, ), ]; @@ -683,7 +536,7 @@ Widget systemAttachmentPickerBuilder({ ); } - return StreamSystemAttachmentPickerBottomSheet( + return StreamSystemAttachmentPicker( controller: controller, options: { ...validOptions.where( diff --git a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker_bottom_sheet.dart b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker_bottom_sheet.dart index cbfbe318f0..4ae640eb6d 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker_bottom_sheet.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker_bottom_sheet.dart @@ -1,8 +1,5 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/src/message_input/attachment_picker/options/stream_gallery_picker.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:flutter/widgets.dart'; +import 'package:stream_chat_flutter/src/message_input/attachment_picker/stream_attachment_picker_option.dart'; /// {@template streamAttachmentPickerOptionsBuilder} /// Signature for a function that creates a list of [AttachmentPickerOption]s @@ -11,232 +8,5 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// The function receives the [BuildContext] and a list of [defaultOptions] /// that can be modified or extended. /// {@endtemplate} -typedef AttachmentPickerOptionsBuilder - = List Function(BuildContext context, List defaultOptions); - -/// Shows a modal bottom sheet with the Stream attachment picker. -/// -/// The picker supports two modes: -/// -/// - **Tabbed interface**: Typically used on mobile platforms. Provide -/// [TabbedAttachmentPickerOption] values in [customOptions]. This mode is -/// active when [useSystemAttachmentPicker] is false (default). -/// -/// - **System integration**: Used on web, desktop, or when -/// [useSystemAttachmentPicker] is true. Provide -/// [SystemAttachmentPickerOption] values in [customOptions]. -/// -/// When using the system picker, all [customOptions] must be -/// [SystemAttachmentPickerOption] instances. If any other type is included, -/// an [ArgumentError] is thrown. -/// -/// Example using the tabbed interface: -/// ```dart -/// showStreamAttachmentPickerModalBottomSheet( -/// context: context, -/// customOptions: [ -/// TabbedAttachmentPickerOption( -/// key: 'gallery', -/// icon: Icon(Icons.photo), -/// supportedTypes: [AttachmentPickerType.images], -/// optionViewBuilder: (context, controller) { -/// return CustomGalleryWidget(); -/// }, -/// ), -/// ], -/// ); -/// ``` -/// -/// Example using the system picker: -/// ```dart -/// showStreamAttachmentPickerModalBottomSheet( -/// context: context, -/// useSystemAttachmentPicker: true, -/// customOptions: [ -/// SystemAttachmentPickerOption( -/// key: 'upload', -/// type: AttachmentPickerType.files, -/// icon: Icon(Icons.upload_file), -/// title: 'Upload File', -/// onTap: (context, controller) async { -/// // Handle file picker -/// }, -/// ), -/// ], -/// ); -/// ``` -/// -/// Returns a [Future] that completes with the value passed to [Navigator.pop], -/// or `null` if the sheet was dismissed. -Future showStreamAttachmentPickerModalBottomSheet({ - required BuildContext context, - AttachmentPickerOptionsBuilder? optionsBuilder, - List allowedTypes = AttachmentPickerType.values, - Poll? initialPoll, - PollConfig? pollConfig, - GalleryPickerConfig? galleryPickerConfig, - List? initialAttachments, - Map? initialExtraData, - StreamAttachmentPickerController? controller, - Color? backgroundColor, - double? elevation, - BoxConstraints? constraints, - Color? barrierColor, - bool isScrollControlled = false, - bool useRootNavigator = false, - bool isDismissible = true, - bool enableDrag = true, - bool useSystemAttachmentPicker = false, - RouteSettings? routeSettings, - AnimationController? transitionAnimationController, - Clip? clipBehavior = Clip.hardEdge, - ShapeBorder? shape, -}) { - final colorTheme = StreamChatTheme.of(context).colorTheme; - final color = backgroundColor ?? colorTheme.inputBg; - - return showModalBottomSheet( - context: context, - backgroundColor: color, - elevation: elevation, - shape: shape, - clipBehavior: clipBehavior, - constraints: constraints, - barrierColor: barrierColor, - isScrollControlled: isScrollControlled, - useRootNavigator: useRootNavigator, - isDismissible: isDismissible, - enableDrag: enableDrag, - routeSettings: routeSettings, - transitionAnimationController: transitionAnimationController, - builder: (BuildContext context) { - return StreamAttachmentPickerBottomSheetBuilder( - controller: controller, - initialPoll: initialPoll, - initialAttachments: initialAttachments, - initialExtraData: initialExtraData, - builder: (context, controller, child) { - final isWebOrDesktop = switch (CurrentPlatform.type) { - PlatformType.android || PlatformType.ios => false, - _ => true, - }; - - final useSystemPicker = useSystemAttachmentPicker || isWebOrDesktop; - - final builder = switch (useSystemPicker) { - true => systemAttachmentPickerBuilder, - false => tabbedAttachmentPickerBuilder, - }; - - return builder.call( - context: context, - controller: controller, - allowedTypes: allowedTypes, - pollConfig: pollConfig, - galleryPickerConfig: galleryPickerConfig, - optionsBuilder: optionsBuilder, - ); - }, - ); - }, - ); -} - -/// Builds the attachment picker bottom sheet. -class StreamAttachmentPickerBottomSheetBuilder extends StatefulWidget { - /// Creates a new instance of the widget. - const StreamAttachmentPickerBottomSheetBuilder({ - super.key, - this.initialPoll, - this.initialAttachments, - this.initialExtraData, - this.child, - this.controller, - required this.builder, - }); - - /// The child widget. - final Widget? child; - - /// Builder for the attachment picker bottom sheet. - final Widget Function( - BuildContext context, - StreamAttachmentPickerController controller, - Widget? child, - ) builder; - - /// The initial poll. - final Poll? initialPoll; - - /// The initial attachments. - final List? initialAttachments; - - /// The initial extra data for the attachment picker. - final Map? initialExtraData; - - /// The controller. - final StreamAttachmentPickerController? controller; - - @override - State createState() => - _StreamAttachmentPickerBottomSheetBuilderState(); -} - -class _StreamAttachmentPickerBottomSheetBuilderState - extends State { - late StreamAttachmentPickerController _controller; - - @override - void initState() { - super.initState(); - _controller = widget.controller ?? - StreamAttachmentPickerController( - initialPoll: widget.initialPoll, - initialAttachments: widget.initialAttachments, - initialExtraData: widget.initialExtraData, - ); - } - - // Handle a potential change in StreamAttachmentPickerController by properly - // disposing of the old one and setting up the new one, if needed. - void _updateAttachmentPickerController( - StreamAttachmentPickerController? old, - StreamAttachmentPickerController? current, - ) { - if ((old == null && current == null) || old == current) return; - if (old == null) { - _controller.dispose(); - _controller = current!; - } else if (current == null) { - _controller = StreamAttachmentPickerController( - initialPoll: widget.initialPoll, - initialAttachments: widget.initialAttachments, - initialExtraData: widget.initialExtraData, - ); - } else { - _controller = current; - } - } - - @override - void didUpdateWidget( - StreamAttachmentPickerBottomSheetBuilder oldWidget, - ) { - super.didUpdateWidget(oldWidget); - _updateAttachmentPickerController( - oldWidget.controller, - widget.controller, - ); - } - - @override - void dispose() { - if (widget.controller == null) _controller.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return widget.builder(context, _controller, widget.child); - } -} +typedef AttachmentPickerOptionsBuilder = + List Function(BuildContext context, List defaultOptions); diff --git a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker_controller.dart b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker_controller.dart index 30add6c40b..53821955f2 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker_controller.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker_controller.dart @@ -10,8 +10,7 @@ const kDefaultMaxAttachmentSize = 100 * 1024 * 1024; // 100MB in Bytes const kDefaultMaxAttachmentCount = 10; /// Controller class for [StreamAttachmentPicker]. -class StreamAttachmentPickerController - extends ValueNotifier { +class StreamAttachmentPickerController extends ValueNotifier { /// Creates a new instance of [StreamAttachmentPickerController]. factory StreamAttachmentPickerController({ Poll? initialPoll, @@ -35,11 +34,11 @@ class StreamAttachmentPickerController this.initialValue, { this.maxAttachmentSize = kDefaultMaxAttachmentSize, this.maxAttachmentCount = kDefaultMaxAttachmentCount, - }) : assert( - (initialValue.attachments.length) <= maxAttachmentCount, - '''The initial attachments count must be less than or equal to maxAttachmentCount''', - ), - super(initialValue); + }) : assert( + (initialValue.attachments.length) <= maxAttachmentCount, + '''The initial attachments count must be less than or equal to maxAttachmentCount''', + ), + super(initialValue); /// Initial value for the controller. final AttachmentPickerValue initialValue; @@ -106,14 +105,16 @@ class StreamAttachmentPickerController // Cache the attachment in a temporary file. final tempFilePath = await _saveToCache(file); - value = value.copyWith(attachments: [ - ...value.attachments, - attachment.copyWith( - file: file.copyWith( - path: tempFilePath, + value = value.copyWith( + attachments: [ + ...value.attachments, + attachment.copyWith( + file: file.copyWith( + path: tempFilePath, + ), ), - ), - ]); + ], + ); } /// Removes the specified [attachment] from the message. @@ -271,9 +272,9 @@ class AttachmentTooLargeError extends StreamChatError { required this.fileSize, required this.maxSize, }) : super( - 'The size of the attachment is $fileSize bytes, ' - 'but the maximum size allowed is $maxSize bytes.', - ); + 'The size of the attachment is $fileSize bytes, ' + 'but the maximum size allowed is $maxSize bytes.', + ); /// The actual size of the attachment in bytes. final int fileSize; @@ -285,7 +286,8 @@ class AttachmentTooLargeError extends StreamChatError { List get props => [...super.props, fileSize, maxSize]; @override - String toString() => 'AttachmentTooLargeError: ' + String toString() => + 'AttachmentTooLargeError: ' 'The size of the attachment is $fileSize bytes, ' 'but the maximum size allowed is $maxSize bytes.'; } @@ -309,6 +311,7 @@ class AttachmentLimitReachedError extends StreamChatError { List get props => [...super.props, maxCount]; @override - String toString() => 'AttachmentLimitReachedError: ' + String toString() => + 'AttachmentLimitReachedError: ' 'The maximum number of attachments is $maxCount.'; } diff --git a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker_option.dart b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker_option.dart index 1eec1d4638..ee618d84f3 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker_option.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker_option.dart @@ -3,16 +3,18 @@ import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/src/message_input/attachment_picker/stream_attachment_picker_controller.dart'; /// Function signature for building the attachment picker option view. -typedef AttachmentPickerOptionViewBuilder = Widget Function( - BuildContext context, - StreamAttachmentPickerController controller, -); +typedef AttachmentPickerOptionViewBuilder = + Widget Function( + BuildContext context, + StreamAttachmentPickerController controller, + ); /// Function signature for system attachment picker option callback. -typedef OnSystemAttachmentPickerOptionTap = Future Function( - BuildContext context, - StreamAttachmentPickerController controller, -); +typedef OnSystemAttachmentPickerOptionTap = + Future Function( + BuildContext context, + StreamAttachmentPickerController controller, + ); /// Base class for attachment picker options. abstract class AttachmentPickerOption { @@ -29,7 +31,7 @@ abstract class AttachmentPickerOption { final String? key; /// The icon of the option. - final Widget icon; + final IconData icon; /// The title of the option. final String? title; diff --git a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker_result.dart b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker_result.dart index 1a2e681f33..dc4bbf563c 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker_result.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker_result.dart @@ -4,8 +4,7 @@ import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; /// Signature for a function that is called when a attachment picker result /// is received. -typedef OnAttachmentPickerResult - = FutureOr Function(T result); +typedef OnAttachmentPickerResult = FutureOr Function(T result); /// {@template streamAttachmentPickerAction} /// A sealed class that represents different results that can be returned diff --git a/packages/stream_chat_flutter/lib/src/message_input/audio_recorder/audio_recorder_controller.dart b/packages/stream_chat_flutter/lib/src/message_input/audio_recorder/audio_recorder_controller.dart index 1c34f3b9fc..63b19a711c 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/audio_recorder/audio_recorder_controller.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/audio_recorder/audio_recorder_controller.dart @@ -30,9 +30,9 @@ class StreamAudioRecorderController extends ValueNotifier { config: switch (config) { final config? => config, _ => const RecordConfig( - numChannels: 1, - encoder: kIsWeb ? AudioEncoder.wav : AudioEncoder.aacLc, - ), + numChannels: 1, + encoder: kIsWeb ? AudioEncoder.wav : AudioEncoder.aacLc, + ), }, ); } @@ -44,8 +44,8 @@ class StreamAudioRecorderController extends ValueNotifier { required AudioRecorder recorder, AudioRecorderState initialState = const RecordStateIdle(), Duration amplitudeInterval = const Duration(milliseconds: 100), - }) : _recorder = recorder, - super(initialState) { + }) : _recorder = recorder, + super(initialState) { // Listen to the recorder amplitude changes _recorderAmplitudeSubscription = _recorder .onAmplitudeChanged(amplitudeInterval) // diff --git a/packages/stream_chat_flutter/lib/src/message_input/audio_recorder/audio_recorder_feedback.dart b/packages/stream_chat_flutter/lib/src/message_input/audio_recorder/audio_recorder_feedback.dart index 9bf98d2e1b..d33ce45657 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/audio_recorder/audio_recorder_feedback.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/audio_recorder/audio_recorder_feedback.dart @@ -175,13 +175,13 @@ class AudioRecorderFeedbackWrapper extends AudioRecorderFeedback { _FeedbackCallback? onCancel, _FeedbackCallback? onStartCancel, _FeedbackCallback? onStop, - }) : _onStop = onStop, - _onStartCancel = onStartCancel, - _onCancel = onCancel, - _onLock = onLock, - _onFinish = onFinish, - _onPause = onPause, - _onStart = onStart; + }) : _onStop = onStop, + _onStartCancel = onStartCancel, + _onCancel = onCancel, + _onLock = onLock, + _onFinish = onFinish, + _onPause = onPause, + _onStart = onStart; // Callback for when recording starts. final _FeedbackCallback? _onStart; diff --git a/packages/stream_chat_flutter/lib/src/message_input/audio_recorder/audio_recorder_state.dart b/packages/stream_chat_flutter/lib/src/message_input/audio_recorder/audio_recorder_state.dart index 95bdeff0bb..96bb1f00cc 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/audio_recorder/audio_recorder_state.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/audio_recorder/audio_recorder_state.dart @@ -51,13 +51,13 @@ sealed class RecordStateRecording extends AudioRecorderState { }) { return switch (this) { RecordStateRecordingHold() => RecordStateRecordingHold( - duration: duration ?? this.duration, - waveform: waveform ?? this.waveform, - ), + duration: duration ?? this.duration, + waveform: waveform ?? this.waveform, + ), RecordStateRecordingLocked() => RecordStateRecordingLocked( - duration: duration ?? this.duration, - waveform: waveform ?? this.waveform, - ), + duration: duration ?? this.duration, + waveform: waveform ?? this.waveform, + ), }; } } diff --git a/packages/stream_chat_flutter/lib/src/message_input/audio_recorder/stream_audio_recorder.dart b/packages/stream_chat_flutter/lib/src/message_input/audio_recorder/stream_audio_recorder.dart index 241b1f5efb..0ff73b379f 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/audio_recorder/stream_audio_recorder.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/audio_recorder/stream_audio_recorder.dart @@ -5,14 +5,13 @@ import 'package:flutter_portal/flutter_portal.dart'; import 'package:stream_chat_flutter/src/audio/audio_playlist_controller.dart'; import 'package:stream_chat_flutter/src/audio/audio_playlist_state.dart'; import 'package:stream_chat_flutter/src/audio/audio_sampling.dart'; -import 'package:stream_chat_flutter/src/icons/stream_svg_icon.dart'; import 'package:stream_chat_flutter/src/message_input/audio_recorder/audio_recorder_feedback.dart'; import 'package:stream_chat_flutter/src/message_input/audio_recorder/audio_recorder_state.dart'; import 'package:stream_chat_flutter/src/message_input/stream_message_input_icon_button.dart'; -import 'package:stream_chat_flutter/src/misc/audio_waveform.dart'; import 'package:stream_chat_flutter/src/misc/empty_widget.dart'; import 'package:stream_chat_flutter/src/theme/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/utils/extensions.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; /// {@template audioRecorderBuilder} /// A builder function for constructing the audio recorder UI. @@ -21,11 +20,12 @@ import 'package:stream_chat_flutter/src/utils/extensions.dart'; /// - [StreamAudioRecorderButton], which uses this builder function. /// - [StreamAudioRecorderState], which provides the state of the recorder. /// {@endtemplate} -typedef AudioRecorderBuilder = Widget Function( - BuildContext, - AudioRecorderState, - Widget, -); +typedef AudioRecorderBuilder = + Widget Function( + BuildContext, + AudioRecorderState, + Widget, + ); /// {@template streamAudioRecorderButton} /// A configurable audio recording button with interactive states and gestures. @@ -172,49 +172,49 @@ class StreamAudioRecorderButton extends StatelessWidget { state: recordState, button: RecordButton( onPressed: () {}, // Allows showing ripple effect on tap. - icon: const StreamSvgIcon(icon: StreamSvgIcons.mic), + icon: Icon(context.streamIcons.microphone), ), builder: (context, state, recordButton) => switch (state) { // Show only the record button if the recording is not in progress. RecordStateIdle() => RecordStateIdleContent( - state: state, - recordButton: recordButton, - ), + state: state, + recordButton: recordButton, + ), RecordStateRecordingHold() => RecordStateHoldRecordingContent( - state: state, - recordButton: recordButton, - cancelThreshold: cancelRecordThreshold, - ), + state: state, + recordButton: recordButton, + cancelThreshold: cancelRecordThreshold, + ), RecordStateRecordingLocked() => RecordStateLockedRecordingContent( - state: state, - onRecordEnd: () async { - await feedback.onRecordFinish(context); - return onRecordFinish?.call(); - }, - onRecordPause: () async { - await feedback.onRecordPause(context); - return onRecordPause?.call(); - }, - onRecordCancel: () async { - await feedback.onRecordCancel(context); - return onRecordCancel?.call(); - }, - onRecordStop: () async { - await feedback.onRecordStop(context); - return onRecordStop?.call(); - }, - ), + state: state, + onRecordEnd: () async { + await feedback.onRecordFinish(context); + return onRecordFinish?.call(); + }, + onRecordPause: () async { + await feedback.onRecordPause(context); + return onRecordPause?.call(); + }, + onRecordCancel: () async { + await feedback.onRecordCancel(context); + return onRecordCancel?.call(); + }, + onRecordStop: () async { + await feedback.onRecordStop(context); + return onRecordStop?.call(); + }, + ), RecordStateStopped() => RecordStateStoppedContent( - state: state, - onRecordCancel: () async { - await feedback.onRecordCancel(context); - return onRecordCancel?.call(); - }, - onRecordFinish: () async { - await feedback.onRecordFinish(context); - return onRecordFinish?.call(); - }, - ), + state: state, + onRecordCancel: () async { + await feedback.onRecordCancel(context); + return onRecordCancel?.call(); + }, + onRecordFinish: () async { + await feedback.onRecordFinish(context); + return onRecordFinish?.call(); + }, + ), }, ), ); @@ -448,20 +448,20 @@ class RecordStateLockedRecordingContent extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ StreamMessageInputIconButton( - icon: const StreamSvgIcon(icon: StreamSvgIcons.delete), + icon: Icon(context.streamIcons.trashBin), color: theme.colorTheme.accentPrimary, onPressed: onRecordCancel, ), StreamMessageInputIconButton( - icon: const StreamSvgIcon(icon: StreamSvgIcons.stop), + icon: Icon(context.streamIcons.stop), color: theme.colorTheme.accentError, onPressed: onRecordStop, ), StreamMessageInputIconButton( - icon: const StreamSvgIcon(icon: StreamSvgIcons.checkSend), + icon: Icon(context.streamIcons.circleCheck), color: theme.colorTheme.accentPrimary, onPressed: onRecordEnd, - ) + ), ], ), ], @@ -498,8 +498,7 @@ class RecordStateStoppedContent extends StatefulWidget { final VoidCallback? onRecordFinish; @override - State createState() => - _RecordStateStoppedContentState(); + State createState() => _RecordStateStoppedContentState(); } class _RecordStateStoppedContentState extends State { @@ -607,15 +606,15 @@ class _RecordStateStoppedContentState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ StreamMessageInputIconButton( - icon: const StreamSvgIcon(icon: StreamSvgIcons.delete), + icon: Icon(context.streamIcons.trashBin), color: theme.colorTheme.accentPrimary, onPressed: widget.onRecordCancel, ), StreamMessageInputIconButton( - icon: const StreamSvgIcon(icon: StreamSvgIcons.checkSend), + icon: Icon(context.streamIcons.circleCheck), color: theme.colorTheme.accentPrimary, onPressed: widget.onRecordFinish, - ) + ), ], ), ], @@ -643,29 +642,31 @@ class SwipeToLockButton extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = StreamChatTheme.of(context); + final streamIcons = context.streamIcons; + final colorScheme = context.streamColorScheme; + return Container( - padding: const EdgeInsets.all(8), + padding: const EdgeInsets.all(10), decoration: BoxDecoration( borderRadius: BorderRadius.circular(24), - color: theme.colorTheme.inputBg, + color: colorScheme.backgroundElevation1, + border: Border.all(color: colorScheme.borderDefault), + boxShadow: context.streamBoxShadow.elevation1, ), child: Column( spacing: 8, mainAxisSize: MainAxisSize.min, children: [ - StreamSvgIcon( - icon: StreamSvgIcons.lock, - size: kDefaultMessageInputIconSize, - color: switch (isLocked) { - true => theme.colorTheme.accentPrimary, - false => theme.colorTheme.textLowEmphasis, - }, + Icon( + isLocked ? streamIcons.lock : streamIcons.unlocked, + size: 20, + color: colorScheme.textPrimary, ), if (!isLocked) ...[ - StreamSvgIcon( - icon: StreamSvgIcons.up, - color: theme.colorTheme.textLowEmphasis, + Icon( + streamIcons.chevronTop, + size: 20, + color: colorScheme.textPrimary, ), ], ], @@ -718,24 +719,24 @@ class PlaybackControlButton extends StatelessWidget { }, icon: switch (state) { TrackState.loading => Builder( - builder: (context) { - final iconTheme = IconTheme.of(context); - return SizedBox.fromSize( - size: Size.square(iconTheme.size!), - child: Padding( - padding: const EdgeInsets.all(8), - child: CircularProgressIndicator.adaptive( - valueColor: AlwaysStoppedAnimation( - theme.colorTheme.accentPrimary, - ), + builder: (context) { + final iconTheme = IconTheme.of(context); + return SizedBox.fromSize( + size: Size.square(iconTheme.size!), + child: Padding( + padding: const EdgeInsets.all(8), + child: CircularProgressIndicator.adaptive( + valueColor: AlwaysStoppedAnimation( + theme.colorTheme.accentPrimary, ), ), - ); - }, - ), - TrackState.idle => const StreamSvgIcon(icon: StreamSvgIcons.play), - TrackState.paused => const StreamSvgIcon(icon: StreamSvgIcons.play), - TrackState.playing => const StreamSvgIcon(icon: StreamSvgIcons.pause), + ), + ); + }, + ), + TrackState.idle => Icon(context.streamIcons.playSolid), + TrackState.paused => Icon(context.streamIcons.playSolid), + TrackState.playing => Icon(context.streamIcons.pause), }, ); } @@ -763,8 +764,8 @@ class PlaybackTimerIndicator extends StatelessWidget { final theme = StreamChatTheme.of(context); return Row( children: [ - StreamSvgIcon( - icon: StreamSvgIcons.mic, + Icon( + context.streamIcons.microphone, size: kDefaultMessageInputIconSize, color: switch (duration.inSeconds) { > 0 => theme.colorTheme.accentError, @@ -854,8 +855,8 @@ class SlideToCancelIndicator extends StatelessWidget { ), ), const SizedBox(width: 8), - StreamSvgIcon( - icon: StreamSvgIcons.left, + Icon( + context.streamIcons.chevronLeft, color: theme.colorTheme.textLowEmphasis, ), ], @@ -949,13 +950,16 @@ class _SlideTransitionWidgetState extends State @override Widget build(BuildContext context) { - final position = Tween( - begin: widget.begin, - end: widget.end, - ).animate(CurvedAnimation( - parent: _controller, - curve: widget.curve, - )); + final position = + Tween( + begin: widget.begin, + end: widget.end, + ).animate( + CurvedAnimation( + parent: _controller, + curve: widget.curve, + ), + ); return SlideTransition( position: position, @@ -985,8 +989,8 @@ class HoldToRecordInfoTooltip extends StatelessWidget { Widget build(BuildContext context) { final theme = StreamChatTheme.of(context); - const recordButtonWidth = kDefaultMessageInputIconSize + - kDefaultMessageInputIconPadding * 2; // right, left padding. + const recordButtonWidth = + kDefaultMessageInputIconSize + kDefaultMessageInputIconPadding * 2; // right, left padding. const arrowSize = Size(recordButtonWidth / 2, 6); diff --git a/packages/stream_chat_flutter/lib/src/message_input/clear_input_item_button.dart b/packages/stream_chat_flutter/lib/src/message_input/clear_input_item_button.dart index ddfb0c20e0..514c84accf 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/clear_input_item_button.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/clear_input_item_button.dart @@ -34,9 +34,9 @@ class ClearInputItemButton extends StatelessWidget { // ignore: deprecated_member_use _streamChatTheme.colorTheme.textHighEmphasis.withOpacity(0.5), child: Center( - child: StreamSvgIcon( + child: Icon( + context.streamIcons.crossMedium, size: 24, - icon: StreamSvgIcons.close, color: _streamChatTheme.colorTheme.barsBg, ), ), diff --git a/packages/stream_chat_flutter/lib/src/message_input/command_button.dart b/packages/stream_chat_flutter/lib/src/message_input/command_button.dart index 1e8c050cb0..c70e1e0d0d 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/command_button.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/command_button.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/src/icons/stream_svg_icon.dart'; import 'package:stream_chat_flutter/src/message_input/stream_message_input_icon_button.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; /// {@template commandButton} /// The button that allows a user to use commands in a chat. @@ -53,7 +53,7 @@ class CommandButton extends StatelessWidget { color: color, iconSize: size, onPressed: onPressed, - icon: icon ?? const StreamSvgIcon(icon: StreamSvgIcons.lightning), + icon: icon ?? Icon(context.streamIcons.thunder), ); } } diff --git a/packages/stream_chat_flutter/lib/src/message_input/dm_checkbox_list_tile.dart b/packages/stream_chat_flutter/lib/src/message_input/dm_checkbox_list_tile.dart index e9b0d7c372..7307c66ee8 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/dm_checkbox_list_tile.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/dm_checkbox_list_tile.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/src/theme/stream_chat_theme.dart'; -import 'package:stream_chat_flutter/src/utils/extensions.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; /// {@template dmCheckboxListTile} /// A widget that represents a checkbox list tile for direct message input. @@ -25,9 +25,7 @@ class DmCheckboxListTile extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = StreamChatTheme.of(context); - final textTheme = theme.textTheme; - final colorTheme = theme.colorTheme; + final textTheme = context.streamTextTheme; const visualDensity = VisualDensity( vertical: VisualDensity.minimumDensity, @@ -35,27 +33,13 @@ class DmCheckboxListTile extends StatelessWidget { ); final checkbox = ExcludeFocus( - child: CheckboxTheme( - data: CheckboxThemeData( - overlayColor: WidgetStatePropertyAll( - colorTheme.accentPrimary.withAlpha(kRadialReactionAlpha), - ), - ), - child: Checkbox( - value: value, - visualDensity: visualDensity, - activeColor: colorTheme.accentPrimary, - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - side: BorderSide(width: 2, color: colorTheme.textLowEmphasis), - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(3)), - onChanged: switch (onChanged) { - final onChanged? => (value) { - if (value == null) return; - return onChanged.call(value); - }, - _ => null, - }, - ), + child: StreamCheckbox( + value: value, + size: StreamCheckboxSize.sm, + onChanged: switch (onChanged) { + final onChanged? => onChanged.call, + _ => null, + }, ), ); @@ -70,10 +54,7 @@ class DmCheckboxListTile extends StatelessWidget { contentPadding: contentPadding, title: Text( context.translations.alsoSendAsDirectMessageLabel, - style: textTheme.footnote.copyWith( - // ignore: deprecated_member_use - color: colorTheme.textHighEmphasis.withOpacity(0.5), - ), + style: textTheme.metadataDefault, ), ), ); diff --git a/packages/stream_chat_flutter/lib/src/message_input/quoted_message_widget.dart b/packages/stream_chat_flutter/lib/src/message_input/quoted_message_widget.dart index 931ea553bc..bed4365e5f 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/quoted_message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/quoted_message_widget.dart @@ -68,12 +68,9 @@ class StreamQuotedMessageWidget extends StatelessWidget { const SizedBox(width: 8), if (message.user != null) StreamUserAvatar( + size: .sm, user: message.user!, - constraints: const BoxConstraints.tightFor( - height: 24, - width: 24, - ), - showOnlineStatus: false, + showOnlineIndicator: false, ), ]; return Padding( @@ -113,11 +110,9 @@ class _QuotedMessage extends StatelessWidget { bool get _containsText => message.text?.isNotEmpty == true; - bool get _containsLinkAttachment => - message.attachments.any((it) => it.type == AttachmentType.urlPreview); + bool get _containsLinkAttachment => message.attachments.any((it) => it.type == AttachmentType.urlPreview); - bool get _isGiphy => message.attachments - .any((element) => element.type == AttachmentType.giphy); + bool get _isGiphy => message.attachments.any((element) => element.type == AttachmentType.giphy); bool get _isDeleted => message.isDeleted || message.deletedAt != null; @@ -176,19 +171,18 @@ class _QuotedMessage extends StatelessWidget { ), if (msg.text!.isNotEmpty && !_isGiphy) Flexible( - child: textBuilder?.call(context, msg) ?? - StreamMessageText( - message: msg, + child: + textBuilder?.call(context, msg) ?? + StreamMarkdownMessage( + data: msg.replaceMentions().text ?? '', messageTheme: isOnlyEmoji && _containsText ? messageTheme.copyWith( - messageTextStyle: - messageTheme.messageTextStyle?.copyWith( + messageTextStyle: messageTheme.messageTextStyle?.copyWith( fontSize: 32, ), ) : messageTheme.copyWith( - messageTextStyle: - messageTheme.messageTextStyle?.copyWith( + messageTextStyle: messageTheme.messageTextStyle?.copyWith( fontSize: 12, ), ), @@ -224,8 +218,7 @@ class _QuotedMessage extends StatelessWidget { child: Row( spacing: 8, mainAxisSize: MainAxisSize.min, - mainAxisAlignment: - reverse ? MainAxisAlignment.end : MainAxisAlignment.start, + mainAxisAlignment: reverse ? MainAxisAlignment.end : MainAxisAlignment.start, children: reverse ? children.reversed.toList() : children, ), ); @@ -270,8 +263,7 @@ class _ParseAttachments extends StatelessWidget { var clipBehavior = Clip.none; ShapeDecoration? decoration; - if (attachment.type != AttachmentType.file && - attachment.type != AttachmentType.voiceRecording) { + if (attachment.type != AttachmentType.file && attachment.type != AttachmentType.voiceRecording) { clipBehavior = Clip.hardEdge; decoration = ShapeDecoration( shape: RoundedRectangleBorder( diff --git a/packages/stream_chat_flutter/lib/src/message_input/quoting_message_top_area.dart b/packages/stream_chat_flutter/lib/src/message_input/quoting_message_top_area.dart index 74f26d9dac..08b00c6fc2 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/quoting_message_top_area.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/quoting_message_top_area.dart @@ -37,7 +37,7 @@ class QuotingMessageTopArea extends StatelessWidget { StreamMessageInputIconButton( iconSize: 24, color: _streamChatTheme.colorTheme.disabled, - icon: const StreamSvgIcon(icon: StreamSvgIcons.reply), + icon: Icon(context.streamIcons.arrowShareLeft), onPressed: null, ), Text( @@ -47,7 +47,7 @@ class QuotingMessageTopArea extends StatelessWidget { StreamMessageInputIconButton( iconSize: 24, color: _streamChatTheme.colorTheme.textLowEmphasis, - icon: const StreamSvgIcon(icon: StreamSvgIcons.closeSmall), + icon: Icon(context.streamIcons.crossSmall), onPressed: onQuotedMessageCleared?.call, ), ], diff --git a/packages/stream_chat_flutter/lib/src/message_input/stream_message_input.dart b/packages/stream_chat_flutter/lib/src/message_input/stream_message_input.dart index 6d268935dc..d23df1b207 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/stream_message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/stream_message_input.dart @@ -1,3 +1,6 @@ +// ignore_for_file: unused_element +// TODO: remove unused elements + import 'dart:async'; import 'dart:math'; @@ -7,7 +10,6 @@ import 'package:flutter/services.dart'; import 'package:stream_chat_flutter/platform_widget_builder/src/platform_widget_builder.dart'; import 'package:stream_chat_flutter/src/message_input/attachment_button.dart'; import 'package:stream_chat_flutter/src/message_input/command_button.dart'; -import 'package:stream_chat_flutter/src/message_input/dm_checkbox_list_tile.dart'; import 'package:stream_chat_flutter/src/message_input/quoting_message_top_area.dart'; import 'package:stream_chat_flutter/src/message_input/stream_message_input_icon_button.dart'; import 'package:stream_chat_flutter/src/message_input/tld.dart'; @@ -21,10 +23,11 @@ const _kMentionTrigger = '@'; /// Signature for the function that determines if a [matchedUri] should be /// previewed as an OG Attachment. -typedef OgPreviewFilter = bool Function( - Uri matchedUri, - String messageText, -); +typedef OgPreviewFilter = + bool Function( + Uri matchedUri, + String messageText, + ); /// Different types of hints that can be shown in [StreamMessageInput]. enum HintType { @@ -47,10 +50,11 @@ enum HintType { typedef HintGetter = String? Function(BuildContext context, HintType type); /// The signature for the function that builds the list of actions. -typedef ActionsBuilder = List Function( - BuildContext context, - List defaultActions, -); +typedef ActionsBuilder = + List Function( + BuildContext context, + List defaultActions, + ); /// Inactive state: /// @@ -121,7 +125,7 @@ class StreamMessageInput extends StatefulWidget { this.focusNode, this.sendButtonLocation = SendButtonLocation.outside, this.autofocus = false, - this.hideSendAsDm = false, + this.canAlsoSendToChannelFromThread = true, this.enableVoiceRecording = false, this.sendVoiceRecordingAutomatically = false, this.voiceRecordingFeedback = const AudioRecorderFeedback(), @@ -152,8 +156,7 @@ class StreamMessageInput extends StatefulWidget { this.onQuotedMessageCleared, this.enableActionAnimation = true, this.sendMessageKeyPredicate = _defaultSendMessageKeyPredicate, - this.clearQuotedMessageKeyPredicate = - _defaultClearQuotedMessageKeyPredicate, + this.clearQuotedMessageKeyPredicate = _defaultClearQuotedMessageKeyPredicate, this.ogPreviewFilter = _defaultOgPreviewFilter, this.hintGetter = _defaultHintGetter, this.contentInsertionConfiguration, @@ -214,8 +217,10 @@ class StreamMessageInput extends StatefulWidget { /// Use this property to hide/show the commands button. final bool showCommandsButton; - /// Hide send as dm checkbox. - final bool hideSendAsDm; + /// Show the checkbox to send the message as a direct message to the channel. + /// + /// Defaults to true. + final bool canAlsoSendToChannelFromThread; /// If true the voice recording button will be displayed. /// @@ -307,8 +312,7 @@ class StreamMessageInput extends StatefulWidget { /// /// This is used to build the thumbnail for the attachment in the quoted /// message. - final Map? - quotedMessageAttachmentThumbnailBuilders; + final Map? quotedMessageAttachmentThumbnailBuilders; /// The focus node associated to the TextField. final FocusNode? focusNode; @@ -516,28 +520,28 @@ class StreamMessageInput extends StatefulWidget { } /// State of [StreamMessageInput] -class StreamMessageInputState extends State - with RestorationMixin { +class StreamMessageInputState extends State with RestorationMixin { bool get _commandEnabled => _effectiveController.message.command != null; bool _actionsShrunk = false; + bool get _isPickerVisible => _pickerController != null; + StreamAttachmentPickerController? _pickerController; + bool _isSyncingControllers = false; + late StreamChatThemeData _streamChatTheme; late StreamMessageInputThemeData _messageInputTheme; - bool get _hasQuotedMessage => - _effectiveController.message.quotedMessage != null; + bool get _hasQuotedMessage => _effectiveController.message.quotedMessage != null; bool get _isEditing => !_effectiveController.message.state.isInitial; late final _audioRecorderController = StreamAudioRecorderController(); - FocusNode get _effectiveFocusNode => - widget.focusNode ?? (_focusNode ??= FocusNode()); + FocusNode get _effectiveFocusNode => widget.focusNode ?? (_focusNode ??= FocusNode()); FocusNode? _focusNode; - StreamMessageInputController get _effectiveController => - widget.messageInputController ?? _controller!.value; + StreamMessageInputController get _effectiveController => widget.messageInputController ?? _controller!.value; StreamRestorableMessageInputController? _controller; void _createLocalController([Message? message]) { @@ -554,7 +558,9 @@ class StreamMessageInputState extends State void _initialiseEffectiveController() { _effectiveController + ..removeListener(_onChangedThrottled) ..removeListener(_onChangedDebounced) + ..addListener(_onChangedThrottled) ..addListener(_onChangedDebounced); } @@ -619,11 +625,9 @@ class StreamMessageInputState extends State @override void didUpdateWidget(covariant StreamMessageInput oldWidget) { super.didUpdateWidget(oldWidget); - if (widget.messageInputController == null && - oldWidget.messageInputController != null) { + if (widget.messageInputController == null && oldWidget.messageInputController != null) { _createLocalController(oldWidget.messageInputController!.message); - } else if (widget.messageInputController != null && - oldWidget.messageInputController == null) { + } else if (widget.messageInputController != null && oldWidget.messageInputController == null) { unregisterFromRestoration(_controller!); _controller!.dispose(); _controller = null; @@ -647,8 +651,11 @@ class StreamMessageInputState extends State @override String? get restorationId => widget.restorationId; - // ignore: no-empty-block - void _focusNodeListener() {} + void _focusNodeListener() { + if (_effectiveFocusNode.hasFocus && _isPickerVisible) { + _hidePicker(); + } + } @override Widget build(BuildContext context) { @@ -671,31 +678,34 @@ class StreamMessageInputState extends State final channel = StreamChannel.of(context).channel; final messageInput = switch (_buildAutocompleteMessageInput(context)) { final messageInput when channel.state != null => BetterStreamBuilder( - stream: channel.ownCapabilitiesStream.map(canSendOrUpdateMessage), - initialData: canSendOrUpdateMessage(channel.ownCapabilities), - builder: (context, enabled) { - // Allow the user to send messages if the user has the permission to - // send messages or if the user is editing a message. - if (enabled) return messageInput; - - // Otherwise, show the no permission message. - return _buildNoPermissionMessage(context); - }, - ), + stream: channel.ownCapabilitiesStream.map(canSendOrUpdateMessage), + initialData: canSendOrUpdateMessage(channel.ownCapabilities), + builder: (context, enabled) { + // Allow the user to send messages if the user has the permission to + // send messages or if the user is editing a message. + if (enabled) return messageInput; + + // Otherwise, show the no permission message. + return _buildNoPermissionMessage(context); + }, + ), final messageInput => messageInput, }; final shadow = widget.shadow ?? _messageInputTheme.shadow; final elevation = widget.elevation ?? _messageInputTheme.elevation; + final spacing = context.streamSpacing; + return Material( elevation: elevation ?? 8, child: DecoratedBox( decoration: BoxDecoration( - color: _messageInputTheme.inputBackgroundColor, + color: context.streamColorScheme.backgroundElevation1, boxShadow: [if (shadow != null) shadow], ), child: SimpleSafeArea( - enabled: widget.enableSafeArea ?? _messageInputTheme.enableSafeArea, + enabled: !_isPickerVisible && (widget.enableSafeArea ?? _messageInputTheme.enableSafeArea ?? true), + minimum: _isPickerVisible ? .zero : .only(bottom: spacing.md), child: Center(heightFactor: 1, child: messageInput), ), ), @@ -712,47 +722,48 @@ class StreamMessageInputState extends State StreamAutocompleteTrigger( trigger: _kCommandTrigger, triggerOnlyAtStart: true, - optionsViewBuilder: ( - context, - autocompleteQuery, - messageEditingController, - ) { - final query = autocompleteQuery.query; - return StreamCommandAutocompleteOptions( - query: query, - channel: StreamChannel.of(context).channel, - onCommandSelected: (command) { - _effectiveController.command = command.name; - // removing the overlay after the command is selected - StreamAutocomplete.of(context).closeSuggestions(); + optionsViewBuilder: + ( + context, + autocompleteQuery, + messageEditingController, + ) { + final query = autocompleteQuery.query; + return StreamCommandAutocompleteOptions( + query: query, + channel: StreamChannel.of(context).channel, + onCommandSelected: (command) { + _effectiveController.command = command.name; + // removing the overlay after the command is selected + StreamAutocomplete.of(context).closeSuggestions(); + }, + ); }, - ); - }, ), if (widget.enableMentionsOverlay) StreamAutocompleteTrigger( trigger: _kMentionTrigger, - optionsViewBuilder: ( - context, - autocompleteQuery, - messageEditingController, - ) { - final query = autocompleteQuery.query; - return StreamMentionAutocompleteOptions( - query: query, - channel: StreamChannel.of(context).channel, - mentionAllAppUsers: widget.mentionAllAppUsers, - mentionsTileBuilder: widget.userMentionsTileBuilder, - onMentionUserTap: (user) { - // adding the mentioned user to the controller. - _effectiveController.addMentionedUser(user); - - // accepting the autocomplete option. - StreamAutocomplete.of(context) - .acceptAutocompleteOption(user.name); + optionsViewBuilder: + ( + context, + autocompleteQuery, + messageEditingController, + ) { + final query = autocompleteQuery.query; + return StreamMentionAutocompleteOptions( + query: query, + channel: StreamChannel.of(context).channel, + mentionAllAppUsers: widget.mentionAllAppUsers, + mentionsTileBuilder: widget.userMentionsTileBuilder, + onMentionUserTap: (user) { + // adding the mentioned user to the controller. + _effectiveController.addMentionedUser(user); + + // accepting the autocomplete option. + StreamAutocomplete.of(context).acceptAutocompleteOption(user.name); + }, + ); }, - ); - }, ), ], ); @@ -763,20 +774,58 @@ class StreamMessageInputState extends State StreamMessageEditingController controller, FocusNode focusNode, ) { + final currentUserId = StreamChat.of(context).currentUser?.id; + return StreamMessageValueListenableBuilder( valueListenable: controller, - builder: (context, value, _) => Padding( - padding: widget.padding, - child: Column( - spacing: 8, - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - _buildTopMessageArea(context), - _buildTextField(context), - _buildDmCheckbox(context), - ].nonNulls.toList(), - ), + builder: (context, value, _) => Column( + mainAxisSize: MainAxisSize.min, + children: [ + StreamChatMessageComposer( + controller: controller, + currentUserId: currentUserId, + onAttachmentButtonPressed: _onAttachmentButtonPressed, + isPickerOpen: _isPickerVisible, + placeholder: _getHint(context) ?? '', + focusNode: focusNode, + onSendPressed: sendMessage, + canAlsoSendToChannel: _shouldShowSendToChannelCheckbox(), + audioRecorderController: widget.enableVoiceRecording ? _audioRecorderController : null, + ), + _buildInlineAttachmentPicker(context), + ].nonNulls.toList(), + ), + ); + } + + Widget? _buildInlineAttachmentPicker(BuildContext context) { + if (!_isPickerVisible) return null; + + final allowedTypes = _getAllowedAttachmentPickerTypes(); + + final messageInputTheme = StreamMessageInputTheme.of(context); + final isWebOrDesktop = switch (CurrentPlatform.type) { + PlatformType.android || PlatformType.ios => false, + _ => true, + }; + final useSystemPicker = + widget.useSystemAttachmentPicker || (messageInputTheme.useSystemAttachmentPicker ?? false) || isWebOrDesktop; + + final builder = switch (useSystemPicker) { + true => systemAttachmentPickerBuilder, + false => tabbedAttachmentPickerBuilder, + }; + + return SizedBox( + height: 333, + child: builder.call( + context: context, + controller: _pickerController!, + allowedTypes: allowedTypes, + pollConfig: widget.pollConfig, + optionsBuilder: widget.attachmentPickerOptionsBuilder, + onError: _onPickerError, + onPollCreated: _onPollCreated, ), ); } @@ -806,17 +855,11 @@ class StreamMessageInputState extends State return null; } - Widget? _buildDmCheckbox(BuildContext context) { - if (widget.hideSendAsDm) return null; + bool _shouldShowSendToChannelCheckbox() { + if (!widget.canAlsoSendToChannelFromThread) return false; final insideThread = _effectiveController.message.parentId != null; - if (!insideThread) return null; - - return DmCheckboxListTile( - value: _effectiveController.showInChannel, - contentPadding: const EdgeInsets.symmetric(horizontal: 8), - onChanged: (value) => _effectiveController.showInChannel = value, - ); + return insideThread; } Widget _buildNoPermissionMessage(BuildContext context) { @@ -838,17 +881,14 @@ class StreamMessageInputState extends State return Row( children: [ if (!isAudioRecordingFlowActive) ...[ - if (!_commandEnabled && - widget.actionsLocation == ActionsLocation.left) + if (!_commandEnabled && widget.actionsLocation == ActionsLocation.left) _buildExpandActionsButton(context), const SizedBox(width: 4), Expanded(child: _buildTextInput(context)), const SizedBox(width: 4), - if (!_commandEnabled && - widget.actionsLocation == ActionsLocation.right) + if (!_commandEnabled && widget.actionsLocation == ActionsLocation.right) _buildExpandActionsButton(context), - if (widget.sendButtonLocation == SendButtonLocation.outside) - _buildSendButton(context), + if (widget.sendButtonLocation == SendButtonLocation.outside) _buildSendButton(context), ], if (widget.enableVoiceRecording) Expanded( @@ -926,11 +966,11 @@ class StreamMessageInputState extends State firstChild: StreamMessageInputIconButton( color: _messageInputTheme.expandButtonColor, icon: Transform.rotate( - angle: (widget.actionsLocation == ActionsLocation.right || - widget.actionsLocation == ActionsLocation.rightInside) + angle: + (widget.actionsLocation == ActionsLocation.right || widget.actionsLocation == ActionsLocation.rightInside) ? pi : 0, - child: const StreamSvgIcon(icon: StreamSvgIcons.emptyCircleRight), + child: Icon(context.streamIcons.chevronRight), ), onPressed: () { if (_actionsShrunk) { @@ -938,9 +978,7 @@ class StreamMessageInputState extends State } }, ), - secondChild: widget.disableAttachments && - !widget.showCommandsButton && - !(widget.actionsBuilder != null) + secondChild: widget.disableAttachments && !widget.showCommandsButton && !(widget.actionsBuilder != null) ? const Empty() : Row( spacing: widget.spaceBetweenActions, @@ -953,8 +991,7 @@ class StreamMessageInputState extends State List _actionsList() { final channel = StreamChannel.of(context).channel; final defaultActions = [ - if (!widget.disableAttachments && channel.canUploadFile) - _buildAttachmentButton(context), + if (!widget.disableAttachments && channel.canUploadFile) _buildAttachmentButton(context), if (widget.showCommandsButton && !_isEditing && channel.state != null && @@ -975,11 +1012,12 @@ class StreamMessageInputState extends State onPressed: _onAttachmentButtonPressed, ); - return widget.attachmentButtonBuilder?.call(context, defaultButton) ?? - defaultButton; + return widget.attachmentButtonBuilder?.call(context, defaultButton) ?? defaultButton; } Future _onPollCreated(Poll poll) async { + _hidePicker(); + final channel = StreamChannel.maybeOf(context)?.channel; if (channel == null) return; @@ -1005,59 +1043,81 @@ class StreamMessageInputState extends State return allowedTypes.toList(growable: false); } - /// Handle the platform-specific logic for selecting files. - /// - /// On mobile, this will open the file selection bottom sheet. On desktop, - /// this will open the native file system and allow the user to select one - /// or more files. - Future _onAttachmentButtonPressed() async { - final initialPoll = _effectiveController.poll; - final initialAttachments = _effectiveController.attachments; - final allowedTypes = _getAllowedAttachmentPickerTypes(); + /// Toggles the inline attachment picker visibility. + void _onAttachmentButtonPressed() => _isPickerVisible ? _hidePicker() : _showPicker(); - final messageInputTheme = StreamMessageInputTheme.of(context); - final useSystemPicker = widget.useSystemAttachmentPicker || - (messageInputTheme.useSystemAttachmentPicker ?? false); + void _showPicker() { + if (_isPickerVisible) return; - final result = await showStreamAttachmentPickerModalBottomSheet( - context: context, - allowedTypes: allowedTypes, - initialPoll: initialPoll, - pollConfig: widget.pollConfig, - initialAttachments: initialAttachments, - useSystemAttachmentPicker: useSystemPicker, - optionsBuilder: widget.attachmentPickerOptionsBuilder, - ); + setState(() { + _pickerController = StreamAttachmentPickerController( + initialAttachments: _effectiveController.attachments, + initialPoll: _effectiveController.poll, + ); + _pickerController!.addListener(_syncPickerToMessage); + _effectiveController.addListener(_syncMessageToPicker); - if (result == null || result is! StreamAttachmentPickerResult) return; + if (_effectiveFocusNode.hasFocus) { + _effectiveFocusNode.unfocus(); + } + }); + } - // Returns early if the result is already handled by the user. - final resultHandled = await widget.onAttachmentPickerResult?.call(result); - if (resultHandled ?? false) return; + void _hidePicker() { + if (!_isPickerVisible) return; + setState(_disposePickerResources); + } + + void _disposePickerResources() { + _pickerController?.removeListener(_syncPickerToMessage); + _effectiveController.removeListener(_syncMessageToPicker); + _pickerController?.dispose(); + _pickerController = null; + } - void _onAttachmentsPicked(List attachments) { - _effectiveController.attachments = attachments; + /// Copies picker attachments into the message controller when the user + /// selects or removes items in the picker. + void _syncPickerToMessage() { + if (_isSyncingControllers) return; + _isSyncingControllers = true; + + try { + _effectiveController.attachments = _pickerController?.value.attachments ?? []; + } finally { + _isSyncingControllers = false; } + } + + /// Removes picker selections that the user deleted from the composer preview. + void _syncMessageToPicker() { + if (_isSyncingControllers) return; + + final pickerController = _pickerController; + if (pickerController == null) return; + + final messageIds = _effectiveController.attachments.map((a) => a.id).toSet(); + final pickerIds = pickerController.value.attachments.map((a) => a.id).toSet(); + + final removedIds = pickerIds.difference(messageIds); + if (removedIds.isEmpty) return; - void _onAttachmentPickerError(AttachmentPickerError error) { - return widget.onError?.call(error.error, error.stackTrace); + _isSyncingControllers = true; + try { + for (final id in removedIds) { + pickerController.removeAttachmentById(id); + } + } finally { + _isSyncingControllers = false; } + } - return switch (result) { - // Add the attachments to the controller. - AttachmentsPicked() => _onAttachmentsPicked(result.attachments), - // Send the created poll in the channel. - PollCreated() => _onPollCreated(result.poll), - // Handle/Notify returned errors. - AttachmentPickerError() => _onAttachmentPickerError(result), - _ => Future.value(), // Ignore other results. - }; + void _onPickerError(AttachmentPickerError error) { + widget.onError?.call(error.error, error.stackTrace); } Widget _buildTextInput(BuildContext context) { - final margin = (widget.sendButtonLocation == SendButtonLocation.inside - ? const EdgeInsets.only(right: 8) - : EdgeInsets.zero) + + final margin = + (widget.sendButtonLocation == SendButtonLocation.inside ? const EdgeInsets.only(right: 8) : EdgeInsets.zero) + (widget.actionsLocation != ActionsLocation.left || _commandEnabled ? const EdgeInsets.only(left: 8) : EdgeInsets.zero); @@ -1115,8 +1175,7 @@ class StreamMessageInputState extends State decoration: _getInputDecoration(context), textCapitalization: widget.textCapitalization, autocorrect: widget.autoCorrect, - contentInsertionConfiguration: - widget.contentInsertionConfiguration, + contentInsertionConfiguration: widget.contentInsertionConfiguration, ), ), ), @@ -1192,10 +1251,10 @@ class StreamMessageInputState extends State child: Row( mainAxisSize: MainAxisSize.min, children: [ - const StreamSvgIcon( + Icon( + context.streamIcons.thunder, size: 16, color: Colors.white, - icon: StreamSvgIcons.lightning, ), Text( _effectiveController.message.command!.toUpperCase(), @@ -1207,11 +1266,11 @@ class StreamMessageInputState extends State ), ) : (widget.actionsLocation == ActionsLocation.leftInside - ? Row( - mainAxisSize: MainAxisSize.min, - children: [_buildExpandActionsButton(context)], - ) - : null), + ? Row( + mainAxisSize: MainAxisSize.min, + children: [_buildExpandActionsButton(context)], + ) + : null), suffixIconConstraints: const BoxConstraints.tightFor(height: 40), prefixIconConstraints: const BoxConstraints.tightFor(height: 40), suffixIcon: Row( @@ -1223,21 +1282,19 @@ class StreamMessageInputState extends State child: StreamMessageInputIconButton( iconSize: 24, color: _messageInputTheme.actionButtonIdleColor, - icon: const StreamSvgIcon(icon: StreamSvgIcons.closeSmall), + icon: Icon(context.streamIcons.crossSmall), onPressed: _effectiveController.clear, ), ), - if (!_commandEnabled && - widget.actionsLocation == ActionsLocation.rightInside) + if (!_commandEnabled && widget.actionsLocation == ActionsLocation.rightInside) _buildExpandActionsButton(context), - if (widget.sendButtonLocation == SendButtonLocation.inside) - _buildSendButton(context), + if (widget.sendButtonLocation == SendButtonLocation.inside) _buildSendButton(context), ].nonNulls.toList(), ), ).merge(passedDecoration); } - late final _onChangedDebounced = debounce( + late final _onChangedThrottled = throttle( () { if (!mounted) return; @@ -1246,13 +1303,24 @@ class StreamMessageInputState extends State final value = _effectiveController.text.trim(); if (value.isNotEmpty && channel.canUseTypingEvents) { - // Notify the server that the user started typing. channel.keyStroke(_effectiveController.message.parentId).onError( (error, stackTrace) { widget.onError?.call(error!, stackTrace); }, ); } + }, + const Duration(milliseconds: 350), + ); + + late final _onChangedDebounced = debounce( + () { + if (!mounted) return; + + final channel = StreamChannel.maybeOf(context)?.channel; + if (channel == null) return; + + final value = _effectiveController.text.trim(); int actionsLength; if (widget.actionsBuilder != null) { @@ -1306,8 +1374,7 @@ class StreamMessageInputState extends State final _parsedMatch = Uri.tryParse(it.group(0) ?? '')?.withScheme; if (_parsedMatch == null) return false; - return _parsedMatch.host.split('.').last.isValidTLD() && - widget.ogPreviewFilter.call(_parsedMatch, value); + return _parsedMatch.host.split('.').last.isValidTLD() && widget.ogPreviewFilter.call(_parsedMatch, value); }).toList(); // Reset the og attachment if the text doesn't contain any url @@ -1325,19 +1392,20 @@ class StreamMessageInputState extends State final client = StreamChat.maybeOf(context)?.client; if (client == null) return; - _enrichUrlOperation = CancelableOperation.fromFuture( - _enrichUrl(firstMatchedUrl, client), - ).then( - (ogAttachment) { - final attachment = Attachment.fromOGAttachment(ogAttachment); - _effectiveController.setOGAttachment(attachment); - }, - onError: (error, stackTrace) { - // Reset the ogAttachment if there was an error - _effectiveController.clearOGAttachment(); - widget.onError?.call(error, stackTrace); - }, - ); + _enrichUrlOperation = + CancelableOperation.fromFuture( + _enrichUrl(firstMatchedUrl, client), + ).then( + (ogAttachment) { + final attachment = Attachment.fromOGAttachment(ogAttachment); + _effectiveController.setOGAttachment(attachment); + }, + onError: (error, stackTrace) { + // Reset the ogAttachment if there was an error + _effectiveController.clearOGAttachment(); + widget.onError?.call(error, stackTrace); + }, + ); } final _ogAttachmentCache = {}; @@ -1380,16 +1448,17 @@ class StreamMessageInputState extends State message: quotedMessage, messageTheme: _streamChatTheme.otherMessageTheme, onQuotedMessageClear: widget.onQuotedMessageCleared, - attachmentThumbnailBuilders: - widget.quotedMessageAttachmentThumbnailBuilders, + attachmentThumbnailBuilders: widget.quotedMessageAttachmentThumbnailBuilders, ); } Widget _buildAttachments() { final attachments = _effectiveController.attachments; - final nonOGAttachments = attachments.where((it) { - return it.titleLink == null; - }).toList(growable: false); + final nonOGAttachments = attachments + .where((it) { + return it.titleLink == null; + }) + .toList(growable: false); // If there are no attachments, return an empty widget if (nonOGAttachments.isEmpty) return const Empty(); @@ -1410,13 +1479,10 @@ class StreamMessageInputState extends State child: StreamMessageInputAttachmentList( attachments: nonOGAttachments, onRemovePressed: _onAttachmentRemovePressed, - fileAttachmentListBuilder: widget.fileAttachmentListBuilder, - mediaAttachmentListBuilder: widget.mediaAttachmentListBuilder, + attachmentListBuilder: widget.mediaAttachmentListBuilder, voiceRecordingAttachmentBuilder: widget.voiceRecordingAttachmentBuilder, fileAttachmentBuilder: widget.fileAttachmentBuilder, mediaAttachmentBuilder: widget.mediaAttachmentBuilder, - voiceRecordingAttachmentListBuilder: - widget.voiceRecordingAttachmentListBuilder, ), ); } @@ -1442,8 +1508,8 @@ class StreamMessageInputState extends State color: s.isNotEmpty ? _streamChatTheme.colorTheme.disabled : (isCommandOptionsVisible - ? _messageInputTheme.actionButtonColor! - : _messageInputTheme.actionButtonIdleColor!), + ? _messageInputTheme.actionButtonColor! + : _messageInputTheme.actionButtonIdleColor!), onPressed: () async { // Clear the text if the commands options are already visible. if (isCommandOptionsVisible) { @@ -1460,8 +1526,7 @@ class StreamMessageInputState extends State }, ); - return widget.commandButtonBuilder?.call(context, defaultButton) ?? - defaultButton; + return widget.commandButtonBuilder?.call(context, defaultButton) ?? defaultButton; } /// Adds an attachment to the [messageInputController.attachments] map @@ -1490,6 +1555,8 @@ class StreamMessageInputState extends State if (_effectiveController.isSlowModeActive) return; if (!widget.validator(_effectiveController.message)) return; + _hidePicker(); + final streamChannel = StreamChannel.maybeOf(context); if (streamChannel == null) return; @@ -1497,12 +1564,13 @@ class StreamMessageInputState extends State var message = _effectiveController.value; if (!channel.canSendLinks && - _urlRegex.allMatches(message.text ?? '').any((element) => - element.group(0)?.split('.').last.isValidTLD() == true)) { + _urlRegex + .allMatches(message.text ?? '') + .any((element) => element.group(0)?.split('.').last.isValidTLD() == true)) { showInfoBottomSheet( context, - icon: StreamSvgIcon( - icon: StreamSvgIcons.error, + icon: Icon( + context.streamIcons.exclamationCircle1, color: StreamChatTheme.of(context).colorTheme.accentError, size: 24, ), @@ -1549,14 +1617,17 @@ class StreamMessageInputState extends State try { // Note: edited messages which are bounced back with an error needs to be // sent as new messages as the backend doesn't store them. - final resp = await switch (_isEditing && !message.isBouncedWithError) { + // Use message.state directly rather than _isEditing, because the + // controller is reset before this method is called. + final isEditing = !message.state.isInitial; + final resp = await switch (isEditing && !message.isBouncedWithError) { true => channel.updateMessage(message), false => channel.sendMessage(message), }; // We don't want to start the cooldown if an already sent message is // being edited. - if (!_isEditing) { + if (!isEditing) { _effectiveController.startCooldown(channel.getRemainingCooldown()); } @@ -1643,11 +1714,15 @@ class StreamMessageInputState extends State @override void dispose() { - _effectiveController.removeListener(_onChangedDebounced); + _disposePickerResources(); + _effectiveController + ..removeListener(_onChangedThrottled) + ..removeListener(_onChangedDebounced); _controller?.dispose(); _effectiveFocusNode.removeListener(_focusNodeListener); _focusNode?.dispose(); _onChangedDebounced.cancel(); + _onChangedThrottled.cancel(); _audioRecorderController.dispose(); _draftStreamSubscription?.cancel(); super.dispose(); @@ -1682,8 +1757,8 @@ class OGAttachmentPreview extends StatelessWidget { children: [ Padding( padding: const EdgeInsets.all(8), - child: StreamSvgIcon( - icon: StreamSvgIcons.link, + child: Icon( + context.streamIcons.chainLink3, color: colorTheme.accentPrimary, ), ), @@ -1721,7 +1796,7 @@ class OGAttachmentPreview extends StatelessWidget { ), IconButton( visualDensity: VisualDensity.compact, - icon: const StreamSvgIcon(icon: StreamSvgIcons.closeSmall), + icon: Icon(context.streamIcons.crossSmall), onPressed: onDismissPreviewPressed, ), ], diff --git a/packages/stream_chat_flutter/lib/src/message_input/stream_message_input_attachment_list.dart b/packages/stream_chat_flutter/lib/src/message_input/stream_message_input_attachment_list.dart index b3fa41e960..2798a1289a 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/stream_message_input_attachment_list.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/stream_message_input_attachment_list.dart @@ -1,34 +1,35 @@ import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/src/attachment/file_attachment.dart'; import 'package:stream_chat_flutter/src/attachment/thumbnail/media_attachment_thumbnail.dart'; import 'package:stream_chat_flutter/src/attachment/voice_recording_attachment.dart'; import 'package:stream_chat_flutter/src/audio/audio_playlist_controller.dart'; -import 'package:stream_chat_flutter/src/icons/stream_svg_icon.dart'; -import 'package:stream_chat_flutter/src/misc/empty_widget.dart'; +import 'package:stream_chat_flutter/src/indicators/upload_progress_indicator.dart'; import 'package:stream_chat_flutter/src/theme/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/utils/utils.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; /// WidgetBuilder used to build the message input attachment list. /// /// see more: /// - [StreamMessageInputAttachmentList] -typedef AttachmentListBuilder = Widget Function( - BuildContext context, - List attachments, - ValueSetter? onRemovePressed, -); +typedef AttachmentListBuilder = + Widget Function( + BuildContext context, + List attachments, + ValueSetter? onRemovePressed, + ); /// WidgetBuilder used to build the message input attachment item. /// /// see more: /// - [StreamMessageInputAttachmentList] -typedef AttachmentItemBuilder = Widget Function( - BuildContext context, - Attachment attachment, - ValueSetter? onRemovePressed, -); +typedef AttachmentItemBuilder = + Widget Function( + BuildContext context, + Attachment attachment, + ValueSetter? onRemovePressed, + ); /// {@template stream_message_input_attachment_list} /// Widget used to display the list of attachments added to the message input. @@ -37,7 +38,7 @@ typedef AttachmentItemBuilder = Widget Function( /// separately. /// /// You can customize the list of file attachments and media attachments using -/// [fileAttachmentListBuilder] and [mediaAttachmentListBuilder] respectively. +/// [fileAttachmentListBuilder] and [attachmentListBuilder] respectively. /// /// You can also customize the attachment item using [fileAttachmentBuilder] and /// [mediaAttachmentBuilder] respectively. @@ -45,7 +46,7 @@ typedef AttachmentItemBuilder = Widget Function( /// You can override the default action of removing an attachment by providing /// [onRemovePressed]. /// {@endtemplate} -class StreamMessageInputAttachmentList extends StatelessWidget { +class StreamMessageInputAttachmentList extends StatefulWidget { /// {@macro stream_message_input_attachment_list} const StreamMessageInputAttachmentList({ super.key, @@ -54,9 +55,7 @@ class StreamMessageInputAttachmentList extends StatelessWidget { this.fileAttachmentBuilder, this.mediaAttachmentBuilder, this.voiceRecordingAttachmentBuilder, - this.fileAttachmentListBuilder, - this.mediaAttachmentListBuilder, - this.voiceRecordingAttachmentListBuilder, + this.attachmentListBuilder, }); /// List of attachments to display thumbnails for. @@ -73,86 +72,94 @@ class StreamMessageInputAttachmentList extends StatelessWidget { /// Builder used to build the voice recording attachment item. final AttachmentItemBuilder? voiceRecordingAttachmentBuilder; - /// Builder used to build the file attachment list. - final AttachmentListBuilder? fileAttachmentListBuilder; - /// Builder used to build the media attachment list. - final AttachmentListBuilder? mediaAttachmentListBuilder; - - /// Builder used to build the voice recording attachment list. - final AttachmentListBuilder? voiceRecordingAttachmentListBuilder; + final AttachmentListBuilder? attachmentListBuilder; /// Callback called when the remove button is pressed. final ValueSetter? onRemovePressed; + List get _audioAttachments => + attachments.where((it) => it.type == AttachmentType.audio || it.type == AttachmentType.voiceRecording).toList(); + @override - Widget build(BuildContext context) { - final groupedAttachments = attachments.groupListsBy((it) => it.type); - final (:files, :media, :voices) = ( - files: [...?groupedAttachments[AttachmentType.file]], - voices: [...?groupedAttachments[AttachmentType.voiceRecording]], - media: [ - ...?groupedAttachments[AttachmentType.image], - ...?groupedAttachments[AttachmentType.video], - ...?groupedAttachments[AttachmentType.giphy], - ...?groupedAttachments[AttachmentType.audio], - ], - ); + State createState() => _StreamMessageInputAttachmentListState(); +} + +class _StreamMessageInputAttachmentListState extends State { + late List _audioAttachments = widget._audioAttachments; + + StreamAudioPlaylistController? _controller; + late final _scrollController = ScrollController(); + + @override + void initState() { + super.initState(); + _updateController(); + } - // If there are no attachments, return an empty widget. - if (files.isEmpty && media.isEmpty && voices.isEmpty) { - return const Empty(); + @override + void didUpdateWidget( + covariant StreamMessageInputAttachmentList oldWidget, + ) { + super.didUpdateWidget(oldWidget); + final equals = const ListEquality().equals; + final newAudioAttachments = widget._audioAttachments; + if (!equals(newAudioAttachments, _audioAttachments)) { + // If the attachments have changed, update the playlist. + _audioAttachments = newAudioAttachments; + _updateController(); } + if (oldWidget.attachments.length < widget.attachments.length) { + // If an attachment has been added, scroll to the end. + WidgetsBinding.instance.addPostFrameCallback( + (_) { + _scrollController.animateTo( + _scrollController.position.maxScrollExtent, + duration: const Duration(milliseconds: 200), + curve: Curves.easeOut, + ); + }, + ); + } + } - return SingleChildScrollView( - padding: const EdgeInsets.only(top: 6), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - if (media.isNotEmpty) - Flexible( - child: switch (mediaAttachmentListBuilder) { - final builder? => builder(context, media, onRemovePressed), - _ => MessageInputMediaAttachments( - attachments: media, - attachmentBuilder: mediaAttachmentBuilder, - onRemovePressed: onRemovePressed, - ), - }, - ), - if (voices.isNotEmpty) - Flexible( - child: switch (voiceRecordingAttachmentListBuilder) { - final builder? => builder(context, voices, onRemovePressed), - _ => MessageInputVoiceRecordingAttachments( - attachments: voices, - attachmentBuilder: voiceRecordingAttachmentBuilder, - onRemovePressed: onRemovePressed, - ), - }, - ), - if (files.isNotEmpty) - Flexible( - child: switch (fileAttachmentListBuilder) { - final builder? => builder(context, files, onRemovePressed), - _ => MessageInputFileAttachments( - attachments: files, - attachmentBuilder: fileAttachmentBuilder, - onRemovePressed: onRemovePressed, - ), - }, - ), - ].insertBetween( - Divider( - height: 16, - indent: 16, - endIndent: 16, - thickness: 1, - color: StreamChatTheme.of(context).colorTheme.disabled, - ), - ), + void _updateController() { + if (_audioAttachments.isNotEmpty) { + if (_controller == null) { + _controller = StreamAudioPlaylistController(_audioAttachments.toPlaylist()); + _controller!.initialize(); + } else { + _controller!.updatePlaylist(_audioAttachments.toPlaylist()); + } + } else if (_controller != null) { + _controller!.dispose(); + _controller = null; + } + } + + @override + void dispose() { + _controller?.dispose(); + _scrollController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final attachmentsList = widget.attachments.toList(); + + return switch (widget.attachmentListBuilder) { + final builder? => builder(context, attachmentsList, widget.onRemovePressed), + _ => MessageInputMediaAttachments( + scrollController: _scrollController, + attachments: attachmentsList, + attachmentBuilder: widget.mediaAttachmentBuilder, + audioPlaylistController: _controller, + voiceRecordingAttachmentBuilder: widget.voiceRecordingAttachmentBuilder, + fileAttachmentBuilder: widget.fileAttachmentBuilder, + onRemovePressed: widget.onRemovePressed, ), - ); + }; } } @@ -183,156 +190,127 @@ class MessageInputFileAttachments extends StatelessWidget { shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), padding: const EdgeInsets.symmetric(horizontal: 8), - children: attachments.reversed.map( - (attachment) { - // If a custom builder is provided, use it. - final builder = attachmentBuilder; - if (builder != null) { - return builder(context, attachment, onRemovePressed); - } - - // Otherwise, use the default builder. - return StreamFileAttachment( - message: Message(), // Dummy message - file: attachment, - constraints: BoxConstraints.loose(Size( - MediaQuery.of(context).size.width * 0.65, - 56, - )), - trailing: Padding( - padding: const EdgeInsets.all(8), - child: RemoveAttachmentButton( - onPressed: onRemovePressed != null - ? () => onRemovePressed!(attachment) - : null, - ), - ), - ); - }, - ).insertBetween(const SizedBox(height: 8)), + children: attachments.reversed + .map( + (attachment) { + // If a custom builder is provided, use it. + final builder = attachmentBuilder; + if (builder != null) { + return builder(context, attachment, onRemovePressed); + } + + return MessageComposerFileAttachment( + fileTypeIcon: StreamFileTypeIcon.fromMimeType(mimeType: attachment.file?.mediaType?.mimeType ?? ''), + title: attachment.title ?? context.translations.fileText, + subtitle: _FileAttachmentSubtitle(attachment: attachment), + onRemovePressed: onRemovePressed != null ? () => onRemovePressed!(attachment) : null, + ); + }, + ) + .insertBetween(const SizedBox(height: 8)), + ); + } +} + +class _FileAttachmentSubtitle extends StatelessWidget { + const _FileAttachmentSubtitle({ + required this.attachment, + }); + + final Attachment attachment; + + @override + Widget build(BuildContext context) { + final theme = StreamChatTheme.of(context); + final size = attachment.file?.size ?? attachment.extraData['file_size']; + final textStyle = theme.textTheme.footnote.copyWith( + color: theme.colorTheme.textLowEmphasis, + ); + return attachment.uploadState.when( + preparing: () => Text(fileSize(size), style: textStyle), + inProgress: (sent, total) => StreamUploadProgressIndicator( + uploaded: sent, + total: total, + showBackground: false, + textStyle: textStyle, + progressIndicatorColor: theme.colorTheme.accentPrimary, + ), + success: () => Text(fileSize(size), style: textStyle), + failed: (_) => Text( + context.translations.uploadErrorLabel, + style: textStyle, + ), ); } } /// Widget used to display the list of voice recording type attachments added to /// the message input. -class MessageInputVoiceRecordingAttachments extends StatefulWidget { +class MessageInputVoiceRecordingAttachment extends StatelessWidget { /// Creates a new MessageInputVoiceRecordingAttachments widget. - const MessageInputVoiceRecordingAttachments({ + const MessageInputVoiceRecordingAttachment({ super.key, - required this.attachments, - this.attachmentBuilder, + required this.attachment, + required this.index, + required this.controller, this.onRemovePressed, }); - /// List of voice recording type attachments to display thumbnails for. - /// - /// Only attachments of type [AttachmentType.voiceRecording] are supported. - final List attachments; + /// Attachment to display. + final Attachment attachment; - /// Builder used to build the voice recording type attachment item. - final AttachmentItemBuilder? attachmentBuilder; + /// Index of the track in the playlist. + final int index; + + /// Controller to use to control the audio playback. + final StreamAudioPlaylistController controller; /// Callback called when the remove button is pressed. final ValueSetter? onRemovePressed; - @override - State createState() => - _MessageInputVoiceRecordingAttachmentsState(); -} - -class _MessageInputVoiceRecordingAttachmentsState - extends State { - late final _controller = StreamAudioPlaylistController( - widget.attachments.toPlaylist(), - ); - - @override - void initState() { - super.initState(); - _controller.initialize(); - } - - @override - void didUpdateWidget( - covariant MessageInputVoiceRecordingAttachments oldWidget, - ) { - super.didUpdateWidget(oldWidget); - final equals = const ListEquality().equals; - if (!equals(widget.attachments, oldWidget.attachments)) { - // If the attachments have changed, update the playlist. - _controller.updatePlaylist(widget.attachments.toPlaylist()); - } - } - - @override - void dispose() { - _controller.dispose(); - super.dispose(); - } - @override Widget build(BuildContext context) { return ValueListenableBuilder( - valueListenable: _controller, + valueListenable: controller, builder: (context, state, _) { - return MediaQuery.removePadding( - context: context, - // Workaround for the bottom padding issue. - // Link: https://github.com/flutter/flutter/issues/156149 - removeTop: true, - removeBottom: true, - child: ListView.separated( - shrinkWrap: true, - padding: const EdgeInsets.symmetric(horizontal: 8), - physics: const NeverScrollableScrollPhysics(), - itemCount: state.tracks.length, - separatorBuilder: (_, __) => const SizedBox(height: 8), - itemBuilder: (context, index) { - final track = state.tracks[index]; - - return StreamVoiceRecordingAttachment( - track: track, - speed: state.speed, - trailingBuilder: (_, __, ___, ____) { - final attachment = widget.attachments[index]; - return RemoveAttachmentButton( - onPressed: switch (widget.onRemovePressed) { - final callback? => () => callback(attachment), - _ => null, - }, - ); - }, - onTrackPause: _controller.pause, - onChangeSpeed: _controller.setSpeed, - onTrackPlay: () async { - // Play the track directly if it is already loaded. - if (state.currentIndex == index) return _controller.play(); - // Otherwise, load the track first and then play it. - return _controller.skipToItem(index); - }, - // Only allow seeking if the current track is the one being - // interacted with. - onTrackSeekStart: (_) async { - if (state.currentIndex != index) return; - return _controller.pause(); - }, - onTrackSeekEnd: (_) async { - if (state.currentIndex != index) return; - return _controller.play(); - }, - onTrackSeekChanged: (progress) async { - if (state.currentIndex != index) return; - - final duration = track.duration.inMicroseconds; - final seekPosition = (duration * progress).toInt(); - final seekDuration = Duration(microseconds: seekPosition); - - return _controller.seek(seekDuration); - }, - ); - }, - ), + final track = state.tracks.where((it) => it.key == attachment).first; + + return StreamVoiceRecordingAttachment( + title: 'Voice Message', + showTitle: true, + track: track, + speed: state.speed, + onRemovePressed: switch (onRemovePressed) { + final callback? => () => callback(attachment), + _ => null, + }, + onTrackPause: controller.pause, + onChangeSpeed: controller.setSpeed, + onTrackPlay: () async { + // Play the track directly if it is already loaded. + if (state.currentIndex == index) return controller.play(); + // Otherwise, load the track first and then play it. + return controller.skipToItem(index); + }, + // Only allow seeking if the current track is the one being + // interacted with. + onTrackSeekStart: (_) async { + if (state.currentIndex != index) return; + return controller.pause(); + }, + onTrackSeekEnd: (_) async { + if (state.currentIndex != index) return; + return controller.play(); + }, + onTrackSeekChanged: (progress) async { + if (state.currentIndex != index) return; + + final duration = track.duration.inMicroseconds; + final seekPosition = (duration * progress).toInt(); + final seekDuration = Duration(microseconds: seekPosition); + + return controller.seek(seekDuration); + }, ); }, ); @@ -346,8 +324,12 @@ class MessageInputMediaAttachments extends StatelessWidget { const MessageInputMediaAttachments({ super.key, required this.attachments, + this.audioPlaylistController, this.attachmentBuilder, + this.voiceRecordingAttachmentBuilder, + this.fileAttachmentBuilder, this.onRemovePressed, + this.scrollController, }); /// List of media type attachments to display thumbnails for. @@ -359,22 +341,80 @@ class MessageInputMediaAttachments extends StatelessWidget { /// Builder used to build the media type attachment item. final AttachmentItemBuilder? attachmentBuilder; + /// Builder used to build the voice recording type attachment item. + final AttachmentItemBuilder? voiceRecordingAttachmentBuilder; + + /// Builder used to build the file type attachment item. + final AttachmentItemBuilder? fileAttachmentBuilder; + /// Callback called when the remove button is pressed. final ValueSetter? onRemovePressed; + /// Controller to use to control the audio playback. + final StreamAudioPlaylistController? audioPlaylistController; + + /// Scroll controller to use to control the scroll position. + final ScrollController? scrollController; + @override Widget build(BuildContext context) { return SizedBox( - height: 104, + height: 80, child: ListView( + controller: scrollController, scrollDirection: Axis.horizontal, - padding: const EdgeInsets.symmetric(vertical: 2, horizontal: 8), + padding: EdgeInsets.symmetric(horizontal: context.streamSpacing.xs), cacheExtent: 104 * 10, // Cache 10 items ahead. children: attachments.map( (attachment) { + if (attachment.type == AttachmentType.file) { + // If a custom builder is provided, use it. + if (fileAttachmentBuilder case final builder?) { + return builder(context, attachment, onRemovePressed); + } + + return SizedBox( + width: 268, + child: MessageComposerFileAttachment( + fileTypeIcon: StreamFileTypeIcon.fromMimeType(mimeType: attachment.file?.mediaType?.mimeType ?? ''), + title: attachment.title ?? context.translations.fileText, + subtitle: _FileAttachmentSubtitle(attachment: attachment), + onRemovePressed: onRemovePressed != null ? () => onRemovePressed!(attachment) : null, + ), + ); + } + + if (attachment.type == AttachmentType.audio || attachment.type == AttachmentType.voiceRecording) { + // If a custom builder is provided, use it. + if (voiceRecordingAttachmentBuilder case final builder?) { + return builder(context, attachment, onRemovePressed); + } + + if (audioPlaylistController == null) { + return const SizedBox.shrink(); + } + + final hasTrack = audioPlaylistController!.value.tracks.any((it) => it.key == attachment); + + if (!hasTrack) { + return const SizedBox.shrink(); + } + + final trackIndex = audioPlaylistController!.value.tracks.indexWhere((it) => it.key == attachment); + + return SizedBox( + width: 268, + child: MessageInputVoiceRecordingAttachment( + attachment: attachment, + index: trackIndex, + controller: audioPlaylistController!, + onRemovePressed: onRemovePressed, + ), + ); + } + // If a custom builder is provided, use it. - final builder = attachmentBuilder; - if (builder != null) { + if (attachmentBuilder case final builder?) { return builder(context, attachment, onRemovePressed); } @@ -383,7 +423,7 @@ class MessageInputMediaAttachments extends StatelessWidget { onRemovePressed: onRemovePressed, ); }, - ).insertBetween(const SizedBox(width: 8)), + ).toList(), ), ); } @@ -392,8 +432,7 @@ class MessageInputMediaAttachments extends StatelessWidget { /// Widget used to display a media type attachment item. class StreamMediaAttachmentBuilder extends StatelessWidget { /// Creates a new media attachment item. - const StreamMediaAttachmentBuilder( - {super.key, required this.attachment, this.onRemovePressed}); + const StreamMediaAttachmentBuilder({super.key, required this.attachment, this.onRemovePressed}); /// The media attachment to display. final Attachment attachment; @@ -403,46 +442,20 @@ class StreamMediaAttachmentBuilder extends StatelessWidget { @override Widget build(BuildContext context) { - final colorTheme = StreamChatTheme.of(context).colorTheme; - final shape = RoundedRectangleBorder( - side: BorderSide( - color: colorTheme.borders, - strokeAlign: BorderSide.strokeAlignOutside, - ), - borderRadius: BorderRadius.circular(14), - ); + final mediaBadge = attachment.type == AttachmentType.video + ? const StreamMediaBadge(type: MediaBadgeType.video) + : null; return Container( key: Key(attachment.id), - clipBehavior: Clip.hardEdge, - decoration: ShapeDecoration(shape: shape), - child: AspectRatio( - aspectRatio: 1, - child: Stack( - alignment: Alignment.center, - children: [ - StreamMediaAttachmentThumbnail( - media: attachment, - width: double.infinity, - height: double.infinity, - fit: BoxFit.cover, - ), - if (attachment.type == AttachmentType.video) - const Positioned( - left: 8, - bottom: 8, - child: StreamSvgIcon(icon: StreamSvgIcons.videoCall), - ), - Positioned( - top: 8, - right: 8, - child: RemoveAttachmentButton( - onPressed: onRemovePressed != null - ? () => onRemovePressed!(attachment) - : null, - ), - ), - ], + child: MessageComposerMediaFileAttachment( + mediaBadge: mediaBadge, + onRemovePressed: onRemovePressed != null ? () => onRemovePressed!(attachment) : null, + child: StreamMediaAttachmentThumbnail( + media: attachment, + width: double.infinity, + height: double.infinity, + fit: BoxFit.cover, ), ), ); @@ -466,7 +479,7 @@ class RemoveAttachmentButton extends StatelessWidget { onPressed: onPressed, color: colorTheme.barsBg, padding: EdgeInsets.zero, - icon: const StreamSvgIcon(icon: StreamSvgIcons.close), + icon: Icon(context.streamIcons.crossMedium), style: IconButton.styleFrom( minimumSize: const Size(24, 24), tapTargetSize: MaterialTapTargetSize.shrinkWrap, diff --git a/packages/stream_chat_flutter/lib/src/message_input/stream_message_send_button.dart b/packages/stream_chat_flutter/lib/src/message_input/stream_message_send_button.dart index 742846c694..e2bf088e20 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/stream_message_send_button.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/stream_message_send_button.dart @@ -52,12 +52,12 @@ class StreamMessageSendButton extends StatelessWidget { final idleIcon = switch (idleSendIcon) { final idleIcon? => idleIcon, - _ => const StreamSvgIcon(icon: StreamSvgIcons.sendMessage), + _ => Icon(context.streamIcons.paperPlaneTopRight), }; final activeIcon = switch (activeSendIcon) { final activeIcon? => activeIcon, - _ => const StreamSvgIcon(icon: StreamSvgIcons.circleUp), + _ => Icon(context.streamIcons.arrowUp), }; final theme = StreamMessageInputTheme.of(context); diff --git a/packages/stream_chat_flutter/lib/src/message_input/stream_message_text_field.dart b/packages/stream_chat_flutter/lib/src/message_input/stream_message_text_field.dart index 0279be811d..b6e9391a79 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/stream_message_text_field.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/stream_message_text_field.dart @@ -9,12 +9,7 @@ import 'package:flutter/services.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; export 'package:flutter/services.dart' - show - TextInputType, - TextInputAction, - TextCapitalization, - SmartQuotesType, - SmartDashesType; + show TextInputType, TextInputAction, TextCapitalization, SmartQuotesType, SmartDashesType; /// A widget the wraps the [TextField] and adds some StreamChat specifics. class StreamMessageTextField extends StatefulWidget { @@ -119,42 +114,36 @@ class StreamMessageTextField extends StatefulWidget { this.scribbleEnabled = true, this.enableIMEPersonalizedLearning = true, this.contentInsertionConfiguration, - }) : assert(obscuringCharacter.length == 1, ''), - smartDashesType = smartDashesType ?? - (obscureText ? SmartDashesType.disabled : SmartDashesType.enabled), - smartQuotesType = smartQuotesType ?? - (obscureText ? SmartQuotesType.disabled : SmartQuotesType.enabled), - assert(maxLines == null || maxLines > 0, ''), - assert(minLines == null || minLines > 0, ''), - assert( - (maxLines == null) || (minLines == null) || (maxLines >= minLines), - "minLines can't be greater than maxLines", - ), - assert( - !expands || (maxLines == null && minLines == null), - 'minLines and maxLines must be null when expands is true.', - ), - assert(!obscureText || maxLines == 1, - 'Obscured fields cannot be multiline.'), - assert( - maxLength == null || - maxLength == TextField.noMaxLength || - maxLength > 0, - 'maxLength must be null or a positive integer.'), - - // Assert the following instead of setting it directly to avoid - // surprising the user by silently changing the value they set. - assert( - !identical(textInputAction, TextInputAction.newline) || - maxLines == 1 || - !identical(keyboardType, TextInputType.text), - 'Use keyboardType TextInputType.multiline when using ' - 'TextInputAction.newline on a multiline TextField.', - ), - keyboardType = keyboardType ?? - (maxLines == 1 ? TextInputType.text : TextInputType.multiline), - enableInteractiveSelection = - enableInteractiveSelection ?? (!readOnly || !obscureText); + }) : assert(obscuringCharacter.length == 1, ''), + smartDashesType = smartDashesType ?? (obscureText ? SmartDashesType.disabled : SmartDashesType.enabled), + smartQuotesType = smartQuotesType ?? (obscureText ? SmartQuotesType.disabled : SmartQuotesType.enabled), + assert(maxLines == null || maxLines > 0, ''), + assert(minLines == null || minLines > 0, ''), + assert( + (maxLines == null) || (minLines == null) || (maxLines >= minLines), + "minLines can't be greater than maxLines", + ), + assert( + !expands || (maxLines == null && minLines == null), + 'minLines and maxLines must be null when expands is true.', + ), + assert(!obscureText || maxLines == 1, 'Obscured fields cannot be multiline.'), + assert( + maxLength == null || maxLength == TextField.noMaxLength || maxLength > 0, + 'maxLength must be null or a positive integer.', + ), + + // Assert the following instead of setting it directly to avoid + // surprising the user by silently changing the value they set. + assert( + !identical(textInputAction, TextInputAction.newline) || + maxLines == 1 || + !identical(keyboardType, TextInputType.text), + 'Use keyboardType TextInputType.multiline when using ' + 'TextInputAction.newline on a multiline TextField.', + ), + keyboardType = keyboardType ?? (maxLines == 1 ? TextInputType.text : TextInputType.multiline), + enableInteractiveSelection = enableInteractiveSelection ?? (!readOnly || !obscureText); /// Controls the message being edited. /// @@ -522,93 +511,78 @@ class StreamMessageTextField extends StatefulWidget { void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties - ..add(DiagnosticsProperty( - 'controller', controller, - defaultValue: null)) - ..add(DiagnosticsProperty('focusNode', focusNode, - defaultValue: null)) + ..add(DiagnosticsProperty('controller', controller, defaultValue: null)) + ..add(DiagnosticsProperty('focusNode', focusNode, defaultValue: null)) ..add(DiagnosticsProperty('enabled', enabled, defaultValue: null)) - ..add(DiagnosticsProperty('decoration', decoration, - defaultValue: const InputDecoration())) - ..add(DiagnosticsProperty('keyboardType', keyboardType, - defaultValue: TextInputType.text)) + ..add(DiagnosticsProperty('decoration', decoration, defaultValue: const InputDecoration())) + ..add(DiagnosticsProperty('keyboardType', keyboardType, defaultValue: TextInputType.text)) ..add(DiagnosticsProperty('style', style, defaultValue: null)) - ..add(DiagnosticsProperty('autofocus', autofocus, - defaultValue: false)) - ..add(DiagnosticsProperty( - 'obscuringCharacter', obscuringCharacter, - defaultValue: '•')) - ..add(DiagnosticsProperty('obscureText', obscureText, - defaultValue: false)) - ..add(DiagnosticsProperty('autocorrect', autocorrect, - defaultValue: true)) - ..add(EnumProperty('smartDashesType', smartDashesType, - defaultValue: - obscureText ? SmartDashesType.disabled : SmartDashesType.enabled)) - ..add(EnumProperty('smartQuotesType', smartQuotesType, - defaultValue: - obscureText ? SmartQuotesType.disabled : SmartQuotesType.enabled)) - ..add(DiagnosticsProperty('enableSuggestions', enableSuggestions, - defaultValue: true)) + ..add(DiagnosticsProperty('autofocus', autofocus, defaultValue: false)) + ..add(DiagnosticsProperty('obscuringCharacter', obscuringCharacter, defaultValue: '•')) + ..add(DiagnosticsProperty('obscureText', obscureText, defaultValue: false)) + ..add(DiagnosticsProperty('autocorrect', autocorrect, defaultValue: true)) + ..add( + EnumProperty( + 'smartDashesType', + smartDashesType, + defaultValue: obscureText ? SmartDashesType.disabled : SmartDashesType.enabled, + ), + ) + ..add( + EnumProperty( + 'smartQuotesType', + smartQuotesType, + defaultValue: obscureText ? SmartQuotesType.disabled : SmartQuotesType.enabled, + ), + ) + ..add(DiagnosticsProperty('enableSuggestions', enableSuggestions, defaultValue: true)) ..add(IntProperty('maxLines', maxLines, defaultValue: 1)) ..add(IntProperty('minLines', minLines, defaultValue: null)) ..add(DiagnosticsProperty('expands', expands, defaultValue: false)) ..add(IntProperty('maxLength', maxLength, defaultValue: null)) - ..add(EnumProperty( - 'maxLengthEnforcement', maxLengthEnforcement, - defaultValue: null)) - ..add(EnumProperty('textInputAction', textInputAction, - defaultValue: null)) - ..add(EnumProperty( - 'textCapitalization', textCapitalization, - defaultValue: TextCapitalization.none)) - ..add(EnumProperty('textAlign', textAlign, - defaultValue: TextAlign.start)) - ..add(DiagnosticsProperty( - 'textAlignVertical', textAlignVertical, - defaultValue: null)) - ..add(EnumProperty('textDirection', textDirection, - defaultValue: null)) + ..add(EnumProperty('maxLengthEnforcement', maxLengthEnforcement, defaultValue: null)) + ..add(EnumProperty('textInputAction', textInputAction, defaultValue: null)) + ..add( + EnumProperty( + 'textCapitalization', + textCapitalization, + defaultValue: TextCapitalization.none, + ), + ) + ..add(EnumProperty('textAlign', textAlign, defaultValue: TextAlign.start)) + ..add(DiagnosticsProperty('textAlignVertical', textAlignVertical, defaultValue: null)) + ..add(EnumProperty('textDirection', textDirection, defaultValue: null)) ..add(DoubleProperty('cursorWidth', cursorWidth, defaultValue: 2.0)) ..add(DoubleProperty('cursorHeight', cursorHeight, defaultValue: null)) - ..add(DiagnosticsProperty('cursorRadius', cursorRadius, - defaultValue: null)) + ..add(DiagnosticsProperty('cursorRadius', cursorRadius, defaultValue: null)) ..add(ColorProperty('cursorColor', cursorColor, defaultValue: null)) - ..add(DiagnosticsProperty( - 'keyboardAppearance', keyboardAppearance, - defaultValue: null)) - ..add(DiagnosticsProperty( - 'scrollPadding', scrollPadding, - defaultValue: const EdgeInsets.all(20))) - ..add(FlagProperty('selectionEnabled', - value: selectionEnabled, - defaultValue: true, - ifFalse: 'selection disabled')) - ..add(DiagnosticsProperty( - 'selectionControls', selectionControls, - defaultValue: null)) - ..add(DiagnosticsProperty( - 'scrollController', scrollController, - defaultValue: null)) - ..add(DiagnosticsProperty('scrollPhysics', scrollPhysics, - defaultValue: null)) - ..add(DiagnosticsProperty('clipBehavior', clipBehavior, - defaultValue: Clip.hardEdge)) - ..add(DiagnosticsProperty('scribbleEnabled', scribbleEnabled, - defaultValue: true)) - ..add(DiagnosticsProperty( - 'enableIMEPersonalizedLearning', enableIMEPersonalizedLearning, - defaultValue: true)) - ..add(DiagnosticsProperty( - 'contentInsertionConfiguration', contentInsertionConfiguration, - defaultValue: null)); + ..add(DiagnosticsProperty('keyboardAppearance', keyboardAppearance, defaultValue: null)) + ..add( + DiagnosticsProperty('scrollPadding', scrollPadding, defaultValue: const EdgeInsets.all(20)), + ) + ..add( + FlagProperty('selectionEnabled', value: selectionEnabled, defaultValue: true, ifFalse: 'selection disabled'), + ) + ..add(DiagnosticsProperty('selectionControls', selectionControls, defaultValue: null)) + ..add(DiagnosticsProperty('scrollController', scrollController, defaultValue: null)) + ..add(DiagnosticsProperty('scrollPhysics', scrollPhysics, defaultValue: null)) + ..add(DiagnosticsProperty('clipBehavior', clipBehavior, defaultValue: Clip.hardEdge)) + ..add(DiagnosticsProperty('scribbleEnabled', scribbleEnabled, defaultValue: true)) + ..add( + DiagnosticsProperty('enableIMEPersonalizedLearning', enableIMEPersonalizedLearning, defaultValue: true), + ) + ..add( + DiagnosticsProperty( + 'contentInsertionConfiguration', + contentInsertionConfiguration, + defaultValue: null, + ), + ); } } -class _StreamMessageTextFieldState extends State - with RestorationMixin { - StreamMessageInputController get _effectiveController => - widget.controller ?? _controller!.value; +class _StreamMessageTextFieldState extends State with RestorationMixin { + StreamMessageInputController get _effectiveController => widget.controller ?? _controller!.value; StreamRestorableMessageInputController? _controller; @override @@ -653,63 +627,62 @@ class _StreamMessageTextFieldState extends State @override Widget build(BuildContext context) => TextField( - controller: _effectiveController.textFieldController, - focusNode: widget.focusNode, - decoration: widget.decoration, - keyboardType: widget.keyboardType, - textInputAction: widget.textInputAction ?? - (widget.keyboardType == TextInputType.multiline - ? TextInputAction.newline - : TextInputAction.send), - textCapitalization: widget.textCapitalization, - style: widget.style, - strutStyle: widget.strutStyle, - textAlign: widget.textAlign, - textAlignVertical: widget.textAlignVertical, - textDirection: widget.textDirection, - readOnly: widget.readOnly, - showCursor: widget.showCursor, - autofocus: widget.autofocus, - obscuringCharacter: widget.obscuringCharacter, - obscureText: widget.obscureText, - autocorrect: widget.autocorrect, - smartDashesType: widget.smartDashesType, - smartQuotesType: widget.smartQuotesType, - enableSuggestions: widget.enableSuggestions, - maxLines: widget.maxLines, - minLines: widget.minLines, - expands: widget.expands, - maxLength: widget.maxLength, - maxLengthEnforcement: widget.maxLengthEnforcement, - onEditingComplete: widget.onEditingComplete, - onSubmitted: widget.onSubmitted, - onAppPrivateCommand: widget.onAppPrivateCommand, - inputFormatters: widget.inputFormatters, - enabled: widget.enabled, - cursorWidth: widget.cursorWidth, - cursorHeight: widget.cursorHeight, - cursorRadius: widget.cursorRadius, - cursorColor: widget.cursorColor, - selectionHeightStyle: widget.selectionHeightStyle, - selectionWidthStyle: widget.selectionWidthStyle, - keyboardAppearance: widget.keyboardAppearance, - scrollPadding: widget.scrollPadding, - dragStartBehavior: widget.dragStartBehavior, - enableInteractiveSelection: widget.enableInteractiveSelection, - selectionControls: widget.selectionControls, - onTap: widget.onTap, - mouseCursor: widget.mouseCursor, - buildCounter: widget.buildCounter, - scrollController: widget.scrollController, - scrollPhysics: widget.scrollPhysics, - autofillHints: widget.autofillHints, - clipBehavior: widget.clipBehavior, - restorationId: widget.restorationId, - // ignore: deprecated_member_use - scribbleEnabled: widget.scribbleEnabled, - enableIMEPersonalizedLearning: widget.enableIMEPersonalizedLearning, - contentInsertionConfiguration: widget.contentInsertionConfiguration, - ); + controller: _effectiveController.textFieldController, + focusNode: widget.focusNode, + decoration: widget.decoration, + keyboardType: widget.keyboardType, + textInputAction: + widget.textInputAction ?? + (widget.keyboardType == TextInputType.multiline ? TextInputAction.newline : TextInputAction.send), + textCapitalization: widget.textCapitalization, + style: widget.style, + strutStyle: widget.strutStyle, + textAlign: widget.textAlign, + textAlignVertical: widget.textAlignVertical, + textDirection: widget.textDirection, + readOnly: widget.readOnly, + showCursor: widget.showCursor, + autofocus: widget.autofocus, + obscuringCharacter: widget.obscuringCharacter, + obscureText: widget.obscureText, + autocorrect: widget.autocorrect, + smartDashesType: widget.smartDashesType, + smartQuotesType: widget.smartQuotesType, + enableSuggestions: widget.enableSuggestions, + maxLines: widget.maxLines, + minLines: widget.minLines, + expands: widget.expands, + maxLength: widget.maxLength, + maxLengthEnforcement: widget.maxLengthEnforcement, + onEditingComplete: widget.onEditingComplete, + onSubmitted: widget.onSubmitted, + onAppPrivateCommand: widget.onAppPrivateCommand, + inputFormatters: widget.inputFormatters, + enabled: widget.enabled, + cursorWidth: widget.cursorWidth, + cursorHeight: widget.cursorHeight, + cursorRadius: widget.cursorRadius, + cursorColor: widget.cursorColor, + selectionHeightStyle: widget.selectionHeightStyle, + selectionWidthStyle: widget.selectionWidthStyle, + keyboardAppearance: widget.keyboardAppearance, + scrollPadding: widget.scrollPadding, + dragStartBehavior: widget.dragStartBehavior, + enableInteractiveSelection: widget.enableInteractiveSelection, + selectionControls: widget.selectionControls, + onTap: widget.onTap, + mouseCursor: widget.mouseCursor, + buildCounter: widget.buildCounter, + scrollController: widget.scrollController, + scrollPhysics: widget.scrollPhysics, + autofillHints: widget.autofillHints, + clipBehavior: widget.clipBehavior, + restorationId: widget.restorationId, + // ignore: deprecated_member_use + scribbleEnabled: widget.scribbleEnabled, + enableIMEPersonalizedLearning: widget.enableIMEPersonalizedLearning, + contentInsertionConfiguration: widget.contentInsertionConfiguration, + ); @override void dispose() { diff --git a/packages/stream_chat_flutter/lib/src/message_input/tld.dart b/packages/stream_chat_flutter/lib/src/message_input/tld.dart index 2bfa52578f..f340af4719 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/tld.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/tld.dart @@ -2,9 +2,7 @@ extension TLDString on String { /// Returns true if the string is a valid TLD. bool isValidTLD() => - isNotEmpty && - tlds.containsKey(this[0].toUpperCase()) && - tlds[this[0].toUpperCase()]!.contains(toUpperCase()); + isNotEmpty && tlds.containsKey(this[0].toUpperCase()) && tlds[this[0].toUpperCase()]!.contains(toUpperCase()); } /// List of valid TLDs. diff --git a/packages/stream_chat_flutter/lib/src/message_list_view/message_details.dart b/packages/stream_chat_flutter/lib/src/message_list_view/message_details.dart index 36ffd887bf..cc5c99eccc 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view/message_details.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view/message_details.dart @@ -13,10 +13,8 @@ class MessageDetails { this.index, ) { isMyMessage = message.user?.id == currentUserId; - isLastUser = index + 1 < messages.length && - message.user?.id == messages[index + 1].user?.id; - isNextUser = - index - 1 >= 0 && message.user!.id == messages[index - 1].user?.id; + isLastUser = index + 1 < messages.length && message.user?.id == messages[index + 1].user?.id; + isNextUser = index - 1 >= 0 && message.user!.id == messages[index - 1].user?.id; } /// True if the message belongs to the current user diff --git a/packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart b/packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart index 5b5be446db..fdf1717e35 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart @@ -1,4 +1,3 @@ -// ignore_for_file: lines_longer_than_80_chars import 'dart:async'; import 'dart:math'; @@ -10,11 +9,11 @@ import 'package:stream_chat_flutter/src/message_list_view/floating_date_divider. import 'package:stream_chat_flutter/src/message_list_view/loading_indicator.dart'; import 'package:stream_chat_flutter/src/message_list_view/mlv_utils.dart'; import 'package:stream_chat_flutter/src/message_list_view/thread_separator.dart'; -import 'package:stream_chat_flutter/src/message_list_view/unread_indicator_button.dart'; import 'package:stream_chat_flutter/src/message_list_view/unread_messages_separator.dart'; import 'package:stream_chat_flutter/src/message_widget/ephemeral_message.dart'; import 'package:stream_chat_flutter/src/misc/empty_widget.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; /// Spacing Types (These are properties of a message to help inform the decision /// of how much space / which widget to build after it) @@ -36,6 +35,21 @@ enum SpacingType { defaultSpacing, } +/// Signature for a function that builds a message widget from its +/// [StreamMessageWidgetProps]. +/// +/// Receives the [BuildContext], the [Message] data, and the pre-configured +/// [StreamMessageWidgetProps] with all list-level callbacks already wired in. +/// +/// Use [DefaultStreamMessage] to build the default UI, optionally modifying +/// the props via [StreamMessageWidgetProps.copyWith] first. +typedef StreamMessageWidgetBuilder = + Widget Function( + BuildContext context, + Message message, + StreamMessageWidgetProps defaultProps, + ); + /// {@template streamMessageListView} /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/packages/stream_chat_flutter/screenshots/message_listview.png) /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/packages/stream_chat_flutter/screenshots/message_listview_paint.png) @@ -84,7 +98,7 @@ class StreamMessageListView extends StatefulWidget { const StreamMessageListView({ super.key, this.showScrollToBottom = true, - this.showUnreadCountOnScrollToBottom = false, + this.showUnreadCountOnScrollToBottom = true, this.scrollToBottomBuilder, this.showUnreadIndicator = true, this.unreadIndicatorBuilder, @@ -94,6 +108,13 @@ class StreamMessageListView extends StatefulWidget { this.parentMessage, this.threadBuilder, this.onThreadTap, + this.onEditMessageTap, + this.onReplyTap, + this.onUserAvatarTap, + this.onReactionsTap, + this.onQuotedMessageTap, + this.onMessageLinkTap, + this.onUserMentionTap, this.dateDividerBuilder, this.floatingDateDividerBuilder, // we need to use ClampingScrollPhysics to avoid the list view to bounce @@ -138,8 +159,22 @@ class StreamMessageListView extends StatefulWidget { /// dismiss the keyboard automatically. final ScrollViewKeyboardDismissBehavior keyboardDismissBehavior; - /// {@macro messageBuilder} - final MessageBuilder? messageBuilder; + /// Optional builder for per-instance message customization. + /// + /// When set, this builder is called for each regular message with + /// pre-configured [StreamMessageWidgetProps] that have all list-level + /// callbacks already wired in. Use [StreamMessageWidgetProps.copyWith] + /// to modify properties, and [DefaultStreamMessage] to build the default + /// widget. + /// + /// For app-wide customization, use [StreamComponentFactory] instead. + final StreamMessageWidgetBuilder? messageBuilder; + + /// Optional builder for the parent message at the top of a thread. + /// + /// Works the same as [messageBuilder] but is called for the parent + /// message only. + final StreamMessageWidgetBuilder? parentMessageBuilder; /// Whether the view scrolls in the reading direction. /// @@ -168,9 +203,6 @@ class StreamMessageListView extends StatefulWidget { /// {@macro moderatedMessageBuilder} final ModeratedMessageBuilder? moderatedMessageBuilder; - /// {@macro parentMessageBuilder} - final ParentMessageBuilder? parentMessageBuilder; - /// {@macro threadBuilder} final ThreadBuilder? threadBuilder; @@ -180,6 +212,46 @@ class StreamMessageListView extends StatefulWidget { /// built using [threadBuilder] final ThreadTapCallback? onThreadTap; + /// {@macro onEditMessageTap} + /// + /// If provided, the inline edit flow is used instead of the edit bottom sheet. + final void Function(Message)? onEditMessageTap; + + /// Called when the reply action is triggered on a message. + /// + /// Forwarded to each [StreamMessageWidget] in the list. + final void Function(Message)? onReplyTap; + + /// Called when a user avatar is tapped. + /// + /// Forwarded to each [StreamMessageWidget] in the list. + final void Function(User)? onUserAvatarTap; + + /// Called when the message reactions are tapped. + /// + /// Forwarded to each [StreamMessageWidget] in the list. + final void Function(Message)? onReactionsTap; + + /// Called when a quoted message is tapped. + /// + /// When provided, this callback is forwarded to each + /// [StreamMessageWidget] in the list. + /// + /// When null (the default), tapping a quoted message scrolls to it in + /// the list, loading it if necessary. + final void Function(Message quotedMessage)? onQuotedMessageTap; + + /// Called when a link is tapped in message text. + /// + /// Receives the [Message] containing the link and the tapped URL. + /// Forwarded to each [StreamMessageWidget] in the list. + final void Function(Message message, String url)? onMessageLinkTap; + + /// Called when a user mention is tapped in message text. + /// + /// Forwarded to each [StreamMessageWidget] in the list. + final void Function(User user)? onUserMentionTap; + /// If true will show a scroll to bottom button when /// the scroll offset is not zero final bool showScrollToBottom; @@ -207,7 +279,8 @@ class StreamMessageListView extends StatefulWidget { final Widget Function( int unreadCount, Future Function(int) scrollToBottomDefaultTapAction, - )? scrollToBottomBuilder; + )? + scrollToBottomBuilder; /// If true will show an indicator with number of unread messages /// that will scroll to latest read message when tapped and mark @@ -323,12 +396,10 @@ class StreamMessageListView extends StatefulWidget { final OnMessageLongPress? onMessageLongPress; /// Builder used to build the thread separator in case it's a thread view - final Function(BuildContext context, Message parentMessage)? - threadSeparatorBuilder; + final Function(BuildContext context, Message parentMessage)? threadSeparatorBuilder; /// Builder used to build the unread message separator - final Widget Function(BuildContext context, int unreadCount)? - unreadMessagesSeparatorBuilder; + final Widget Function(BuildContext context, int unreadCount)? unreadMessagesSeparatorBuilder; /// A [MessageListController] allows pagination. /// @@ -394,8 +465,7 @@ class _StreamMessageListViewState extends State { late final _defaultController = MessageListController(); - MessageListController get _messageListController => - widget.messageListController ?? _defaultController; + MessageListController get _messageListController => widget.messageListController ?? _defaultController; StreamSubscription? _messageNewListener; StreamSubscription? _userReadListener; @@ -407,10 +477,8 @@ class _StreamMessageListViewState extends State { super.initState(); _scrollController = widget.scrollController ?? ItemScrollController(); - _itemPositionListener = - widget.itemPositionListener ?? ItemPositionsListener.create(); - _itemPositionListener.itemPositions - .addListener(_handleItemPositionsChanged); + _itemPositionListener = widget.itemPositionListener ?? ItemPositionsListener.create(); + _itemPositionListener.itemPositions.addListener(_handleItemPositionsChanged); _getOnThreadTap(); } @@ -448,14 +516,12 @@ class _StreamMessageListViewState extends State { ); } - _messageNewListener = - streamChannel!.channel.on(EventType.messageNew).listen((event) { + _messageNewListener = streamChannel!.channel.on(EventType.messageNew).listen((event) { if (_upToDate) { _bottomPaginationActive = false; } if (event.message?.parentId == widget.parentMessage?.id && - event.message!.user!.id == - streamChannel!.channel.client.state.currentUser!.id) { + event.message!.user!.id == streamChannel!.channel.client.state.currentUser!.id) { setState(() => unreadCount = 0); WidgetsBinding.instance.addPostFrameCallback((_) { @@ -481,49 +547,55 @@ class _StreamMessageListViewState extends State { debouncedMarkThreadRead.cancel(); _messageNewListener?.cancel(); _userReadListener?.cancel(); - _itemPositionListener.itemPositions - .removeListener(_handleItemPositionsChanged); + _itemPositionListener.itemPositions.removeListener(_handleItemPositionsChanged); super.dispose(); } @override Widget build(BuildContext context) { + // TODO: Revisit this nested Portal setup during desktop reactions refactor + // and remove the extra layer if a dedicated message-list portal label is + // no longer required. return Portal( labels: const [kPortalMessageListViewLabel], - child: ScaffoldMessenger( - child: MessageListCore( - paginationLimit: widget.paginationLimit, - messageFilter: widget.messageFilter, - loadingBuilder: widget.loadingBuilder ?? - (context) => const Center( - child: CircularProgressIndicator.adaptive(), - ), - emptyBuilder: widget.emptyBuilder ?? - (context) => Center( - child: Text( - context.translations.emptyChatMessagesText, - style: _streamTheme.textTheme.footnote.copyWith( - color: _streamTheme.colorTheme.textHighEmphasis - // ignore: deprecated_member_use - .withOpacity(0.5), - ), + child: Portal( + child: ScaffoldMessenger( + child: MessageListCore( + paginationLimit: widget.paginationLimit, + messageFilter: widget.messageFilter, + loadingBuilder: + widget.loadingBuilder ?? + (context) => const Center( + child: CircularProgressIndicator.adaptive(), + ), + emptyBuilder: + widget.emptyBuilder ?? + (context) => Center( + child: Text( + context.translations.emptyChatMessagesText, + style: _streamTheme.textTheme.footnote.copyWith( + color: _streamTheme.colorTheme.textHighEmphasis + // ignore: deprecated_member_use + .withOpacity(0.5), ), ), - messageListBuilder: widget.messageListBuilder ?? - (context, list) => _buildListView(list), - messageListController: _messageListController, - parentMessage: widget.parentMessage, - errorBuilder: widget.errorBuilder ?? - (BuildContext context, Object error) => Center( - child: Text( - context.translations.genericErrorText, - style: _streamTheme.textTheme.footnote.copyWith( - color: _streamTheme.colorTheme.textHighEmphasis - // ignore: deprecated_member_use - .withOpacity(0.5), - ), + ), + messageListBuilder: widget.messageListBuilder ?? (context, list) => _buildListView(list), + messageListController: _messageListController, + parentMessage: widget.parentMessage, + errorBuilder: + widget.errorBuilder ?? + (BuildContext context, Object error) => Center( + child: Text( + context.translations.genericErrorText, + style: _streamTheme.textTheme.footnote.copyWith( + color: _streamTheme.colorTheme.textHighEmphasis + // ignore: deprecated_member_use + .withOpacity(0.5), ), ), + ), + ), ), ), ); @@ -543,8 +615,7 @@ class _StreamMessageListViewState extends State { final first = _itemPositionListener.itemPositions.value.first; final diff = newMessagesListLength - _messageListLength!; if (diff > 0) { - if (messages[0].user?.id != - streamChannel!.channel.client.state.currentUser?.id) { + if (messages[0].user?.id != streamChannel!.channel.client.state.currentUser?.id) { initialIndex = first.index + diff; initialAlignment = first.itemLeadingEdge; } @@ -555,10 +626,11 @@ class _StreamMessageListViewState extends State { _messageListLength = newMessagesListLength; - final itemCount = messages.length + // total messages - 2 + // top + bottom loading indicator - 2 + // header + footer - 1 // parent message + final itemCount = + messages.length + // total messages + 2 + // top + bottom loading indicator + 2 + // header + footer + 1 // parent message ; final child = Stack( @@ -666,7 +738,6 @@ class _StreamMessageListViewState extends State { // BottomLoader -> 1 (count-7) // Separator(Footer -> 8??30) -> 0 (count-8) // Footer -> 0 (count-8) - separatorBuilder: (context, i) { Widget maybeBuildWithUnreadMessagesSeparator({ required Message message, @@ -693,8 +764,7 @@ class _StreamMessageListViewState extends State { } if (widget.threadSeparatorBuilder != null) { - return widget.threadSeparatorBuilder! - .call(context, widget.parentMessage!); + return widget.threadSeparatorBuilder!.call(context, widget.parentMessage!); } return ThreadSeparator( @@ -702,9 +772,7 @@ class _StreamMessageListViewState extends State { ); } if (i == itemCount - 3) { - if (widget.reverse - ? widget.headerBuilder == null - : widget.footerBuilder == null) { + if (widget.reverse ? widget.headerBuilder == null : widget.footerBuilder == null) { if (messages.isNotEmpty) { final message = messages.last; return maybeBuildWithUnreadMessagesSeparator( @@ -719,9 +787,7 @@ class _StreamMessageListViewState extends State { return const SizedBox(height: 8); } if (i == 0) { - if (widget.reverse - ? widget.footerBuilder == null - : widget.headerBuilder == null) { + if (widget.reverse ? widget.footerBuilder == null : widget.headerBuilder == null) { return const SizedBox(height: 30); } return const SizedBox(height: 8); @@ -740,8 +806,7 @@ class _StreamMessageListViewState extends State { Widget separator; - final isPartOfThread = message.replyCount! > 0 || - message.showInChannel == true; + final isPartOfThread = message.replyCount! > 0 || message.showInChannel == true; final createdAt = Jiffy.parseFromDateTime( message.createdAt.toLocal(), @@ -759,8 +824,7 @@ class _StreamMessageListViewState extends State { unit: Unit.minute, ); - final isNextUserSame = - message.user!.id == nextMessage.user?.id; + final isNextUserSame = message.user!.id == nextMessage.user?.id; final isDeleted = message.isDeleted; final spacingRules = [ @@ -795,16 +859,13 @@ class _StreamMessageListViewState extends State { if (i == itemCount - 2) { if (widget.reverse) { - return widget.headerBuilder?.call(context) ?? - const Empty(); + return widget.headerBuilder?.call(context) ?? const Empty(); } else { - return widget.footerBuilder?.call(context) ?? - const Empty(); + return widget.footerBuilder?.call(context) ?? const Empty(); } } - final indicatorBuilder = - widget.paginationLoadingIndicatorBuilder; + final indicatorBuilder = widget.paginationLoadingIndicatorBuilder; if (i == itemCount - 3) { return LoadingIndicator( @@ -828,11 +889,9 @@ class _StreamMessageListViewState extends State { if (i == 0) { if (widget.reverse) { - return widget.footerBuilder?.call(context) ?? - const Empty(); + return widget.footerBuilder?.call(context) ?? const Empty(); } else { - return widget.headerBuilder?.call(context) ?? - const Empty(); + return widget.headerBuilder?.call(context) ?? const Empty(); } } @@ -892,10 +951,8 @@ class _StreamMessageListViewState extends State { ], ); - final backgroundColor = - StreamMessageListViewTheme.of(context).backgroundColor; - final backgroundImage = - StreamMessageListViewTheme.of(context).backgroundImage; + final backgroundColor = StreamMessageListViewTheme.of(context).backgroundColor; + final backgroundImage = StreamMessageListViewTheme.of(context).backgroundImage; if (backgroundColor != null || backgroundImage != null) { return DecoratedBox( @@ -913,15 +970,14 @@ class _StreamMessageListViewState extends State { Widget _buildUnreadMessagesSeparator(int unreadCount) { final unreadMessagesSeparator = widget.unreadMessagesSeparatorBuilder?.call(context, unreadCount) ?? - UnreadMessagesSeparator(unreadCount: unreadCount); + UnreadMessagesSeparator(unreadCount: unreadCount); return unreadMessagesSeparator; } Future _paginateData( StreamChannelState? channel, QueryDirection direction, - ) => - _messageListController.paginateData!(direction: direction); + ) => _messageListController.paginateData!(direction: direction); Future scrollToBottomDefaultTapAction(int unreadCount) async { // If the channel is not up to date, we need to reload it before scrolling @@ -992,99 +1048,47 @@ class _StreamMessageListViewState extends State { return switch (widget.dateDividerBuilder) { final builder? => builder(createdAt), _ => Padding( - padding: const EdgeInsets.symmetric(vertical: 12), - child: StreamDateDivider(dateTime: createdAt), - ), + padding: const EdgeInsets.symmetric(vertical: 12), + child: StreamDateDivider(dateTime: createdAt), + ), }; } Widget buildParentMessage( Message message, ) { - final isMyMessage = - message.user!.id == StreamChat.of(context).currentUser!.id; - final isOnlyEmoji = message.text?.isOnlyEmoji ?? false; - - final hasFileAttachment = - message.attachments.any((it) => it.type == AttachmentType.file); - - final hasUrlAttachment = - message.attachments.any((it) => it.type == AttachmentType.urlPreview); - - final attachmentBorderRadius = hasUrlAttachment - ? 8.0 - : hasFileAttachment - ? 12.0 - : 14.0; - - final borderSide = isOnlyEmoji ? BorderSide.none : null; - - final defaultMessageWidget = StreamMessageWidget( + final parentMessageProps = StreamMessageWidgetProps( message: message, - reverse: isMyMessage, - showUsername: !isMyMessage, - showReactions: !message.isDeleted && !message.state.isDeletingFailed, - showReactionPicker: !message.isDeleted && !message.state.isDeletingFailed, - showReplyMessage: false, - showResendMessage: false, - showThreadReplyMessage: false, - showCopyMessage: false, - showDeleteMessage: false, - showEditMessage: false, - showMarkUnreadMessage: false, - padding: const EdgeInsets.all(8), - showSendingIndicator: false, - attachmentPadding: EdgeInsets.all( - hasUrlAttachment - ? 8 - : hasFileAttachment - ? 4 - : 2, - ), - attachmentShape: RoundedRectangleBorder( - side: BorderSide( - color: _streamTheme.colorTheme.borders, - strokeAlign: BorderSide.strokeAlignOutside, - ), - borderRadius: BorderRadius.only( - topLeft: Radius.circular(attachmentBorderRadius), - bottomLeft: isMyMessage - ? Radius.circular(attachmentBorderRadius) - : Radius.zero, - topRight: Radius.circular(attachmentBorderRadius), - bottomRight: isMyMessage - ? Radius.zero - : Radius.circular(attachmentBorderRadius), - ), - ), - borderRadiusGeometry: BorderRadius.only( - topLeft: const Radius.circular(16), - bottomLeft: isMyMessage ? const Radius.circular(16) : Radius.zero, - topRight: const Radius.circular(16), - bottomRight: isMyMessage ? Radius.zero : const Radius.circular(16), - ), - textPadding: EdgeInsets.symmetric( - vertical: 8, - horizontal: isOnlyEmoji ? 0 : 16.0, - ), - borderSide: borderSide, - showUserAvatar: isMyMessage ? DisplayWidget.gone : DisplayWidget.show, - messageTheme: isMyMessage - ? _streamTheme.ownMessageTheme - : _streamTheme.otherMessageTheme, + onThreadTap: _onThreadTap, onMessageTap: widget.onMessageTap, onMessageLongPress: widget.onMessageLongPress, + onEditMessageTap: widget.onEditMessageTap, + onReplyTap: widget.onReplyTap, + onUserAvatarTap: widget.onUserAvatarTap, + onReactionsTap: widget.onReactionsTap, + onQuotedMessageTap: widget.onQuotedMessageTap, + onMessageLinkTap: widget.onMessageLinkTap, + onUserMentionTap: widget.onUserMentionTap, ); - if (widget.parentMessageBuilder != null) { - return widget.parentMessageBuilder!.call( - context, - widget.parentMessage, - defaultMessageWidget, - ); - } + final userId = StreamChat.of(context).currentUser!.id; + final isMyMessage = message.user?.id == userId; - return defaultMessageWidget; + final isInThread = widget.parentMessage != null; + + return StreamMessagePlacement( + data: StreamMessagePlacementData( + stackPosition: .single, + alignment: isMyMessage ? .end : .start, + listKind: isInThread ? .thread : .channel, + ), + child: Builder( + builder: (context) => switch (widget.parentMessageBuilder) { + final builder? => builder.call(context, message, parentMessageProps), + _ => StreamMessageWidget.fromProps(props: parentMessageProps), + }, + ), + ); } Widget _buildScrollToBottom() { @@ -1103,60 +1107,45 @@ class _StreamMessageListViewState extends State { scrollToBottomDefaultTapAction, ); } - final showUnreadCount = unreadCount > 0 && - streamChannel!.channel.state!.members.any((e) => - e.userId == - streamChannel!.channel.client.state.currentUser!.id); + final showUnreadCount = + unreadCount > 0 && + streamChannel!.channel.state!.members.any( + (e) => e.userId == streamChannel!.channel.client.state.currentUser!.id, + ); return Positioned( - bottom: 8, - right: 8, + bottom: 16, + right: 16, width: 40, height: 40, child: Stack( clipBehavior: Clip.none, children: [ FloatingActionButton( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(24), + ), backgroundColor: _streamTheme.colorTheme.barsBg, onPressed: () async { return scrollToBottomDefaultTapAction(unreadCount); }, child: widget.reverse - ? StreamSvgIcon( - icon: StreamSvgIcons.down, + ? Icon( + context.streamIcons.arrowDown, color: _streamTheme.colorTheme.textHighEmphasis, ) - : StreamSvgIcon( - icon: StreamSvgIcons.up, + : Icon( + context.streamIcons.arrowUp, color: _streamTheme.colorTheme.textHighEmphasis, ), ), if (showUnreadCount && widget.showUnreadCountOnScrollToBottom) Positioned( - left: 0, - right: 0, - top: -10, - child: Center( - child: Material( - borderRadius: BorderRadius.circular(8), - color: - StreamChatTheme.of(context).colorTheme.accentPrimary, - child: Padding( - padding: const EdgeInsets.only( - left: 5, - right: 5, - top: 2, - bottom: 2, - ), - child: Text( - '${unreadCount > 99 ? '99+' : unreadCount}', - style: const TextStyle( - fontSize: 11, - color: Colors.white, - ), - ), - ), - ), + right: -4, + top: -4, + child: StreamBadgeNotification( + label: '${unreadCount > 99 ? '99+' : unreadCount}', + size: StreamBadgeNotificationSize.sm, ), ), ], @@ -1212,215 +1201,75 @@ class _StreamMessageListViewState extends State { return buildModeratedMessage(message); } + final messageWidgetProps = StreamMessageWidgetProps( + message: message, + onThreadTap: _onThreadTap, + onMessageTap: widget.onMessageTap, + onMessageLongPress: widget.onMessageLongPress, + onEditMessageTap: widget.onEditMessageTap, + onReplyTap: widget.onReplyTap, + onUserAvatarTap: widget.onUserAvatarTap, + onReactionsTap: widget.onReactionsTap, + onMessageLinkTap: widget.onMessageLinkTap, + onUserMentionTap: widget.onUserMentionTap, + onQuotedMessageTap: switch (widget.onQuotedMessageTap) { + final onTap? => onTap, + _ => (quotedMessage) async { + final quotedMessageId = quotedMessage.id; + if (messages.map((e) => e.id).contains(quotedMessageId)) { + final index = messages.indexWhere((m) => m.id == quotedMessageId); + _scrollController?.scrollTo( + index: index + 2, // +2 to account for loader and footer + duration: const Duration(seconds: 1), + curve: Curves.easeInOut, + alignment: 0.1, + ); + } else { + await streamChannel!.loadChannelAtMessage(quotedMessageId).then((_) async { + initialIndex = 21; // 19 + 2 | 19 is the index of the message + initialAlignment = 0.1; + }); + } + }, + }, + ); + final userId = StreamChat.of(context).currentUser!.id; final isMyMessage = message.user?.id == userId; final nextMessage = index - 1 >= 0 ? messages[index - 1] : null; - final isNextUserSame = - nextMessage != null && message.user!.id == nextMessage.user!.id; - - var hasTimeDiff = false; - if (nextMessage != null) { - final createdAt = Jiffy.parseFromDateTime(message.createdAt.toLocal()); - final nextCreatedAt = Jiffy.parseFromDateTime( - nextMessage.createdAt.toLocal(), - ); - - hasTimeDiff = !createdAt.isSame(nextCreatedAt, unit: Unit.minute); - } - - final hasVoiceRecordingAttachment = message.attachments - .any((it) => it.type == AttachmentType.voiceRecording); - - final hasFileAttachment = - message.attachments.any((it) => it.type == AttachmentType.file); - - final hasUrlAttachment = - message.attachments.any((it) => it.type == AttachmentType.urlPreview); + final prevMessage = index + 1 < messages.length ? messages[index + 1] : null; - final isThreadMessage = - message.parentId != null && message.showInChannel == true; + final stackPosition = computeStackPosition(message: message, previous: prevMessage, next: nextMessage); - final hasReplies = message.replyCount! > 0; - - final attachmentBorderRadius = hasUrlAttachment - ? 8.0 - : hasFileAttachment - ? 12.0 - : 14.0; - - final showTimeStamp = (!isThreadMessage || _isThreadConversation) && - !hasReplies && - (hasTimeDiff || !isNextUserSame); - - final showUsername = !isMyMessage && - (!isThreadMessage || _isThreadConversation) && - !hasReplies && - (hasTimeDiff || !isNextUserSame); - - final showMarkUnread = streamChannel?.channel.config?.readEvents == true && - !isMyMessage && - (!isThreadMessage || _isThreadConversation); - - final showUserAvatar = isMyMessage - ? DisplayWidget.gone - : (hasTimeDiff || !isNextUserSame) - ? DisplayWidget.show - : DisplayWidget.hide; - - final showSendingIndicator = - isMyMessage && (index == 0 || hasTimeDiff || !isNextUserSame); - - final showInChannelIndicator = !_isThreadConversation && isThreadMessage; - final showThreadReplyIndicator = !_isThreadConversation && hasReplies; - final isOnlyEmoji = message.text?.isOnlyEmoji ?? false; - - final borderSide = isOnlyEmoji ? BorderSide.none : null; + final isInThread = widget.parentMessage != null; - Widget messageWidget = StreamMessageWidget( - message: message, - reverse: isMyMessage, - showReactions: !message.isDeleted && !message.state.isDeletingFailed, - showReactionPicker: !message.isDeleted && !message.state.isDeletingFailed, - padding: const EdgeInsets.symmetric(horizontal: 8), - showInChannelIndicator: showInChannelIndicator, - showThreadReplyIndicator: showThreadReplyIndicator, - showUsername: showUsername, - showTimestamp: showTimeStamp, - showSendingIndicator: showSendingIndicator, - showUserAvatar: showUserAvatar, - showMarkUnreadMessage: showMarkUnread, - onQuotedMessageTap: (quotedMessageId) async { - if (messages.map((e) => e.id).contains(quotedMessageId)) { - final index = messages.indexWhere((m) => m.id == quotedMessageId); - _scrollController?.scrollTo( - index: index + 2, // +2 to account for loader and footer - duration: const Duration(seconds: 1), - curve: Curves.easeInOut, - alignment: 0.1, - ); - } else { - await streamChannel! - .loadChannelAtMessage(quotedMessageId) - .then((_) async { - initialIndex = 21; // 19 + 2 | 19 is the index of the message - initialAlignment = 0.1; - }); - } - }, - showEditMessage: isMyMessage, - showDeleteMessage: isMyMessage, - showThreadReplyMessage: - !isThreadMessage && streamChannel?.channel.canSendReply == true, - showFlagButton: !isMyMessage, - borderSide: borderSide, - onThreadTap: _onThreadTap, - attachmentShape: RoundedRectangleBorder( - side: BorderSide( - color: _streamTheme.colorTheme.borders, - strokeAlign: BorderSide.strokeAlignOutside, - ), - borderRadius: BorderRadius.only( - topLeft: Radius.circular(attachmentBorderRadius), - bottomLeft: isMyMessage - ? Radius.circular(attachmentBorderRadius) - : Radius.circular( - (hasTimeDiff || !isNextUserSame) && - !(hasReplies || - isThreadMessage || - hasFileAttachment || - hasVoiceRecordingAttachment) - ? 0 - : attachmentBorderRadius, - ), - topRight: Radius.circular(attachmentBorderRadius), - bottomRight: isMyMessage - ? Radius.circular( - (hasTimeDiff || !isNextUserSame) && - !(hasReplies || - isThreadMessage || - hasFileAttachment || - hasVoiceRecordingAttachment) - ? 0 - : attachmentBorderRadius, - ) - : Radius.circular(attachmentBorderRadius), - ), - ), - attachmentPadding: EdgeInsets.all( - hasUrlAttachment - ? 8 - : hasFileAttachment || hasVoiceRecordingAttachment - ? 4 - : 2, + Widget child = StreamMessagePlacement( + data: StreamMessagePlacementData( + stackPosition: stackPosition, + alignment: isMyMessage ? .end : .start, + listKind: isInThread ? .thread : .channel, + // channelKind: , ), - borderRadiusGeometry: BorderRadius.only( - topLeft: const Radius.circular(16), - bottomLeft: isMyMessage - ? const Radius.circular(16) - : Radius.circular( - (hasTimeDiff || !isNextUserSame) && - !(hasReplies || isThreadMessage) - ? 0 - : 16, - ), - topRight: const Radius.circular(16), - bottomRight: isMyMessage - ? Radius.circular( - (hasTimeDiff || !isNextUserSame) && - !(hasReplies || isThreadMessage) - ? 0 - : 16, - ) - : const Radius.circular(16), - ), - textPadding: EdgeInsets.symmetric( - vertical: 8, - horizontal: isOnlyEmoji ? 0 : 16.0, + child: Builder( + builder: (context) => switch (widget.messageBuilder) { + final builder? => builder.call(context, message, messageWidgetProps), + _ => StreamMessageWidget.fromProps(props: messageWidgetProps), + }, ), - messageTheme: isMyMessage - ? _streamTheme.ownMessageTheme - : _streamTheme.otherMessageTheme, - onMessageTap: widget.onMessageTap, - onMessageLongPress: widget.onMessageLongPress, ); - if (widget.messageBuilder != null) { - messageWidget = widget.messageBuilder!( - context, - MessageDetails( - userId, - message, - messages, - index, - ), - messages, - messageWidget as StreamMessageWidget, - ); - } - - var child = messageWidget; + // Highlight the initial message with an animated background color flash. if (!initialMessageHighlightComplete && widget.highlightInitialMessage && isInitialMessage(message.id, streamChannel)) { - final colorTheme = _streamTheme.colorTheme; - final highlightColor = - widget.messageHighlightColor ?? colorTheme.highlight; + final colorScheme = context.streamColorScheme; + final highlightColor = widget.messageHighlightColor ?? colorScheme.backgroundHighlight; child = TweenAnimationBuilder( - tween: ColorTween( - begin: highlightColor, - // ignore: deprecated_member_use - end: colorTheme.barsBg.withOpacity(0), - ), + tween: ColorTween(begin: highlightColor, end: highlightColor.withValues(alpha: 0)), duration: const Duration(seconds: 3), onEnd: () => initialMessageHighlightComplete = true, - builder: (_, color, child) => ColoredBox( - color: color!, - child: child, - ), - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 4), - child: child, - ), + builder: (_, color, child) => ColoredBox(color: color!, child: child), + child: Padding(padding: const EdgeInsets.symmetric(vertical: 4), child: child), ); } @@ -1503,35 +1352,35 @@ class _StreamMessageListViewState extends State { // The created callback will use widget.onThreadTap, passing the result // of widget.threadBuilder (if provided) as the second argument. (final onThreadTap?, final threadBuilder) => (Message message) { - onThreadTap( - message, - threadBuilder?.call(context, message), - ); - }, + onThreadTap( + message, + threadBuilder?.call(context, message), + ); + }, // Case 2: widget.onThreadTap is null, but widget.threadBuilder is provided. // The created callback will perform the default navigation action, // using widget.threadBuilder to build the thread page. (null, final threadBuilder?) => (Message message) { - final threadPage = StreamChatConfiguration( - // This is needed to provide the nearest reaction icons to the - // StreamMessageReactionsModal. - data: StreamChatConfiguration.of(context), - child: StreamChannel( - channel: streamChannel!.channel, - child: BetterStreamBuilder( - initialData: message, - stream: streamChannel!.channel.state?.messagesStream.map( - (it) => it.firstWhere((m) => m.id == message.id), - ), - builder: (_, data) => threadBuilder(context, data), + final threadPage = StreamChatConfiguration( + // This is needed to provide the nearest reaction icons to the + // StreamMessageReactionsModal. + data: StreamChatConfiguration.of(context), + child: StreamChannel( + channel: streamChannel!.channel, + child: BetterStreamBuilder( + initialData: message, + stream: streamChannel!.channel.state?.messagesStream.map( + (it) => it.firstWhere((m) => m.id == message.id), ), + builder: (_, data) => threadBuilder(context, data), ), - ); + ), + ); - Navigator.of(context).push( - MaterialPageRoute(builder: (_) => threadPage), - ); - }, + Navigator.of(context).push( + MaterialPageRoute(builder: (_) => threadPage), + ); + }, _ => null, }; } diff --git a/packages/stream_chat_flutter/lib/src/message_list_view/mlv_utils.dart b/packages/stream_chat_flutter/lib/src/message_list_view/mlv_utils.dart index 00177e0fb5..a213e6de58 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view/mlv_utils.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view/mlv_utils.dart @@ -16,8 +16,7 @@ int getInitialIndex( if (currentUser == null) return 0; final messages = [ - ...channelState.channel.state!.messages - .where(messageFilter ?? defaultMessageFilter(currentUser.id)) + ...channelState.channel.state!.messages.where(messageFilter ?? defaultMessageFilter(currentUser.id)), ].reversed.toList(growable: false); // Return the initial message index if available. @@ -101,3 +100,39 @@ bool isElementAtIndexVisible( bool isInitialMessage(String id, StreamChannelState? channelState) { return channelState!.initialMessageId == id; } + +/// Computes the [StreamMessageStackPosition] for [message] based on its +/// [previous] and [next] neighbors in the message list. +/// +/// A new group starts when: +/// - The neighbor is null (first/last message) +/// - The sender changes +/// - The timestamps fall in different calendar minutes +/// - The neighbor is a system, ephemeral, or error message +StreamMessageStackPosition computeStackPosition({ + required Message message, + Message? previous, + Message? next, +}) { + final isFirst = _isGroupBoundary(message, previous); + final isLast = _isGroupBoundary(message, next); + + return switch ((isFirst, isLast)) { + (true, true) => StreamMessageStackPosition.single, + (true, false) => StreamMessageStackPosition.top, + (false, false) => StreamMessageStackPosition.middle, + (false, true) => StreamMessageStackPosition.bottom, + }; +} + +bool _isGroupBoundary(Message message, Message? neighbor) { + if (neighbor == null) return true; + if (message.user?.id != neighbor.user?.id) return true; + if (neighbor.isSystem || neighbor.isEphemeral || neighbor.isError) return true; + + final createdAt = Jiffy.parseFromDateTime(message.createdAt.toLocal()); + final neighborCreatedAt = Jiffy.parseFromDateTime(neighbor.createdAt.toLocal()); + if (!createdAt.isSame(neighborCreatedAt, unit: Unit.minute)) return true; + + return false; +} diff --git a/packages/stream_chat_flutter/lib/src/message_list_view/thread_separator.dart b/packages/stream_chat_flutter/lib/src/message_list_view/thread_separator.dart index 186c4f75a6..39bf67e6b6 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view/thread_separator.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view/thread_separator.dart @@ -18,16 +18,27 @@ class ThreadSeparator extends StatelessWidget { @override Widget build(BuildContext context) { final replyCount = parentMessage!.replyCount!; - return DecoratedBox( - decoration: BoxDecoration( - gradient: StreamChatTheme.of(context).colorTheme.bgGradient, - ), - child: Padding( - padding: const EdgeInsets.all(8), - child: Text( - context.translations.threadSeparatorText(replyCount), - textAlign: TextAlign.center, - style: StreamChannelHeaderTheme.of(context).subtitleStyle, + final spacing = context.streamSpacing; + final colorScheme = context.streamColorScheme; + final textTheme = context.streamTextTheme; + + return Padding( + padding: EdgeInsets.symmetric(vertical: spacing.xs), + child: DecoratedBox( + decoration: BoxDecoration( + color: colorScheme.backgroundSurfaceSubtle, + border: Border( + top: BorderSide(color: colorScheme.borderSubtle), + bottom: BorderSide(color: colorScheme.borderSubtle), + ), + ), + child: Padding( + padding: EdgeInsets.all(spacing.xs), + child: Text( + context.translations.threadSeparatorText(replyCount), + textAlign: TextAlign.center, + style: textTheme.metadataEmphasis, + ), ), ), ); diff --git a/packages/stream_chat_flutter/lib/src/message_list_view/unread_indicator_button.dart b/packages/stream_chat_flutter/lib/src/message_list_view/unread_indicator_button.dart index 6c66c118ce..b3e76170eb 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view/unread_indicator_button.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view/unread_indicator_button.dart @@ -1,10 +1,9 @@ import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/src/icons/stream_svg_icon.dart'; +import 'package:stream_chat_flutter/src/components/stream_chat_component_builders.dart'; import 'package:stream_chat_flutter/src/misc/empty_widget.dart'; -import 'package:stream_chat_flutter/src/theme/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/utils/extensions.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; -import 'package:svg_icon_widget/svg_icon_widget.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; /// Function signature for handling the dismiss action on the unread indicator. typedef OnUnreadIndicatorDismissTap = Future Function(); @@ -18,11 +17,38 @@ typedef OnUnreadIndicatorTap = Future Function(String? lastReadMessageId); /// [unreadCount] is the number of unread messages. /// [onTap] is called when the indicator is tapped. /// [onDismissTap] is called when the dismiss action is triggered. -typedef UnreadIndicatorBuilder = Widget Function( - int unreadCount, - OnUnreadIndicatorTap onTap, - OnUnreadIndicatorDismissTap onDismissTap, -); +typedef UnreadIndicatorBuilder = + Widget Function( + int unreadCount, + OnUnreadIndicatorTap onTap, + OnUnreadIndicatorDismissTap onDismissTap, + ); + +/// Properties for configuring an [UnreadIndicatorButton]. +/// +/// This class holds all the configuration options for an unread indicator, +/// allowing them to be passed through the [StreamComponentFactory]. +/// +/// See also: +/// +/// * [UnreadIndicatorButton], which uses these properties. +class UnreadIndicatorProps { + /// Creates properties for an unread indicator. + const UnreadIndicatorProps({ + required this.unreadCount, + required this.onTap, + required this.onDismissTap, + }); + + /// The number of unread messages. + final int unreadCount; + + /// Callback triggered when the indicator is tapped. + final OnUnreadIndicatorTap onTap; + + /// Callback triggered when the dismiss button is tapped. + final OnUnreadIndicatorDismissTap onDismissTap; +} /// {@template unreadIndicatorButton} /// A button that displays the number of unread messages in a channel. @@ -52,7 +78,8 @@ class UnreadIndicatorButton extends StatelessWidget { /// Optional builder for customizing the appearance of the unread indicator. /// - /// If not provided, a default indicator will be built. + /// If not provided, falls back to [StreamComponentFactory], then to the + /// default indicator. final UnreadIndicatorBuilder? unreadIndicatorBuilder; @override @@ -67,46 +94,68 @@ class UnreadIndicatorButton extends StatelessWidget { final unreadCount = currentUserRead.unreadMessages; if (unreadCount <= 0) return const Empty(); + final props = UnreadIndicatorProps( + unreadCount: unreadCount, + onTap: onTap, + onDismissTap: onDismissTap, + ); + if (unreadIndicatorBuilder case final builder?) { return builder(unreadCount, onTap, onDismissTap); } - final theme = StreamChatTheme.of(context); - final textTheme = theme.textTheme; - final colorTheme = theme.colorTheme; + final factoryBuilder = context.chatComponentBuilder(); + if (factoryBuilder != null) return factoryBuilder(context, props); + + final colorTheme = context.streamColorScheme; + final textTheme = context.streamTextTheme; return Material( - elevation: 4, + elevation: 3, clipBehavior: Clip.antiAlias, - color: colorTheme.textLowEmphasis, + color: colorTheme.backgroundElevation1, shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(18), + borderRadius: BorderRadiusDirectional.all(context.streamRadius.max), ), - child: InkWell( - onTap: () => onTap(currentUserRead.lastReadMessageId), - child: Padding( - padding: const EdgeInsets.fromLTRB(16, 2, 8, 2), - child: Row( - children: [ - Text( - context.translations.unreadCountIndicatorLabel( - unreadCount: unreadCount, - ), - style: textTheme.body.copyWith(color: colorTheme.barsBg), - ), - const SizedBox(width: 12), - IconButton( - iconSize: 24, - icon: const SvgIcon(StreamSvgIcons.close), - padding: const EdgeInsets.all(4), - style: IconButton.styleFrom( - foregroundColor: colorTheme.barsBg, - minimumSize: const Size.square(24), - tapTargetSize: MaterialTapTargetSize.shrinkWrap, - ), - onPressed: onDismissTap, + child: SizedBox( + height: 40, + child: InkWell( + onTap: () => onTap(currentUserRead.lastReadMessageId), + child: Padding( + padding: const EdgeInsets.fromLTRB(16, 2, 8, 2), + child: IntrinsicHeight( + child: Row( + children: [ + Icon( + context.streamIcons.arrowUp, + size: 20, + ), + SizedBox(width: context.streamSpacing.xs), + Text( + context.translations.unreadCountIndicatorLabel( + unreadCount: unreadCount, + ), + style: textTheme.bodyEmphasis.copyWith(color: colorTheme.textSecondary), + ), + SizedBox(width: context.streamSpacing.md), + VerticalDivider( + color: colorTheme.borderDefault, + thickness: 1, + ), + IconButton( + iconSize: 20, + icon: Icon(context.streamIcons.crossMedium), + padding: const EdgeInsets.all(5), + style: IconButton.styleFrom( + foregroundColor: colorTheme.textSecondary, + minimumSize: const Size.square(20), + tapTargetSize: MaterialTapTargetSize.shrinkWrap, + ), + onPressed: onDismissTap, + ), + ], ), - ], + ), ), ), ), diff --git a/packages/stream_chat_flutter/lib/src/message_list_view/unread_messages_separator.dart b/packages/stream_chat_flutter/lib/src/message_list_view/unread_messages_separator.dart index b32c36d8eb..d3dd550f38 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view/unread_messages_separator.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view/unread_messages_separator.dart @@ -15,18 +15,26 @@ class UnreadMessagesSeparator extends StatelessWidget { @override Widget build(BuildContext context) { + final spacing = context.streamSpacing; + final colorScheme = context.streamColorScheme; + final textTheme = context.streamTextTheme; + return Padding( - padding: const EdgeInsets.symmetric(vertical: 8), + padding: EdgeInsets.symmetric(vertical: spacing.xs), child: DecoratedBox( decoration: BoxDecoration( - gradient: StreamChatTheme.of(context).colorTheme.bgGradient, + color: colorScheme.backgroundSurfaceSubtle, + border: Border( + top: BorderSide(color: colorScheme.borderSubtle), + bottom: BorderSide(color: colorScheme.borderSubtle), + ), ), child: Padding( - padding: const EdgeInsets.all(8), + padding: EdgeInsets.all(spacing.xs), child: Text( context.translations.unreadMessagesSeparatorText(), textAlign: TextAlign.center, - style: StreamChannelHeaderTheme.of(context).subtitleStyle, + style: textTheme.metadataEmphasis, ), ), ), diff --git a/packages/stream_chat_flutter/lib/src/message_modal/message_actions_modal.dart b/packages/stream_chat_flutter/lib/src/message_modal/message_actions_modal.dart index 02e929b26f..5c8f3a5596 100644 --- a/packages/stream_chat_flutter/lib/src/message_modal/message_actions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_modal/message_actions_modal.dart @@ -1,6 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/src/reactions/picker/reaction_picker_bubble_overlay.dart'; - import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// {@template streamMessageActionsModal} @@ -19,10 +17,8 @@ class StreamMessageActionsModal extends StatelessWidget { required this.message, required this.messageActions, required this.messageWidget, - this.reverse = false, + this.alignment, this.showReactionPicker = false, - this.reactionPickerBuilder = StreamReactionPicker.builder, - this.onActionTap, }); /// The message object that actions will be performed on. @@ -30,26 +26,24 @@ class StreamMessageActionsModal extends StatelessWidget { /// This is the message the user selected to see available actions. final Message message; - /// List of custom actions that will be displayed in the modal. + /// List of widgets that will be displayed as actions in the modal. /// - /// Each action is represented by a [StreamMessageAction] object which defines - /// the action's appearance and behavior. - final List messageActions; + /// Typically built by [StreamMessageActionsBuilder] and optionally modified + /// by [StreamMessageWidget.actionsBuilder]. Each item is rendered directly + /// as a child of [StreamContextMenu]. + final List messageActions; /// The widget representing the message being acted upon. /// - /// This is typically displayed at the top of the modal as a reference for the - /// user. + /// This is typically displayed in the content section of the modal as a + /// reference for the user. final Widget messageWidget; - /// Whether the message should be displayed in reverse direction. - /// - /// This affects how the modal and reactions are displayed and aligned. - /// Set to `true` for right-aligned messages (typically the current user's). - /// Set to `false` for left-aligned messages (typically other users'). + /// Alignment of the modal content. /// - /// Defaults to `false`. - final bool reverse; + /// When null (the default), falls back to + /// [StreamMessagePlacement.alignmentDirectionalOf]. + final AlignmentGeometry? alignment; /// Controls whether to show the reaction picker at the top of the modal. /// @@ -59,75 +53,28 @@ class StreamMessageActionsModal extends StatelessWidget { /// Defaults to `false`. final bool showReactionPicker; - /// {@macro reactionPickerBuilder} - final ReactionPickerBuilder reactionPickerBuilder; - - /// Callback triggered when a message action is tapped. - /// - /// Provides the tapped [MessageAction] object to the callback. - final OnMessageActionTap? onActionTap; - @override Widget build(BuildContext context) { - final theme = StreamChatTheme.of(context); - - final alignment = switch (reverse) { - true => AlignmentDirectional.centerEnd, - false => AlignmentDirectional.centerStart, - }; + final spacing = context.streamSpacing; + final effectiveAlignment = alignment ?? StreamMessagePlacement.alignmentDirectionalOf(context); - final onReactionPicked = switch (onActionTap) { - null => null, - final onActionTap => (reaction) => onActionTap( - SelectReaction(message: message, reaction: reaction), - ), - }; + void onReactionPicked(Reaction reaction) { + final action = SelectReaction(message: message, reaction: reaction); + return Navigator.pop(context, action); + } return StreamMessageDialog( - spacing: 4, - alignment: alignment, - headerBuilder: (context) { - final safeArea = MediaQuery.paddingOf(context); - - return Padding( - padding: EdgeInsets.only(top: safeArea.top), - child: ReactionPickerBubbleOverlay( - message: message, - reverse: reverse, - visible: showReactionPicker, - anchorOffset: const Offset(0, -8), - onReactionPicked: onReactionPicked, - reactionPickerBuilder: reactionPickerBuilder, - child: IgnorePointer(child: messageWidget), - ), - ); - }, - contentBuilder: (context) { - final actions = Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: { - ...messageActions.map( - (action) => StreamMessageActionItem( - action: action, - onTap: onActionTap, - ), - ), - }.insertBetween(Divider(height: 1, color: theme.colorTheme.borders)), - ); - - return FractionallySizedBox( - widthFactor: 0.78, - child: Material( - type: MaterialType.transparency, - clipBehavior: Clip.antiAlias, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - ), - child: actions, - ), - ); + spacing: spacing.xs, + alignment: effectiveAlignment, + headerBuilder: switch (showReactionPicker) { + true => (context) => StreamMessageReactionPicker( + message: message, + onReactionPicked: onReactionPicked, + ), + false => null, }, + contentBuilder: (context) => IgnorePointer(child: messageWidget), + footerBuilder: (context) => StreamContextMenu(children: messageActions), ); } } diff --git a/packages/stream_chat_flutter/lib/src/message_modal/message_modal.dart b/packages/stream_chat_flutter/lib/src/message_modal/message_modal.dart index ec80700364..8e28dbd815 100644 --- a/packages/stream_chat_flutter/lib/src/message_modal/message_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_modal/message_modal.dart @@ -8,20 +8,22 @@ import 'package:stream_chat_flutter/src/utils/extensions.dart'; /// message-related dialog content. It handles layout, animation, and keyboard /// adjustments automatically. /// -/// The dialog can contain a header (optional) and content section (required), -/// and will adjust its position when the keyboard appears. +/// The dialog is laid out as a [Column] with three optional sections: +/// header, content, and footer. It adjusts its position when the keyboard +/// appears. /// {@endtemplate} class StreamMessageDialog extends StatelessWidget { /// Creates a Stream message dialog. /// /// The [contentBuilder] parameter is required to build the main content - /// of the dialog. The [headerBuilder] is optional and can be used to add - /// a header above the main content. + /// of the dialog. The [headerBuilder] and [footerBuilder] are optional and + /// can be used to add sections above and below the main content. const StreamMessageDialog({ super.key, this.spacing = 8.0, this.headerBuilder, required this.contentBuilder, + this.footerBuilder, this.useSafeArea = true, this.insetAnimationDuration = const Duration(milliseconds: 100), this.insetAnimationCurve = Curves.decelerate, @@ -29,7 +31,7 @@ class StreamMessageDialog extends StatelessWidget { this.alignment = Alignment.center, }); - /// Vertical spacing between header and content sections. + /// Vertical spacing between sections. final double spacing; /// Optional builder for the header section of the dialog. @@ -38,6 +40,9 @@ class StreamMessageDialog extends StatelessWidget { /// Required builder for the main content of the dialog. final WidgetBuilder contentBuilder; + /// Optional builder for the footer section of the dialog. + final WidgetBuilder? footerBuilder; + /// Whether to use a [SafeArea] to avoid system UI intrusions. /// /// Defaults to `true`. @@ -83,7 +88,8 @@ class StreamMessageDialog extends StatelessWidget { crossAxisAlignment: alignment.toColumnCrossAxisAlignment(), children: [ if (headerBuilder case final builder?) builder(context), - Flexible(child: contentBuilder(context)), + contentBuilder(context), + if (footerBuilder case final builder?) Flexible(child: builder(context)), ], ), ), diff --git a/packages/stream_chat_flutter/lib/src/message_modal/message_reactions_modal.dart b/packages/stream_chat_flutter/lib/src/message_modal/message_reactions_modal.dart deleted file mode 100644 index 96920b3e9c..0000000000 --- a/packages/stream_chat_flutter/lib/src/message_modal/message_reactions_modal.dart +++ /dev/null @@ -1,115 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/src/misc/empty_widget.dart'; -import 'package:stream_chat_flutter/src/reactions/picker/reaction_picker_bubble_overlay.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; - -/// {@template streamMessageReactionsModal} -/// A modal that displays message reactions and allows users to add reactions. -/// -/// This modal contains: -/// 1. A reaction picker (optional) that appears at the top -/// 2. The original message widget -/// 3. A display of all current reactions with user avatars -/// -/// The modal uses [StreamMessageDialog] as its base layout and customizes -/// both the header and content sections to display reaction-specific -/// information. -/// {@endtemplate} -class StreamMessageReactionsModal extends StatelessWidget { - /// {@macro streamMessageReactionsModal} - const StreamMessageReactionsModal({ - super.key, - required this.message, - required this.messageWidget, - this.reverse = false, - this.showReactionPicker = true, - this.reactionPickerBuilder = StreamReactionPicker.builder, - this.onReactionPicked, - this.onUserAvatarTap, - }); - - /// The message for which to display and manage reactions. - final Message message; - - /// The original message widget that will be displayed in the modal. - final Widget messageWidget; - - /// Whether the message should be displayed in reverse direction. - /// - /// This affects how the modal and reactions are displayed and aligned. - /// Set to `true` for right-aligned messages (typically the current user's). - /// Set to `false` for left-aligned messages (typically other users'). - /// - /// Defaults to `false`. - final bool reverse; - - /// Controls whether to show the reaction picker at the top of the modal. - /// - /// When `true`, users can add reactions directly from the modal. - /// When `false`, the reaction picker is hidden. - final bool showReactionPicker; - - /// {@macro reactionPickerBuilder} - final ReactionPickerBuilder reactionPickerBuilder; - - /// Callback triggered when a user adds or toggles a reaction. - /// - /// Provides the selected [Reaction] object to the callback. - final OnMessageActionTap? onReactionPicked; - - /// Callback triggered when a user avatar is tapped in the reactions list. - /// - /// Provides the [User] object associated with the tapped avatar. - final void Function(User)? onUserAvatarTap; - - @override - Widget build(BuildContext context) { - final alignment = switch (reverse) { - true => AlignmentDirectional.centerEnd, - false => AlignmentDirectional.centerStart, - }; - - final onReactionPicked = switch (this.onReactionPicked) { - null => null, - final onPicked => (reaction) { - return onPicked.call( - SelectReaction(message: message, reaction: reaction), - ); - }, - }; - - return StreamMessageDialog( - spacing: 4, - alignment: alignment, - headerBuilder: (context) { - final safeArea = MediaQuery.paddingOf(context); - - return Padding( - padding: EdgeInsets.only(top: safeArea.top), - child: ReactionPickerBubbleOverlay( - message: message, - reverse: reverse, - visible: showReactionPicker, - anchorOffset: const Offset(0, -8), - onReactionPicked: onReactionPicked, - reactionPickerBuilder: reactionPickerBuilder, - child: IgnorePointer(child: messageWidget), - ), - ); - }, - contentBuilder: (context) { - final reactions = message.latestReactions; - final hasReactions = reactions != null && reactions.isNotEmpty; - if (!hasReactions) return const Empty(); - - return FractionallySizedBox( - widthFactor: 0.78, - child: StreamUserReactions( - message: message, - onUserAvatarTap: onUserAvatarTap, - ), - ); - }, - ); - } -} diff --git a/packages/stream_chat_flutter/lib/src/message_modal/moderated_message_actions_modal.dart b/packages/stream_chat_flutter/lib/src/message_modal/moderated_message_actions_modal.dart index da655979a7..098189f6a7 100644 --- a/packages/stream_chat_flutter/lib/src/message_modal/moderated_message_actions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_modal/moderated_message_actions_modal.dart @@ -1,11 +1,9 @@ import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/src/icons/stream_svg_icon.dart'; -import 'package:stream_chat_flutter/src/message_action/message_action.dart'; import 'package:stream_chat_flutter/src/misc/adaptive_dialog_action.dart'; -import 'package:stream_chat_flutter/src/misc/empty_widget.dart'; import 'package:stream_chat_flutter/src/theme/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/utils/extensions.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; /// {@template moderatedMessageActionsModal} /// A modal that is shown when a message is flagged by moderation policies. @@ -24,7 +22,6 @@ class ModeratedMessageActionsModal extends StatelessWidget { super.key, required this.message, required this.messageActions, - this.onActionTap, }); /// The message object that actions will be performed on. @@ -34,14 +31,8 @@ class ModeratedMessageActionsModal extends StatelessWidget { /// List of custom actions that will be displayed in the modal. /// - /// Each action is represented by a [StreamMessageAction] object which defines - /// the action's appearance and behavior. - final List messageActions; - - /// Callback triggered when a moderated message action is tapped. - /// - /// Provides the tapped [MessageAction] object to the callback. - final OnMessageActionTap? onActionTap; + /// Each action is represented by a [StreamContextMenuAction] object. + final List messageActions; @override Widget build(BuildContext context) { @@ -52,12 +43,9 @@ class ModeratedMessageActionsModal extends StatelessWidget { final actions = [ ...messageActions.map( (action) => AdaptiveDialogAction( - onPressed: switch (onActionTap) { - final onTap? => () => onTap.call(action.action), - _ => null, - }, - isDestructiveAction: action.isDestructive, - child: action.title ?? const Empty(), + onPressed: () => Navigator.pop(context, action.props.value), + isDestructiveAction: action.props.isDestructive, + child: action.props.label, ), ), ]; @@ -66,7 +54,7 @@ class ModeratedMessageActionsModal extends StatelessWidget { clipBehavior: Clip.antiAlias, backgroundColor: colorTheme.barsBg, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), - icon: const StreamSvgIcon(icon: StreamSvgIcons.flag), + icon: Icon(context.streamIcons.flag2), iconColor: colorTheme.accentPrimary, title: Text(context.translations.moderationReviewModalTitle), titleTextStyle: textTheme.headline.copyWith( diff --git a/packages/stream_chat_flutter/lib/src/message_widget/bottom_row.dart b/packages/stream_chat_flutter/lib/src/message_widget/bottom_row.dart deleted file mode 100644 index 0b408aa750..0000000000 --- a/packages/stream_chat_flutter/lib/src/message_widget/bottom_row.dart +++ /dev/null @@ -1,302 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/src/message_widget/sending_indicator_builder.dart'; -import 'package:stream_chat_flutter/src/message_widget/thread_painter.dart'; -import 'package:stream_chat_flutter/src/message_widget/thread_participants.dart'; -import 'package:stream_chat_flutter/src/message_widget/username.dart'; -import 'package:stream_chat_flutter/src/misc/timestamp.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; - -/// {@template bottomRow} -/// The bottom row of a [StreamMessageWidget]. -/// -/// Used in [MessageWidgetContent]. Should not be used elsewhere. -/// {@endtemplate} -class BottomRow extends StatelessWidget { - /// {@macro bottomRow} - const BottomRow({ - super.key, - required this.isDeleted, - required this.message, - required this.showThreadReplyIndicator, - required this.showInChannel, - required this.showTimeStamp, - required this.showUsername, - required this.showEditedLabel, - required this.reverse, - required this.showSendingIndicator, - required this.hasUrlAttachments, - required this.isGiphy, - required this.isOnlyEmoji, - required this.messageTheme, - required this.streamChatTheme, - required this.hasNonUrlAttachments, - required this.streamChat, - this.deletedBottomRowBuilder, - this.onThreadTap, - this.usernameBuilder, - this.sendingIndicatorBuilder, - }); - - /// {@macro messageIsDeleted} - final bool isDeleted; - - /// {@macro deletedBottomRowBuilder} - final Widget Function(BuildContext, Message)? deletedBottomRowBuilder; - - /// {@macro message} - final Message message; - - /// {@macro showThreadReplyIndicator} - final bool showThreadReplyIndicator; - - /// {@macro showInChannelIndicator} - final bool showInChannel; - - /// {@macro showTimestamp} - final bool showTimeStamp; - - /// {@macro showUsername} - final bool showUsername; - - /// {@macro showEdited} - final bool showEditedLabel; - - /// {@macro reverse} - final bool reverse; - - /// {@macro showSendingIndicator} - final bool showSendingIndicator; - - /// {@macro hasUrlAttachments} - final bool hasUrlAttachments; - - /// {@macro isGiphy} - final bool isGiphy; - - /// {@macro isOnlyEmoji} - final bool isOnlyEmoji; - - /// {@macro hasNonUrlAttachments} - final bool hasNonUrlAttachments; - - /// {@macro messageTheme} - final StreamMessageThemeData messageTheme; - - /// {@macro onThreadTap} - final void Function(Message)? onThreadTap; - - /// {@macro streamChatThemeData} - final StreamChatThemeData streamChatTheme; - - /// {@macro streamChat} - final StreamChatState streamChat; - - /// {@macro usernameBuilder} - final Widget Function(BuildContext, Message)? usernameBuilder; - - /// {@macro sendingIndicatorBuilder} - final Widget Function(BuildContext, Message)? sendingIndicatorBuilder; - - /// {@template copyWith} - /// Creates a copy of [BottomRow] with specified attributes - /// overridden. - /// {@endtemplate} - BottomRow copyWith({ - Key? key, - bool? isDeleted, - Message? message, - bool? showThreadReplyIndicator, - bool? showInChannel, - bool? showTimeStamp, - bool? showUsername, - bool? showEditedLabel, - bool? reverse, - bool? showSendingIndicator, - bool? hasUrlAttachments, - bool? isGiphy, - bool? isOnlyEmoji, - StreamMessageThemeData? messageTheme, - StreamChatThemeData? streamChatTheme, - bool? hasNonUrlAttachments, - StreamChatState? streamChat, - Widget Function(BuildContext, Message)? deletedBottomRowBuilder, - void Function(Message)? onThreadTap, - Widget Function(BuildContext, Message)? usernameBuilder, - Widget Function(BuildContext, Message)? sendingIndicatorBuilder, - }) => - BottomRow( - key: key ?? this.key, - isDeleted: isDeleted ?? this.isDeleted, - message: message ?? this.message, - showThreadReplyIndicator: - showThreadReplyIndicator ?? this.showThreadReplyIndicator, - showInChannel: showInChannel ?? this.showInChannel, - showTimeStamp: showTimeStamp ?? this.showTimeStamp, - showUsername: showUsername ?? this.showUsername, - showEditedLabel: showEditedLabel ?? this.showEditedLabel, - reverse: reverse ?? this.reverse, - showSendingIndicator: showSendingIndicator ?? this.showSendingIndicator, - hasUrlAttachments: hasUrlAttachments ?? this.hasUrlAttachments, - isGiphy: isGiphy ?? this.isGiphy, - isOnlyEmoji: isOnlyEmoji ?? this.isOnlyEmoji, - messageTheme: messageTheme ?? this.messageTheme, - streamChatTheme: streamChatTheme ?? this.streamChatTheme, - hasNonUrlAttachments: hasNonUrlAttachments ?? this.hasNonUrlAttachments, - streamChat: streamChat ?? this.streamChat, - deletedBottomRowBuilder: - deletedBottomRowBuilder ?? this.deletedBottomRowBuilder, - onThreadTap: onThreadTap ?? this.onThreadTap, - usernameBuilder: usernameBuilder ?? this.usernameBuilder, - sendingIndicatorBuilder: - sendingIndicatorBuilder ?? this.sendingIndicatorBuilder, - ); - - @override - Widget build(BuildContext context) { - if (isDeleted) { - final deletedBottomRowBuilder = this.deletedBottomRowBuilder; - if (deletedBottomRowBuilder != null) { - return deletedBottomRowBuilder(context, message); - } - } - - final threadParticipants = message.threadParticipants?.take(2); - final showThreadParticipants = threadParticipants?.isNotEmpty == true; - final replyCount = message.replyCount; - final isEdited = message.messageTextUpdatedAt != null; - - var msg = context.translations.threadReplyLabel; - if (showThreadReplyIndicator && replyCount! > 1) { - msg = context.translations.threadReplyCountText(replyCount); - } - - Future _onThreadTap() async { - try { - var message = this.message; - if (showInChannel) { - final channel = StreamChannel.of(context); - message = await channel.getMessage(message.parentId!); - } - return onThreadTap?.call(message); - } catch (e, stk) { - debugPrint('Error while fetching message: $e, $stk'); - } - } - - const usernameKey = Key('username'); - - final children = [ - if (showSendingIndicator) - switch (sendingIndicatorBuilder) { - final builder? => builder(context, message), - _ => SendingIndicatorBuilder( - messageTheme: messageTheme, - message: message, - hasNonUrlAttachments: hasNonUrlAttachments, - streamChat: streamChat, - streamChatTheme: streamChatTheme, - ), - }, - if (showUsername) - switch (usernameBuilder) { - final builder? => builder(context, message), - _ => Username( - key: usernameKey, - message: message, - messageTheme: messageTheme, - ), - }, - if (showEditedLabel && isEdited) - Text( - context.translations.editedMessageLabel, - style: messageTheme.createdAtStyle, - ), - if (showTimeStamp) - StreamTimestamp( - date: message.createdAt.toLocal(), - style: messageTheme.createdAtStyle, - formatter: (context, date) { - if (messageTheme.createdAtFormatter case final formatter?) { - return formatter.call(context, date); - } - - return Jiffy.parseFromDateTime(date).jm; - }, - ), - ]; - - final showThreadTail = - (showThreadReplyIndicator || showInChannel) && !isOnlyEmoji; - - final threadIndicatorWidgets = [ - if (showThreadTail) - // Added builder to use the nearest context to get the right - // textScaleFactor value. - Builder( - builder: (context) { - return Padding( - padding: EdgeInsets.only( - bottom: context.textScaleFactor * - ((messageTheme.repliesStyle?.fontSize ?? 1) / 2), - ), - child: CustomPaint( - size: const Size(16, 32) * context.textScaleFactor, - painter: ThreadReplyPainter( - context: context, - color: messageTheme.messageBorderColor, - reverse: reverse, - ), - ), - ); - }, - ), - if (showInChannel || showThreadReplyIndicator) ...[ - if (showThreadParticipants) - SizedBox.fromSize( - size: Size((threadParticipants!.length * 8.0) + 8, 16), - child: ThreadParticipants( - threadParticipants: threadParticipants, - streamChatTheme: streamChatTheme, - ), - ), - MouseRegion( - cursor: SystemMouseCursors.click, - child: GestureDetector( - onTap: _onThreadTap, - child: Text(msg, style: messageTheme.repliesStyle), - ), - ), - ], - ]; - - if (reverse) { - children.addAll(threadIndicatorWidgets.reversed); - } else { - children.insertAll(0, threadIndicatorWidgets); - } - - return Text.rich( - TextSpan( - children: [ - ...children.insertBetween(const SizedBox(width: 8)).map((child) { - final mediaQueryData = MediaQuery.of(context); - return WidgetSpan( - child: MediaQuery( - // Hardcoding the textScaleFactor to 1 to avoid the multiple - // resizing of the text. This is needed because the - // textScaleFactor is already applied to the textSpan. - // - // issue: https://github.com/GetStream/stream-chat-flutter/issues/1250 - // ignore: deprecated_member_use - data: mediaQueryData.copyWith(textScaleFactor: 1), - child: child, - ), - ); - }), - ], - ), - maxLines: 1, - textAlign: reverse ? TextAlign.right : TextAlign.left, - ); - } -} diff --git a/packages/stream_chat_flutter/lib/src/message_widget/components/stream_message_content.dart b/packages/stream_chat_flutter/lib/src/message_widget/components/stream_message_content.dart new file mode 100644 index 0000000000..e1ee7f07a1 --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/message_widget/components/stream_message_content.dart @@ -0,0 +1,213 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_markdown/flutter_markdown.dart'; +import 'package:stream_chat_flutter/src/attachment/builder/attachment_widget_builder.dart'; +import 'package:stream_chat_flutter/src/channel/stream_message_preview_text.dart'; +import 'package:stream_chat_flutter/src/components/avatar/stream_user_avatar.dart'; +import 'package:stream_chat_flutter/src/message_widget/components/stream_message_deleted.dart'; +import 'package:stream_chat_flutter/src/message_widget/components/stream_message_reactions.dart'; +import 'package:stream_chat_flutter/src/message_widget/components/stream_message_text.dart'; +import 'package:stream_chat_flutter/src/message_widget/parse_attachments.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart' as core; + +/// Composes the main message content including the bubble, attachments, text, +/// reactions, and thread reply indicator. +/// +/// For deleted messages a [StreamMessageDeleted] placeholder is shown. +/// Otherwise the content displays attachments, message text, reactions, and +/// a thread reply indicator (when [Message.replyCount] is greater than zero). +/// +/// When the message consists of three or fewer emoji-only characters, the +/// bubble background is hidden so the emoji appear at a larger visual size. +/// +/// See also: +/// +/// * [StreamMessageReactions], which renders reactions around the bubble. +/// * [StreamMessageText], which renders the markdown message text. +/// * [DefaultStreamMessage], which hosts this widget. +class StreamMessageContent extends StatefulWidget { + /// Creates a message content widget for the given [message]. + const StreamMessageContent({ + super.key, + required this.message, + this.header, + this.footer, + this.attachmentBuilders, + this.onLinkTap, + this.onMentionTap, + this.onReactionsTap, + this.onRepliesTap, + this.onQuotedMessageTap, + this.reactionSorting, + }); + + /// The message to display. + final Message message; + + /// Optional header widget displayed above the message content column. + /// + /// Typically a [streamMessageHeader] result containing pinned, reminder, + /// or show-in-channel annotations. + final Widget? header; + + /// Optional footer widget displayed below the message content column. + /// + /// Typically a [StreamMessageFooter] containing the author name, timestamp, + /// and sending status. + final Widget? footer; + + /// Custom attachment builders for rendering message attachments. + /// + /// When non-null, these builders are passed to [ParseAttachments] and + /// take priority over the default builders. + final List? attachmentBuilders; + + /// Called when a link is tapped in the rendered message text. + /// + /// If null, tapping a link has no effect. + final MarkdownTapLinkCallback? onLinkTap; + + /// Called when a `@mention` is tapped in the rendered message text. + /// + /// If null, tapping a mention has no effect. + final core.MarkdownTapMentionCallback? onMentionTap; + + /// Called when the reactions area is tapped. + /// + /// If null, tapping reactions has no effect. + final VoidCallback? onReactionsTap; + + /// Called when the thread reply indicator is tapped. + /// + /// If null, tapping the reply indicator has no effect. + final VoidCallback? onRepliesTap; + + /// Called when the quoted message is tapped. + /// + /// If null, tapping the quoted message has no effect. + final void Function(Message quotedMessage)? onQuotedMessageTap; + + /// Controls how reaction groups are sorted when displayed. + /// + /// Passed through to [StreamMessageReactions.sorting]. + final Comparator? reactionSorting; + + @override + State createState() => _StreamMessageContentState(); +} + +class _StreamMessageContentState extends State { + // Tracks the rendered width of the attachments to constrain the bubble. + double? widthLimit; + late final attachmentsKey = GlobalKey(debugLabel: 'ParseAttachments'); + + // Measures the attachment width after layout and constrains the bubble. + void _updateWidthLimit() { + final attachmentContext = attachmentsKey.currentContext; + final renderBox = attachmentContext?.findRenderObject() as RenderBox?; + final attachmentsWidth = renderBox?.size.width; + + if (attachmentsWidth == null || attachmentsWidth == 0) return; + if (mounted) setState(() => widthLimit = attachmentsWidth); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + WidgetsBinding.instance.addPostFrameCallback((_) => _updateWidthLimit()); + } + + @override + Widget build(BuildContext context) { + final spacing = context.streamSpacing; + final crossAxisAlignment = core.StreamMessagePlacement.crossAxisAlignmentOf(context); + + if (widget.message.isDeleted) return const StreamMessageDeleted(); + + return core.StreamMessageContent( + header: widget.header, + footer: widget.footer, + child: core.StreamColumn( + mainAxisSize: .min, + crossAxisAlignment: crossAxisAlignment, + children: [ + StreamMessageReactions( + message: widget.message, + sorting: widget.reactionSorting, + onPressed: widget.onReactionsTap, + child: Builder( + builder: (context) { + final bubbleContent = ConstrainedBox( + constraints: const BoxConstraints().copyWith(maxWidth: widthLimit), + child: Column( + spacing: spacing.xxs, + mainAxisSize: .min, + crossAxisAlignment: .start, + children: [ + if (widget.message.quotedMessage case final quotedMessage?) + // TODO: Refactor this with attachments + GestureDetector( + onTap: !quotedMessage.isDeleted && widget.onQuotedMessageTap != null + ? () => widget.onQuotedMessageTap!(quotedMessage) + : null, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: core.StreamMessageTheme( + data: core.StreamMessageThemeData( + incoming: core.StreamMessageStyle( + backgroundColor: context.streamColorScheme.backgroundSurfaceStrong, + ), + outgoing: core.StreamMessageStyle( + backgroundColor: context.streamColorScheme.brand.shade150, + ), + ), + child: core.MessageComposerReplyAttachment( + title: Text(quotedMessage.user?.name ?? ''), + subtitle: StreamMessagePreviewText(message: quotedMessage), + style: switch (core.StreamMessagePlacement.messageAlignmentOf(context)) { + core.StreamMessageAlignment.start => .incoming, + core.StreamMessageAlignment.end => .outgoing, + }, + ), + ), + ), + ), + ParseAttachments( + key: attachmentsKey, + message: widget.message, + attachmentBuilders: widget.attachmentBuilders, + attachmentPadding: .symmetric(horizontal: spacing.xs), + ), + if (widget.message.text case final text? when text.isNotEmpty) + StreamMessageText( + message: widget.message, + onLinkTap: widget.onLinkTap, + onMentionTap: widget.onMentionTap, + ), + ], + ), + ); + + final emojiCount = core.StreamMessageText.emojiOnlyCount(widget.message.text); + final hideBubble = emojiCount != null && emojiCount <= 3; + + if (hideBubble) return bubbleContent; + return core.StreamMessageBubble(child: bubbleContent); + }, + ), + ), + if (widget.message.replyCount case final replyCount? when replyCount > 0) + core.StreamMessageReplies( + maxAvatars: 3, + showConnector: true, + onTap: widget.onRepliesTap, + label: Text('$replyCount replies'), + avatars: widget.message.threadParticipants?.map( + (user) => StreamUserAvatar(user: user, showOnlineIndicator: false), + ), + ), + ], + ), + ); + } +} diff --git a/packages/stream_chat_flutter/lib/src/message_widget/components/stream_message_deleted.dart b/packages/stream_chat_flutter/lib/src/message_widget/components/stream_message_deleted.dart new file mode 100644 index 0000000000..e24f8d187b --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/message_widget/components/stream_message_deleted.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/src/utils/extensions.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart' as core; + +/// Displays a "Message deleted" indicator inside a message bubble. +/// +/// Shown in place of the normal message content when [Message.isDeleted] +/// is true. +/// +/// See also: +/// +/// * [StreamMessageScaffold], which shows this widget for deleted messages. +class StreamMessageDeleted extends StatelessWidget { + /// Creates a deleted message widget. + const StreamMessageDeleted({super.key}); + + @override + Widget build(BuildContext context) { + final icons = context.streamIcons; + final spacing = context.streamSpacing; + + return core.StreamMessageBubble( + padding: .symmetric( + horizontal: spacing.sm, + vertical: spacing.xs, + ), + child: Row( + spacing: spacing.xxs, + mainAxisSize: .min, + children: [ + Icon(icons.circleBanSign, size: 16), + core.StreamMessageText(padding: .zero, context.translations.messageDeletedLabel), + ], + ), + ); + } +} diff --git a/packages/stream_chat_flutter/lib/src/message_widget/components/stream_message_footer.dart b/packages/stream_chat_flutter/lib/src/message_widget/components/stream_message_footer.dart new file mode 100644 index 0000000000..9d8396754d --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/message_widget/components/stream_message_footer.dart @@ -0,0 +1,68 @@ +import 'package:flutter/material.dart'; +import 'package:jiffy/jiffy.dart'; +import 'package:stream_chat_flutter/src/message_widget/components/stream_message_sending_status.dart'; +import 'package:stream_chat_flutter/src/misc/timestamp.dart'; +import 'package:stream_chat_flutter/src/stream_chat.dart'; +import 'package:stream_chat_flutter/src/utils/extensions.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart' as core; + +/// Displays the message footer containing the author name, sending status, +/// creation timestamp, and an edited indicator. +/// +/// The footer can show up to four metadata pieces depending on the message: +/// +/// * **Username** — for messages from other users. +/// * **Sending status** — for the current user's own messages. +/// * **Timestamp** — always shown, formatted as a short time string. +/// * **Edited label** — when the message text has been updated. +/// +/// See also: +/// +/// * [StreamMessageSendingStatus], which renders the sent/delivered/read +/// indicator. +/// * [DefaultStreamMessage], which controls footer visibility. +class StreamMessageFooter extends StatelessWidget { + /// Creates a message footer for the given [message]. + const StreamMessageFooter({super.key, required this.message}); + + /// The message whose footer to display. + final Message message; + + @override + Widget build(BuildContext context) { + final currentUser = StreamChat.of(context).currentUser; + + Widget? usernameWidget; + if (message.user case final user? when user.id != currentUser?.id) { + usernameWidget = Text(user.name, maxLines: 1, overflow: .ellipsis); + } + + Widget? statusWidget; + if (message.user case final user? when user.id == currentUser?.id) { + statusWidget = StreamMessageSendingStatus(message: message); + } + + final Widget timestampWidget; + if (message.createdAt case final createdAt) { + timestampWidget = StreamTimestamp( + date: createdAt.toLocal(), + formatter: (context, date) { + return Jiffy.parseFromDateTime(date).jm; + }, + ); + } + + Widget? editedWidget; + if (message.messageTextUpdatedAt != null) { + editedWidget = Text(context.translations.editedMessageLabel); + } + + return core.StreamMessageMetadata( + username: usernameWidget, + status: statusWidget, + timestamp: timestampWidget, + edited: editedWidget, + ); + } +} diff --git a/packages/stream_chat_flutter/lib/src/message_widget/components/stream_message_header.dart b/packages/stream_chat_flutter/lib/src/message_widget/components/stream_message_header.dart new file mode 100644 index 0000000000..8355ea9b6c --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/message_widget/components/stream_message_header.dart @@ -0,0 +1,112 @@ +import 'package:flutter/material.dart'; +import 'package:jiffy/jiffy.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart' as core; +import 'package:stream_core_flutter/stream_core_flutter.dart'; + +/// Builds the message header containing contextual annotations for the given +/// [message]. +/// +/// Annotations are shown in the following order when applicable: +/// +/// 1. **Saved for later** — when a reminder exists without a scheduled time. +/// 2. **Pinned** — when [Message.pinned] is true, showing who pinned it. +/// 3. **Show in channel / Replied to thread** — when [Message.showInChannel] +/// is true. The label adapts based on whether the message list is a +/// channel or thread view, and includes a tappable "View" link that +/// invokes [onViewChannelTap]. +/// 4. **Reminder** — when a reminder exists with a scheduled time. +/// +/// Returns `null` when no annotations apply, allowing the caller to skip +/// rendering the header entirely. +/// +/// See also: +/// +/// * [DefaultStreamMessage], which controls header visibility. +Widget? streamMessageHeader({ + required BuildContext context, + required Message message, + VoidCallback? onViewChannelTap, +}) { + final icons = context.streamIcons; + final textTheme = context.streamTextTheme; + final colorScheme = context.streamColorScheme; + final crossAxisAlignment = StreamMessagePlacement.crossAxisAlignmentOf(context); + + Widget? savedForLaterAnnotation; + if (message.reminder case final reminder? when reminder.remindAt == null) { + savedForLaterAnnotation = StreamMessageAnnotation( + leading: Icon(icons.bookmark, color: colorScheme.accentPrimary), + label: Text('Saved for later', style: TextStyle(color: colorScheme.accentPrimary)), + ); + } + + Widget? pinnedAnnotation; + if (message.pinned case true) { + pinnedAnnotation = StreamMessageAnnotation( + leading: Icon(icons.pin), + label: switch (message.pinnedBy) { + final pinnedBy? => Text('Pinned by ${pinnedBy.name}'), + _ => const Text('Pinned by You'), + }, + ); + } + + Widget? showInChannelAnnotation; + if (message.showInChannel case true) { + final listKind = StreamMessagePlacement.listKindOf(context); + final annotationLabel = switch (listKind) { + .channel => 'Replied to a thread · ', + .thread => 'Also sent in channel · ', + }; + + showInChannelAnnotation = StreamMessageAnnotation( + onTap: onViewChannelTap, + leading: Icon(icons.arrowUp), + label: Text.rich( + TextSpan( + text: annotationLabel, + children: [ + TextSpan( + text: 'View', + style: textTheme.metadataDefault.copyWith(color: colorScheme.textLink), + ), + ], + ), + ), + ); + } + + Widget? reminderAnnotation; + if (message.reminder?.remindAt?.toLocal() case final remindAt?) { + reminderAnnotation = StreamMessageAnnotation( + leading: Icon(icons.bellNotification), + label: Text.rich( + TextSpan( + text: 'Reminder set · ', + children: [ + TextSpan( + text: 'Today at ${Jiffy.parseFromDateTime(remindAt).jm}', + style: textTheme.metadataDefault, + ), + ], + ), + ), + ); + } + + final children = [ + ?savedForLaterAnnotation, + ?pinnedAnnotation, + ?showInChannelAnnotation, + ?reminderAnnotation, + ]; + + if (children.isEmpty) return null; + + return StreamColumn( + mainAxisSize: .min, + crossAxisAlignment: crossAxisAlignment, + children: children, + ); +} diff --git a/packages/stream_chat_flutter/lib/src/message_widget/components/stream_message_leading.dart b/packages/stream_chat_flutter/lib/src/message_widget/components/stream_message_leading.dart new file mode 100644 index 0000000000..ab1a956a83 --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/message_widget/components/stream_message_leading.dart @@ -0,0 +1,33 @@ +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/src/components/avatar/stream_user_avatar.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; + +/// Displays the message author's avatar as the leading widget in a message +/// row. +/// +/// Visibility of this widget (visible, hidden, or gone) is controlled by +/// [StreamMessageItemThemeData.leadingVisibility] in the parent +/// [DefaultStreamMessage]. +/// +/// See also: +/// +/// * [StreamUserAvatar], which renders the avatar image. +/// * [DefaultStreamMessage], which controls when this widget is shown. +class StreamMessageLeading extends StatelessWidget { + /// Creates a message leading widget for the given [author]. + const StreamMessageLeading({ + super.key, + required this.author, + }); + + /// The user whose avatar to display. + final User author; + + @override + Widget build(BuildContext context) { + return StreamUserAvatar( + user: author, + showOnlineIndicator: false, + ); + } +} diff --git a/packages/stream_chat_flutter/lib/src/message_widget/components/stream_message_reactions.dart b/packages/stream_chat_flutter/lib/src/message_widget/components/stream_message_reactions.dart new file mode 100644 index 0000000000..679a069f90 --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/message_widget/components/stream_message_reactions.dart @@ -0,0 +1,88 @@ +import 'package:collection/collection.dart'; +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/src/stream_chat_configuration.dart'; +import 'package:stream_chat_flutter/src/utils/device_segmentation.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart' as core; + +/// Displays reaction groups for a message as emoji chips overlaid on, or +/// placed beneath, the [child] widget. +/// +/// Reaction icons are resolved through the +/// [StreamChatConfigurationData.reactionIconResolver]. Groups are sorted +/// using [sorting] (defaults to [ReactionSorting.byFirstReactionAt]). +/// +/// See also: +/// +/// * [StreamMessageScaffold], which hosts this widget around the bubble. +/// * [StreamChatConfigurationData.reactionIconResolver], which maps reaction +/// type strings to emoji widgets. +class StreamMessageReactions extends StatelessWidget { + /// Creates a message reactions widget for the given [message]. + const StreamMessageReactions({ + super.key, + required this.message, + this.type, + this.position, + this.sorting, + this.onPressed, + this.child, + }); + + /// The message whose reactions to display. + final Message message; + + /// The visual type of the reactions display. + /// + /// Defaults to [core.StreamReactionsType.segmented] when null. + final core.StreamReactionsType? type; + + /// Where the reactions appear relative to the message bubble. + /// + /// Defaults to [core.StreamReactionsPosition.footer] on desktop and web, + /// and [core.StreamReactionsPosition.header] on mobile. + final core.StreamReactionsPosition? position; + + /// Controls how reaction groups are sorted when displayed. + /// + /// Defaults to [ReactionSorting.byFirstReactionAt] when null. + final Comparator? sorting; + + /// Called when the reactions area is pressed. + /// + /// If null, pressing the reactions area has no effect. + final VoidCallback? onPressed; + + /// The child widget (typically the message bubble) that reactions are + /// displayed on. + final Widget? child; + + @override + Widget build(BuildContext context) { + final config = StreamChatConfiguration.of(context); + final resolver = config.reactionIconResolver; + + final effectiveType = type ?? config.reactionType ?? core.StreamReactionsType.segmented; + final effectivePosition = position ?? config.reactionPosition ?? core.StreamReactionsPosition.header; + + final reactionGroups = message.reactionGroups?.entries; + final effectiveReactionSorting = sorting ?? ReactionSorting.byFirstReactionAt; + final sortedReactionGroups = reactionGroups?.sortedByCompare((it) => it.value, effectiveReactionSorting); + + final items = sortedReactionGroups?.map( + (group) => core.StreamReactionsItem( + count: group.value.count, + emoji: core.StreamEmoji(size: .sm, emoji: resolver.resolve(context, group.key)), + ), + ); + + return core.StreamReactions( + type: effectiveType, + position: effectivePosition, + overlap: !isDesktopDeviceOrWeb, + onPressed: onPressed, + items: [...?items], + child: child, + ); + } +} diff --git a/packages/stream_chat_flutter/lib/src/message_widget/components/stream_message_sending_status.dart b/packages/stream_chat_flutter/lib/src/message_widget/components/stream_message_sending_status.dart new file mode 100644 index 0000000000..9c9e157176 --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/message_widget/components/stream_message_sending_status.dart @@ -0,0 +1,68 @@ +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/src/indicators/sending_indicator.dart'; +import 'package:stream_chat_flutter/src/utils/extensions.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; + +/// Displays the sending status of a message, including attachment upload +/// progress and sent/delivered/read indicators. +/// +/// While attachments are still uploading, a textual progress label is shown. +/// Once the message is fully sent, an icon indicates whether it has been +/// sent, delivered, or read. +/// +/// This widget is typically used inside [StreamMessageFooter] and is only +/// shown for messages sent by the current user. +/// +/// See also: +/// +/// * [StreamSendingIndicator], which renders the sent/delivered/read icon. +/// * [StreamMessageFooter], which hosts this widget. +class StreamMessageSendingStatus extends StatelessWidget { + /// Creates a sending status widget for the given [message]. + const StreamMessageSendingStatus({ + super.key, + required this.message, + }); + + /// The message whose sending status to display. + final Message message; + + @override + Widget build(BuildContext context) { + final hasNonUrlAttachments = message.attachments.any((it) => it.type != AttachmentType.urlPreview); + + if (hasNonUrlAttachments && message.state.isOutgoing) { + final totalAttachments = message.attachments.length; + final attachmentsToUpload = message.attachments.where((it) => !it.uploadState.isSuccess); + + if (attachmentsToUpload.isNotEmpty) { + return Text( + context.translations.attachmentsUploadProgressText( + remaining: attachmentsToUpload.length, + total: totalAttachments, + ), + ); + } + } + + final channel = StreamChannel.maybeOf(context)?.channel; + + return BetterStreamBuilder>( + stream: channel?.state?.readStream, + initialData: channel?.state?.read, + builder: (context, data) { + final readList = data.readsOf(message: message); + final isMessageRead = readList.isNotEmpty; + + final deliveriesList = data.deliveriesOf(message: message); + final isMessageDelivered = deliveriesList.isNotEmpty; + + return StreamSendingIndicator( + message: message, + isMessageRead: isMessageRead, + isMessageDelivered: isMessageDelivered, + ); + }, + ); + } +} diff --git a/packages/stream_chat_flutter/lib/src/message_widget/components/stream_message_text.dart b/packages/stream_chat_flutter/lib/src/message_widget/components/stream_message_text.dart new file mode 100644 index 0000000000..f273f32ea2 --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/message_widget/components/stream_message_text.dart @@ -0,0 +1,69 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_markdown/flutter_markdown.dart'; +import 'package:stream_chat_flutter/src/misc/empty_widget.dart'; +import 'package:stream_chat_flutter/src/stream_chat.dart'; +import 'package:stream_chat_flutter/src/utils/device_segmentation.dart'; +import 'package:stream_chat_flutter/src/utils/extensions.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart' as core; + +/// Displays the translated markdown message text, reacting to the current +/// user's language preference. +/// +/// The message text is translated into the current user's language, mention +/// syntax is replaced with display names, and the result is rendered as +/// markdown. +/// +/// The widget rebuilds automatically when the current user's language +/// changes, ensuring the displayed text stays in sync. +/// +/// On desktop and web the text is selectable; on mobile it is not. +/// +/// See also: +/// +/// * [StreamMessageScaffold], which hosts this widget inside a message bubble. +class StreamMessageText extends StatelessWidget { + /// Creates a message text widget for the given [message]. + const StreamMessageText({ + super.key, + required this.message, + this.onLinkTap, + this.onMentionTap, + }); + + /// The message whose text to display. + final Message message; + + /// Called when a link in the rendered markdown is tapped. + /// + /// If null, tapping a link has no effect. + final MarkdownTapLinkCallback? onLinkTap; + + /// Called when a `@mention` in the rendered markdown is tapped. + /// + /// Mentions use the `[text](mention:id)` format in the raw markdown. + /// If null, tapping a mention has no effect. + final core.MarkdownTapMentionCallback? onMentionTap; + + @override + Widget build(BuildContext context) { + final streamChat = StreamChat.of(context); + + return BetterStreamBuilder( + initialData: streamChat.currentUser?.language ?? 'en', + stream: streamChat.currentUserStream.map((it) => it?.language ?? 'en'), + builder: (context, language) { + final messageText = message.translate(language).replaceMentions().text?.replaceAll('\n', '\n\n').trim(); + + if (messageText == null || messageText.trim().isEmpty) return const Empty(); + + return core.StreamMessageText( + messageText, + selectable: isDesktopDeviceOrWeb, + onTapLink: onLinkTap, + onTapMention: onMentionTap, + ); + }, + ); + } +} diff --git a/packages/stream_chat_flutter/lib/src/message_widget/deleted_message.dart b/packages/stream_chat_flutter/lib/src/message_widget/deleted_message.dart deleted file mode 100644 index 10004802fa..0000000000 --- a/packages/stream_chat_flutter/lib/src/message_widget/deleted_message.dart +++ /dev/null @@ -1,57 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; - -/// {@template streamDeletedMessage} -/// Displays that a message was deleted at this position in the message list. -/// {@endtemplate} -class StreamDeletedMessage extends StatelessWidget { - /// {@macro streamDeletedMessage} - const StreamDeletedMessage({ - super.key, - required this.messageTheme, - this.borderRadiusGeometry, - this.shape, - this.borderSide, - this.reverse = false, - }); - - /// The theme of the message - final StreamMessageThemeData messageTheme; - - /// The border radius of the message text - final BorderRadiusGeometry? borderRadiusGeometry; - - /// The shape of the message text - final ShapeBorder? shape; - - /// The [BorderSide] of the message text - final BorderSide? borderSide; - - /// If true the widget will be mirrored - final bool reverse; - - @override - Widget build(BuildContext context) { - return Material( - color: messageTheme.messageBackgroundColor, - shape: shape ?? - RoundedRectangleBorder( - borderRadius: borderRadiusGeometry ?? BorderRadius.zero, - side: borderSide ?? - BorderSide( - color: messageTheme.messageBorderColor ?? Colors.transparent, - ), - ), - child: Padding( - padding: const EdgeInsets.symmetric( - vertical: 8, - horizontal: 16, - ), - child: Text( - context.translations.messageDeletedLabel, - style: messageTheme.messageDeletedStyle, - ), - ), - ); - } -} diff --git a/packages/stream_chat_flutter/lib/src/message_widget/message_card.dart b/packages/stream_chat_flutter/lib/src/message_widget/message_card.dart deleted file mode 100644 index f41b20cd7a..0000000000 --- a/packages/stream_chat_flutter/lib/src/message_widget/message_card.dart +++ /dev/null @@ -1,256 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; - -/// {@template messageCard} -/// The widget containing a quoted message. -/// -/// Used in [MessageWidgetContent]. Should not be used elsewhere. -/// {@endtemplate} -class MessageCard extends StatefulWidget { - /// {@macro messageCard} - const MessageCard({ - super.key, - required this.message, - required this.isFailedState, - required this.showUserAvatar, - required this.messageTheme, - required this.hasQuotedMessage, - required this.hasUrlAttachments, - required this.hasNonUrlAttachments, - required this.isOnlyEmoji, - required this.isGiphy, - required this.attachmentBuilders, - required this.attachmentPadding, - required this.attachmentShape, - required this.onAttachmentTap, - required this.onShowMessage, - required this.onReplyTap, - required this.attachmentActionsModalBuilder, - required this.textPadding, - required this.reverse, - this.shape, - this.borderSide, - this.borderRadiusGeometry, - this.textBuilder, - this.quotedMessageBuilder, - this.onLinkTap, - this.onMentionTap, - this.onQuotedMessageTap, - }); - - /// {@macro isFailedState} - final bool isFailedState; - - /// {@macro showUserAvatar} - final DisplayWidget showUserAvatar; - - /// {@macro shape} - final ShapeBorder? shape; - - /// {@macro borderSide} - final BorderSide? borderSide; - - /// {@macro messageTheme} - final StreamMessageThemeData messageTheme; - - /// {@macro borderRadiusGeometry} - final BorderRadiusGeometry? borderRadiusGeometry; - - /// {@macro hasQuotedMessage} - final bool hasQuotedMessage; - - /// {@macro hasUrlAttachments} - final bool hasUrlAttachments; - - /// {@macro hasNonUrlAttachments} - final bool hasNonUrlAttachments; - - /// {@macro isOnlyEmoji} - final bool isOnlyEmoji; - - /// {@macro isGiphy} - final bool isGiphy; - - /// {@macro message} - final Message message; - - /// {@macro attachmentBuilders} - final List? attachmentBuilders; - - /// {@macro attachmentPadding} - final EdgeInsetsGeometry attachmentPadding; - - /// {@macro attachmentShape} - final ShapeBorder? attachmentShape; - - /// {@macro onAttachmentWidgetTap} - final OnAttachmentWidgetTap? onAttachmentTap; - - /// {@macro onShowMessage} - final ShowMessageCallback? onShowMessage; - - /// {@macro onReplyTap} - final void Function(Message)? onReplyTap; - - /// {@macro attachmentActionsBuilder} - final AttachmentActionsBuilder? attachmentActionsModalBuilder; - - /// {@macro textPadding} - final EdgeInsets textPadding; - - /// {@macro textBuilder} - final Widget Function(BuildContext, Message)? textBuilder; - - /// {@macro quotedMessageBuilder} - final Widget Function(BuildContext, Message)? quotedMessageBuilder; - - /// {@macro onLinkTap} - final void Function(String)? onLinkTap; - - /// {@macro onMentionTap} - final void Function(User)? onMentionTap; - - /// {@macro onQuotedMessageTap} - final OnQuotedMessageTap? onQuotedMessageTap; - - /// {@macro reverse} - final bool reverse; - - @override - State createState() => _MessageCardState(); -} - -class _MessageCardState extends State { - final attachmentsKey = GlobalKey(); - double? widthLimit; - - void _updateWidthLimit() { - final attachmentContext = attachmentsKey.currentContext; - final renderBox = attachmentContext?.findRenderObject() as RenderBox?; - final attachmentsWidth = renderBox?.size.width; - - if (attachmentsWidth == null || attachmentsWidth == 0) return; - - if (mounted) { - setState(() => widthLimit = attachmentsWidth); - } - } - - @override - void didChangeDependencies() { - super.didChangeDependencies(); - // If there is an attachment, we need to wait for the attachment to be - // rendered to get the width of the attachment and set it as the width - // limit of the message card. - WidgetsBinding.instance.addPostFrameCallback((_) { - _updateWidthLimit(); - }); - } - - @override - Widget build(BuildContext context) { - final onQuotedMessageTap = widget.onQuotedMessageTap; - final quotedMessageBuilder = widget.quotedMessageBuilder; - - return Container( - constraints: const BoxConstraints().copyWith(maxWidth: widthLimit), - margin: EdgeInsetsDirectional.only( - end: widget.reverse && widget.isFailedState ? 12.0 : 0.0, - start: !widget.reverse && widget.isFailedState ? 12.0 : 0.0, - ), - clipBehavior: Clip.hardEdge, - decoration: _buildDecoration(widget.messageTheme), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (widget.hasQuotedMessage) - InkWell( - onTap: !widget.message.quotedMessage!.isDeleted && - onQuotedMessageTap != null - ? () => onQuotedMessageTap(widget.message.quotedMessageId) - : null, - child: quotedMessageBuilder?.call( - context, - widget.message.quotedMessage!, - ) ?? - QuotedMessage( - message: widget.message, - textBuilder: widget.textBuilder, - hasNonUrlAttachments: widget.hasNonUrlAttachments, - ), - ), - ParseAttachments( - key: attachmentsKey, - message: widget.message, - attachmentBuilders: widget.attachmentBuilders, - attachmentPadding: widget.attachmentPadding, - attachmentShape: widget.attachmentShape, - onAttachmentTap: widget.onAttachmentTap, - onShowMessage: widget.onShowMessage, - onLinkTap: widget.onLinkTap, - onReplyTap: widget.onReplyTap, - attachmentActionsModalBuilder: widget.attachmentActionsModalBuilder, - ), - TextBubble( - messageTheme: widget.messageTheme, - message: widget.message, - textPadding: widget.textPadding, - textBuilder: widget.textBuilder, - isOnlyEmoji: widget.isOnlyEmoji, - hasQuotedMessage: widget.hasQuotedMessage, - hasUrlAttachments: widget.hasUrlAttachments, - onLinkTap: widget.onLinkTap, - onMentionTap: widget.onMentionTap, - ), - ], - ), - ); - } - - ShapeDecoration _buildDecoration(StreamMessageThemeData theme) { - final gradient = _getBackgroundGradient(theme); - final color = gradient == null ? _getBackgroundColor(theme) : null; - - final borderColor = theme.messageBorderColor ?? Colors.transparent; - final borderRadius = widget.borderRadiusGeometry ?? BorderRadius.zero; - - return ShapeDecoration( - color: color, - gradient: gradient, - shape: switch (widget.shape) { - final shape? => shape, - _ => RoundedRectangleBorder( - borderRadius: borderRadius, - side: switch (widget.borderSide) { - final side? => side, - _ => BorderSide(color: borderColor), - }, - ), - }, - ); - } - - Color? _getBackgroundColor(StreamMessageThemeData theme) { - if (widget.hasQuotedMessage) { - return theme.messageBackgroundColor; - } - - final containsOnlyUrlAttachment = - widget.hasUrlAttachments && !widget.hasNonUrlAttachments; - - if (containsOnlyUrlAttachment) { - return theme.urlAttachmentBackgroundColor; - } - - if (widget.isOnlyEmoji) return null; - - return theme.messageBackgroundColor; - } - - Gradient? _getBackgroundGradient(StreamMessageThemeData theme) { - if (widget.isOnlyEmoji) return null; - - return theme.messageBackgroundGradient; - } -} diff --git a/packages/stream_chat_flutter/lib/src/message_widget/message_text.dart b/packages/stream_chat_flutter/lib/src/message_widget/message_text.dart deleted file mode 100644 index e46e4fc420..0000000000 --- a/packages/stream_chat_flutter/lib/src/message_widget/message_text.dart +++ /dev/null @@ -1,74 +0,0 @@ -import 'package:collection/collection.dart' show IterableExtension; -import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; - -/// {@template streamMessageText} -/// The text content of a message. -/// {@endtemplate} -class StreamMessageText extends StatelessWidget { - /// {@macro streamMessageText} - const StreamMessageText({ - super.key, - required this.message, - required this.messageTheme, - this.onMentionTap, - this.onLinkTap, - }); - - /// Message whose text is to be displayed - final Message message; - - /// The action to perform when a mention is tapped - final void Function(User)? onMentionTap; - - /// The action to perform when a link is tapped - final void Function(String)? onLinkTap; - - /// [StreamMessageThemeData] whose text theme is to be applied - final StreamMessageThemeData messageTheme; - - @override - Widget build(BuildContext context) { - final streamChat = StreamChat.of(context); - assert(streamChat.currentUser != null, ''); - return BetterStreamBuilder( - stream: streamChat.currentUserStream.map((it) => it!.language ?? 'en'), - initialData: streamChat.currentUser!.language ?? 'en', - builder: (context, language) { - final messageText = message - .translate(language) - .replaceMentions() - .text - ?.replaceAll('\n', '\n\n') - .trim(); - - return StreamMarkdownMessage( - data: messageText ?? '', - messageTheme: messageTheme, - selectable: isDesktopDeviceOrWeb, - onTapLink: ( - String text, - String? href, - String title, - ) { - if (text.startsWith('@')) { - final mentionedUser = message.mentionedUsers.firstWhereOrNull( - (u) => '@${u.name}' == text, - ); - - if (mentionedUser == null) return; - - onMentionTap?.call(mentionedUser); - } else if (href != null) { - if (onLinkTap != null) { - onLinkTap!(href); - } else { - launchURL(context, href); - } - } - }, - ); - }, - ); - } -} diff --git a/packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart b/packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart index 685a98abd4..42f3f253a8 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart @@ -1,784 +1,494 @@ +import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:flutter_portal/flutter_portal.dart'; -import 'package:stream_chat_flutter/platform_widget_builder/platform_widget_builder.dart'; +import 'package:stream_chat_flutter/platform_widget_builder/src/platform_widget_builder.dart'; import 'package:stream_chat_flutter/src/context_menu/context_menu.dart'; import 'package:stream_chat_flutter/src/context_menu/context_menu_region.dart'; -import 'package:stream_chat_flutter/src/message_widget/message_widget_content.dart'; +import 'package:stream_chat_flutter/src/message_widget/components/stream_message_content.dart'; +import 'package:stream_chat_flutter/src/message_widget/components/stream_message_footer.dart'; +import 'package:stream_chat_flutter/src/message_widget/components/stream_message_header.dart'; +import 'package:stream_chat_flutter/src/message_widget/components/stream_message_leading.dart'; import 'package:stream_chat_flutter/src/misc/flexible_fractionally_sized_box.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart' hide StreamMessageContent; -/// The display behaviour of a widget -enum DisplayWidget { - /// Hides the widget replacing its space with a spacer - hide, - - /// Hides the widget not replacing its space - gone, +/// A chat message widget that renders a single message with its attachments, +/// reactions, and interaction callbacks. +/// +/// [StreamMessageWidget] displays a single [Message] within a chat message +/// list. It handles the complete message layout including the author avatar, +/// message content (text, attachments, polls, quoted messages), reactions, +/// thread indicators, and user interaction gestures such as tap, long-press, +/// and context menus. +/// +/// On mobile platforms, a long-press opens the [StreamMessageActionsModal] +/// with available actions (reply, edit, delete, pin, etc.). On desktop and +/// web, those same actions appear in a right-click context menu. +/// +/// This widget delegates rendering to either a custom builder registered via +/// [StreamComponentFactory], or [DefaultStreamMessage] when no custom builder +/// is provided. Register a custom builder through [StreamChatConfigurationData] +/// to fully replace the default message layout while still receiving the same +/// [StreamMessageWidgetProps]. +/// +/// {@tool snippet} +/// +/// Display a message with default settings: +/// +/// ```dart +/// StreamMessageWidget( +/// message: message, +/// ) +/// ``` +/// {@end-tool} +/// +/// {@tool snippet} +/// +/// Customise interaction callbacks: +/// +/// ```dart +/// StreamMessageWidget( +/// message: message, +/// onMessageTap: (msg) => print('Tapped: ${msg.id}'), +/// onThreadTap: (msg) => Navigator.push(...), +/// onUserAvatarTap: (user) => showProfile(user), +/// ) +/// ``` +/// {@end-tool} +/// +/// See also: +/// +/// * [StreamMessageWidgetProps], which holds every configurable property. +/// * [DefaultStreamMessage], the default implementation used when no custom +/// builder is registered. +/// * [StreamMessageActionsModal], the modal shown on long-press (mobile). +/// * [StreamMessageListView], which hosts a scrollable list of these widgets. +class StreamMessageWidget extends StatelessWidget { + /// Creates a chat message widget. + /// + /// The [message] is required. All other parameters are optional and have + /// sensible defaults resolved from the ambient theme and message data. + StreamMessageWidget({ + super.key, + required Message message, + EdgeInsetsGeometry? padding, + double? spacing, + Color? backgroundColor, + double widthFactor = 0.8, + void Function(Message)? onMessageTap, + void Function(Message)? onMessageLongPress, + void Function(User)? onUserAvatarTap, + void Function(Message message, String url)? onMessageLinkTap, + void Function(User user)? onUserMentionTap, + void Function(Message)? onThreadTap, + void Function(Message)? onReplyTap, + void Function(Message)? onReactionsTap, + void Function(Message quotedMessage)? onQuotedMessageTap, + Comparator? reactionSorting, + MessageActionsBuilder? actionsBuilder, + void Function(BuildContext, Message)? onMessageActions, + void Function(BuildContext, Message)? onBouncedErrorMessageActions, + void Function(Message)? onEditMessageTap, + List? attachmentBuilders, + }) : props = .new( + message: message, + padding: padding, + spacing: spacing, + backgroundColor: backgroundColor, + widthFactor: widthFactor, + onMessageTap: onMessageTap, + onMessageLongPress: onMessageLongPress, + onUserAvatarTap: onUserAvatarTap, + onMessageLinkTap: onMessageLinkTap, + onUserMentionTap: onUserMentionTap, + onThreadTap: onThreadTap, + onReplyTap: onReplyTap, + onReactionsTap: onReactionsTap, + onQuotedMessageTap: onQuotedMessageTap, + reactionSorting: reactionSorting, + actionsBuilder: actionsBuilder, + onMessageActions: onMessageActions, + onBouncedErrorMessageActions: onBouncedErrorMessageActions, + onEditMessageTap: onEditMessageTap, + attachmentBuilders: attachmentBuilders, + ); + + /// Creates a chat message widget from pre-built [props]. + const StreamMessageWidget.fromProps({super.key, required this.props}); + + /// The properties that configure this message widget. + final StreamMessageWidgetProps props; - /// Shows the widget normally - show, + @override + Widget build(BuildContext context) { + final builder = context.chatComponentBuilder(); + if (builder != null) return builder(context, props); + return DefaultStreamMessage(props: props); + } } -/// {@template messageWidget} -/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/packages/stream_chat_flutter/screenshots/message_widget.png) -/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/packages/stream_chat_flutter/screenshots/message_widget_paint.png) +/// Properties for configuring a [StreamMessageWidget]. /// -/// Shows a message with reactions, replies and user avatar. +/// This class holds every configuration option for a chat message widget, +/// allowing them to be passed through the [StreamComponentFactory] when a +/// custom builder is registered. /// -/// Usually you don't use this widget as it's the default message widget used by -/// [MessageListView]. +/// Visual properties such as [padding], [spacing], and [backgroundColor] +/// override the corresponding values from [StreamMessageItemThemeData] when +/// non-null. When left null, the theme values are used instead. /// -/// The widget components render the ui based on the first ancestor of type -/// [StreamChatTheme]. -/// Modify it to change the widget appearance. -/// {@endtemplate} -class StreamMessageWidget extends StatefulWidget { - /// {@macro messageWidget} - const StreamMessageWidget({ - super.key, +/// See also: +/// +/// * [StreamMessageWidget], which uses these properties. +/// * [DefaultStreamMessage], the default implementation. +class StreamMessageWidgetProps { + /// Creates properties for a chat message widget. + const StreamMessageWidgetProps({ required this.message, - required this.messageTheme, - this.reverse = false, - this.translateUserAvatar = true, - this.shape, - this.borderSide, - this.borderRadiusGeometry, - this.attachmentShape, - this.onMentionTap, + this.padding, + this.spacing, + this.backgroundColor, + this.widthFactor = 0.8, this.onMessageTap, this.onMessageLongPress, - this.onReactionsTap, - this.onReactionsHover, - this.showReactionPicker = true, - this.showUserAvatar = DisplayWidget.show, - this.showSendingIndicator = true, - this.showThreadReplyIndicator = false, - this.showInChannelIndicator = false, - this.onReplyTap, - this.onThreadTap, - this.onConfirmDeleteTap, - this.showUsername = true, - this.showTimestamp = true, - this.showEditedLabel = true, - this.showReactions = true, - this.showDeleteMessage = true, - this.showEditMessage = true, - this.showReplyMessage = true, - this.showThreadReplyMessage = true, - this.showMarkUnreadMessage = true, - this.showResendMessage = true, - this.showCopyMessage = true, - this.showFlagButton = true, - this.showPinButton = true, - this.showPinHighlight = true, this.onUserAvatarTap, - this.onLinkTap, + this.onMessageLinkTap, + this.onUserMentionTap, + this.onThreadTap, + this.onReplyTap, + this.onReactionsTap, + this.onQuotedMessageTap, + this.reactionSorting, + this.actionsBuilder, this.onMessageActions, this.onBouncedErrorMessageActions, - this.onShowMessage, - this.userAvatarBuilder, - this.quotedMessageBuilder, - this.deletedMessageBuilder, - this.editMessageInputBuilder, - this.textBuilder, - this.bottomRowBuilderWithDefaultWidget, + this.onEditMessageTap, this.attachmentBuilders, - this.padding, - this.textPadding = const EdgeInsets.symmetric( - horizontal: 16, - vertical: 8, - ), - this.attachmentPadding = EdgeInsets.zero, - this.widthFactor = 0.78, - this.onQuotedMessageTap, - this.customActions = const [], - this.onCustomActionTap, - this.onAttachmentTap, - this.imageAttachmentThumbnailSize = const Size(400, 400), - this.imageAttachmentThumbnailResizeType = 'clip', - this.imageAttachmentThumbnailCropType = 'center', - this.attachmentActionsModalBuilder, - this.reactionPickerBuilder = StreamReactionPicker.builder, - this.reactionIndicatorBuilder = StreamReactionIndicator.builder, }); - /// {@template onMentionTap} - /// Function called on mention tap - /// {@endtemplate} - final void Function(User)? onMentionTap; - - /// {@template onThreadTap} - /// The function called when tapping on threads - /// {@endtemplate} - final void Function(Message)? onThreadTap; - - /// {@template onReplyTap} - /// The function called when tapping on replies - /// {@endtemplate} - final void Function(Message)? onReplyTap; - - /// {@template onDeleteTap} - /// The function called when delete confirmation button is tapped. - /// {@endtemplate} - final Future Function(Message)? onConfirmDeleteTap; - - /// {@template editMessageInputBuilder} - /// Widget builder for edit message layout - /// {@endtemplate} - final Widget Function(BuildContext, Message)? editMessageInputBuilder; - - /// {@template textBuilder} - /// Widget builder for building text - /// {@endtemplate} - final Widget Function(BuildContext, Message)? textBuilder; - - /// {@template onMessageActions} - /// Function called when a message is long-pressed to show actions. - /// If provided, this callback will be called instead of showing the default - /// message actions modal dialog. - /// {@endtemplate} - final void Function(BuildContext, Message)? onMessageActions; - - /// {@template onBouncedErrorMessageActions} - /// Function called when a message that has bounced with an error is long - /// pressed. If provided, this callback will be called instead of showing the - /// default bounced error message actions dialog. - /// {@endtemplate} - final void Function(BuildContext, Message)? onBouncedErrorMessageActions; - - /// {@template bottomRowBuilderWithDefaultWidget} - /// Widget builder for building a bottom row below the message. - /// Also contains the default bottom row widget. - /// {@endtemplate} - final BottomRowBuilderWithDefaultWidget? bottomRowBuilderWithDefaultWidget; - - /// {@template userAvatarBuilder} - /// Widget builder for building user avatar - /// {@endtemplate} - final Widget Function(BuildContext, User)? userAvatarBuilder; - - /// {@template quotedMessageBuilder} - /// Widget builder for building quoted message - /// {@endtemplate} - final Widget Function(BuildContext, Message)? quotedMessageBuilder; - - /// {@template deletedMessageBuilder} - /// Widget builder for building deleted message - /// {@endtemplate} - final Widget Function(BuildContext, Message)? deletedMessageBuilder; - - /// {@template message} /// The message to display. - /// {@endtemplate} final Message message; - /// {@template messageTheme} - /// The message theme - /// {@endtemplate} - final StreamMessageThemeData messageTheme; - - /// {@template reverse} - /// If true the widget will be mirrored - /// {@endtemplate} - final bool reverse; - - /// {@template shape} - /// The shape of the message text - /// {@endtemplate} - final ShapeBorder? shape; - - /// {@template attachmentShape} - /// The shape of an attachment - /// {@endtemplate} - final ShapeBorder? attachmentShape; - - /// {@template borderSide} - /// The borderSide of the message text - /// {@endtemplate} - final BorderSide? borderSide; - - /// {@template borderRadiusGeometry} - /// The border radius of the message text - /// {@endtemplate} - final BorderRadiusGeometry? borderRadiusGeometry; - - /// {@template padding} - /// The padding of the widget - /// {@endtemplate} + /// Outer padding around the entire message item. + /// + /// When non-null, takes precedence over the theme value from + /// [StreamMessageItemThemeData.padding]. + /// + /// When null (the default), the padding is determined by the theme. final EdgeInsetsGeometry? padding; - /// {@template textPadding} - /// The internal padding of the message text - /// {@endtemplate} - final EdgeInsets textPadding; + /// Horizontal spacing between the leading avatar and the content. + /// + /// When non-null, takes precedence over the theme value from + /// [StreamMessageItemThemeData.spacing]. + /// + /// When null (the default), the spacing is determined by the theme. + final double? spacing; - /// {@template attachmentPadding} - /// The internal padding of an attachment - /// {@endtemplate} - final EdgeInsetsGeometry attachmentPadding; + /// Background color for the entire message item row. + /// + /// When non-null, takes precedence over the theme value from + /// [StreamMessageItemThemeData.backgroundColor]. + /// + /// When null (the default), the background color is determined by the theme. + final Color? backgroundColor; - /// {@template widthFactor} - /// The percentage of the available width the message content should take - /// {@endtemplate} + /// Maximum width of the message content as a fraction of the parent width. + /// + /// Values should be between 0.0 and 1.0. Defaults to 0.8 when not specified. final double widthFactor; - /// {@template showUserAvatar} - /// It controls the display behaviour of the user avatar - /// {@endtemplate} - final DisplayWidget showUserAvatar; - - /// {@template showSendingIndicator} - /// It controls the display behaviour of the sending indicator - /// {@endtemplate} - final bool showSendingIndicator; - - /// {@template showReactions} - /// If `true` the message's reactions will be shown. - /// {@endtemplate} - final bool showReactions; - - /// {@template showThreadReplyIndicator} - /// If true the widget will show the thread reply indicator - /// {@endtemplate} - final bool showThreadReplyIndicator; - - /// {@template showInChannelIndicator} - /// If true the widget will show the show in channel indicator - /// {@endtemplate} - final bool showInChannelIndicator; - - /// {@template onUserAvatarTap} - /// The function called when tapping on UserAvatar - /// {@endtemplate} - final void Function(User)? onUserAvatarTap; - - /// {@template onLinkTap} - /// The function called when tapping on a link - /// {@endtemplate} - final void Function(String)? onLinkTap; - - /// {@template showReactionPicker} - /// Whether or not to show the reaction picker. - /// Used in [StreamMessageReactionsModal] and [StreamMessageActionsModal]. - /// {@endtemplate} - final bool showReactionPicker; - - /// {@template onShowMessage} - /// Callback when show message is tapped - /// {@endtemplate} - final ShowMessageCallback? onShowMessage; - - /// {@template showUsername} - /// If true show the users username next to the timestamp of the message - /// {@endtemplate} - final bool showUsername; - - /// {@template showTimestamp} - /// Show message timestamp - /// {@endtemplate} - final bool showTimestamp; - - /// {@template showTimestamp} - /// Show edited label if message is edited - /// {@endtemplate} - final bool showEditedLabel; - - /// {@template showReplyMessage} - /// Show reply action - /// {@endtemplate} - final bool showReplyMessage; - - /// {@template showThreadReplyMessage} - /// Show thread reply action - /// {@endtemplate} - final bool showThreadReplyMessage; - - /// {@template showMarkUnreadMessage} - /// Show mark unread action - /// {@endtemplate} - final bool showMarkUnreadMessage; - - /// {@template showEditMessage} - /// Show edit action - /// {@endtemplate} - final bool showEditMessage; - - /// {@template showCopyMessage} - /// Show copy action - /// {@endtemplate} - final bool showCopyMessage; - - /// {@template showDeleteMessage} - /// Show delete action - /// {@endtemplate} - final bool showDeleteMessage; - - /// {@template showResendMessage} - /// Show resend action - /// {@endtemplate} - final bool showResendMessage; - - /// {@template showFlagButton} - /// Show flag action - /// {@endtemplate} - final bool showFlagButton; - - /// {@template showPinButton} - /// Show pin action - /// {@endtemplate} - final bool showPinButton; - - /// {@template showPinHighlight} - /// Display Pin Highlight - /// {@endtemplate} - final bool showPinHighlight; - - /// {@template attachmentBuilders} - /// List of attachment builders for rendering attachment widgets pre-defined - /// and custom attachment types. + /// Called when the message is tapped. /// - /// If null, the widget will create a default list of attachment builders - /// based on the [Attachment.type] of the attachment. - /// {@endtemplate} - final List? attachmentBuilders; + /// If null, no tap gesture is registered on mobile. On desktop and web, + /// tap behaviour is unaffected because interactions are driven by the + /// context menu instead. + final void Function(Message message)? onMessageTap; - /// {@template translateUserAvatar} - /// Center user avatar with bottom of the message - /// {@endtemplate} - final bool translateUserAvatar; - - /// {@macro onQuotedMessageTap} - final OnQuotedMessageTap? onQuotedMessageTap; + /// Called when the message is long-pressed. + /// + /// If null, the default long-press behaviour is used, which opens the + /// [StreamMessageActionsModal] on mobile. Provide this callback to + /// override that behaviour entirely. + final void Function(Message message)? onMessageLongPress; - /// {@macro onMessageTap} - final OnMessageTap? onMessageTap; + /// Called when the author's avatar is tapped. + /// + /// If null, tapping the avatar has no effect. A common use is to navigate + /// to the user's profile screen. + final void Function(User user)? onUserAvatarTap; - /// {@macro onMessageLongPress} - final OnMessageLongPress? onMessageLongPress; + /// Called when a link is tapped in the message text. + /// + /// Receives the [Message] containing the link and the tapped URL string. + /// If null, the default link handling behaviour is used. + final void Function(Message message, String url)? onMessageLinkTap; - /// {@macro onReactionsTap} + /// Called when a `@mention` is tapped in the message text. /// - /// Note: Only used in mobile devices (iOS and Android). Do not confuse this - /// with the tap action on the reactions picker. - final OnReactionsTap? onReactionsTap; + /// Receives the mentioned [User] resolved from the message's + /// [Message.mentionedUsers] list. If null, tapping a mention has no effect. + final void Function(User user)? onUserMentionTap; - /// {@macro onReactionsHover} + /// Called when the thread reply indicator is tapped. + /// + /// Receives the parent [Message] of the thread. If the message was shown + /// in-channel via [Message.showInChannel], the original parent message is + /// fetched before invoking the callback. /// - /// Note: Only used in desktop devices (web and desktop). - final OnReactionsHover? onReactionsHover; + /// If null, tapping the thread indicator has no effect. + final void Function(Message message)? onThreadTap; - /// {@template customActions} - /// List of custom actions shown on message long tap - /// {@endtemplate} - final List customActions; + /// Called when the quoted-reply action is selected from the actions list. + /// + /// Receives the [Message] that should be quoted. Typically used to set the + /// quoted message on the message input. + /// + /// If null, the quoted-reply action is still shown but has no effect. + final void Function(Message message)? onReplyTap; - /// Callback for when a custom message action is tapped. + /// Called when the reactions row beneath the message bubble is tapped. /// - /// {@macro onMessageActionTap} - final OnMessageActionTap? onCustomActionTap; + /// If null, the default behaviour opens a [ReactionDetailSheet] showing + /// the full list of reactions. Provide this callback to replace that + /// default with custom handling. + final void Function(Message message)? onReactionsTap; - /// {@macro onAttachmentWidgetTap} - final OnAttachmentWidgetTap? onAttachmentTap; + /// Called when an inline quoted message is tapped. + /// + /// Receives the [Message] that was quoted. Typically used to scroll to + /// the original message in the list. + /// + /// If null, tapping the quoted message has no effect. + final void Function(Message quotedMessage)? onQuotedMessageTap; - /// {@macro attachmentActionsBuilder} - final AttachmentActionsBuilder? attachmentActionsModalBuilder; + /// Controls how reaction groups are sorted when displayed. + /// + /// Defaults to [ReactionSorting.byFirstReactionAt]. + final Comparator? reactionSorting; - /// {@macro reactionPickerBuilder} - final ReactionPickerBuilder reactionPickerBuilder; + /// Allows customizing the default message actions list. + /// + /// Receives the [BuildContext] and the default list of + /// [StreamContextMenuAction] items built by the widget. Return a modified + /// list to add, remove, or reorder actions. + final MessageActionsBuilder? actionsBuilder; - /// {@macro reactionIndicatorBuilder} - final ReactionIndicatorBuilder reactionIndicatorBuilder; + /// Called when a normal message is long-pressed to show actions. + /// + /// When provided, this callback replaces the default behaviour of showing + /// the [StreamMessageActionsModal]. + final void Function(BuildContext context, Message message)? onMessageActions; - /// Size of the image attachment thumbnail. - final Size imageAttachmentThumbnailSize; + /// Called when a bounced-error message is long-pressed. + /// + /// When provided, this callback replaces the default behaviour of showing + /// the [ModeratedMessageActionsModal]. + final void Function(BuildContext context, Message message)? onBouncedErrorMessageActions; - /// Resize type of the image attachment thumbnail. + /// Called when the edit-message action is selected. /// - /// Defaults to [crop] - final String /*clip|crop|scale|fill*/ imageAttachmentThumbnailResizeType; + /// When provided, this callback replaces the default behaviour of showing + /// the edit-message bottom sheet via [showEditMessageSheet]. + final void Function(Message message)? onEditMessageTap; - /// Crop type of the image attachment thumbnail. + /// Custom attachment builders for rendering message attachments. /// - /// Defaults to [center] - final String /*center|top|bottom|left|right*/ - imageAttachmentThumbnailCropType; - - /// {@template copyWith} - /// Creates a copy of [StreamMessageWidget] with specified attributes - /// overridden. - /// {@endtemplate} - StreamMessageWidget copyWith({ - Key? key, - void Function(User)? onMentionTap, - void Function(Message)? onThreadTap, - void Function(Message)? onReplyTap, - Future Function(Message)? onConfirmDeleteTap, - Widget Function(BuildContext, Message)? editMessageInputBuilder, - Widget Function(BuildContext, Message)? textBuilder, - Widget Function(BuildContext, Message)? quotedMessageBuilder, - Widget Function(BuildContext, Message)? deletedMessageBuilder, - BottomRowBuilderWithDefaultWidget? bottomRowBuilderWithDefaultWidget, - void Function(BuildContext, Message)? onMessageActions, - void Function(BuildContext, Message)? onBouncedErrorMessageActions, + /// When non-null, these builders are used instead of the default ones + /// provided by [StreamChatConfigurationData.attachmentBuilders]. + /// + /// Custom builders are prepended to the default builder list, so they take + /// priority for attachment types they can handle. + final List? attachmentBuilders; + + /// Returns a copy of this [StreamMessageWidgetProps] with the given fields + /// replaced with new values. + StreamMessageWidgetProps copyWith({ Message? message, - StreamMessageThemeData? messageTheme, - bool? reverse, - ShapeBorder? shape, - ShapeBorder? attachmentShape, - BorderSide? borderSide, - BorderRadiusGeometry? borderRadiusGeometry, EdgeInsetsGeometry? padding, - EdgeInsets? textPadding, - EdgeInsetsGeometry? attachmentPadding, + double? spacing, + Color? backgroundColor, double? widthFactor, - DisplayWidget? showUserAvatar, - bool? showSendingIndicator, - bool? showReactions, - bool? allRead, - bool? showThreadReplyIndicator, - bool? showInChannelIndicator, + void Function(Message)? onMessageTap, + void Function(Message)? onMessageLongPress, void Function(User)? onUserAvatarTap, - void Function(String)? onLinkTap, - bool? showReactionBrowser, - bool? showReactionPicker, - List? readList, - ShowMessageCallback? onShowMessage, - bool? showUsername, - bool? showTimestamp, - bool? showEditedLabel, - bool? showReplyMessage, - bool? showThreadReplyMessage, - bool? showEditMessage, - bool? showCopyMessage, - bool? showDeleteMessage, - bool? showResendMessage, - bool? showFlagButton, - bool? showPinButton, - bool? showPinHighlight, - bool? showMarkUnreadMessage, + void Function(Message, String)? onMessageLinkTap, + void Function(User)? onUserMentionTap, + void Function(Message)? onThreadTap, + void Function(Message)? onReplyTap, + void Function(Message)? onReactionsTap, + void Function(Message)? onQuotedMessageTap, + Comparator? reactionSorting, + MessageActionsBuilder? actionsBuilder, + void Function(BuildContext, Message)? onMessageActions, + void Function(BuildContext, Message)? onBouncedErrorMessageActions, + void Function(Message)? onEditMessageTap, List? attachmentBuilders, - bool? translateUserAvatar, - OnQuotedMessageTap? onQuotedMessageTap, - OnMessageTap? onMessageTap, - OnMessageLongPress? onMessageLongPress, - OnReactionsTap? onReactionsTap, - OnReactionsHover? onReactionsHover, - List? customActions, - OnMessageActionTap? onCustomActionTap, - OnAttachmentWidgetTap? onAttachmentTap, - Widget Function(BuildContext, User)? userAvatarBuilder, - Size? imageAttachmentThumbnailSize, - String? imageAttachmentThumbnailResizeType, - String? imageAttachmentThumbnailCropType, - AttachmentActionsBuilder? attachmentActionsModalBuilder, - ReactionPickerBuilder? reactionPickerBuilder, - ReactionIndicatorBuilder? reactionIndicatorBuilder, }) { - return StreamMessageWidget( - key: key ?? this.key, - onMentionTap: onMentionTap ?? this.onMentionTap, - onThreadTap: onThreadTap ?? this.onThreadTap, - onReplyTap: onReplyTap ?? this.onReplyTap, - onConfirmDeleteTap: onConfirmDeleteTap ?? this.onConfirmDeleteTap, - editMessageInputBuilder: - editMessageInputBuilder ?? this.editMessageInputBuilder, - textBuilder: textBuilder ?? this.textBuilder, - quotedMessageBuilder: quotedMessageBuilder ?? this.quotedMessageBuilder, - deletedMessageBuilder: - deletedMessageBuilder ?? this.deletedMessageBuilder, - bottomRowBuilderWithDefaultWidget: bottomRowBuilderWithDefaultWidget ?? - this.bottomRowBuilderWithDefaultWidget, - onMessageActions: onMessageActions ?? this.onMessageActions, - onBouncedErrorMessageActions: - onBouncedErrorMessageActions ?? this.onBouncedErrorMessageActions, + return StreamMessageWidgetProps( message: message ?? this.message, - messageTheme: messageTheme ?? this.messageTheme, - reverse: reverse ?? this.reverse, - shape: shape ?? this.shape, - attachmentShape: attachmentShape ?? this.attachmentShape, - borderSide: borderSide ?? this.borderSide, - borderRadiusGeometry: borderRadiusGeometry ?? this.borderRadiusGeometry, padding: padding ?? this.padding, - textPadding: textPadding ?? this.textPadding, - attachmentPadding: attachmentPadding ?? this.attachmentPadding, + spacing: spacing ?? this.spacing, + backgroundColor: backgroundColor ?? this.backgroundColor, widthFactor: widthFactor ?? this.widthFactor, - showUserAvatar: showUserAvatar ?? this.showUserAvatar, - showSendingIndicator: showSendingIndicator ?? this.showSendingIndicator, - showEditedLabel: showEditedLabel ?? this.showEditedLabel, - showReactions: showReactions ?? this.showReactions, - showThreadReplyIndicator: - showThreadReplyIndicator ?? this.showThreadReplyIndicator, - showInChannelIndicator: - showInChannelIndicator ?? this.showInChannelIndicator, - onUserAvatarTap: onUserAvatarTap ?? this.onUserAvatarTap, - onLinkTap: onLinkTap ?? this.onLinkTap, - showReactionPicker: showReactionPicker ?? this.showReactionPicker, - onShowMessage: onShowMessage ?? this.onShowMessage, - showUsername: showUsername ?? this.showUsername, - showTimestamp: showTimestamp ?? this.showTimestamp, - showReplyMessage: showReplyMessage ?? this.showReplyMessage, - showThreadReplyMessage: - showThreadReplyMessage ?? this.showThreadReplyMessage, - showEditMessage: showEditMessage ?? this.showEditMessage, - showCopyMessage: showCopyMessage ?? this.showCopyMessage, - showDeleteMessage: showDeleteMessage ?? this.showDeleteMessage, - showResendMessage: showResendMessage ?? this.showResendMessage, - showFlagButton: showFlagButton ?? this.showFlagButton, - showPinButton: showPinButton ?? this.showPinButton, - showPinHighlight: showPinHighlight ?? this.showPinHighlight, - showMarkUnreadMessage: - showMarkUnreadMessage ?? this.showMarkUnreadMessage, - attachmentBuilders: attachmentBuilders ?? this.attachmentBuilders, - translateUserAvatar: translateUserAvatar ?? this.translateUserAvatar, - onQuotedMessageTap: onQuotedMessageTap ?? this.onQuotedMessageTap, onMessageTap: onMessageTap ?? this.onMessageTap, onMessageLongPress: onMessageLongPress ?? this.onMessageLongPress, + onUserAvatarTap: onUserAvatarTap ?? this.onUserAvatarTap, + onMessageLinkTap: onMessageLinkTap ?? this.onMessageLinkTap, + onUserMentionTap: onUserMentionTap ?? this.onUserMentionTap, + onThreadTap: onThreadTap ?? this.onThreadTap, + onReplyTap: onReplyTap ?? this.onReplyTap, onReactionsTap: onReactionsTap ?? this.onReactionsTap, - onReactionsHover: onReactionsHover ?? this.onReactionsHover, - customActions: customActions ?? this.customActions, - onCustomActionTap: onCustomActionTap ?? this.onCustomActionTap, - onAttachmentTap: onAttachmentTap ?? this.onAttachmentTap, - userAvatarBuilder: userAvatarBuilder ?? this.userAvatarBuilder, - imageAttachmentThumbnailSize: - imageAttachmentThumbnailSize ?? this.imageAttachmentThumbnailSize, - imageAttachmentThumbnailResizeType: imageAttachmentThumbnailResizeType ?? - this.imageAttachmentThumbnailResizeType, - imageAttachmentThumbnailCropType: imageAttachmentThumbnailCropType ?? - this.imageAttachmentThumbnailCropType, - attachmentActionsModalBuilder: - attachmentActionsModalBuilder ?? this.attachmentActionsModalBuilder, - reactionPickerBuilder: - reactionPickerBuilder ?? this.reactionPickerBuilder, - reactionIndicatorBuilder: - reactionIndicatorBuilder ?? this.reactionIndicatorBuilder, + onQuotedMessageTap: onQuotedMessageTap ?? this.onQuotedMessageTap, + reactionSorting: reactionSorting ?? this.reactionSorting, + actionsBuilder: actionsBuilder ?? this.actionsBuilder, + onMessageActions: onMessageActions ?? this.onMessageActions, + onBouncedErrorMessageActions: onBouncedErrorMessageActions ?? this.onBouncedErrorMessageActions, + onEditMessageTap: onEditMessageTap ?? this.onEditMessageTap, + attachmentBuilders: attachmentBuilders ?? this.attachmentBuilders, ); } - - @override - _StreamMessageWidgetState createState() => _StreamMessageWidgetState(); } -class _StreamMessageWidgetState extends State - with AutomaticKeepAliveClientMixin { - bool get showThreadReplyIndicator => widget.showThreadReplyIndicator; - - bool get showSendingIndicator => widget.showSendingIndicator; - - bool get isDeleted => widget.message.isDeleted; - - bool get showUsername => widget.showUsername; - - bool get showTimeStamp => widget.showTimestamp; - - bool get showEditedLabel => widget.showEditedLabel; - - bool get isTextEdited => widget.message.messageTextUpdatedAt != null; - - bool get showInChannel => widget.showInChannelIndicator; - - /// {@template hasQuotedMessage} - /// `true` if [StreamMessageWidget.quotedMessage] is not null. - /// {@endtemplate} - bool get hasQuotedMessage => widget.message.quotedMessage != null; - - bool get isSendFailed => widget.message.state.isSendingFailed; - - bool get isUpdateFailed => widget.message.state.isUpdatingFailed; - - bool get isDeleteFailed => widget.message.state.isDeletingFailed; - - bool get isBouncedWithError => widget.message.isBouncedWithError; - - /// {@template isFailedState} - /// Whether the message has failed to be sent, updated, deleted or is bounced - /// back with the message type as error. - /// {@endtemplate} - bool get isFailedState => - isSendFailed || isUpdateFailed || isDeleteFailed || isBouncedWithError; - - /// {@template isGiphy} - /// `true` if any of the [message]'s attachments are a giphy. - /// {@endtemplate} - bool get isGiphy => widget.message.attachments - .any((element) => element.type == AttachmentType.giphy); - - /// {@template isOnlyEmoji} - /// `true` if [message.text] contains only emoji. - /// {@endtemplate} - bool get isOnlyEmoji => widget.message.text?.isOnlyEmoji == true; - - /// {@template hasNonUrlAttachments} - /// `true` if any of the [message]'s attachments are a giphy and do not - /// have a [Attachment.titleLink]. - /// {@endtemplate} - bool get hasNonUrlAttachments => widget.message.attachments - .any((it) => it.type != AttachmentType.urlPreview); - - /// {@template hasUrlAttachments} - /// `true` if any of the [message]'s attachments are a giphy with a - /// [Attachment.titleLink]. - /// {@endtemplate} - bool get hasUrlAttachments => widget.message.attachments - .any((it) => it.type == AttachmentType.urlPreview); - - /// {@template showBottomRow} - /// Show the [BottomRow] widget if any of the following are `true`: - /// * [StreamMessageWidget.showThreadReplyIndicator] - /// * [StreamMessageWidget.showUsername] - /// * [StreamMessageWidget.showTimestamp] - /// * [StreamMessageWidget.showInChannelIndicator] - /// * [StreamMessageWidget.showSendingIndicator] - /// * [StreamMessageWidget.message.isDeleted] - /// {@endtemplate} - bool get showBottomRow => - showThreadReplyIndicator || - showUsername || - showTimeStamp || - showInChannel || - showSendingIndicator || - isTextEdited; - - /// {@template isPinned} - /// Whether [StreamMessageWidget.message] is pinned or not. - /// {@endtemplate} - bool get isPinned => widget.message.pinned && !widget.message.isDeleted; - - /// {@template shouldShowReactions} - /// Should show message reactions if [StreamMessageWidget.showReactions] is - /// `true`, if there are reactions to show, and if the message is not deleted. - /// {@endtemplate} - bool get shouldShowReactions => - widget.showReactions && - (widget.message.latestReactions?.isNotEmpty == true) && - !widget.message.isDeleted; +/// The default implementation of [StreamMessageWidget]. +/// +/// Composes a full message row with an author avatar, content bubble, +/// header annotations, footer metadata, and platform-adaptive interaction +/// handling (tap and long-press on mobile, right-click context menu on +/// desktop and web). +/// +/// Message actions can be customised through +/// [StreamMessageWidgetProps.actionsBuilder]. +/// +/// See also: +/// +/// * [StreamMessageWidget], the public API widget. +/// * [StreamMessageWidgetProps], which configures this widget. +/// * [StreamMessageItemTheme], provides theme data to this widget. +class DefaultStreamMessage extends StatelessWidget { + /// Creates a default chat message widget with the given [props]. + const DefaultStreamMessage({super.key, required this.props}); - @override - bool get wantKeepAlive => widget.message.attachments.isNotEmpty; + /// The properties that configure this widget. + final StreamMessageWidgetProps props; @override Widget build(BuildContext context) { - super.build(context); - final theme = StreamChatTheme.of(context); - final streamChat = StreamChat.of(context); - - final avatarWidth = - widget.messageTheme.avatarTheme?.constraints.maxWidth ?? 40; - final bottomRowPadding = - widget.showUserAvatar != DisplayWidget.gone ? avatarWidth + 8.5 : 0.5; - - return Portal( - child: Material( - color: switch (isPinned && widget.showPinHighlight) { - true => theme.colorTheme.highlight, - false => Colors.transparent, + final message = props.message; + + final placement = StreamMessagePlacement.of(context); + final theme = StreamMessageItemTheme.of(context); + final defaults = _StreamMessageWidgetDefaults(context, isPinned: message.pinned, state: message.state); + + final resolve = StreamMessageStyleResolver(placement, [theme, defaults]); + + final effectivePadding = props.padding ?? theme.padding ?? defaults.padding; + final effectiveSpacing = props.spacing ?? theme.spacing ?? defaults.spacing; + final effectiveBackgroundColor = props.backgroundColor ?? theme.backgroundColor ?? defaults.backgroundColor; + final effectiveLeadingVisibility = resolve((theme) => theme?.leadingVisibility); + final effectiveHeaderVisibility = resolve((theme) => theme?.headerVisibility); + final effectiveFooterVisibility = resolve((theme) => theme?.footerVisibility); + + Widget? leadingWidget; + if (props.message.user case final user?) { + final effectiveAvatarSize = theme.avatarSize ?? defaults.avatarSize; + + leadingWidget = effectiveLeadingVisibility.apply( + StreamAvatarTheme( + data: .new(size: effectiveAvatarSize), + child: StreamMessageLeading(author: user), + ), + ); + } + + final headerWidget = effectiveHeaderVisibility.apply( + streamMessageHeader( + context: context, + message: message, + onViewChannelTap: () => _onViewThread(context, message), + ), + ); + final footerWidget = effectiveFooterVisibility.apply(StreamMessageFooter(message: message)); + + final contentWidget = StreamMessageContent( + message: message, + header: headerWidget, + footer: footerWidget, + attachmentBuilders: props.attachmentBuilders, + reactionSorting: props.reactionSorting, + onQuotedMessageTap: props.onQuotedMessageTap, + onRepliesTap: () => _onViewThread(context, message), + onLinkTap: (_, href, __) { + if (href == null) return; + if (props.onMessageLinkTap case final onTap?) return onTap(message, href); + return launchURL(context, href).ignore(); + }, + onMentionTap: switch (props.onUserMentionTap) { + final onTap? => (_, id) { + final user = message.mentionedUsers.firstWhereOrNull((u) => u.id == id); + if (user != null) onTap(user); }, - child: PlatformWidgetBuilder( - mobile: (context, child) { - final message = widget.message; - return InkWell( - onTap: switch (widget.onMessageTap) { - final onTap? => () => onTap(message), - _ => null, - }, - onLongPress: switch (widget.onMessageLongPress) { - final onLongPress? => () => onLongPress(message), - // If the message is not yet sent or deleted, we don't want - // to handle long press events by default. - _ when message.state.isDeleted => null, - _ when message.state.isOutgoing => null, - _ => () => _onMessageLongPressed(context, message), - }, - child: child, - ); + _ => null, + }, + onReactionsTap: switch (props.onReactionsTap) { + final onReactionsTap? => () => onReactionsTap(message), + _ => () => _showMessageReactionsModal(context, message), + }, + ); + + return Material( + animateColor: true, + color: effectiveBackgroundColor, + child: PlatformWidgetBuilder( + mobile: (context, child) => InkWell( + onTap: switch (props.onMessageTap) { + final onMessageTap? => () => onMessageTap(message), + _ => null, }, - desktopOrWeb: (context, child) { - final message = widget.message; - final messageState = message.state; + onLongPress: switch (props.onMessageLongPress) { + final onMessageLongPress? => () => onMessageLongPress(message), + _ when message.state.isDeleted => null, + _ when message.state.isOutgoing => null, + _ => () => _onMessageLongPressed(context, message), + }, + child: child, + ), + desktopOrWeb: (context, child) { + final messageState = message.state; - // If the message is deleted or not yet sent, we don't want to - // show any context menu actions. - if (messageState.isDeleted || messageState.isOutgoing) return child; + // If the message is deleted or not yet sent, we don't want to + // show any context menu actions. + if (messageState.isDeleted || messageState.isOutgoing) return child; - final menuItems = _buildDesktopOrWebActions(context, message); - if (menuItems.isEmpty) return MouseRegion(child: child); + final channel = StreamChannel.of(context).channel; + final menuItems = _buildDesktopOrWebActions(context, message); + if (menuItems.isEmpty) return MouseRegion(child: child); - return ContextMenuRegion( - contextMenuBuilder: (_, anchor) => ContextMenu( - anchor: anchor, - menuItems: menuItems, - ), - child: MouseRegion(child: child), - ); - }, - child: FlexibleFractionallySizedBox( - widthFactor: widget.widthFactor, - alignment: switch (widget.reverse) { - true => AlignmentDirectional.centerEnd, - false => AlignmentDirectional.centerStart, + return ContextMenuRegion( + onSelected: (result) { + if (result is! MessageAction) return; + return _onActionTap(context, channel, result).ignore(); }, - child: Padding( - padding: widget.padding ?? const EdgeInsets.all(8), - child: MessageWidgetContent( - streamChatTheme: theme, - showUsername: showUsername, - showTimeStamp: showTimeStamp, - showEditedLabel: showEditedLabel, - showThreadReplyIndicator: showThreadReplyIndicator, - showSendingIndicator: showSendingIndicator, - showInChannel: showInChannel, - isGiphy: isGiphy, - isOnlyEmoji: isOnlyEmoji, - hasUrlAttachments: hasUrlAttachments, - messageTheme: widget.messageTheme, - reverse: widget.reverse, - message: widget.message, - hasNonUrlAttachments: hasNonUrlAttachments, - hasQuotedMessage: hasQuotedMessage, - textPadding: widget.textPadding, - attachmentBuilders: widget.attachmentBuilders, - attachmentPadding: widget.attachmentPadding, - attachmentShape: widget.attachmentShape, - onAttachmentTap: widget.onAttachmentTap, - onReplyTap: widget.onReplyTap, - onThreadTap: widget.onThreadTap, - onShowMessage: widget.onShowMessage, - attachmentActionsModalBuilder: - widget.attachmentActionsModalBuilder, - avatarWidth: avatarWidth, - bottomRowPadding: bottomRowPadding, - isFailedState: isFailedState, - isPinned: isPinned, - messageWidget: widget, - showBottomRow: showBottomRow, - showPinHighlight: widget.showPinHighlight, - showReactions: shouldShowReactions, - onReactionsTap: () { - final message = widget.message; - return switch (widget.onReactionsTap) { - final onReactionsTap? => onReactionsTap(message), - _ => _showMessageReactionsModal(context, message), - }; - }, - onReactionsHover: widget.onReactionsHover, - showUserAvatar: widget.showUserAvatar, - streamChat: streamChat, - translateUserAvatar: widget.translateUserAvatar, - shape: widget.shape, - borderSide: widget.borderSide, - borderRadiusGeometry: widget.borderRadiusGeometry, - textBuilder: widget.textBuilder, - quotedMessageBuilder: widget.quotedMessageBuilder, - deletedMessageBuilder: widget.deletedMessageBuilder, - onLinkTap: widget.onLinkTap, - onMentionTap: widget.onMentionTap, - onQuotedMessageTap: widget.onQuotedMessageTap, - bottomRowBuilderWithDefaultWidget: - widget.bottomRowBuilderWithDefaultWidget, - onUserAvatarTap: widget.onUserAvatarTap, - userAvatarBuilder: widget.userAvatarBuilder, - reactionIndicatorBuilder: widget.reactionIndicatorBuilder, - ), + menuBuilder: (_, anchor) => ContextMenu( + anchor: anchor, + menuItems: menuItems, + ), + child: MouseRegion(child: child), + ); + }, + child: FlexibleFractionallySizedBox( + widthFactor: props.widthFactor, + alignment: StreamMessagePlacement.alignmentDirectionalOf(context), + child: Padding( + padding: effectivePadding, + child: Row( + mainAxisSize: .min, + spacing: effectiveSpacing, + crossAxisAlignment: .end, + children: [ + ?leadingWidget, + Flexible(child: contentWidget), + ], ), ), ), @@ -786,194 +496,149 @@ class _StreamMessageWidgetState extends State ); } - List _buildBouncedErrorMessageActions({ + // Builds the action list for a bounced (moderation-error) message. + List _buildBouncedErrorMessageActions({ required BuildContext context, required Message message, }) { - final actions = StreamMessageActionsBuilder.buildBouncedErrorActions( + return StreamMessageActionsBuilder.buildBouncedErrorActions( context: context, message: message, ); - - return actions; } - List _buildMessageActions({ + // Builds the standard action list, applying the custom actionsBuilder if set. + List _buildMessageActions({ required BuildContext context, required Message message, required Channel channel, OwnUser? currentUser, - List? customActions, }) { final actions = StreamMessageActionsBuilder.buildActions( context: context, message: message, channel: channel, currentUser: currentUser, - customActions: customActions, - )..retainWhere( - (it) => switch (it.action) { - QuotedReply() => widget.showReplyMessage, - ThreadReply() => widget.showThreadReplyMessage, - MarkUnread() => widget.showMarkUnreadMessage, - ResendMessage() => widget.showResendMessage, - EditMessage() => widget.showEditMessage, - CopyMessage() => widget.showCopyMessage, - FlagMessage() => widget.showFlagButton, - PinMessage() => widget.showPinButton, - DeleteMessage() => widget.showDeleteMessage, - _ => true, // Retain all the remaining actions. - }, - ); + ); - return actions; + if (props.actionsBuilder case final builder?) { + return builder(context, actions); + } + + return StreamContextMenuAction.partitioned(items: actions); } + // Dispatches to bounced-error or normal actions for desktop/web. List _buildDesktopOrWebActions( BuildContext context, Message message, ) { - if (isBouncedWithError) { + if (message.isBouncedWithError) { return _buildBouncedErrorMessageDesktopOrWebActions(context, message); } return _buildMessageDesktopOrWebActions(context, message); } + // Builds partitioned bounced-error actions for the desktop/web context menu. List _buildBouncedErrorMessageDesktopOrWebActions( BuildContext context, Message message, ) { - final channel = StreamChannel.of(context).channel; - - final actions = _buildBouncedErrorMessageActions( + final actions = StreamMessageActionsBuilder.buildBouncedErrorActions( context: context, message: message, ); - return [ - ...actions.map((action) { - return StreamMessageActionItem( - action: action, - onTap: (action) async { - final popped = await Navigator.of(context).maybePop(); - if (popped) return _onActionTap(context, channel, action).ignore(); - }, - ); - }), - ]; + return StreamContextMenuAction.partitioned(items: actions); } + // Builds normal actions + reaction picker for the desktop/web context menu. List _buildMessageDesktopOrWebActions( BuildContext context, Message message, ) { final channel = StreamChannel.of(context).channel; final currentUser = channel.client.state.currentUser; - final showPicker = widget.showReactionPicker && channel.canSendReaction; + final showPicker = channel.canSendReaction; final actions = _buildMessageActions( context: context, message: message, channel: channel, currentUser: currentUser, - customActions: widget.customActions, ); - void onActionTap(MessageAction action) async { - final popped = await Navigator.of(context).maybePop(); - if (popped) return _onActionTap(context, channel, action).ignore(); + void onReactionPicked(Reaction reaction) { + final action = SelectReaction(message: message, reaction: reaction); + return Navigator.pop(context, action); // Pop the modal with the selected reaction action } return [ - if (showPicker) - widget.reactionPickerBuilder( - context, - message, - (reaction) => onActionTap( - SelectReaction(message: message, reaction: reaction), - ), - ), - ...actions.map((action) { - return StreamMessageActionItem( - action: action, - onTap: onActionTap, - ); - }), + if (showPicker) StreamMessageReactionPicker(message: message, onReactionPicked: onReactionPicked), + ...actions, ]; } + // Opens the reaction detail sheet and handles the returned action. Future _showMessageReactionsModal( BuildContext context, Message message, - ) { + ) async { final channel = StreamChannel.of(context).channel; - final showPicker = widget.showReactionPicker && channel.canSendReaction; - - void onReactionPicked(SelectReaction action) async { - final popped = await Navigator.of(context).maybePop(); - if (popped) return _onActionTap(context, channel, action).ignore(); - } - return showStreamDialog( + final action = await ReactionDetailSheet.show( context: context, - useRootNavigator: false, - builder: (_) => StreamChatConfiguration( - // This is needed to provide the nearest reaction icons to the - // StreamMessageReactionsModal. - data: StreamChatConfiguration.of(context), - child: StreamMessageReactionsModal( - message: message, - reverse: widget.reverse, - onUserAvatarTap: widget.onUserAvatarTap, - showReactionPicker: showPicker, - reactionPickerBuilder: widget.reactionPickerBuilder, - onReactionPicked: onReactionPicked, - messageWidget: StreamChannel( - channel: channel, - child: widget.copyWith( - key: const Key('MessageWidget'), - message: message.trimmed, - showReactions: false, - showUsername: false, - showTimestamp: false, - translateUserAvatar: false, - showSendingIndicator: false, - padding: EdgeInsets.zero, - showPinHighlight: false, - showUserAvatar: switch (widget.reverse) { - true => DisplayWidget.gone, - false => DisplayWidget.show, - }, - ), - ), - ), - ), + message: message, ); + + if (action is! MessageAction) return; + return _onActionTap(context, channel, action).ignore(); + } + + // Resolves the thread parent (fetching if shown in-channel) and invokes + // the onThreadTap callback. + Future _onViewThread( + BuildContext context, + Message message, + ) async { + try { + var threadMessage = message; + if (message.showInChannel case true) { + final streamChannel = StreamChannel.of(context); + threadMessage = await streamChannel.getMessage(message.parentId!); + } + return props.onThreadTap?.call(threadMessage); + } catch (e, stk) { + debugPrint('Error while fetching message: $e, $stk'); + } } + // Routes a long-press to bounced-error or normal actions handler. Future _onMessageLongPressed( BuildContext context, Message message, ) { - if (isBouncedWithError) { + if (message.isBouncedWithError) { return _onBouncedErrorMessageActions(context, message); } return _onMessageActions(context, message); } + // Delegates to the custom callback or falls back to the default dialog. Future _onBouncedErrorMessageActions( BuildContext context, Message message, ) async { - if (widget.onBouncedErrorMessageActions case final onActions?) { + if (props.onBouncedErrorMessageActions case final onActions?) { return onActions(context, message); } return _showBouncedErrorMessageActionsDialog(context, message); } + // Shows the ModeratedMessageActionsModal for a bounced-error message. Future _showBouncedErrorMessageActionsDialog( BuildContext context, Message message, @@ -985,116 +650,99 @@ class _StreamMessageWidgetState extends State message: message, ); - void onActionTap(MessageAction action) async { - final popped = await Navigator.of(context).maybePop(); - if (popped) return _onActionTap(context, channel, action).ignore(); - } - - return showStreamDialog( + final action = await showStreamDialog( context: context, useRootNavigator: false, builder: (_) => ModeratedMessageActionsModal( message: message, messageActions: actions, - onActionTap: onActionTap, ), ); + + if (action is! MessageAction) return; + return _onActionTap(context, channel, action).ignore(); } + // Delegates to the custom callback or falls back to the default modal. Future _onMessageActions( BuildContext context, Message message, ) async { - if (widget.onMessageActions case final onActions?) { + if (props.onMessageActions case final onActions?) { return onActions(context, message); } return _showMessageActionModalDialog(context, message); } + // Shows the StreamMessageActionsModal with a reaction picker and actions. Future _showMessageActionModalDialog( BuildContext context, Message message, - ) { + ) async { final channel = StreamChannel.of(context).channel; final currentUser = channel.client.state.currentUser; - final showPicker = widget.showReactionPicker && channel.canSendReaction; + final showPicker = channel.canSendReaction; final actions = _buildMessageActions( context: context, message: message, channel: channel, currentUser: currentUser, - customActions: widget.customActions, ); - void onActionTap(MessageAction action) async { - final popped = await Navigator.of(context).maybePop(); - if (popped) return _onActionTap(context, channel, action).ignore(); - } - - return showStreamDialog( + final action = await showStreamDialog( context: context, useRootNavigator: false, builder: (_) => StreamChatConfiguration( - // This is needed to provide the nearest reaction icons to the - // StreamMessageActionsModal. data: StreamChatConfiguration.of(context), - child: StreamMessageActionsModal( - message: message, - reverse: widget.reverse, - messageActions: actions, - showReactionPicker: showPicker, - reactionPickerBuilder: widget.reactionPickerBuilder, - onActionTap: onActionTap, - messageWidget: StreamChannel( - channel: channel, - child: widget.copyWith( - key: const Key('MessageWidget'), - message: message.trimmed, - showReactions: false, - showUsername: false, - showTimestamp: false, - translateUserAvatar: false, - showSendingIndicator: false, - padding: EdgeInsets.zero, - showPinHighlight: false, - showUserAvatar: switch (widget.reverse) { - true => DisplayWidget.gone, - false => DisplayWidget.show, - }, + child: StreamMessagePlacement( + data: StreamMessagePlacement.of(context), + child: StreamMessageActionsModal( + message: message, + messageActions: actions, + showReactionPicker: showPicker, + messageWidget: StreamChannel( + channel: channel, + child: StreamMessageWidget( + key: const Key('MessageWidget'), + message: message.trimmed, + padding: EdgeInsets.zero, + backgroundColor: StreamColors.transparent, + ), ), ), ), ), ); + + if (action is! MessageAction) return; + return _onActionTap(context, channel, action).ignore(); } + // Dispatches a MessageAction to the appropriate channel or callback handler. Future _onActionTap( BuildContext context, Channel channel, MessageAction action, - ) async { - return switch (action) { - SelectReaction() => - _selectReaction(context, action.message, channel, action.reaction), - CopyMessage() => _copyMessage(action.message, channel), - DeleteMessage() => _maybeDeleteMessage(context, action.message, channel), - HardDeleteMessage() => channel.deleteMessage(action.message, hard: true), - EditMessage() => _editMessage(context, action.message, channel), - FlagMessage() => _maybeFlagMessage(context, action.message, channel), - MarkUnread() => channel.markUnread(action.message.id), - MuteUser() => channel.client.muteUser(action.user.id), - UnmuteUser() => channel.client.unmuteUser(action.user.id), - PinMessage() => channel.pinMessage(action.message), - UnpinMessage() => channel.unpinMessage(action.message), - ResendMessage() => channel.retryMessage(action.message), - QuotedReply() => widget.onReplyTap?.call(action.message), - ThreadReply() => widget.onThreadTap?.call(action.message), - CustomMessageAction() => widget.onCustomActionTap?.call(action), - }; - } - + ) async => switch (action) { + SelectReaction() => _selectReaction(context, action.message, channel, action.reaction), + CopyMessage() => _copyMessage(action.message, channel), + DeleteMessage() => _maybeDeleteMessage(context, action.message, channel), + HardDeleteMessage() => channel.deleteMessage(action.message, hard: true), + EditMessage() => props.onEditMessageTap?.call(action.message), + FlagMessage() => _maybeFlagMessage(context, action.message, channel), + MarkUnread() => channel.markUnread(action.message.id), + MuteUser() => channel.client.muteUser(action.user.id), + UnmuteUser() => channel.client.unmuteUser(action.user.id), + PinMessage() => channel.pinMessage(action.message), + UnpinMessage() => channel.unpinMessage(action.message), + ResendMessage() => channel.retryMessage(action.message), + QuotedReply() => props.onReplyTap?.call(action.message), + ThreadReply() => props.onThreadTap?.call(action.message), + }; + + // Copies the message text (with mentions replaced) to the clipboard. Future _copyMessage( Message message, Channel channel, @@ -1107,6 +755,7 @@ class _StreamMessageWidgetState extends State return Clipboard.setData(ClipboardData(text: messageText)); } + // Shows a confirmation dialog before deleting the message. Future _maybeDeleteMessage( BuildContext context, Message message, @@ -1128,19 +777,7 @@ class _StreamMessageWidgetState extends State return channel.deleteMessage(message); } - Future _editMessage( - BuildContext context, - Message message, - Channel channel, - ) { - return showEditMessageSheet( - context: context, - channel: channel, - message: message, - editMessageInputBuilder: widget.editMessageInputBuilder, - ); - } - + // Shows a confirmation dialog before flagging the message. Future _maybeFlagMessage( BuildContext context, Message message, @@ -1163,6 +800,7 @@ class _StreamMessageWidgetState extends State return channel.client.flagMessage(messageId); } + // Toggles a reaction: removes it if already present, otherwise sends it. Future _selectReaction( BuildContext context, Message message, @@ -1187,7 +825,9 @@ class _StreamMessageWidgetState extends State } } +// Truncates long message text for display in the actions modal preview. extension on Message { + // Returns a copy with text and nested content truncated to 100 characters. Message get trimmed { final trimmedText = switch (text) { final text? when text.length > 100 => '${text.substring(0, 100)}...', @@ -1202,7 +842,9 @@ extension on Message { } } +// Truncates long poll names for display in the actions modal preview. extension on Poll { + // Returns a copy with name truncated to 100 characters. Poll get trimmed { final trimmedName = switch (name) { final name when name.length > 100 => '${name.substring(0, 100)}...', @@ -1212,3 +854,58 @@ extension on Poll { return copyWith(name: trimmedName); } } + +// Built-in fallback theme values for [DefaultStreamMessage]. +// +// Used when neither the explicit props nor the ambient +// [StreamMessageItemThemeData] provide a value for a given property. +class _StreamMessageWidgetDefaults extends StreamMessageItemThemeData { + _StreamMessageWidgetDefaults( + this._context, { + this.isPinned = false, + required MessageState state, + }) : _messageState = state; + + final bool isPinned; + + final BuildContext _context; + final MessageState _messageState; + + late final StreamSpacing _spacing = _context.streamSpacing; + late final StreamColorScheme _colorScheme = _context.streamColorScheme; + + @override + double get spacing => _spacing.xs; + + @override + StreamAvatarSize get avatarSize => .md; + + @override + EdgeInsetsGeometry get padding => .symmetric(horizontal: _spacing.md); + + @override + Color? get backgroundColor { + if (isPinned && !_messageState.isDeleted) return _colorScheme.backgroundHighlight; + return StreamColors.transparent; + } + + @override + StreamMessageStyleVisibility get leadingVisibility => .resolveWith( + (placement) => switch ((placement.channelKind, placement.alignment, placement.stackPosition)) { + (.direct, _, _) || (_, .end, _) => .gone, + (_, _, .top || .middle) => .hidden, + (_, _, .single || .bottom) => .visible, + }, + ); + + @override + StreamMessageStyleVisibility get headerVisibility => .all(.visible); + + @override + StreamMessageStyleVisibility get footerVisibility => .resolveWith( + (placement) => switch (placement.stackPosition) { + .single || .bottom => .visible, + _ => .gone, + }, + ); +} diff --git a/packages/stream_chat_flutter/lib/src/message_widget/message_widget_content.dart b/packages/stream_chat_flutter/lib/src/message_widget/message_widget_content.dart deleted file mode 100644 index 6acf726283..0000000000 --- a/packages/stream_chat_flutter/lib/src/message_widget/message_widget_content.dart +++ /dev/null @@ -1,452 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:meta/meta.dart'; -import 'package:stream_chat_flutter/src/reactions/desktop_reactions_builder.dart'; -import 'package:stream_chat_flutter/src/reactions/indicator/reaction_indicator_bubble_overlay.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; - -/// Signature for the builder function that will be called when the message -/// bottom row is built. Includes the [Message]. -typedef BottomRowBuilder = Widget Function(BuildContext, Message); - -/// Signature for the builder function that will be called when the message -/// bottom row is built. Includes the [Message] and the default [BottomRow]. -typedef BottomRowBuilderWithDefaultWidget = Widget Function( - BuildContext, - Message, - BottomRow, -); - -/// {@template messageWidgetContent} -/// The main content of a [StreamMessageWidget]. -/// -/// Should not be used outside of [MessageWidget. -/// {@endtemplate} -@internal -class MessageWidgetContent extends StatelessWidget { - /// {@macro messageWidgetContent} - const MessageWidgetContent({ - super.key, - required this.reverse, - required this.isPinned, - required this.showPinHighlight, - required this.showBottomRow, - required this.message, - required this.showUserAvatar, - required this.avatarWidth, - required this.showReactions, - required this.onReactionsTap, - required this.onReactionsHover, - required this.messageTheme, - required this.streamChatTheme, - required this.isFailedState, - required this.hasQuotedMessage, - required this.hasUrlAttachments, - required this.hasNonUrlAttachments, - required this.isOnlyEmoji, - required this.isGiphy, - required this.attachmentBuilders, - required this.attachmentPadding, - required this.attachmentShape, - required this.onAttachmentTap, - required this.onShowMessage, - required this.onReplyTap, - required this.attachmentActionsModalBuilder, - required this.textPadding, - required this.translateUserAvatar, - required this.bottomRowPadding, - required this.showInChannel, - required this.streamChat, - required this.showSendingIndicator, - required this.showThreadReplyIndicator, - required this.showTimeStamp, - required this.showUsername, - required this.showEditedLabel, - required this.messageWidget, - required this.onThreadTap, - required this.reactionIndicatorBuilder, - this.onUserAvatarTap, - this.borderRadiusGeometry, - this.borderSide, - this.shape, - this.onQuotedMessageTap, - this.onMentionTap, - this.onLinkTap, - this.textBuilder, - this.quotedMessageBuilder, - this.deletedMessageBuilder, - this.bottomRowBuilderWithDefaultWidget, - this.userAvatarBuilder, - }); - - /// {@macro reverse} - final bool reverse; - - /// {@macro isPinned} - final bool isPinned; - - /// {@macro showPinHighlight} - final bool showPinHighlight; - - /// {@macro showBottomRow} - final bool showBottomRow; - - /// {@macro message} - final Message message; - - /// {@macro showUserAvatar} - final DisplayWidget showUserAvatar; - - /// The width of the avatar. - final double avatarWidth; - - /// {@macro showReactions} - final bool showReactions; - - /// {@macro onReactionsTap} - final VoidCallback onReactionsTap; - - /// {@macro onReactionsHover} - final OnReactionsHover? onReactionsHover; - - /// {@macro messageTheme} - final StreamMessageThemeData messageTheme; - - /// {@macro onUserAvatarTap} - final void Function(User)? onUserAvatarTap; - - /// {@macro streamChatThemeData} - final StreamChatThemeData streamChatTheme; - - /// {@macro isFailedState} - final bool isFailedState; - - /// {@macro borderRadiusGeometry} - final BorderRadiusGeometry? borderRadiusGeometry; - - /// {@macro borderSide} - final BorderSide? borderSide; - - /// {@macro shape} - final ShapeBorder? shape; - - /// {@macro hasQuotedMessage} - final bool hasQuotedMessage; - - /// {@macro hasUrlAttachments} - final bool hasUrlAttachments; - - /// {@macro hasNonUrlAttachments} - final bool hasNonUrlAttachments; - - /// {@macro isOnlyEmoji} - final bool isOnlyEmoji; - - /// {@macro isGiphy} - final bool isGiphy; - - /// {@macro attachmentBuilders} - final List? attachmentBuilders; - - /// {@macro attachmentPadding} - final EdgeInsetsGeometry attachmentPadding; - - /// {@macro attachmentShape} - final ShapeBorder? attachmentShape; - - /// {@macro onAttachmentWidgetTap} - final OnAttachmentWidgetTap? onAttachmentTap; - - /// {@macro onShowMessage} - final ShowMessageCallback? onShowMessage; - - /// {@macro onReplyTap} - final void Function(Message)? onReplyTap; - - /// {@macro onThreadTap} - final void Function(Message)? onThreadTap; - - /// {@macro attachmentActionsBuilder} - final AttachmentActionsBuilder? attachmentActionsModalBuilder; - - /// {@macro textPadding} - final EdgeInsets textPadding; - - /// {@macro onQuotedMessageTap} - final OnQuotedMessageTap? onQuotedMessageTap; - - /// {@macro onMentionTap} - final void Function(User)? onMentionTap; - - /// {@macro onLinkTap} - final void Function(String)? onLinkTap; - - /// {@macro textBuilder} - final Widget Function(BuildContext, Message)? textBuilder; - - /// {@macro quotedMessageBuilder} - final Widget Function(BuildContext, Message)? quotedMessageBuilder; - - /// {@macro deletedMessageBuilder} - final Widget Function(BuildContext, Message)? deletedMessageBuilder; - - /// {@macro translateUserAvatar} - final bool translateUserAvatar; - - /// The padding to use for this widget. - final double bottomRowPadding; - - /// {@macro bottomRowBuilderWithDefaultWidget} - final BottomRowBuilderWithDefaultWidget? bottomRowBuilderWithDefaultWidget; - - /// {@macro showInChannelIndicator} - final bool showInChannel; - - /// {@macro streamChat} - final StreamChatState streamChat; - - /// {@macro showSendingIndicator} - final bool showSendingIndicator; - - /// {@macro showThreadReplyIndicator} - final bool showThreadReplyIndicator; - - /// {@macro showTimestamp} - final bool showTimeStamp; - - /// {@macro showUsername} - final bool showUsername; - - /// {@macro showEdited} - final bool showEditedLabel; - - /// {@macro messageWidget} - final StreamMessageWidget messageWidget; - - /// {@macro userAvatarBuilder} - final Widget Function(BuildContext, User)? userAvatarBuilder; - - /// {@macro reactionIndicatorBuilder} - final ReactionIndicatorBuilder reactionIndicatorBuilder; - - @override - Widget build(BuildContext context) { - return Column( - crossAxisAlignment: - reverse ? CrossAxisAlignment.end : CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Stack( - clipBehavior: Clip.none, - alignment: reverse - ? AlignmentDirectional.bottomEnd - : AlignmentDirectional.bottomStart, - children: [ - if (showBottomRow) - Padding( - padding: EdgeInsets.only( - left: !reverse ? bottomRowPadding : 0, - right: reverse ? bottomRowPadding : 0, - bottom: isPinned && showPinHighlight ? 6.0 : 0.0, - ), - child: _buildBottomRow(context), - ), - Padding( - padding: EdgeInsets.only( - bottom: isPinned && showPinHighlight ? 8.0 : 0.0, - ), - child: Column( - crossAxisAlignment: - reverse ? CrossAxisAlignment.end : CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - if (isPinned && message.pinnedBy != null && showPinHighlight) - PinnedMessage( - pinnedBy: message.pinnedBy!, - currentUser: streamChat.currentUser!, - ), - Row( - spacing: 8, - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - ...[ - Flexible( - child: ReactionIndicatorBubbleOverlay( - reverse: reverse, - message: message, - onTap: onReactionsTap, - visible: isMobileDevice && showReactions, - anchorOffset: const Offset(0, 36), - reactionIndicatorBuilder: reactionIndicatorBuilder, - childSizeDelta: switch (showUserAvatar) { - DisplayWidget.gone => Offset.zero, - // Size adjustment for the user avatar - _ => const Offset(40, 0), - }, - child: Padding( - padding: switch (showReactions) { - true => const EdgeInsets.only(top: 28), - false => EdgeInsets.zero, - }, - child: _buildMessageCard(context), - ), - ), - ), - ].addConditionally( - reverse: reverse, - condition: (_) => message.user != null, - switch (showUserAvatar) { - DisplayWidget.gone => null, - DisplayWidget.hide => SizedBox(width: avatarWidth), - DisplayWidget.show => UserAvatarTransform( - onUserAvatarTap: onUserAvatarTap, - userAvatarBuilder: userAvatarBuilder, - translateUserAvatar: translateUserAvatar, - messageTheme: messageTheme, - message: message, - ), - }, - ), - ], - ), - if (isDesktopDeviceOrWeb && showReactions) ...[ - Padding( - padding: switch (showUserAvatar) { - DisplayWidget.gone => EdgeInsets.zero, - _ => EdgeInsets.only( - left: avatarWidth + 4, - right: avatarWidth + 4, - ) - }, - child: DesktopReactionsBuilder( - message: message, - messageTheme: messageTheme, - onHover: onReactionsHover, - borderSide: borderSide, - reverse: reverse, - ), - ), - ], - if (showBottomRow) - SizedBox( - height: context.textScaleFactor * 18.0, - ), - ], - ), - ), - if (isFailedState) - Positioned( - right: reverse ? 0 : null, - left: reverse ? null : 0, - bottom: showBottomRow ? 18 : -2, - child: StreamSvgIcon( - icon: StreamSvgIcons.error, - color: streamChatTheme.colorTheme.accentError, - ), - ), - ], - ), - ], - ); - } - - Widget _buildDeletedMessage(BuildContext context) { - if (deletedMessageBuilder case final builder?) { - return builder(context, message); - } - - return StreamDeletedMessage( - borderRadiusGeometry: borderRadiusGeometry, - borderSide: borderSide, - shape: shape, - messageTheme: messageTheme, - ); - } - - Widget _buildMessageCard(BuildContext context) { - if (message.isDeleted) { - return Container( - margin: EdgeInsetsDirectional.only( - end: reverse && isFailedState ? 12.0 : 0.0, - start: !reverse && isFailedState ? 12.0 : 0.0, - ), - child: _buildDeletedMessage(context), - ); - } - - return MessageCard( - message: message, - isFailedState: isFailedState, - showUserAvatar: showUserAvatar, - messageTheme: messageTheme, - hasQuotedMessage: hasQuotedMessage, - hasUrlAttachments: hasUrlAttachments, - hasNonUrlAttachments: hasNonUrlAttachments, - isOnlyEmoji: isOnlyEmoji, - isGiphy: isGiphy, - attachmentBuilders: attachmentBuilders, - attachmentPadding: attachmentPadding, - attachmentShape: attachmentShape, - onAttachmentTap: onAttachmentTap, - onReplyTap: onReplyTap, - onShowMessage: onShowMessage, - attachmentActionsModalBuilder: attachmentActionsModalBuilder, - textPadding: textPadding, - reverse: reverse, - onQuotedMessageTap: onQuotedMessageTap, - onMentionTap: onMentionTap, - onLinkTap: onLinkTap, - textBuilder: textBuilder, - quotedMessageBuilder: quotedMessageBuilder, - borderRadiusGeometry: borderRadiusGeometry, - borderSide: borderSide, - shape: shape, - ); - } - - Widget _buildBottomRow(BuildContext context) { - final defaultWidget = BottomRow( - onThreadTap: onThreadTap, - message: message, - reverse: reverse, - messageTheme: messageTheme, - hasUrlAttachments: hasUrlAttachments, - isOnlyEmoji: isOnlyEmoji, - isDeleted: message.isDeleted, - isGiphy: isGiphy, - showInChannel: showInChannel, - showSendingIndicator: showSendingIndicator, - showThreadReplyIndicator: showThreadReplyIndicator, - showTimeStamp: showTimeStamp, - showUsername: showUsername, - showEditedLabel: showEditedLabel, - streamChatTheme: streamChatTheme, - streamChat: streamChat, - hasNonUrlAttachments: hasNonUrlAttachments, - ); - - if (bottomRowBuilderWithDefaultWidget != null) { - return bottomRowBuilderWithDefaultWidget!( - context, - message, - defaultWidget, - ); - } - - return defaultWidget; - } -} - -extension on Iterable { - Iterable addConditionally( - T? item, { - required bool condition(T element), - bool reverse = false, - }) sync* { - for (final element in this) { - if (item != null && !reverse && condition(element)) yield item; - yield element; - if (item != null && reverse && condition(element)) yield item; - } - } -} diff --git a/packages/stream_chat_flutter/lib/src/message_widget/message_widget_content_components.dart b/packages/stream_chat_flutter/lib/src/message_widget/message_widget_content_components.dart deleted file mode 100644 index a3a6782630..0000000000 --- a/packages/stream_chat_flutter/lib/src/message_widget/message_widget_content_components.dart +++ /dev/null @@ -1,6 +0,0 @@ -export 'bottom_row.dart'; -export 'message_card.dart'; -export 'parse_attachments.dart'; -export 'pinned_message.dart'; -export 'quoted_message.dart'; -export 'user_avatar_transform.dart'; diff --git a/packages/stream_chat_flutter/lib/src/message_widget/parse_attachments.dart b/packages/stream_chat_flutter/lib/src/message_widget/parse_attachments.dart index b788a73b6c..5e4c386576 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget/parse_attachments.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget/parse_attachments.dart @@ -29,11 +29,7 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// ) /// ``` /// {@endtemplate} -typedef OnAttachmentWidgetTap = FutureOr Function( - BuildContext context, - Message message, - Attachment attachment, -); +typedef OnAttachmentWidgetTap = FutureOr Function(BuildContext context, Message message, Attachment attachment); /// {@template parseAttachments} /// Parses the attachments of a [StreamMessageWidget]. @@ -45,8 +41,8 @@ class ParseAttachments extends StatelessWidget { const ParseAttachments({ super.key, required this.message, - required this.attachmentBuilders, - required this.attachmentPadding, + this.attachmentBuilders, + this.attachmentPadding, this.attachmentShape, this.onAttachmentTap, this.onShowMessage, @@ -62,7 +58,7 @@ class ParseAttachments extends StatelessWidget { final List? attachmentBuilders; /// {@macro attachmentPadding} - final EdgeInsetsGeometry attachmentPadding; + final EdgeInsetsGeometry? attachmentPadding; /// {@macro attachmentShape} final ShapeBorder? attachmentShape; @@ -97,13 +93,16 @@ class ParseAttachments extends StatelessWidget { return _defaultAttachmentTapHandler(context, message, attachment); } + final config = StreamChatConfiguration.maybeOf(context); + final effectiveAttachmentBuilder = attachmentBuilders ?? config?.attachmentBuilders; + // Create a default attachmentBuilders list if not provided. final builders = StreamAttachmentWidgetBuilder.defaultBuilders( message: message, shape: attachmentShape, padding: attachmentPadding, onAttachmentTap: effectiveOnAttachmentTap, - customAttachmentBuilders: attachmentBuilders, + customAttachmentBuilders: effectiveAttachmentBuilder, ); final catalog = AttachmentWidgetCatalog(builders: builders); @@ -185,7 +184,7 @@ extension on Message { attachment: it, message: this, ); - }) + }), ]; } } diff --git a/packages/stream_chat_flutter/lib/src/message_widget/pinned_message.dart b/packages/stream_chat_flutter/lib/src/message_widget/pinned_message.dart deleted file mode 100644 index 49b9a4f219..0000000000 --- a/packages/stream_chat_flutter/lib/src/message_widget/pinned_message.dart +++ /dev/null @@ -1,52 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; - -/// {@template pinnedMessage} -/// A pinned message in a chat. -/// -/// Used in [MessageWidgetContent]. Should not be used elsewhere. -/// {@endtemplate} -class PinnedMessage extends StatelessWidget { - /// {@macro pinnedMessage} - const PinnedMessage({ - super.key, - required this.pinnedBy, - required this.currentUser, - }); - - /// The [User] who pinned this message. - final User pinnedBy; - - /// The current [User]. - final User currentUser; - - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.only(left: 8, right: 8, top: 4, bottom: 8), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const StreamSvgIcon( - size: 16, - icon: StreamSvgIcons.pin, - ), - const SizedBox( - width: 4, - ), - Text( - context.translations.pinnedByUserText( - pinnedBy: pinnedBy, - currentUser: currentUser, - ), - style: TextStyle( - color: StreamChatTheme.of(context).colorTheme.textLowEmphasis, - fontSize: 13, - fontWeight: FontWeight.w400, - ), - ), - ], - ), - ); - } -} diff --git a/packages/stream_chat_flutter/lib/src/message_widget/quoted_message.dart b/packages/stream_chat_flutter/lib/src/message_widget/quoted_message.dart deleted file mode 100644 index b6b7837b98..0000000000 --- a/packages/stream_chat_flutter/lib/src/message_widget/quoted_message.dart +++ /dev/null @@ -1,50 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; - -/// {@template quotedMessage} -/// A quoted message in a chat. -/// -/// Used in [QuotedMessageCard]. Should not be used elsewhere. -/// {@endtemplate} -class QuotedMessage extends StatelessWidget { - /// {@macro quotedMessage} - const QuotedMessage({ - super.key, - required this.message, - required this.hasNonUrlAttachments, - this.textBuilder, - }); - - /// {@macro message} - final Message message; - - /// {@macro hasNonUrlAttachments} - final bool hasNonUrlAttachments; - - /// {@macro textBuilder} - final Widget Function(BuildContext, Message)? textBuilder; - - @override - Widget build(BuildContext context) { - final streamChat = StreamChat.of(context); - final chatThemeData = StreamChatTheme.of(context); - - final isMyMessage = message.user?.id == streamChat.currentUser?.id; - final isMyQuotedMessage = - message.quotedMessage?.user?.id == streamChat.currentUser?.id; - return StreamQuotedMessageWidget( - message: message.quotedMessage!, - messageTheme: isMyMessage - ? chatThemeData.otherMessageTheme - : chatThemeData.ownMessageTheme, - reverse: !isMyQuotedMessage, - textBuilder: textBuilder, - padding: EdgeInsets.only( - right: 8, - left: 8, - top: 8, - bottom: hasNonUrlAttachments ? 8 : 0, - ), - ); - } -} diff --git a/packages/stream_chat_flutter/lib/src/message_widget/sending_indicator_builder.dart b/packages/stream_chat_flutter/lib/src/message_widget/sending_indicator_builder.dart deleted file mode 100644 index cb42429007..0000000000 --- a/packages/stream_chat_flutter/lib/src/message_widget/sending_indicator_builder.dart +++ /dev/null @@ -1,100 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; - -/// {@template sendingIndicatorWrapper} -/// Helper widget for building a [StreamSendingIndicator]. -/// -/// Used in [BottomRow]. Should not be used elsewhere. -/// {@endtemplate} -class SendingIndicatorBuilder extends StatelessWidget { - /// {@macro sendingIndicatorWrapper} - const SendingIndicatorBuilder({ - super.key, - required this.messageTheme, - required this.message, - required this.hasNonUrlAttachments, - required this.streamChat, - required this.streamChatTheme, - this.channel, - }); - - /// {@macro messageTheme} - final StreamMessageThemeData messageTheme; - - /// {@macro message} - final Message message; - - /// {@macro hasNonUrlAttachments} - final bool hasNonUrlAttachments; - - /// {@macro streamChat} - final StreamChatState streamChat; - - /// {@macro streamChatThemeData} - final StreamChatThemeData streamChatTheme; - - /// {@macro channel} - final Channel? channel; - - @override - Widget build(BuildContext context) { - final style = messageTheme.createdAtStyle; - final channel = this.channel ?? StreamChannel.of(context).channel; - final memberCount = channel.memberCount ?? 0; - - if (hasNonUrlAttachments && message.state.isOutgoing) { - final totalAttachments = message.attachments.length; - final attachmentsToUpload = message.attachments.where((it) { - return !it.uploadState.isSuccess; - }); - - if (attachmentsToUpload.isNotEmpty) { - return Text( - context.translations.attachmentsUploadProgressText( - remaining: attachmentsToUpload.length, - total: totalAttachments, - ), - style: style, - ); - } - } - - return BetterStreamBuilder>( - stream: channel.state?.readStream, - initialData: channel.state?.read, - builder: (context, data) { - final readList = data.readsOf(message: message); - final isMessageRead = readList.isNotEmpty; - - final deliveriesList = data.deliveriesOf(message: message); - final isMessageDelivered = deliveriesList.isNotEmpty; - - Widget child = StreamSendingIndicator( - message: message, - isMessageRead: isMessageRead, - isMessageDelivered: isMessageDelivered, - size: style?.fontSize, - ); - - if (isMessageRead) { - child = Row( - mainAxisSize: MainAxisSize.min, - children: [ - if (memberCount > 2) - Text( - readList.length.toString(), - style: style?.copyWith( - color: streamChatTheme.colorTheme.accentPrimary, - ), - ), - const SizedBox(width: 2), - child, - ], - ); - } - - return child; - }, - ); - } -} diff --git a/packages/stream_chat_flutter/lib/src/message_widget/text_bubble.dart b/packages/stream_chat_flutter/lib/src/message_widget/text_bubble.dart deleted file mode 100644 index 7874319ab7..0000000000 --- a/packages/stream_chat_flutter/lib/src/message_widget/text_bubble.dart +++ /dev/null @@ -1,73 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/src/misc/empty_widget.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; - -/// {@template textBubble} -/// The bubble around a [StreamMessageText]. -/// -/// Used in [MessageCard]. Should not be used elsewhere. -/// {@endtemplate} -class TextBubble extends StatelessWidget { - /// {@macro textBubble} - const TextBubble({ - super.key, - required this.message, - required this.isOnlyEmoji, - required this.textPadding, - required this.messageTheme, - required this.hasUrlAttachments, - required this.hasQuotedMessage, - this.textBuilder, - this.onLinkTap, - this.onMentionTap, - }); - - /// {@macro message} - final Message message; - - /// {@macro isOnlyEmoji} - final bool isOnlyEmoji; - - /// {@macro textPadding} - final EdgeInsets textPadding; - - /// {@macro textBuilder} - final Widget Function(BuildContext, Message)? textBuilder; - - /// {@macro onLinkTap} - final void Function(String)? onLinkTap; - - /// {@macro onMentionTap} - final void Function(User)? onMentionTap; - - /// {@macro messageTheme} - final StreamMessageThemeData messageTheme; - - /// {@macro hasUrlAttachments} - final bool hasUrlAttachments; - - /// {@macro hasQuotedMessage} - final bool hasQuotedMessage; - - @override - Widget build(BuildContext context) { - if (message.text?.trim().isEmpty ?? true) return const Empty(); - return Padding( - padding: isOnlyEmoji ? EdgeInsets.zero : textPadding, - child: textBuilder != null - ? textBuilder!(context, message) - : StreamMessageText( - onLinkTap: onLinkTap, - message: message, - onMentionTap: onMentionTap, - messageTheme: isOnlyEmoji - ? messageTheme.copyWith( - messageTextStyle: messageTheme.messageTextStyle!.copyWith( - fontSize: 42, - ), - ) - : messageTheme, - ), - ); - } -} diff --git a/packages/stream_chat_flutter/lib/src/message_widget/thread_painter.dart b/packages/stream_chat_flutter/lib/src/message_widget/thread_painter.dart deleted file mode 100644 index 09bb63c93f..0000000000 --- a/packages/stream_chat_flutter/lib/src/message_widget/thread_painter.dart +++ /dev/null @@ -1,53 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; - -/// {@template threadReplyPainter} -/// A custom painter used to render thread replies. -/// -/// Used in [BottomRow]. -/// {@endtemplate} -class ThreadReplyPainter extends CustomPainter { - /// {@macro threadReplyPainter} - const ThreadReplyPainter({ - this.context, - required this.color, - this.reverse = false, - }); - - /// The color to paint the thread reply with. - final Color? color; - - /// The [BuildContext] to use to retrieve the [StreamChatTheme]. - final BuildContext? context; - - /// {@macro reverse} - final bool reverse; - - @override - void paint(Canvas canvas, Size size) { - final paint = Paint() - ..color = color ?? StreamChatTheme.of(context!).colorTheme.disabled - ..style = PaintingStyle.stroke - ..strokeWidth = 1 - ..strokeCap = StrokeCap.round; - - final path = Path() - ..moveTo(reverse ? size.width : 0, 0) - ..quadraticBezierTo( - reverse ? size.width : 0, - size.height * 0.38, - reverse ? size.width : 0, - size.height * 0.5, - ) - ..quadraticBezierTo( - reverse ? size.width : 0, - size.height, - reverse ? 0 : size.width, - size.height, - ); - canvas.drawPath(path, paint); - } - - @override - bool shouldRepaint(covariant CustomPainter oldDelegate) => false; -} diff --git a/packages/stream_chat_flutter/lib/src/message_widget/thread_participants.dart b/packages/stream_chat_flutter/lib/src/message_widget/thread_participants.dart deleted file mode 100644 index 5068e2931e..0000000000 --- a/packages/stream_chat_flutter/lib/src/message_widget/thread_participants.dart +++ /dev/null @@ -1,49 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; - -/// {@template threadParticipants} -/// Shows the users participating in a thread. -/// -/// Used in [BottomRow]. -/// {@endtemplate} -class ThreadParticipants extends StatelessWidget { - /// {@macro threadParticipants} - const ThreadParticipants({ - super.key, - required StreamChatThemeData streamChatTheme, - required this.threadParticipants, - }) : _streamChatTheme = streamChatTheme; - - /// {@macro streamChatThemeData} - final StreamChatThemeData _streamChatTheme; - - /// The users participating in the thread. - final Iterable threadParticipants; - - @override - Widget build(BuildContext context) { - var padding = 0.0; - return Stack( - children: threadParticipants.map((user) { - padding += 8.0; - return Positioned( - right: padding - 8, - bottom: 0, - top: 0, - child: Container( - decoration: BoxDecoration( - shape: BoxShape.circle, - color: _streamChatTheme.colorTheme.barsBg, - ), - padding: const EdgeInsets.all(1), - child: StreamUserAvatar( - user: user, - constraints: BoxConstraints.tight(const Size.fromRadius(7)), - showOnlineStatus: false, - ), - ), - ); - }).toList(), - ); - } -} diff --git a/packages/stream_chat_flutter/lib/src/message_widget/user_avatar_transform.dart b/packages/stream_chat_flutter/lib/src/message_widget/user_avatar_transform.dart deleted file mode 100644 index 275d63e05e..0000000000 --- a/packages/stream_chat_flutter/lib/src/message_widget/user_avatar_transform.dart +++ /dev/null @@ -1,54 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; - -/// {@template userAvatarTransform} -/// Transforms a [StreamUserAvatar] according to the specified translation. -/// -/// Used in [MessageWidgetContent]. -/// {@endtemplate} -class UserAvatarTransform extends StatelessWidget { - /// {@macro userAvatarTransform} - const UserAvatarTransform({ - super.key, - required this.translateUserAvatar, - required this.messageTheme, - required this.message, - this.userAvatarBuilder, - this.onUserAvatarTap, - }); - - /// {@macro translateUserAvatar} - final bool translateUserAvatar; - - /// {@macro messageTheme} - final StreamMessageThemeData messageTheme; - - /// {@macro userAvatarBuilder} - final Widget Function(BuildContext, User)? userAvatarBuilder; - - /// {@macro message} - final Message message; - - /// {@macro onUserAvatarTap} - final void Function(User)? onUserAvatarTap; - - @override - Widget build(BuildContext context) { - return Transform.translate( - offset: Offset( - 0, - translateUserAvatar - ? (messageTheme.avatarTheme?.constraints.maxHeight ?? 40) / 2 - : 0, - ), - child: userAvatarBuilder?.call(context, message.user!) ?? - StreamUserAvatar( - user: message.user!, - onTap: onUserAvatarTap, - constraints: messageTheme.avatarTheme!.constraints, - borderRadius: messageTheme.avatarTheme!.borderRadius, - showOnlineStatus: false, - ), - ); - } -} diff --git a/packages/stream_chat_flutter/lib/src/message_widget/username.dart b/packages/stream_chat_flutter/lib/src/message_widget/username.dart deleted file mode 100644 index fad0ff6fb1..0000000000 --- a/packages/stream_chat_flutter/lib/src/message_widget/username.dart +++ /dev/null @@ -1,31 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; - -/// {@template username} -/// Displays the username of a particular message's sender. -/// {@endtemplate} -class Username extends StatelessWidget { - /// {@macro username} - const Username({ - super.key, - required this.message, - required this.messageTheme, - }); - - /// {@macro message} - final Message message; - - /// {@macro messageTheme} - final StreamMessageThemeData messageTheme; - - @override - Widget build(BuildContext context) { - return Text( - message.user?.name ?? '', - maxLines: 1, - key: key, - style: messageTheme.messageAuthorStyle, - overflow: TextOverflow.ellipsis, - ); - } -} diff --git a/packages/stream_chat_flutter/lib/src/misc/adaptive_dialog_action.dart b/packages/stream_chat_flutter/lib/src/misc/adaptive_dialog_action.dart index 159c55b557..0a674e949d 100644 --- a/packages/stream_chat_flutter/lib/src/misc/adaptive_dialog_action.dart +++ b/packages/stream_chat_flutter/lib/src/misc/adaptive_dialog_action.dart @@ -47,25 +47,25 @@ class AdaptiveDialogAction extends StatelessWidget { return switch (Theme.of(context).platform) { TargetPlatform.iOS || TargetPlatform.macOS => CupertinoTheme( - data: CupertinoTheme.of(context).copyWith( - primaryColor: theme.colorTheme.accentPrimary, - ), - child: CupertinoDialogAction( - onPressed: onPressed, - isDefaultAction: isDefaultAction, - isDestructiveAction: isDestructiveAction, - child: child, - ), + data: CupertinoTheme.of(context).copyWith( + primaryColor: theme.colorTheme.accentPrimary, ), - _ => TextButton( + child: CupertinoDialogAction( onPressed: onPressed, - style: TextButton.styleFrom( - textStyle: theme.textTheme.body, - foregroundColor: theme.colorTheme.accentPrimary, - disabledForegroundColor: theme.colorTheme.disabled, - ), + isDefaultAction: isDefaultAction, + isDestructiveAction: isDestructiveAction, child: child, ), + ), + _ => TextButton( + onPressed: onPressed, + style: TextButton.styleFrom( + textStyle: theme.textTheme.body, + foregroundColor: theme.colorTheme.accentPrimary, + disabledForegroundColor: theme.colorTheme.disabled, + ), + child: child, + ), }; } } diff --git a/packages/stream_chat_flutter/lib/src/misc/audio_waveform.dart b/packages/stream_chat_flutter/lib/src/misc/audio_waveform.dart deleted file mode 100644 index e35f9277a5..0000000000 --- a/packages/stream_chat_flutter/lib/src/misc/audio_waveform.dart +++ /dev/null @@ -1,487 +0,0 @@ -import 'dart:math' as math; - -import 'package:collection/collection.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/src/theme/audio_waveform_slider_theme.dart'; -import 'package:stream_chat_flutter/src/theme/audio_waveform_theme.dart'; - -const _kAudioWaveformSliderThumbWidth = 4.0; -const _kAudioWaveformSliderThumbHeight = 28.0; - -/// {@template streamAudioWaveformSlider} -/// A widget that displays an audio waveform and allows the user to interact -/// with it using a slider. -/// {@endtemplate} -class StreamAudioWaveformSlider extends StatefulWidget { - /// {@macro streamAudioWaveformSlider} - const StreamAudioWaveformSlider({ - super.key, - required this.waveform, - this.onChangeStart, - required this.onChanged, - this.onChangeEnd, - this.limit = 100, - this.color, - this.progress = 0, - this.progressColor, - this.minBarHeight, - this.spacingRatio, - this.heightScale, - this.inverse = true, - this.thumbColor, - this.thumbBorderColor, - }); - - /// The waveform data to be drawn. - /// - /// Note: The values should be between 0 and 1. - final List waveform; - - /// Called when the thumb starts being dragged. - final ValueChanged? onChangeStart; - - /// Called while the thumb is being dragged. - final ValueChanged? onChanged; - - /// Called when the thumb stops being dragged. - final ValueChanged? onChangeEnd; - - /// The color of the wave bars. - /// - /// Defaults to [StreamAudioWaveformSliderThemeData.color]. - final Color? color; - - /// The number of wave bars that will be draw in the screen. When the length - /// of [waveform] is bigger than [limit] only the X last bars will be shown. - /// - /// Defaults to 100. - final int limit; - - /// The progress of the audio track. Used to show the progress of the audio. - /// - /// Defaults to 0. - final double progress; - - /// The color of the progressed wave bars. - /// - /// Defaults to [StreamAudioWaveformSliderThemeData.progressColor]. - final Color? progressColor; - - /// The minimum height of the bars. - /// - /// Defaults to [StreamAudioWaveformSliderThemeData.minBarHeight]. - final double? minBarHeight; - - /// The ratio of the spacing between the bars. - /// - /// Defaults to [StreamAudioWaveformSliderThemeData.spacingRatio]. - final double? spacingRatio; - - /// The scale of the height of the bars. - /// - /// Defaults to [StreamAudioWaveformSliderThemeData.heightScale]. - final double? heightScale; - - /// If true, the bars grow from right to left otherwise they grow from left - /// to right. - /// - /// Defaults to true. - final bool inverse; - - /// The color of the slider thumb. - /// - /// Defaults to [StreamAudioWaveformSliderThemeData.thumbColor]. - final Color? thumbColor; - - /// The color of the slider thumb border. - /// - /// Defaults to [StreamAudioWaveformSliderThemeData.thumbBorderColor]. - final Color? thumbBorderColor; - - @override - State createState() => - _StreamAudioWaveformSliderState(); -} - -class _StreamAudioWaveformSliderState extends State { - @override - Widget build(BuildContext context) { - final theme = StreamAudioWaveformSliderTheme.of(context); - final waveformTheme = theme.audioWaveformTheme; - - final color = widget.color ?? waveformTheme!.color!; - final progressColor = widget.progressColor ?? waveformTheme!.progressColor!; - final minBarHeight = widget.minBarHeight ?? waveformTheme!.minBarHeight!; - final spacingRatio = widget.spacingRatio ?? waveformTheme!.spacingRatio!; - final heightScale = widget.heightScale ?? waveformTheme!.heightScale!; - final thumbColor = widget.thumbColor ?? theme.thumbColor!; - final thumbBorderColor = widget.thumbBorderColor ?? theme.thumbBorderColor!; - - return HorizontalSlider( - onChangeStart: widget.onChangeStart, - onChanged: widget.onChanged, - onChangeEnd: widget.onChangeEnd, - child: LayoutBuilder( - builder: (context, constraints) => Stack( - fit: StackFit.expand, - clipBehavior: Clip.none, - alignment: Alignment.center, - children: [ - StreamAudioWaveform( - waveform: widget.waveform, - limit: widget.limit, - color: color, - progress: widget.progress, - progressColor: progressColor, - minBarHeight: minBarHeight, - spacingRatio: spacingRatio, - heightScale: heightScale, - inverse: widget.inverse, - ), - Builder( - // Just using it for the calculation of the thumb position. - builder: (context) { - final progressWidth = constraints.maxWidth * widget.progress; - return AnimatedPositioned( - curve: const ElasticOutCurve(1.05), - duration: const Duration(milliseconds: 300), - left: progressWidth - _kAudioWaveformSliderThumbWidth / 2, - child: StreamAudioWaveformSliderThumb( - color: thumbColor, - borderColor: thumbBorderColor, - height: constraints.maxHeight, - ), - ); - }, - ), - ], - ), - ), - ); - } -} - -/// {@template streamAudioWaveformSliderThumb} -/// A widget that represents the thumb of the [StreamAudioWaveformSlider]. -/// {@endtemplate} -class StreamAudioWaveformSliderThumb extends StatelessWidget { - /// {@macro streamAudioWaveformSliderThumb} - const StreamAudioWaveformSliderThumb({ - super.key, - this.width = _kAudioWaveformSliderThumbWidth, - this.height = _kAudioWaveformSliderThumbHeight, - this.color = Colors.white, - this.borderColor = const Color(0xffecebeb), - }); - - /// The width of the thumb. - final double width; - - /// The height of the thumb. - final double height; - - /// The color of the thumb. - final Color color; - - /// The border color of the thumb. - final Color borderColor; - - @override - Widget build(BuildContext context) { - return Container( - width: width, - height: height, - decoration: BoxDecoration( - color: color, - border: Border.all( - color: borderColor, - strokeAlign: BorderSide.strokeAlignOutside, - ), - borderRadius: BorderRadius.circular(2), - ), - ); - } -} - -/// {@template streamAudioWaveform} -/// A widget that displays an audio waveform. -/// -/// The waveform is drawn using the [waveform] data. The waveform is drawn -/// horizontally and the bars grow from right to left. -/// {@endtemplate} -class StreamAudioWaveform extends StatelessWidget { - /// {@macro streamAudioWaveform} - const StreamAudioWaveform({ - super.key, - required this.waveform, - this.limit = 100, - this.color, - this.progress = 0, - this.progressColor, - this.minBarHeight, - this.spacingRatio, - this.heightScale, - this.inverse = true, - }); - - /// The waveform data to be drawn. - /// - /// Note: The values should be between 0 and 1. - final List waveform; - - /// The color of the wave bars. - /// - /// Defaults to [StreamAudioWaveformThemeData.color]. - final Color? color; - - /// The number of wave bars that will be draw in the screen. When the length - /// of [waveform] is bigger than [limit] only the X last bars will be shown. - /// - /// Defaults to 100. - final int limit; - - /// The progress of the audio track. Used to show the progress of the audio. - /// - /// Defaults to 0. - final double progress; - - /// The color of the progressed wave bars. - /// - /// Defaults to [StreamAudioWaveformThemeData.progressColor]. - final Color? progressColor; - - /// The minimum height of the bars. - /// - /// Defaults to [StreamAudioWaveformThemeData.minBarHeight]. - final double? minBarHeight; - - /// The ratio of the spacing between the bars. - /// - /// Defaults to [StreamAudioWaveformThemeData.spacingRatio]. - final double? spacingRatio; - - /// The scale of the height of the bars. - /// - /// Defaults to [StreamAudioWaveformThemeData.heightScale]. - final double? heightScale; - - /// If true, the bars grow from right to left otherwise they grow from left - /// to right. - /// - /// Defaults to true. - final bool inverse; - - @override - Widget build(BuildContext context) { - final theme = StreamAudioWaveformTheme.of(context); - - final color = this.color ?? theme.color!; - final progressColor = this.progressColor ?? theme.progressColor!; - final minBarHeight = this.minBarHeight ?? theme.minBarHeight!; - final spacingRatio = this.spacingRatio ?? theme.spacingRatio!; - final heightScale = this.heightScale ?? theme.heightScale!; - - return CustomPaint( - willChange: true, - painter: _WaveformPainter( - waveform: waveform.reversed, - limit: limit, - color: color, - progress: progress, - progressColor: progressColor, - minBarHeight: minBarHeight, - spacingRatio: spacingRatio, - heightScale: heightScale, - inverse: inverse, - ), - ); - } -} - -class _WaveformPainter extends CustomPainter { - _WaveformPainter({ - required Iterable waveform, - this.limit = 100, - this.color = const Color(0xff7E828B), - this.progress = 0, - this.progressColor = const Color(0xff005FFF), - this.minBarHeight = 2, - double spacingRatio = 0.3, - this.heightScale = 1, - this.inverse = true, - }) : waveform = [ - ...waveform.take(limit), - if (waveform.length < limit) - // Fill the remaining bars with 0 value - ...List.filled(limit - waveform.length, 0) - ], - spacingRatio = spacingRatio.clamp(0, 1); - - final List waveform; - final Color color; - final int limit; - final double progress; - final Color progressColor; - final double minBarHeight; - final double spacingRatio; - final bool inverse; - final double heightScale; - - @override - void paint(Canvas canvas, Size size) { - final canvasWidth = size.width; - final canvasHeight = size.height; - - // The total spacing between the bars in the canvas. - final spacingWidth = canvasWidth * spacingRatio; - final barsWidth = canvasWidth - spacingWidth; - final barWidth = barsWidth / limit; - final barSpacing = spacingWidth / (limit - 1); - final progressWidth = progress * canvasWidth; - - void _paintBar(int index, double barValue) { - var dx = index * (barWidth + barSpacing) + barWidth / 2; - if (inverse) dx = canvasWidth - dx; - final dy = canvasHeight / 2; - - final barHeight = math.max(barValue * canvasHeight, minBarHeight); - - final rect = RRect.fromRectAndRadius( - Rect.fromCenter( - center: Offset(dx, dy), - width: barWidth, - height: barHeight, - ), - const Radius.circular(2), - ); - - final waveColor = switch (dx <= progressWidth) { - true => progressColor, - false => color, - }; - - final wavePaint = Paint() - ..color = waveColor - ..strokeCap = StrokeCap.round; - - canvas.drawRRect(rect, wavePaint); - } - - // Paint all the bars - waveform.forEachIndexed(_paintBar); - } - - @override - bool shouldRepaint(covariant _WaveformPainter oldDelegate) => - !const ListEquality().equals(waveform, oldDelegate.waveform) || - color != oldDelegate.color || - limit != oldDelegate.limit || - progress != oldDelegate.progress || - progressColor != oldDelegate.progressColor || - minBarHeight != oldDelegate.minBarHeight || - spacingRatio != oldDelegate.spacingRatio || - heightScale != oldDelegate.heightScale || - inverse != oldDelegate.inverse; -} - -/// {@template horizontalSlider} -/// A widget that allows interactive horizontal sliding gestures. -/// -/// The `HorizontalSlider` widget wraps a child widget and allows users to -/// perform sliding gestures horizontally. It can be configured with callbacks -/// to notify the parent widget about the changes in the horizontal value. -/// {@endtemplate} -class HorizontalSlider extends StatefulWidget { - /// Creates a horizontal slider. - const HorizontalSlider({ - super.key, - required this.child, - required this.onChanged, - this.onChangeStart, - this.onChangeEnd, - }); - - /// The child widget wrapped by the slider. - final Widget child; - - /// Called when the horizontal value starts changing. - final ValueChanged? onChangeStart; - - /// Called when the horizontal value changes. - final ValueChanged? onChanged; - - /// Called when the horizontal value stops changing. - final ValueChanged? onChangeEnd; - - @override - State createState() => _HorizontalSliderState(); -} - -class _HorizontalSliderState extends State { - bool _active = false; - - /// Returns true if the slider is interactive. - bool get isInteractive => widget.onChanged != null; - - /// Converts the visual position to a value based on the text direction. - double _getValueFromVisualPosition(double visualPosition) { - final textDirection = Directionality.of(context); - final value = switch (textDirection) { - TextDirection.rtl => 1.0 - visualPosition, - TextDirection.ltr => visualPosition, - }; - - return clampDouble(value, 0, 1); - } - - /// Converts the local position to a horizontal value. - double _getValueFromLocalPosition(Offset globalPosition) { - final box = context.findRenderObject()! as RenderBox; - final localPosition = box.globalToLocal(globalPosition); - final visualPosition = localPosition.dx / box.size.width; - return _getValueFromVisualPosition(visualPosition); - } - - void _handleDragStart(DragStartDetails details) { - if (!_active && isInteractive) { - _active = true; - final value = _getValueFromLocalPosition(details.globalPosition); - widget.onChangeStart?.call(value); - } - } - - void _handleDragUpdate(DragUpdateDetails details) { - _handleHorizontalDrag(details.globalPosition); - } - - void _handleDragEnd(DragEndDetails details) { - if (!mounted) return; - - if (_active && mounted) { - final value = _getValueFromLocalPosition(details.globalPosition); - widget.onChangeEnd?.call(value); - _active = false; - } - } - - /// Handles the sliding gesture. - void _handleHorizontalDrag(Offset globalPosition) { - if (!mounted) return; - - if (isInteractive) { - final value = _getValueFromLocalPosition(globalPosition); - widget.onChanged?.call(value); - } - } - - @override - Widget build(BuildContext context) { - return GestureDetector( - onHorizontalDragStart: _handleDragStart, - onHorizontalDragUpdate: _handleDragUpdate, - onHorizontalDragEnd: _handleDragEnd, - child: widget.child, - ); - } -} diff --git a/packages/stream_chat_flutter/lib/src/misc/back_button.dart b/packages/stream_chat_flutter/lib/src/misc/back_button.dart index d2c11d003c..8f8f31c026 100644 --- a/packages/stream_chat_flutter/lib/src/misc/back_button.dart +++ b/packages/stream_chat_flutter/lib/src/misc/back_button.dart @@ -26,9 +26,9 @@ class StreamBackButton extends StatelessWidget { Widget build(BuildContext context) { final theme = StreamChatTheme.of(context); - Widget icon = StreamSvgIcon( + Widget icon = Icon( + context.streamIcons.chevronLeft, size: 24, - icon: StreamSvgIcons.left, color: theme.colorTheme.textHighEmphasis, ); diff --git a/packages/stream_chat_flutter/lib/src/misc/connection_status_builder.dart b/packages/stream_chat_flutter/lib/src/misc/connection_status_builder.dart index e6703420b4..3a44e1465e 100644 --- a/packages/stream_chat_flutter/lib/src/misc/connection_status_builder.dart +++ b/packages/stream_chat_flutter/lib/src/misc/connection_status_builder.dart @@ -29,13 +29,11 @@ class StreamConnectionStatusBuilder extends StatelessWidget { final WidgetBuilder? loadingBuilder; /// The builder that will be used in case of data - final Widget Function(BuildContext context, ConnectionStatus status) - statusBuilder; + final Widget Function(BuildContext context, ConnectionStatus status) statusBuilder; @override Widget build(BuildContext context) { - final stream = connectionStatusStream ?? - StreamChat.of(context).client.wsConnectionStatusStream; + final stream = connectionStatusStream ?? StreamChat.of(context).client.wsConnectionStatusStream; final client = StreamChat.of(context).client; return BetterStreamBuilder( initialData: client.wsConnectionStatus, diff --git a/packages/stream_chat_flutter/lib/src/misc/date_divider.dart b/packages/stream_chat_flutter/lib/src/misc/date_divider.dart index b927fca54f..7726f612c8 100644 --- a/packages/stream_chat_flutter/lib/src/misc/date_divider.dart +++ b/packages/stream_chat_flutter/lib/src/misc/date_divider.dart @@ -26,19 +26,19 @@ class StreamDateDivider extends StatelessWidget { @override Widget build(BuildContext context) { - final chatThemeData = StreamChatTheme.of(context); + final textTheme = context.streamTextTheme; + final colorScheme = context.streamColorScheme; + return Center( child: Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 1), decoration: BoxDecoration( - color: chatThemeData.colorTheme.overlayDark, + color: colorScheme.backgroundSurfaceSubtle, borderRadius: BorderRadius.circular(8), ), child: StreamTimestamp( date: dateTime.toLocal(), - style: chatThemeData.textTheme.footnote.copyWith( - color: chatThemeData.colorTheme.barsBg, - ), + style: textTheme.metadataEmphasis.copyWith(color: colorScheme.textSecondary), formatter: (context, date) { if (formatter case final formatter?) { final timestamp = formatter.call(context, date); diff --git a/packages/stream_chat_flutter/lib/src/misc/flex_grid.dart b/packages/stream_chat_flutter/lib/src/misc/flex_grid.dart index 5210cf9aa1..ca0e8955a6 100644 --- a/packages/stream_chat_flutter/lib/src/misc/flex_grid.dart +++ b/packages/stream_chat_flutter/lib/src/misc/flex_grid.dart @@ -80,19 +80,19 @@ class FlexGrid extends StatelessWidget { this.reverse = false, this.spacing = 2.0, this.runSpacing = 2.0, - }) : assert( - pattern.count == children.length, - 'The number of children must match the number of cells in the matrix', - ), - assert( - maxChildren == null || maxChildren <= pattern.count, - 'The number of maxChildren must be less than or equal to the number ' - 'of cells in the matrix', - ), - assert( - maxChildren == null || overlayBuilder != null, - 'overlayBuilder must be provided when maxChildren is not null', - ); + }) : assert( + pattern.count == children.length, + 'The number of children must match the number of cells in the matrix', + ), + assert( + maxChildren == null || maxChildren <= pattern.count, + 'The number of maxChildren must be less than or equal to the number ' + 'of cells in the matrix', + ), + assert( + maxChildren == null || overlayBuilder != null, + 'overlayBuilder must be provided when maxChildren is not null', + ); /// The pattern of the grid. /// diff --git a/packages/stream_chat_flutter/lib/src/misc/flexible_fractionally_sized_box.dart b/packages/stream_chat_flutter/lib/src/misc/flexible_fractionally_sized_box.dart index 86da240000..c95ba8fae1 100644 --- a/packages/stream_chat_flutter/lib/src/misc/flexible_fractionally_sized_box.dart +++ b/packages/stream_chat_flutter/lib/src/misc/flexible_fractionally_sized_box.dart @@ -13,8 +13,8 @@ class FlexibleFractionallySizedBox extends StatelessWidget { this.widthFactor, this.heightFactor, this.child, - }) : assert(widthFactor == null || widthFactor >= 0.0, ''), - assert(heightFactor == null || heightFactor >= 0.0, ''); + }) : assert(widthFactor == null || widthFactor >= 0.0, ''), + assert(heightFactor == null || heightFactor >= 0.0, ''); /// The widget below this widget in the tree. /// diff --git a/packages/stream_chat_flutter/lib/src/misc/giphy_chip.dart b/packages/stream_chat_flutter/lib/src/misc/giphy_chip.dart index c956517ba5..b31659f274 100644 --- a/packages/stream_chat_flutter/lib/src/misc/giphy_chip.dart +++ b/packages/stream_chat_flutter/lib/src/misc/giphy_chip.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/src/icons/stream_svg_icon.dart'; import 'package:stream_chat_flutter/src/theme/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/utils/extensions.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; /// {@template giphy_chip} /// Simple widget which displays a Giphy attribution chip. @@ -21,9 +21,9 @@ class GiphyChip extends StatelessWidget { padding: const EdgeInsets.fromLTRB(4, 4, 8, 4), child: Row( children: [ - StreamSvgIcon( + Icon( + context.streamIcons.thunder, size: 16, - icon: StreamSvgIcons.lightning, color: colorTheme.barsBg, ), Text( diff --git a/packages/stream_chat_flutter/lib/src/misc/gradient_box_border.dart b/packages/stream_chat_flutter/lib/src/misc/gradient_box_border.dart index 7a881edd16..2ecf925e5d 100644 --- a/packages/stream_chat_flutter/lib/src/misc/gradient_box_border.dart +++ b/packages/stream_chat_flutter/lib/src/misc/gradient_box_border.dart @@ -4,15 +4,19 @@ import 'dart:ui' as ui show lerpDouble; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; -const _kDefaultGradient = LinearGradient(colors: [ - Color(0xFF000000), - Color(0xFF000000), -]); - -const _kTransparentGradient = LinearGradient(colors: [ - Color(0x00000000), - Color(0x00000000), -]); +const _kDefaultGradient = LinearGradient( + colors: [ + Color(0xFF000000), + Color(0xFF000000), + ], +); + +const _kTransparentGradient = LinearGradient( + colors: [ + Color(0x00000000), + Color(0x00000000), + ], +); /// {@template gradientBoxBorder} /// A border that draws a gradient instead of a solid color. @@ -109,8 +113,7 @@ class GradientBoxBorder extends BoxBorder { /// Two sides can be merged if one or both are zero-width with /// [GradientBoxBorder.none], or if they both have the same gradient and style bool canMerge(GradientBoxBorder b) { - if ((style == BorderStyle.none && width == 0.0) || - (b.style == BorderStyle.none && b.width == 0.0)) { + if ((style == BorderStyle.none && width == 0.0) || (b.style == BorderStyle.none && b.width == 0.0)) { return true; } return style == b.style && gradient == b.gradient; @@ -209,8 +212,7 @@ class GradientBoxBorder extends BoxBorder { /// Linearly interpolate between two gradient borders. /// /// {@macro dart.ui.shadow.lerp} - static GradientBoxBorder? lerp( - GradientBoxBorder? a, GradientBoxBorder? b, double t) { + static GradientBoxBorder? lerp(GradientBoxBorder? a, GradientBoxBorder? b, double t) { if (identical(a, b)) return a; if (a == null) return b!.scale(t); if (b == null) return a.scale(1.0 - t); @@ -270,10 +272,9 @@ class GradientBoxBorder extends BoxBorder { return switch (shape) { BoxShape.circle => _paintUniformBorderWithCircle(canvas, rect), BoxShape.rectangle => switch (borderRadius) { - final radius? when radius != BorderRadius.zero => - _paintUniformBorderWithRadius(canvas, rect, radius), - _ => _paintUniformBorderWithRectangle(canvas, rect), - }, + final radius? when radius != BorderRadius.zero => _paintUniformBorderWithRadius(canvas, rect, radius), + _ => _paintUniformBorderWithRectangle(canvas, rect), + }, }; } @@ -307,14 +308,16 @@ class GradientBoxBorder extends BoxBorder { Paint _getPaint(Rect rect) { return switch (style) { - BorderStyle.solid => Paint() - ..strokeWidth = width - ..style = PaintingStyle.stroke - ..shader = gradient.createShader(rect), - BorderStyle.none => Paint() - ..strokeWidth = 0.0 - ..style = PaintingStyle.stroke - ..shader = _kTransparentGradient.createShader(rect), + BorderStyle.solid => + Paint() + ..strokeWidth = width + ..style = PaintingStyle.stroke + ..shader = gradient.createShader(rect), + BorderStyle.none => + Paint() + ..strokeWidth = 0.0 + ..style = PaintingStyle.stroke + ..shader = _kTransparentGradient.createShader(rect), }; } diff --git a/packages/stream_chat_flutter/lib/src/misc/info_tile.dart b/packages/stream_chat_flutter/lib/src/misc/info_tile.dart index fd6106491a..0cd7ff9d52 100644 --- a/packages/stream_chat_flutter/lib/src/misc/info_tile.dart +++ b/packages/stream_chat_flutter/lib/src/misc/info_tile.dart @@ -50,13 +50,15 @@ class StreamInfoTile extends StatelessWidget { ), portalFollower: Container( height: 25, - color: backgroundColor ?? + color: + backgroundColor ?? // ignore: deprecated_member_use chatThemeData.colorTheme.textLowEmphasis.withOpacity(0.9), child: Center( child: Text( message, - style: textStyle ?? + style: + textStyle ?? chatThemeData.textTheme.body.copyWith( color: Colors.white, ), diff --git a/packages/stream_chat_flutter/lib/src/misc/markdown_message.dart b/packages/stream_chat_flutter/lib/src/misc/markdown_message.dart index 9b0926ed08..5977692118 100644 --- a/packages/stream_chat_flutter/lib/src/misc/markdown_message.dart +++ b/packages/stream_chat_flutter/lib/src/misc/markdown_message.dart @@ -87,22 +87,23 @@ class StreamMarkdownMessage extends StatelessWidget { syntaxHighlighter: syntaxHighlighter, builders: builders, paddingBuilders: paddingBuilders, - styleSheet: MarkdownStyleSheet.fromTheme( - themeData.copyWith( - textTheme: themeData.textTheme.apply( - bodyColor: messageTheme?.messageTextStyle?.color, - decoration: messageTheme?.messageTextStyle?.decoration, - decorationColor: messageTheme?.messageTextStyle?.decorationColor, - decorationStyle: messageTheme?.messageTextStyle?.decorationStyle, - fontFamily: messageTheme?.messageTextStyle?.fontFamily, - ), - ), - ) - .copyWith( - a: messageTheme?.messageLinksStyle, - p: messageTheme?.messageTextStyle, - ) - .merge(styleSheet), + styleSheet: + MarkdownStyleSheet.fromTheme( + themeData.copyWith( + textTheme: themeData.textTheme.apply( + bodyColor: messageTheme?.messageTextStyle?.color, + decoration: messageTheme?.messageTextStyle?.decoration, + decorationColor: messageTheme?.messageTextStyle?.decorationColor, + decorationStyle: messageTheme?.messageTextStyle?.decorationStyle, + fontFamily: messageTheme?.messageTextStyle?.fontFamily, + ), + ), + ) + .copyWith( + a: messageTheme?.messageLinksStyle, + p: messageTheme?.messageTextStyle, + ) + .merge(styleSheet), ); } } diff --git a/packages/stream_chat_flutter/lib/src/misc/option_list_tile.dart b/packages/stream_chat_flutter/lib/src/misc/option_list_tile.dart index 2c093c9db3..3307a31bbb 100644 --- a/packages/stream_chat_flutter/lib/src/misc/option_list_tile.dart +++ b/packages/stream_chat_flutter/lib/src/misc/option_list_tile.dart @@ -60,15 +60,13 @@ class StreamOptionListTile extends StatelessWidget { onTap: onTap, child: Row( children: [ - if (leading != null) - Center(child: leading) - else - const SizedBox(width: 16), + if (leading != null) Center(child: leading) else const SizedBox(width: 16), Expanded( flex: 4, child: Text( title, - style: titleTextStyle ?? + style: + titleTextStyle ?? (titleColor == null ? chatThemeData.textTheme.bodyBold : chatThemeData.textTheme.bodyBold.copyWith( diff --git a/packages/stream_chat_flutter/lib/src/misc/reaction_icon.dart b/packages/stream_chat_flutter/lib/src/misc/reaction_icon.dart deleted file mode 100644 index fb5ccde92e..0000000000 --- a/packages/stream_chat_flutter/lib/src/misc/reaction_icon.dart +++ /dev/null @@ -1,174 +0,0 @@ -// ignore_for_file: avoid_positional_boolean_parameters - -import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/src/icons/stream_svg_icon.dart'; -import 'package:stream_chat_flutter/src/theme/stream_chat_theme.dart'; -import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; - -/// {@template reactionIconBuilder} -/// Signature for a function that builds a reaction icon. -/// {@endtemplate} -typedef ReactionIconBuilder = Widget Function( - BuildContext context, - bool isHighlighted, - double iconSize, -); - -/// {@template streamReactionIcon} -/// Reaction icon data -/// {@endtemplate} -class StreamReactionIcon { - /// {@macro streamReactionIcon} - const StreamReactionIcon({ - required this.type, - this.emojiCode, - required this.builder, - }); - - /// Creates a reaction icon with a default unknown icon. - const StreamReactionIcon.unknown() - : type = 'unknown', - emojiCode = null, - builder = _unknownBuilder; - - /// Converts this [StreamReactionIcon] to a [Reaction] object. - Reaction toReaction() => Reaction(type: type, emojiCode: emojiCode); - - /// Type of reaction - final String type; - - /// Optional emoji code for the reaction. - /// - /// Used to display a custom emoji in the notification. - final String? emojiCode; - - /// {@macro reactionIconBuilder} - final ReactionIconBuilder builder; - - /// The default list of reaction icons provided by Stream Chat. - /// - /// This includes five reactions: - /// - love: Represented by a heart icon - /// - like: Represented by a thumbs up icon - /// - sad: Represented by a thumbs down icon - /// - haha: Represented by a laughing face icon - /// - wow: Represented by a surprised face icon - /// - /// These default reactions can be used directly or as a starting point for - /// custom reaction configurations. - static const List defaultReactions = [ - StreamReactionIcon(type: 'love', emojiCode: '❤️', builder: _loveBuilder), - StreamReactionIcon(type: 'like', emojiCode: '👍', builder: _likeBuilder), - StreamReactionIcon(type: 'sad', emojiCode: '👎', builder: _sadBuilder), - StreamReactionIcon(type: 'haha', emojiCode: '😂', builder: _hahaBuilder), - StreamReactionIcon(type: 'wow', emojiCode: '😮', builder: _wowBuilder), - ]; - - static Widget _loveBuilder( - BuildContext context, - bool highlighted, - double size, - ) { - final theme = StreamChatTheme.of(context); - final iconColor = switch (highlighted) { - true => theme.colorTheme.accentPrimary, - false => theme.primaryIconTheme.color, - }; - - return StreamSvgIcon( - icon: StreamSvgIcons.loveReaction, - color: iconColor, - size: size, - ); - } - - static Widget _likeBuilder( - BuildContext context, - bool highlighted, - double size, - ) { - final theme = StreamChatTheme.of(context); - final iconColor = switch (highlighted) { - true => theme.colorTheme.accentPrimary, - false => theme.primaryIconTheme.color, - }; - - return StreamSvgIcon( - icon: StreamSvgIcons.thumbsUpReaction, - color: iconColor, - size: size, - ); - } - - static Widget _sadBuilder( - BuildContext context, - bool highlighted, - double size, - ) { - final theme = StreamChatTheme.of(context); - final iconColor = switch (highlighted) { - true => theme.colorTheme.accentPrimary, - false => theme.primaryIconTheme.color, - }; - - return StreamSvgIcon( - icon: StreamSvgIcons.thumbsDownReaction, - color: iconColor, - size: size, - ); - } - - static Widget _hahaBuilder( - BuildContext context, - bool highlighted, - double size, - ) { - final theme = StreamChatTheme.of(context); - final iconColor = switch (highlighted) { - true => theme.colorTheme.accentPrimary, - false => theme.primaryIconTheme.color, - }; - - return StreamSvgIcon( - icon: StreamSvgIcons.lolReaction, - color: iconColor, - size: size, - ); - } - - static Widget _wowBuilder( - BuildContext context, - bool highlighted, - double size, - ) { - final theme = StreamChatTheme.of(context); - final iconColor = switch (highlighted) { - true => theme.colorTheme.accentPrimary, - false => theme.primaryIconTheme.color, - }; - - return StreamSvgIcon( - icon: StreamSvgIcons.wutReaction, - color: iconColor, - size: size, - ); - } - - static Widget _unknownBuilder( - BuildContext context, - bool highlighted, - double size, - ) { - final theme = StreamChatTheme.of(context); - final iconColor = switch (highlighted) { - true => theme.colorTheme.accentPrimary, - false => theme.primaryIconTheme.color, - }; - - return Icon( - Icons.help_outline_rounded, - color: iconColor, - size: size, - ); - } -} diff --git a/packages/stream_chat_flutter/lib/src/misc/reaction_icon_resolver.dart b/packages/stream_chat_flutter/lib/src/misc/reaction_icon_resolver.dart new file mode 100644 index 0000000000..9ead8547bc --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/misc/reaction_icon_resolver.dart @@ -0,0 +1,80 @@ +import 'package:flutter/material.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; + +/// {@template reactionIconResolver} +/// Resolves reaction types to emoji codes and provides the list of +/// available reactions. +/// +/// Consumers can query supported reactions, default quick-pick reactions, +/// and map reaction types to emoji characters — all from a single source +/// of truth. +/// {@endtemplate} +abstract class ReactionIconResolver { + /// {@macro reactionIconResolver} + const ReactionIconResolver(); + + /// A small set of commonly used reaction types shown for quick + /// access in the reaction picker bar. + Set get defaultReactions; + + /// All supported reaction types, in display order. + /// + /// Iteration order of this set is used as reaction display order in + /// default components. Implementations should use a + /// [LinkedHashSet] or equivalent to preserve insertion order. + Set get supportedReactions; + + /// Returns the emoji code for the given reaction [type], or null + /// if the type is not supported. + String? emojiCode(String type); + + /// Resolves the given reaction [type] into a widget for display. + Widget resolve(BuildContext context, String type); +} + +/// {@template defaultReactionIconResolver} +/// Default implementation of [ReactionIconResolver]. +/// +/// Uses [streamSupportedEmojis] from `stream_core_flutter` to resolve +/// reaction types to emoji characters. +/// +/// Resolution chain for [resolve]: +/// 1. [emojiCode] lookup by type -> render as emoji text +/// 2. Fallback -> render type string as text +/// +/// Override [buildEmojiReaction] to customize emoji rendering +/// (e.g., Twemoji images). +/// Override [buildFallbackReaction] to customize the fallback. +/// {@endtemplate} +class DefaultReactionIconResolver extends ReactionIconResolver { + /// {@macro defaultReactionIconResolver} + const DefaultReactionIconResolver(); + + static const _defaultQuickReactions = {'like', 'haha', 'love', 'wow', 'sad'}; + + @override + Set get defaultReactions => _defaultQuickReactions; + + @override + Set get supportedReactions => streamSupportedEmojis.keys.toSet(); + + @override + String? emojiCode(String type) => streamSupportedEmojis[type]?.emoji; + + @override + Widget resolve(BuildContext context, String type) { + if (emojiCode(type) case final emoji?) { + return buildEmojiReaction(context, emoji); + } + + return buildFallbackReaction(context, type); + } + + /// Builds a widget for a resolved emoji character. + @protected + Widget buildEmojiReaction(BuildContext context, String emoji) => Text(emoji); + + /// Builds a fallback widget when no emoji could be resolved. + @protected + Widget buildFallbackReaction(BuildContext context, String type) => const Text('❓'); +} diff --git a/packages/stream_chat_flutter/lib/src/misc/separated_reorderable_list_view.dart b/packages/stream_chat_flutter/lib/src/misc/separated_reorderable_list_view.dart index 24e09455d8..bd0e672eb6 100644 --- a/packages/stream_chat_flutter/lib/src/misc/separated_reorderable_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/misc/separated_reorderable_list_view.dart @@ -33,53 +33,51 @@ class SeparatedReorderableListView extends ReorderableListView { super.restorationId, super.clipBehavior, }) : super.builder( - buildDefaultDragHandles: false, - itemCount: math.max(0, itemCount * 2 - 1), - itemBuilder: (BuildContext context, int index) { - final itemIndex = index ~/ 2; - if (index.isEven) { - final listItem = itemBuilder(context, itemIndex); - return ReorderableDelayedDragStartListener( - key: listItem.key, - index: index, - child: listItem, - ); - } + buildDefaultDragHandles: false, + itemCount: math.max(0, itemCount * 2 - 1), + itemBuilder: (BuildContext context, int index) { + final itemIndex = index ~/ 2; + if (index.isEven) { + final listItem = itemBuilder(context, itemIndex); + return ReorderableDelayedDragStartListener( + key: listItem.key, + index: index, + child: listItem, + ); + } - final separator = separatorBuilder(context, itemIndex); - if (separator.key == null) { - return KeyedSubtree( - key: ValueKey('reorderable_separator_$itemIndex'), - child: IgnorePointer(child: separator), - ); - } + final separator = separatorBuilder(context, itemIndex); + if (separator.key == null) { + return KeyedSubtree( + key: ValueKey('reorderable_separator_$itemIndex'), + child: IgnorePointer(child: separator), + ); + } - return separator; - }, - onReorder: (int oldIndex, int newIndex) { - // Adjust the indexes due to an issue in the ReorderableListView - // which isn't going to be fixed in the near future. - // - // issue: https://github.com/flutter/flutter/issues/24786 - if (newIndex > oldIndex) { - newIndex -= 1; - } + return separator; + }, + onReorder: (int oldIndex, int newIndex) { + // Adjust the indexes due to an issue in the ReorderableListView + // which isn't going to be fixed in the near future. + // + // issue: https://github.com/flutter/flutter/issues/24786 + if (newIndex > oldIndex) { + newIndex -= 1; + } - // Ideally should never happen as separators are wrapped in the - // IgnorePointer widget. This is just a safety check. - if (oldIndex % 2 == 1) return; + // Ideally should never happen as separators are wrapped in the + // IgnorePointer widget. This is just a safety check. + if (oldIndex % 2 == 1) return; - // The item moved behind the top/bottom separator we should not - // reorder it. - if ((oldIndex - newIndex).abs() == 1) return; + // The item moved behind the top/bottom separator we should not + // reorder it. + if ((oldIndex - newIndex).abs() == 1) return; - // Calculate the updated indexes - final updatedOldIndex = oldIndex ~/ 2; - final updatedNewIndex = oldIndex > newIndex && newIndex % 2 == 1 - ? (newIndex + 1) ~/ 2 - : newIndex ~/ 2; + // Calculate the updated indexes + final updatedOldIndex = oldIndex ~/ 2; + final updatedNewIndex = oldIndex > newIndex && newIndex % 2 == 1 ? (newIndex + 1) ~/ 2 : newIndex ~/ 2; - return onReorder(updatedOldIndex, updatedNewIndex); - }, - ); + return onReorder(updatedOldIndex, updatedNewIndex); + }, + ); } diff --git a/packages/stream_chat_flutter/lib/src/misc/simple_safe_area.dart b/packages/stream_chat_flutter/lib/src/misc/simple_safe_area.dart index 7aeeb8615c..b43c1216e5 100644 --- a/packages/stream_chat_flutter/lib/src/misc/simple_safe_area.dart +++ b/packages/stream_chat_flutter/lib/src/misc/simple_safe_area.dart @@ -20,10 +20,10 @@ class SimpleSafeArea extends StatelessWidget { this.minimum = EdgeInsets.zero, this.maintainBottomViewPadding = false, required this.child, - }) : left = enabled ?? true, - top = enabled ?? true, - right = enabled ?? true, - bottom = enabled ?? true; + }) : left = enabled ?? true, + top = enabled ?? true, + right = enabled ?? true, + bottom = enabled ?? true; /// Creates a [SimpleSafeArea] that avoids system intrusions only on the /// specified sides. diff --git a/packages/stream_chat_flutter/lib/src/misc/staggered_scale_transition.dart b/packages/stream_chat_flutter/lib/src/misc/staggered_scale_transition.dart new file mode 100644 index 0000000000..8dc1dddd40 --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/misc/staggered_scale_transition.dart @@ -0,0 +1,148 @@ +import 'package:ezanimation/ezanimation.dart'; +import 'package:flutter/material.dart'; + +/// {@template staggeredScaleTransition} +/// A widget that scales in its [children] with a staggered animation. +/// +/// Each child pops in sequentially with a configurable [staggerDelay]. +/// +/// By default, children animate from last to first. Set [animateReversed] +/// to `false` to animate from first to last. +/// +/// {@tool snippet} +/// +/// ```dart +/// StaggeredScaleTransition( +/// children: [ +/// Icon(Icons.star), +/// Icon(Icons.favorite), +/// Icon(Icons.thumb_up), +/// ], +/// ) +/// ``` +/// {@end-tool} +/// {@endtemplate} +class StaggeredScaleTransition extends StatefulWidget { + /// {@macro staggeredScaleTransition} + const StaggeredScaleTransition({ + super.key, + required this.children, + this.staggerDelay = const Duration(milliseconds: 30), + this.animateReversed = true, + }); + + /// The widgets to display with staggered scale-in animation. + final List children; + + /// The delay between the start of each child's animation. + /// + /// Defaults to 30 milliseconds. + final Duration staggerDelay; + + /// Whether to animate children in reversed list order. + /// + /// When `true`, children animate from last to first in list order. + /// When `false`, children animate from first to last in list order. + /// + /// Defaults to `true`. + final bool animateReversed; + + @override + State createState() => _StaggeredScaleTransitionState(); +} + +class _StaggeredScaleTransitionState extends State { + List _animations = []; + + void _initAnimations() { + _animations = List.generate( + widget.children.length, + (index) => EzAnimation.tween( + Tween(begin: 0.0, end: 1.0), + const Duration(milliseconds: 120), + curve: Curves.bounceOut, + ), + ); + } + + void _triggerAnimations() async { + final iterable = switch (widget.animateReversed) { + true => _animations.reversed, + false => _animations, + }; + + for (final animation in iterable) { + if (!mounted) return; + animation.start(); + await Future.delayed(widget.staggerDelay); + } + } + + void _dismissAnimations() { + for (final animation in _animations) { + animation.stop(); + } + } + + void _disposeAnimations() { + for (final animation in _animations) { + animation.dispose(); + } + } + + @override + void initState() { + super.initState(); + _initAnimations(); + + // Trigger animations at the end of the frame to avoid jank. + WidgetsBinding.instance.endOfFrame.then((_) => _triggerAnimations()); + } + + @override + void didUpdateWidget(covariant StaggeredScaleTransition oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.children.length != widget.children.length) { + // Dismiss and dispose old animations. + _dismissAnimations(); + _disposeAnimations(); + + // Initialize new animations. + _initAnimations(); + + // Trigger animations at the end of the frame to avoid jank. + WidgetsBinding.instance.endOfFrame.then((_) => _triggerAnimations()); + } + } + + @override + void dispose() { + _dismissAnimations(); + _disposeAnimations(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + for (var i = 0; i < widget.children.length; i++) + AnimatedBuilder( + animation: _animations[i], + builder: (context, child) { + final value = _animations[i].value; + // Width grows at 2x the scale rate so the row reaches full + // width before the bounce oscillation starts. + return Align( + widthFactor: (value * 2.0).clamp(0.0, 1.0), + heightFactor: 1, + child: Transform.scale(scale: value, child: child), + ); + }, + child: widget.children[i], + ), + ], + ); + } +} diff --git a/packages/stream_chat_flutter/lib/src/misc/stream_modal.dart b/packages/stream_chat_flutter/lib/src/misc/stream_modal.dart index 19269ebd9c..03665115a0 100644 --- a/packages/stream_chat_flutter/lib/src/misc/stream_modal.dart +++ b/packages/stream_chat_flutter/lib/src/misc/stream_modal.dart @@ -1,6 +1,7 @@ import 'dart:ui'; import 'package:flutter/material.dart'; +import 'package:flutter_portal/flutter_portal.dart'; import 'package:stream_chat_flutter/src/theme/stream_chat_theme.dart'; /// Shows a modal dialog with customized transitions and backdrop effects. @@ -54,8 +55,8 @@ Future showStreamDialog({ ); }, pageBuilder: (context, animation, secondaryAnimation) { - final pageChild = Builder(builder: builder); - return capturedThemes.wrap(pageChild); + final pageChild = Portal(child: Builder(builder: builder)); + return StreamChatTheme(data: theme, child: capturedThemes.wrap(pageChild)); }, ); } diff --git a/packages/stream_chat_flutter/lib/src/misc/swipeable.dart b/packages/stream_chat_flutter/lib/src/misc/swipeable.dart index 45b9b2dceb..8fd78e8c9e 100644 --- a/packages/stream_chat_flutter/lib/src/misc/swipeable.dart +++ b/packages/stream_chat_flutter/lib/src/misc/swipeable.dart @@ -13,10 +13,11 @@ typedef SwipeDirectionCallback = void Function(SwipeDirection direction); /// dismissing action. /// /// Used by [Swipeable.backgroundBuilder]. -typedef BackgroundWidgetBuilder = Widget Function( - BuildContext context, - SwipeUpdateDetails details, -); +typedef BackgroundWidgetBuilder = + Widget Function( + BuildContext context, + SwipeUpdateDetails details, + ); /// The direction in which a [Swipeable] can be swiped. enum SwipeDirection { @@ -32,7 +33,7 @@ enum SwipeDirection { startToEnd, /// The [Swipeable] cannot be swiped by dragging. - none + none, } /// A widget that can be swiped in a specified direction. @@ -95,9 +96,9 @@ class Swipeable extends StatefulWidget { this.dragStartBehavior = DragStartBehavior.start, this.behavior = HitTestBehavior.opaque, }) : assert( - swipeThreshold >= 0.0 && swipeThreshold <= 1.0, - 'swipeThreshold must be between 0.0 and 1.0', - ); + swipeThreshold >= 0.0 && swipeThreshold <= 1.0, + 'swipeThreshold must be between 0.0 and 1.0', + ); /// The widget below this widget in the tree. /// @@ -225,8 +226,7 @@ class _SwipeableClipper extends CustomClipper { } } -class _SwipeableState extends State - with SingleTickerProviderStateMixin, AutomaticKeepAliveClientMixin { +class _SwipeableState extends State with SingleTickerProviderStateMixin, AutomaticKeepAliveClientMixin { @override void initState() { super.initState(); @@ -261,13 +261,9 @@ class _SwipeableState extends State } switch (Directionality.of(context)) { case TextDirection.rtl: - return extent < 0 - ? SwipeDirection.startToEnd - : SwipeDirection.endToStart; + return extent < 0 ? SwipeDirection.startToEnd : SwipeDirection.endToStart; case TextDirection.ltr: - return extent > 0 - ? SwipeDirection.startToEnd - : SwipeDirection.endToStart; + return extent > 0 ? SwipeDirection.startToEnd : SwipeDirection.endToStart; } } @@ -280,8 +276,7 @@ class _SwipeableState extends State void _handleDragStart(DragStartDetails details) { _dragUnderway = true; if (_moveController!.isAnimating) { - _dragExtent = - _moveController!.value * _overallDragAxisExtent * _dragExtent.sign; + _dragExtent = _moveController!.value * _overallDragAxisExtent * _dragExtent.sign; _moveController!.stop(); } else { _dragExtent = 0.0; diff --git a/packages/stream_chat_flutter/lib/src/misc/thread_header.dart b/packages/stream_chat_flutter/lib/src/misc/thread_header.dart index 1142ac4507..9f7eb4ebda 100644 --- a/packages/stream_chat_flutter/lib/src/misc/thread_header.dart +++ b/packages/stream_chat_flutter/lib/src/misc/thread_header.dart @@ -57,8 +57,7 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// and the [ChannelTheme.channelHeaderTheme] property. Modify it to change /// the widget's appearance. /// {@endtemplate} -class StreamThreadHeader extends StatelessWidget - implements PreferredSizeWidget { +class StreamThreadHeader extends StatelessWidget implements PreferredSizeWidget { /// {@macro streamThreadHeader} const StreamThreadHeader({ super.key, @@ -73,7 +72,8 @@ class StreamThreadHeader extends StatelessWidget this.onTitleTap, this.showTypingIndicator = true, this.backgroundColor, - this.elevation = 1, + this.elevation = 0, + this.scrolledUnderElevation = 0, }) : preferredSize = const Size.fromHeight(kToolbarHeight); /// Whether to show the leading back button. @@ -118,6 +118,9 @@ class StreamThreadHeader extends StatelessWidget /// The elevation for this [StreamThreadHeader]. final double elevation; + /// The scrolled under elevation for this [StreamThreadHeader]. + final double scrolledUnderElevation; + @override Widget build(BuildContext context) { final effectiveCenterTitle = getEffectiveCenterTitle( @@ -127,35 +130,38 @@ class StreamThreadHeader extends StatelessWidget ); final channelHeaderTheme = StreamChannelHeaderTheme.of(context); - - final defaultSubtitle = subtitle ?? - Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - '${context.translations.withText} ', - style: channelHeaderTheme.subtitleStyle, - ), - Flexible( - child: StreamChannelName( - channel: StreamChannel.of(context).channel, - textStyle: channelHeaderTheme.subtitleStyle, - ), - ), - ], - ); + final textTheme = context.streamTextTheme; + final colorScheme = context.streamColorScheme; + + final replyCount = parent.replyCount; + + final defaultSubtitle = + subtitle ?? + (replyCount != null + ? Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + context.translations.threadReplyCountText(replyCount), + style: + channelHeaderTheme.subtitleStyle ?? + textTheme.captionDefault.copyWith(color: colorScheme.textSecondary), + ), + ], + ) + : const SizedBox.shrink()); final theme = Theme.of(context); return AppBar( automaticallyImplyLeading: false, toolbarTextStyle: theme.textTheme.bodyMedium, titleTextStyle: theme.textTheme.titleLarge, - systemOverlayStyle: theme.brightness == Brightness.dark - ? SystemUiOverlayStyle.light - : SystemUiOverlayStyle.dark, + systemOverlayStyle: theme.brightness == Brightness.dark ? SystemUiOverlayStyle.light : SystemUiOverlayStyle.dark, elevation: elevation, - leading: leading ?? + scrolledUnderElevation: scrolledUnderElevation, + leading: + leading ?? (showBackButton ? StreamBackButton( channelId: StreamChannel.of(context).channel.cid, @@ -166,6 +172,13 @@ class StreamThreadHeader extends StatelessWidget backgroundColor: backgroundColor ?? channelHeaderTheme.color, centerTitle: centerTitle, actions: actions, + shape: LinearBorder( + side: BorderSide( + color: colorScheme.borderDefault, + width: 1, + ), + bottom: const LinearBorderEdge(), + ), title: InkWell( onTap: onTitleTap, child: SizedBox( @@ -173,14 +186,12 @@ class StreamThreadHeader extends StatelessWidget width: 250, child: Column( mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: effectiveCenterTitle - ? CrossAxisAlignment.center - : CrossAxisAlignment.stretch, + crossAxisAlignment: effectiveCenterTitle ? CrossAxisAlignment.center : CrossAxisAlignment.stretch, children: [ title ?? Text( context.translations.threadReplyLabel, - style: channelHeaderTheme.titleStyle, + style: channelHeaderTheme.titleStyle ?? textTheme.headingSm, ), const SizedBox(height: 2), if (showTypingIndicator) diff --git a/packages/stream_chat_flutter/lib/src/misc/visible_footnote.dart b/packages/stream_chat_flutter/lib/src/misc/visible_footnote.dart index d2308f9d7d..99d4fcc6c0 100644 --- a/packages/stream_chat_flutter/lib/src/misc/visible_footnote.dart +++ b/packages/stream_chat_flutter/lib/src/misc/visible_footnote.dart @@ -17,9 +17,9 @@ class StreamVisibleFootnote extends StatelessWidget { return Row( mainAxisSize: MainAxisSize.min, children: [ - StreamSvgIcon( + Icon( + context.streamIcons.eyeOpen, size: 16, - icon: StreamSvgIcons.eye, color: chatThemeData.colorTheme.textLowEmphasis, ), const SizedBox(width: 8), diff --git a/packages/stream_chat_flutter/lib/src/poll/creator/poll_option_reorderable_list_view.dart b/packages/stream_chat_flutter/lib/src/poll/creator/poll_option_reorderable_list_view.dart index cff3c96253..1e0b2013ec 100644 --- a/packages/stream_chat_flutter/lib/src/poll/creator/poll_option_reorderable_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/poll/creator/poll_option_reorderable_list_view.dart @@ -116,16 +116,16 @@ class PollOptionListItem extends StatelessWidget { contentPadding: const EdgeInsets.symmetric(vertical: 18), onChanged: switch (onChanged) { final onChanged? => (text) { - final updated = option.copyWith(text: text); - return onChanged.call(updated); - }, + final updated = option.copyWith(text: text); + return onChanged.call(updated); + }, _ => null, }, ), ), IconButton( iconSize: 24, - icon: const StreamSvgIcon(icon: StreamSvgIcons.delete), + icon: Icon(context.streamIcons.trashBin), style: IconButton.styleFrom( foregroundColor: colorTheme.textLowEmphasis, ), @@ -181,12 +181,10 @@ class PollOptionReorderableListView extends StatefulWidget { final ValueSetter>? onOptionsChanged; @override - State createState() => - _PollOptionReorderableListViewState(); + State createState() => _PollOptionReorderableListViewState(); } -class _PollOptionReorderableListViewState - extends State { +class _PollOptionReorderableListViewState extends State { late Map _focusNodes; late Map _options; diff --git a/packages/stream_chat_flutter/lib/src/poll/creator/stream_poll_creator_dialog.dart b/packages/stream_chat_flutter/lib/src/poll/creator/stream_poll_creator_dialog.dart index f9d2a76777..00864ed5a7 100644 --- a/packages/stream_chat_flutter/lib/src/poll/creator/stream_poll_creator_dialog.dart +++ b/packages/stream_chat_flutter/lib/src/poll/creator/stream_poll_creator_dialog.dart @@ -92,8 +92,7 @@ class StreamPollCreatorDialog extends StatefulWidget { final EdgeInsets padding; @override - State createState() => - _StreamPollCreatorDialogState(); + State createState() => _StreamPollCreatorDialogState(); } class _StreamPollCreatorDialogState extends State { @@ -202,12 +201,10 @@ class StreamPollCreatorFullScreenDialog extends StatefulWidget { final EdgeInsets padding; @override - State createState() => - _StreamPollCreatorFullScreenDialogState(); + State createState() => _StreamPollCreatorFullScreenDialogState(); } -class _StreamPollCreatorFullScreenDialogState - extends State { +class _StreamPollCreatorFullScreenDialogState extends State { late final _controller = StreamPollController( poll: widget.poll, config: widget.config, @@ -244,7 +241,7 @@ class _StreamPollCreatorFullScreenDialogState return IconButton( color: colorTheme.accentPrimary, disabledColor: colorTheme.disabled, - icon: const StreamSvgIcon(icon: StreamSvgIcons.send), + icon: Icon(context.streamIcons.paperPlane), onPressed: isValid ? () { final errors = _controller.validateGranularly(); diff --git a/packages/stream_chat_flutter/lib/src/poll/creator/stream_poll_creator_widget.dart b/packages/stream_chat_flutter/lib/src/poll/creator/stream_poll_creator_widget.dart index dcb9edce7c..93e4872ce9 100644 --- a/packages/stream_chat_flutter/lib/src/poll/creator/stream_poll_creator_widget.dart +++ b/packages/stream_chat_flutter/lib/src/poll/creator/stream_poll_creator_widget.dart @@ -67,12 +67,10 @@ class StreamPollCreatorWidget extends StatelessWidget { allowDuplicate: config.allowDuplicateOptions, optionsRange: config.optionsRange, initialOptions: [ - for (final option in poll.options) - PollOptionItem(id: option.id, text: option.text), + for (final option in poll.options) PollOptionItem(id: option.id, text: option.text), ], onOptionsChanged: (options) => controller.options = [ - for (final option in options) - PollOption(id: option.id, text: option.text), + for (final option in options) PollOption(id: option.id, text: option.text), ], ), const SizedBox(height: 32), @@ -118,7 +116,8 @@ class StreamPollCreatorWidget extends StatelessWidget { PollSwitchListTile( title: translations.anonymousPollLabel, value: poll.votingVisibility == VotingVisibility.anonymous, - onChanged: (anon) => controller.votingVisibility = anon // + onChanged: (anon) => controller.votingVisibility = + anon // ? VotingVisibility.anonymous : VotingVisibility.public, ), diff --git a/packages/stream_chat_flutter/lib/src/poll/interactor/poll_add_comment_dialog.dart b/packages/stream_chat_flutter/lib/src/poll/interactor/poll_add_comment_dialog.dart index 47c67a7b76..293b10cb7b 100644 --- a/packages/stream_chat_flutter/lib/src/poll/interactor/poll_add_comment_dialog.dart +++ b/packages/stream_chat_flutter/lib/src/poll/interactor/poll_add_comment_dialog.dart @@ -12,14 +12,13 @@ import 'package:stream_chat_flutter/src/utils/extensions.dart'; Future showPollAddCommentDialog({ required BuildContext context, String initialValue = '', -}) => - showDialog( - context: context, - barrierDismissible: false, - builder: (_) => PollAddCommentDialog( - initialValue: initialValue, - ), - ); +}) => showDialog( + context: context, + barrierDismissible: false, + builder: (_) => PollAddCommentDialog( + initialValue: initialValue, + ), +); /// {@template pollAddCommentDialog} /// A dialog that allows the user to add or update a poll comment. diff --git a/packages/stream_chat_flutter/lib/src/poll/interactor/poll_footer.dart b/packages/stream_chat_flutter/lib/src/poll/interactor/poll_footer.dart index 807e5071e0..e03cb45788 100644 --- a/packages/stream_chat_flutter/lib/src/poll/interactor/poll_footer.dart +++ b/packages/stream_chat_flutter/lib/src/poll/interactor/poll_footer.dart @@ -109,8 +109,7 @@ class PollFooter extends StatelessWidget { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - if (visibleOptionCount case final count? - when count < poll.options.length) + if (visibleOptionCount case final count? when count < poll.options.length) PollFooterButton( title: translations.seeAllOptionsLabel(count: poll.options.length), onPressed: onSeeMoreOptions, diff --git a/packages/stream_chat_flutter/lib/src/poll/interactor/poll_options_list_view.dart b/packages/stream_chat_flutter/lib/src/poll/interactor/poll_options_list_view.dart index 2aa627a176..2f8510d914 100644 --- a/packages/stream_chat_flutter/lib/src/poll/interactor/poll_options_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/poll/interactor/poll_options_list_view.dart @@ -1,8 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/src/avatars/user_avatar.dart'; +import 'package:stream_chat_flutter/src/components/avatar/stream_user_avatar_stack.dart'; import 'package:stream_chat_flutter/src/misc/empty_widget.dart'; import 'package:stream_chat_flutter/src/theme/poll_interactor_theme.dart'; -import 'package:stream_chat_flutter/src/theme/stream_chat_theme.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; /// {@template pollOptionsListView} @@ -198,9 +197,7 @@ class PollOptionItem extends StatelessWidget { if (showProgressBar) OptionVotesProgressBar( value: poll.voteRatioFor(option), - borderRadius: - theme.pollOptionVotesProgressBarBorderRadius ?? - BorderRadius.circular(4), + borderRadius: theme.pollOptionVotesProgressBarBorderRadius ?? BorderRadius.circular(4), trackColor: theme.pollOptionVotesProgressBarTrackColor, valueColor: switch (poll.isOptionWinner(option)) { true => theme.pollOptionVotesProgressBarWinnerColor, @@ -209,7 +206,7 @@ class PollOptionItem extends StatelessWidget { ), ], ), - ) + ), ], ), ), @@ -230,9 +227,9 @@ class OptionVoters extends StatelessWidget { this.overlap = 0.5, required this.voters, }) : assert( - overlap >= 0 && overlap <= 1, - 'Overlap must be between 0 and 1', - ); + overlap >= 0 && overlap <= 1, + 'Overlap must be between 0 and 1', + ); /// The radius of the avatars. final double radius; @@ -249,42 +246,7 @@ class OptionVoters extends StatelessWidget { Widget build(BuildContext context) { if (voters.isEmpty) return const Empty(); - final theme = StreamChatTheme.of(context); - - final diameter = radius * 2; - final width = diameter + (voters.length * diameter * overlap); - - var overlapPadding = 0.0; - - return SizedBox.fromSize( - size: Size(width, diameter), - child: Stack( - children: [ - ...voters.map( - (user) { - overlapPadding += diameter * overlap; - return Positioned( - right: overlapPadding - (diameter * overlap), - bottom: 0, - top: 0, - child: Container( - decoration: BoxDecoration( - shape: BoxShape.circle, - color: theme.colorTheme.barsBg, - ), - padding: const EdgeInsets.all(1), - child: StreamUserAvatar( - user: user, - constraints: BoxConstraints.tight(Size.fromRadius(radius)), - showOnlineStatus: false, - ), - ), - ); - }, - ), - ], - ), - ); + return StreamUserAvatarStack(size: .xs, users: voters, overlap: overlap); } } diff --git a/packages/stream_chat_flutter/lib/src/poll/interactor/poll_suggest_option_dialog.dart b/packages/stream_chat_flutter/lib/src/poll/interactor/poll_suggest_option_dialog.dart index fd4c02f934..35442842e6 100644 --- a/packages/stream_chat_flutter/lib/src/poll/interactor/poll_suggest_option_dialog.dart +++ b/packages/stream_chat_flutter/lib/src/poll/interactor/poll_suggest_option_dialog.dart @@ -12,14 +12,13 @@ import 'package:stream_chat_flutter/src/utils/extensions.dart'; Future showPollSuggestOptionDialog({ required BuildContext context, String initialOption = '', -}) => - showDialog( - context: context, - barrierDismissible: false, - builder: (_) => PollSuggestOptionDialog( - initialOption: initialOption, - ), - ); +}) => showDialog( + context: context, + barrierDismissible: false, + builder: (_) => PollSuggestOptionDialog( + initialOption: initialOption, + ), +); /// {@template pollSuggestOptionDialog} /// A dialog that allows the user to suggest an option for a poll. @@ -39,8 +38,7 @@ class PollSuggestOptionDialog extends StatefulWidget { final String initialOption; @override - State createState() => - _PollSuggestOptionDialogState(); + State createState() => _PollSuggestOptionDialogState(); } class _PollSuggestOptionDialogState extends State { diff --git a/packages/stream_chat_flutter/lib/src/poll/stream_poll_comments_dialog.dart b/packages/stream_chat_flutter/lib/src/poll/stream_poll_comments_dialog.dart index eb6ba6db92..deecc7c6f0 100644 --- a/packages/stream_chat_flutter/lib/src/poll/stream_poll_comments_dialog.dart +++ b/packages/stream_chat_flutter/lib/src/poll/stream_poll_comments_dialog.dart @@ -77,8 +77,7 @@ class StreamPollCommentsDialog extends StatefulWidget { final VoidCallback? onUpdateComment; @override - State createState() => - _StreamPollCommentsDialogState(); + State createState() => _StreamPollCommentsDialogState(); } class _StreamPollCommentsDialogState extends State { diff --git a/packages/stream_chat_flutter/lib/src/poll/stream_poll_option_votes_dialog.dart b/packages/stream_chat_flutter/lib/src/poll/stream_poll_option_votes_dialog.dart index a22e994c57..ba87b9eacc 100644 --- a/packages/stream_chat_flutter/lib/src/poll/stream_poll_option_votes_dialog.dart +++ b/packages/stream_chat_flutter/lib/src/poll/stream_poll_option_votes_dialog.dart @@ -1,11 +1,11 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/src/icons/stream_svg_icon.dart'; import 'package:stream_chat_flutter/src/misc/empty_widget.dart'; import 'package:stream_chat_flutter/src/scroll_view/poll_vote_scroll_view/stream_poll_vote_list_view.dart'; import 'package:stream_chat_flutter/src/theme/poll_option_votes_dialog_theme.dart'; import 'package:stream_chat_flutter/src/utils/extensions.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; /// {@template showStreamPollOptionVotesDialog} /// Displays an interactive dialog to show all the votes for a poll option. @@ -66,12 +66,10 @@ class StreamPollOptionVotesDialog extends StatefulWidget { final int? pollVotesCount; @override - State createState() => - _StreamPollOptionVotesDialogState(); + State createState() => _StreamPollOptionVotesDialogState(); } -class _StreamPollOptionVotesDialogState - extends State { +class _StreamPollOptionVotesDialogState extends State { late StreamPollVoteListController _controller; @override @@ -83,8 +81,7 @@ class _StreamPollOptionVotesDialogState @override void didUpdateWidget(covariant StreamPollOptionVotesDialog oldWidget) { super.didUpdateWidget(oldWidget); - if (oldWidget.poll.id != widget.poll.id || - oldWidget.option.id != widget.option.id) { + if (oldWidget.poll.id != widget.poll.id || oldWidget.option.id != widget.option.id) { _controller.dispose(); // Dispose the old controller. _initializeController(); // Initialize a new controller. } @@ -131,8 +128,8 @@ class _StreamPollOptionVotesDialogState mainAxisAlignment: MainAxisAlignment.end, children: [ if (isOptionWinner) ...[ - StreamSvgIcon( - icon: StreamSvgIcons.award, + Icon( + context.streamIcons.trophy, color: theme.pollOptionWinnerVoteCountTextStyle?.color, ), const SizedBox(width: 8), @@ -141,9 +138,7 @@ class _StreamPollOptionVotesDialogState context.translations.voteCountLabel( count: widget.pollVotesCount, ), - style: isOptionWinner - ? theme.pollOptionWinnerVoteCountTextStyle - : theme.pollOptionVoteCountTextStyle, + style: isOptionWinner ? theme.pollOptionWinnerVoteCountTextStyle : theme.pollOptionVoteCountTextStyle, ), ], ), diff --git a/packages/stream_chat_flutter/lib/src/poll/stream_poll_results_dialog.dart b/packages/stream_chat_flutter/lib/src/poll/stream_poll_results_dialog.dart index e1b5ee1064..edce0bdaea 100644 --- a/packages/stream_chat_flutter/lib/src/poll/stream_poll_results_dialog.dart +++ b/packages/stream_chat_flutter/lib/src/poll/stream_poll_results_dialog.dart @@ -1,13 +1,13 @@ import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/src/icons/stream_svg_icon.dart'; import 'package:stream_chat_flutter/src/misc/empty_widget.dart'; import 'package:stream_chat_flutter/src/poll/stream_poll_option_votes_dialog.dart'; import 'package:stream_chat_flutter/src/scroll_view/poll_vote_scroll_view/stream_poll_vote_list_tile.dart'; import 'package:stream_chat_flutter/src/theme/poll_results_dialog_theme.dart'; import 'package:stream_chat_flutter/src/utils/extensions.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; /// {@template showStreamPollResultsDialog} /// Displays an interactive dialog to show the results of a poll. @@ -270,9 +270,7 @@ class PollVotesByOptionItem extends StatelessWidget { vertical: 12, horizontal: 16, ), - decoration: isOptionWinner - ? theme.pollOptionsWinnerDecoration - : theme.pollOptionsDecoration, + decoration: isOptionWinner ? theme.pollOptionsWinnerDecoration : theme.pollOptionsDecoration, child: Column( spacing: 16, mainAxisSize: MainAxisSize.min, @@ -283,24 +281,20 @@ class PollVotesByOptionItem extends StatelessWidget { Expanded( child: Text( option.text, - style: isOptionWinner - ? theme.pollOptionsWinnerTextStyle - : theme.pollOptionsTextStyle, + style: isOptionWinner ? theme.pollOptionsWinnerTextStyle : theme.pollOptionsTextStyle, ), ), const SizedBox(width: 8), if (isOptionWinner) ...[ - StreamSvgIcon( - icon: StreamSvgIcons.award, + Icon( + context.streamIcons.trophy, color: theme.pollOptionsWinnerVoteCountTextStyle?.color, ), const SizedBox(width: 8), ], Text( context.translations.voteCountLabel(count: pollVotesCount), - style: isOptionWinner - ? theme.pollOptionsWinnerVoteCountTextStyle - : theme.pollOptionsVoteCountTextStyle, + style: isOptionWinner ? theme.pollOptionsWinnerVoteCountTextStyle : theme.pollOptionsVoteCountTextStyle, ), ], ), diff --git a/packages/stream_chat_flutter/lib/src/poll/stream_poll_text_field.dart b/packages/stream_chat_flutter/lib/src/poll/stream_poll_text_field.dart index 3a4af9b9c4..9a28423978 100644 --- a/packages/stream_chat_flutter/lib/src/poll/stream_poll_text_field.dart +++ b/packages/stream_chat_flutter/lib/src/poll/stream_poll_text_field.dart @@ -90,9 +90,9 @@ class _StreamPollTextFieldState extends State { if (currValue != newValue) { _controller.value = switch (newValue) { final value? => TextEditingValue( - text: value, - selection: TextSelection.collapsed(offset: value.length), - ), + text: value, + selection: TextSelection.collapsed(offset: value.length), + ), _ => TextEditingValue.empty, }; } @@ -129,7 +129,8 @@ class _StreamPollTextFieldState extends State { right: horizontalPadding / 2, ), errorText: widget.errorText, - errorStyle: widget.errorStyle ?? + errorStyle: + widget.errorStyle ?? theme.textTheme.footnote.copyWith( color: theme.colorTheme.accentError, ), diff --git a/packages/stream_chat_flutter/lib/src/reactions/desktop_reactions_builder.dart b/packages/stream_chat_flutter/lib/src/reactions/desktop_reactions_builder.dart index f228833564..719cb31d09 100644 --- a/packages/stream_chat_flutter/lib/src/reactions/desktop_reactions_builder.dart +++ b/packages/stream_chat_flutter/lib/src/reactions/desktop_reactions_builder.dart @@ -1,6 +1,5 @@ // ignore_for_file: cascade_invocations -import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_portal/flutter_portal.dart'; @@ -41,8 +40,7 @@ class DesktopReactionsBuilder extends StatefulWidget { final bool reverse; @override - State createState() => - _DesktopReactionsBuilderState(); + State createState() => _DesktopReactionsBuilderState(); @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { @@ -70,19 +68,18 @@ class _DesktopReactionsBuilderState extends State { Widget build(BuildContext context) { final streamChat = StreamChat.of(context); final currentUser = streamChat.currentUser!; - final reactionIcons = StreamChatConfiguration.of(context).reactionIcons; + final config = StreamChatConfiguration.of(context); + final resolver = config.reactionIconResolver; final streamChatTheme = StreamChatTheme.of(context); final reactionsMap = {}; widget.message.latestReactions?.forEach((element) { - if (!reactionsMap.containsKey(element.type) || - element.user!.id == currentUser.id) { + if (!reactionsMap.containsKey(element.type) || element.user!.id == currentUser.id) { reactionsMap[element.type] = element; } }); - final reactionsList = reactionsMap.values.toList() - ..sort((a, b) => a.user!.id == currentUser.id ? 1 : -1); + final reactionsList = reactionsMap.values.toList()..sort((a, b) => a.user!.id == currentUser.id ? 1 : -1); return PortalTarget( visible: _showReactionsPopup, @@ -117,17 +114,13 @@ class _DesktopReactionsBuilderState extends State { runSpacing: 4, children: [ ...reactionsList.map((reaction) { - final reactionIcon = reactionIcons.firstWhereOrNull( - (r) => r.type == reaction.type, - ); - return _BottomReaction( currentUser: currentUser, reaction: reaction, message: widget.message, borderSide: widget.borderSide, messageTheme: widget.messageTheme, - reactionIcon: reactionIcon, + resolver: resolver, streamChatTheme: streamChatTheme, ); }).toList(), @@ -154,7 +147,7 @@ class _BottomReaction extends StatelessWidget { required this.message, required this.borderSide, required this.messageTheme, - required this.reactionIcon, + required this.resolver, required this.streamChatTheme, }); @@ -163,7 +156,7 @@ class _BottomReaction extends StatelessWidget { final Message message; final BorderSide? borderSide; final StreamMessageThemeData? messageTheme; - final StreamReactionIcon? reactionIcon; + final ReactionIconResolver resolver; final StreamChatThemeData streamChatTheme; @override @@ -177,16 +170,18 @@ class _BottomReaction extends StatelessWidget { onTap: () { if (reaction.userId == userId) { StreamChannel.of(context).channel.deleteReaction( - message, - reaction, - ); - } else if (reactionIcon != null) { + message, + reaction, + ); + } else { StreamChannel.of(context).channel.sendReaction( - message, - reactionIcon!.toReaction(), - enforceUnique: - StreamChatConfiguration.of(context).enforceUniqueReactions, - ); + message, + Reaction( + type: reaction.type, + emojiCode: resolver.emojiCode(reaction.type), + ), + enforceUnique: StreamChatConfiguration.of(context).enforceUniqueReactions, + ); } }, child: Card( @@ -195,7 +190,8 @@ class _BottomReaction extends StatelessWidget { // This is done to avoid shadow when background color is transparent. elevation: backgroundColor == Colors.transparent ? 0 : null, shape: RoundedRectangleBorder( - side: borderSide ?? + side: + borderSide ?? BorderSide( color: messageTheme?.reactionsBorderColor ?? Colors.transparent, ), @@ -211,18 +207,7 @@ class _BottomReaction extends StatelessWidget { constraints: BoxConstraints.tight( const Size.square(14), ), - child: reactionIcon?.builder( - context, - reaction.user?.id == userId, - 14, - ) ?? - Icon( - Icons.help_outline_rounded, - size: 14, - color: reaction.user?.id == userId - ? streamChatTheme.colorTheme.accentPrimary - : streamChatTheme.colorTheme.textLowEmphasis, - ), + child: resolver.resolve(context, reaction.type), ), const SizedBox(width: 4), Text( diff --git a/packages/stream_chat_flutter/lib/src/reactions/detail/reaction_detail_sheet.dart b/packages/stream_chat_flutter/lib/src/reactions/detail/reaction_detail_sheet.dart new file mode 100644 index 0000000000..1d3f5595b0 --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/reactions/detail/reaction_detail_sheet.dart @@ -0,0 +1,228 @@ +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/src/components/avatar/stream_user_avatar.dart'; +import 'package:stream_chat_flutter/src/message_action/message_action.dart'; +import 'package:stream_chat_flutter/src/scroll_view/reaction_scroll_view/stream_reaction_list_view.dart'; +import 'package:stream_chat_flutter/src/stream_chat.dart'; +import 'package:stream_chat_flutter/src/stream_chat_configuration.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; + +/// A bottom sheet that displays detailed reaction information for a message. +/// +/// Shows the total reaction count, emoji filter chips for each reaction type, +/// and a paginated scrollable list of users who reacted. +/// +/// Reactions are fetched from the server using [StreamReactionListController], +/// supporting cursor-based pagination for large reaction lists. +/// +/// Use [ReactionDetailSheet.show] to display the sheet. +class ReactionDetailSheet extends StatefulWidget { + /// Creates a reaction detail sheet. + /// + /// This constructor is private. Use [ReactionDetailSheet.show] to display + /// the sheet as a modal bottom sheet. + const ReactionDetailSheet._({ + required this.scrollController, + required this.message, + this.initialReactionType, + }); + + /// The message whose reactions are displayed. + final Message message; + + /// Scroll controller provided by [DraggableScrollableSheet]. + final ScrollController scrollController; + + /// The reaction type to pre-select when the sheet opens. + /// + /// When non-null, the sheet opens with this reaction type already filtered + /// and the corresponding chip scrolled into view. + final String? initialReactionType; + + /// Shows the reaction detail sheet as a modal bottom sheet. + /// + /// Returns a [SelectReaction] if the user selects a reaction, or `null` + /// if the sheet is dismissed without any selection. + static Future show({ + required BuildContext context, + required Message message, + String? initialReactionType, + }) { + final radius = context.streamRadius; + final colorScheme = context.streamColorScheme; + + return showModalBottomSheet( + context: context, + useSafeArea: true, + isScrollControlled: true, + showDragHandle: true, + backgroundColor: colorScheme.backgroundElevation1, + shape: RoundedRectangleBorder( + borderRadius: BorderRadiusDirectional.only( + topStart: radius.xl, + topEnd: radius.xl, + ), + ), + builder: (context) => DraggableScrollableSheet( + snap: true, + expand: false, + minChildSize: 0.5, + snapSizes: const [0.5, 1], + builder: (_, scrollController) => ReactionDetailSheet._( + scrollController: scrollController, + message: message, + initialReactionType: initialReactionType, + ), + ), + ); + } + + @override + State createState() => _ReactionDetailSheetState(); +} + +class _ReactionDetailSheetState extends State { + late StreamReactionListController _controller; + late String? _currentReactionType = widget.initialReactionType; + + @override + void initState() { + super.initState(); + _initializeController(); + } + + @override + void didUpdateWidget(covariant ReactionDetailSheet oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.message.id != widget.message.id) { + _controller.dispose(); // Dispose the old controller. + _initializeController(); // Initialize a new controller. + } + } + + void _initializeController() { + _controller = .new( + client: StreamChat.of(context).client, + messageId: widget.message.id, + sort: const [.desc(ReactionSortKey.createdAt)], + filter: switch (_currentReactionType) { + final type? => .equal('type', type), + _ => null, + }, + ); + } + + void _onReactionTypeSelected(String? type) { + if (type == _currentReactionType) return; + setState(() => _currentReactionType = type); + + final updatedFilter = switch (type) { + final type? => Filter.equal('type', type), + _ => null, + }; + + _controller.filter = updatedFilter; + _controller.doInitialLoad(); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final spacing = context.streamSpacing; + final textTheme = context.streamTextTheme; + + final config = StreamChatConfiguration.of(context); + final resolver = config.reactionIconResolver; + + final ownReactions = [...?widget.message.ownReactions]; + final ownReactionsMap = {for (final it in ownReactions) it.type: it}; + final reactionGroups = widget.message.reactionGroups ?? {}; + + final currentUserId = StreamChatCore.of(context).currentUser?.id; + + final visibleCount = switch (_currentReactionType) { + final type? => reactionGroups[type]?.count ?? 0, + _ => reactionGroups.values.fold(0, (sum, g) => sum + g.count), + }; + + return Column( + mainAxisSize: .min, + crossAxisAlignment: .stretch, + children: [ + Padding( + padding: .symmetric(horizontal: spacing.sm), + child: Text( + switch (visibleCount) { + 1 => '1 Reaction', + _ => '$visibleCount Reactions', + }, + textAlign: .center, + style: textTheme.headingSm, + ), + ), + SizedBox(height: spacing.sm), + StreamEmojiChipBar( + selected: _currentReactionType, + onSelected: _onReactionTypeSelected, + items: [ + for (final MapEntry(:key, :value) in reactionGroups.entries) + StreamEmojiChipItem( + value: key, + emoji: resolver.resolve(context, key), + count: value.count, + ), + ], + leading: StreamEmojiChip.addEmoji( + onPressed: () async { + final selectedReactions = ownReactionsMap.keys.toSet(); + final emoji = await StreamEmojiPickerSheet.show( + context: context, + selectedReactions: selectedReactions, + ); + + if (!context.mounted) return; + if (emoji == null) return Navigator.of(context).pop(); + + final reaction = Reaction(type: emoji.shortName, emojiCode: emoji.emoji); + return Navigator.of(context).pop(SelectReaction(message: widget.message, reaction: reaction)); + }, + ), + ), + SizedBox(height: spacing.md), + Expanded( + child: StreamReactionListView( + controller: _controller, + scrollController: widget.scrollController, + padding: .symmetric(horizontal: spacing.xxs), + itemBuilder: (context, reactions, index) { + final reaction = reactions[index]; + final user = reaction.user; + if (user == null) return const SizedBox.shrink(); + + final isOwnReaction = currentUserId != null && reaction.userId == currentUserId; + + return StreamListTile( + leading: StreamUserAvatar(size: .md, user: user, showOnlineIndicator: false), + title: Text(user.name), + subtitle: isOwnReaction ? const Text('Tap to remove') : null, + trailing: StreamEmoji(size: .md, emoji: resolver.resolve(context, reaction.type)), + onTap: switch (isOwnReaction) { + true => () { + final action = SelectReaction(message: widget.message, reaction: reaction); + return Navigator.of(context).pop(action); + }, + _ => null, + }, + ); + }, + ), + ), + ], + ); + } +} diff --git a/packages/stream_chat_flutter/lib/src/reactions/indicator/reaction_indicator.dart b/packages/stream_chat_flutter/lib/src/reactions/indicator/reaction_indicator.dart deleted file mode 100644 index 3df7d1818c..0000000000 --- a/packages/stream_chat_flutter/lib/src/reactions/indicator/reaction_indicator.dart +++ /dev/null @@ -1,167 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; - -/// {@template reactionIndicatorBuilder} -/// Signature for a function that builds a custom reaction indicator widget. -/// -/// This allows users to customize how reactions are displayed on messages, -/// including showing reaction counts alongside emojis. -/// -/// Parameters: -/// - [context]: The build context. -/// - [message]: The message containing the reactions to display. -/// - [onTap]: An optional callback triggered when the reaction indicator -/// is tapped. -/// {@endtemplate} -typedef ReactionIndicatorBuilder = Widget Function( - BuildContext context, - Message message, - VoidCallback? onTap, -); - -/// {@template streamReactionIndicator} -/// A widget that displays a horizontal list of reaction icons that users have -/// reacted with on a message. -/// -/// This widget is typically used to show the reactions on a message in a -/// compact way, allowing users to see which reactions have been added -/// to a message without opening a full user reactions view. -/// {@endtemplate} -class StreamReactionIndicator extends StatelessWidget { - /// {@macro streamReactionIndicator} - const StreamReactionIndicator({ - super.key, - this.onTap, - required this.message, - required this.reactionIcons, - this.reactionIconBuilder, - this.backgroundColor, - this.padding = const EdgeInsets.all(8), - this.scrollable = true, - this.borderRadius = const BorderRadius.all(Radius.circular(26)), - this.reactionSorting = ReactionSorting.byFirstReactionAt, - }); - - /// Creates a [StreamReactionIndicator] using the default reaction icons - /// provided by the [StreamChatConfiguration]. - /// - /// This is the recommended way to create a reaction indicator - /// as it ensures that the icons are consistent with the rest of the app. - /// - /// The [onTap] callback is optional and can be used to handle - /// when the reaction indicator is tapped. - factory StreamReactionIndicator.builder( - BuildContext context, - Message message, - VoidCallback? onTap, - ) { - final config = StreamChatConfiguration.of(context); - final reactionIcons = config.reactionIcons; - - final currentUser = StreamChat.maybeOf(context)?.currentUser; - final isMyMessage = message.user?.id == currentUser?.id; - - final theme = StreamChatTheme.of(context); - final messageTheme = theme.getMessageTheme(reverse: isMyMessage); - - return StreamReactionIndicator( - onTap: onTap, - message: message, - reactionIcons: reactionIcons, - backgroundColor: messageTheme.reactionsBackgroundColor, - ); - } - - /// Callback triggered when the reaction indicator is tapped. - final VoidCallback? onTap; - - /// Message to attach the reaction to. - final Message message; - - /// The list of available reaction icons. - final List reactionIcons; - - /// Optional custom builder for reaction indicator icons. - final ReactionIndicatorIconBuilder? reactionIconBuilder; - - /// Background color for the reaction indicator. - final Color? backgroundColor; - - /// Padding around the reaction indicator. - /// - /// Defaults to `EdgeInsets.all(8)`. - final EdgeInsets padding; - - /// Whether the reaction indicator should be scrollable. - /// - /// Defaults to `true`. - final bool scrollable; - - /// Border radius for the reaction indicator. - /// - /// Defaults to a circular border with a radius of 26. - final BorderRadius? borderRadius; - - /// Sorting strategy for the reaction. - /// - /// Defaults to sorting by the first reaction at. - final Comparator reactionSorting; - - @override - Widget build(BuildContext context) { - final theme = StreamChatTheme.of(context); - - final ownReactions = {...?message.ownReactions?.map((it) => it.type)}; - final reactionIcons = {for (final it in this.reactionIcons) it.type: it}; - - final sortedReactionGroups = message.reactionGroups?.entries - .sortedByCompare((it) => it.value, reactionSorting); - - final indicatorIcons = sortedReactionGroups?.map( - (group) { - final reactionType = group.key; - final reactionIcon = switch (reactionIcons[reactionType]) { - final icon? => icon, - _ => const StreamReactionIcon.unknown(), - }; - - return ReactionIndicatorIcon( - type: reactionType, - builder: reactionIcon.builder, - isSelected: ownReactions.contains(reactionType), - ); - }, - ); - - final reactionIndicator = ReactionIndicatorIconList( - iconBuilder: reactionIconBuilder, - indicatorIcons: [...?indicatorIcons], - ); - - final isSingleIndicatorIcon = indicatorIcons?.length == 1; - final extraPadding = switch (isSingleIndicatorIcon) { - true => EdgeInsets.zero, - false => const EdgeInsets.symmetric(horizontal: 4), - }; - - return Material( - borderRadius: borderRadius, - clipBehavior: Clip.antiAlias, - color: backgroundColor ?? theme.colorTheme.barsBg, - child: InkWell( - onTap: onTap, - child: Padding( - padding: padding.add(extraPadding), - child: switch (scrollable) { - false => reactionIndicator, - true => SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: reactionIndicator, - ), - }, - ), - ), - ); - } -} diff --git a/packages/stream_chat_flutter/lib/src/reactions/indicator/reaction_indicator_bubble_overlay.dart b/packages/stream_chat_flutter/lib/src/reactions/indicator/reaction_indicator_bubble_overlay.dart deleted file mode 100644 index 5e7e02fb21..0000000000 --- a/packages/stream_chat_flutter/lib/src/reactions/indicator/reaction_indicator_bubble_overlay.dart +++ /dev/null @@ -1,74 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/src/reactions/indicator/reaction_indicator.dart'; -import 'package:stream_chat_flutter/src/reactions/reaction_bubble_overlay.dart'; -import 'package:stream_chat_flutter/src/theme/stream_chat_theme.dart'; -import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; - -/// {@template reactionIndicatorBubbleOverlay} -/// A widget that displays a reaction indicator bubble overlay attached to a -/// [child] widget. Typically used to show the reactions for a [Message]. -/// -/// It positions the reaction indicator relative to the provided [child] widget, -/// using the given [anchorOffset] and [childSizeDelta] for fine-tuned placement -/// {@endtemplate} -class ReactionIndicatorBubbleOverlay extends StatelessWidget { - /// {@macro reactionIndicatorBubbleOverlay} - const ReactionIndicatorBubbleOverlay({ - super.key, - this.onTap, - required this.message, - required this.child, - this.visible = true, - this.reverse = false, - this.anchorOffset = Offset.zero, - this.childSizeDelta = Offset.zero, - this.reactionIndicatorBuilder = StreamReactionIndicator.builder, - }); - - /// Whether the overlay should be visible. - final bool visible; - - /// Whether to reverse the alignment of the overlay. - final bool reverse; - - /// The widget to which the overlay is anchored. - final Widget child; - - /// The message to display reactions for. - final Message message; - - /// Callback triggered when the reaction indicator is tapped. - final VoidCallback? onTap; - - /// The offset to apply to the anchor position. - final Offset anchorOffset; - - /// The additional size delta to apply to the child widget for positioning. - final Offset childSizeDelta; - - /// Builder for the reaction indicator widget. - final ReactionIndicatorBuilder reactionIndicatorBuilder; - - @override - Widget build(BuildContext context) { - final theme = StreamChatTheme.of(context); - final messageTheme = theme.getMessageTheme(reverse: reverse); - - return ReactionBubbleOverlay( - visible: visible, - childSizeDelta: childSizeDelta, - config: ReactionBubbleConfig( - fillColor: messageTheme.reactionsBackgroundColor, - borderColor: messageTheme.reactionsBorderColor, - maskColor: messageTheme.reactionsMaskColor, - ), - anchor: ReactionBubbleAnchor( - offset: anchorOffset, - follower: AlignmentDirectional.bottomCenter, - target: AlignmentDirectional(reverse ? -1 : 1, -1), - ), - reaction: reactionIndicatorBuilder.call(context, message, onTap), - child: child, - ); - } -} diff --git a/packages/stream_chat_flutter/lib/src/reactions/indicator/reaction_indicator_icon_list.dart b/packages/stream_chat_flutter/lib/src/reactions/indicator/reaction_indicator_icon_list.dart deleted file mode 100644 index eff65cf6f8..0000000000 --- a/packages/stream_chat_flutter/lib/src/reactions/indicator/reaction_indicator_icon_list.dart +++ /dev/null @@ -1,91 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/src/misc/reaction_icon.dart'; - -/// {@template reactionIndicatorIconBuilder} -/// Function signature for building a custom reaction icon widget. -/// -/// This is used to customize how each reaction icon is displayed in the -/// [ReactionIndicatorIconList]. -/// -/// Parameters: -/// - [context]: The build context. -/// - [icon]: The reaction icon data containing type and selection state. -/// {@endtemplate} -typedef ReactionIndicatorIconBuilder = Widget Function( - BuildContext context, - ReactionIndicatorIcon icon, -); - -/// {@template reactionIndicatorIconList} -/// A widget that displays a list of reactionIcons that users have reacted with -/// on a message. -/// -/// See also: -/// - [StreamReactionIndicator], which is a higher-level widget that uses this -/// widget to display a reaction indicator in a modal or inline. -/// {@endtemplate} -class ReactionIndicatorIconList extends StatelessWidget { - /// {@macro reactionIndicatorIconList} - const ReactionIndicatorIconList({ - super.key, - required this.indicatorIcons, - ReactionIndicatorIconBuilder? iconBuilder, - }) : iconBuilder = iconBuilder ?? _defaultIconBuilder; - - /// The list of available reaction indicator icons. - final List indicatorIcons; - - /// The builder used to create the reaction indicator icons. - final ReactionIndicatorIconBuilder iconBuilder; - - static Widget _defaultIconBuilder( - BuildContext context, - ReactionIndicatorIcon icon, - ) { - return icon.build(context); - } - - @override - Widget build(BuildContext context) { - return Wrap( - spacing: 8, - runSpacing: 4, - runAlignment: WrapAlignment.center, - alignment: WrapAlignment.spaceAround, - crossAxisAlignment: WrapCrossAlignment.center, - children: [...indicatorIcons.map((icon) => iconBuilder(context, icon))], - ); - } -} - -/// {@template reactionIndicatorIcon} -/// A data class that represents a reaction icon within the reaction indicator. -/// -/// This class holds information about a specific reaction, such as its type, -/// whether it's currently selected by the user, and a builder function -/// to construct its visual representation. -/// {@endtemplate} -class ReactionIndicatorIcon { - /// {@macro reactionIndicatorIcon} - const ReactionIndicatorIcon({ - required this.type, - this.isSelected = false, - this.iconSize = 16, - required ReactionIconBuilder builder, - }) : _builder = builder; - - /// The unique identifier for the reaction type (e.g., "like", "love"). - final String type; - - /// A boolean indicating whether this reaction is currently selected by the - /// user. - final bool isSelected; - - /// The size of the reaction icon. - final double iconSize; - - /// Builds the actual widget for this reaction icon using the provided - /// [context], selection state, and icon size. - Widget build(BuildContext context) => _builder(context, isSelected, iconSize); - final ReactionIconBuilder _builder; -} diff --git a/packages/stream_chat_flutter/lib/src/reactions/picker/reaction_picker.dart b/packages/stream_chat_flutter/lib/src/reactions/picker/reaction_picker.dart index 3b3ece7805..69146611bf 100644 --- a/packages/stream_chat_flutter/lib/src/reactions/picker/reaction_picker.dart +++ b/packages/stream_chat_flutter/lib/src/reactions/picker/reaction_picker.dart @@ -1,172 +1,89 @@ -import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:flutter/widgets.dart'; +import 'package:stream_chat_flutter/src/stream_chat_configuration.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; /// {@template onReactionPicked} /// Callback called when a reaction is picked. /// {@endtemplate} typedef OnReactionPicked = ValueSetter; -/// {@template reactionPickerBuilder} -/// Function signature for building a custom reaction picker widget. +/// {@template streamMessageReactionPicker} +/// A chat-specific reaction picker that bridges [StreamReactionPicker] with +/// chat domain models. /// -/// Use this to provide a custom reaction picker in [StreamMessageActionsModal] -/// or [StreamMessageReactionsModal]. +/// Resolves reaction icons via [ReactionIconResolver], tracks the current +/// user's own reactions on the [Message], and wires the "add reaction" button +/// to [StreamEmojiPickerSheet]. /// -/// Parameters: -/// - [context]: The build context. -/// - [message]: The message to show reactions for. -/// - [onReactionPicked]: Callback when a reaction is picked. -/// {@endtemplate} -typedef ReactionPickerBuilder = Widget Function( - BuildContext context, - Message message, - OnReactionPicked? onReactionPicked, -); - -/// {@template streamReactionPicker} -/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/packages/stream_chat_flutter/screenshots/reaction_picker.png) -/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/packages/stream_chat_flutter/screenshots/reaction_picker_paint.png) +/// Visual customisation is controlled through [StreamReactionPickerTheme] in +/// the widget tree. /// -/// A widget that displays a horizontal list of reaction icons that users can -/// select to react to a message. +/// See also: /// -/// The reaction picker can be configured with custom reaction icons, padding, -/// border radius, and can be made scrollable or static depending on the -/// specific needs. +/// * [StreamReactionPicker], the domain-agnostic core picker. +/// * [ReactionIconResolver], which maps reaction types to emoji widgets. +/// * [StreamReactionPickerTheme], for customising the picker appearance. /// {@endtemplate} -class StreamReactionPicker extends StatelessWidget { - /// {@macro streamReactionPicker} - const StreamReactionPicker({ +class StreamMessageReactionPicker extends StatelessWidget { + /// {@macro streamMessageReactionPicker} + const StreamMessageReactionPicker({ super.key, - this.onReactionPicked, required this.message, - required this.reactionIcons, - this.reactionIconBuilder, - this.backgroundColor, - this.padding = const EdgeInsets.all(4), - this.scrollable = true, - this.borderRadius = const BorderRadius.all(Radius.circular(24)), + this.onReactionPicked, }); - /// Creates a [StreamReactionPicker] using the default reaction icons - /// provided by the [StreamChatConfiguration]. - /// - /// This is the recommended way to create a reaction picker - /// as it ensures that the icons are consistent with the rest of the app. - /// - /// The [onReactionPicked] callback is optional and can be used to handle - /// the reaction selection. - factory StreamReactionPicker.builder( - BuildContext context, - Message message, - OnReactionPicked? onReactionPicked, - ) { - final config = StreamChatConfiguration.of(context); - final reactionIcons = config.reactionIcons; - - final platform = Theme.of(context).platform; - return switch (platform) { - TargetPlatform.iOS || TargetPlatform.android => StreamReactionPicker( - message: message, - reactionIcons: reactionIcons, - onReactionPicked: onReactionPicked, - ), - _ => StreamReactionPicker( - message: message, - scrollable: false, - borderRadius: BorderRadius.zero, - reactionIcons: reactionIcons, - onReactionPicked: onReactionPicked, - ), - }; - } - - /// Message to attach the reaction to. + /// The message to attach the reaction to. final Message message; - /// List of reaction icons to display. - final List reactionIcons; - - /// {@macro onReactionPressed} + /// {@macro onReactionPicked} final OnReactionPicked? onReactionPicked; - /// Optional custom builder for reaction picker icons. - final ReactionPickerIconBuilder? reactionIconBuilder; - - /// Background color for the reaction picker. - final Color? backgroundColor; - - /// Padding around the reaction picker. - /// - /// Defaults to `EdgeInsets.all(4)`. - final EdgeInsets padding; - - /// Whether the reaction picker should be scrollable. - /// - /// Defaults to `true`. - final bool scrollable; - - /// Border radius for the reaction picker. - /// - /// Defaults to a circular border with a radius of 24. - final BorderRadius? borderRadius; - @override Widget build(BuildContext context) { - final theme = StreamChatTheme.of(context); + final config = StreamChatConfiguration.of(context); + final resolver = config.reactionIconResolver; + final reactionTypes = resolver.defaultReactions; final ownReactions = [...?message.ownReactions]; final ownReactionsMap = {for (final it in ownReactions) it.type: it}; - final indicatorIcons = reactionIcons.map( - (reactionIcon) { - final reactionType = reactionIcon.type; - - return ReactionPickerIcon( - type: reactionType, - builder: reactionIcon.builder, - emojiCode: reactionIcon.emojiCode, + final items = [ + ...reactionTypes.map( + (type) => StreamReactionPickerItem( + key: type, + emoji: resolver.resolve(context, type), // If the reaction is present in ownReactions, it is selected. - isSelected: ownReactionsMap[reactionType] != null, + isSelected: ownReactionsMap[type] != null, + ), + ), + ]; + + void onItemPicked(StreamReactionPickerItem item) { + final reactionEmojiCode = resolver.emojiCode(item.key); + final pickedReaction = switch (ownReactionsMap[item.key]) { + final reaction? => reaction, + _ => Reaction(type: item.key, emojiCode: reactionEmojiCode), + }; + + return onReactionPicked?.call(pickedReaction); + } + + return StreamReactionPicker( + items: items, + onReactionPicked: onItemPicked, + onAddReactionTap: () async { + final selectedReactions = ownReactionsMap.keys.toSet(); + final emoji = await StreamEmojiPickerSheet.show( + context: context, + selectedReactions: selectedReactions, ); - }, - ); - final reactionPicker = ReactionPickerIconList( - iconBuilder: reactionIconBuilder, - reactionIcons: [...indicatorIcons], - onIconPicked: (reactionIcon) { - final reactionType = reactionIcon.type; - final reactionEmojiCode = reactionIcon.emojiCode; - final pickedReaction = switch (ownReactionsMap[reactionType]) { - final reaction? => reaction, - _ => Reaction(type: reactionType, emojiCode: reactionEmojiCode), - }; + if (!context.mounted || emoji == null) return; - return onReactionPicked?.call(pickedReaction); + final reaction = Reaction(type: emoji.shortName, emojiCode: emoji.emoji); + return onReactionPicked?.call(reaction); }, ); - - final isSinglePickerIcon = reactionIcons.length == 1; - final extraPadding = switch (isSinglePickerIcon) { - true => EdgeInsets.zero, - false => const EdgeInsets.symmetric(horizontal: 4), - }; - - return Material( - borderRadius: borderRadius, - clipBehavior: Clip.antiAlias, - color: backgroundColor ?? theme.colorTheme.barsBg, - child: Padding( - padding: padding.add(extraPadding), - child: switch (scrollable) { - false => reactionPicker, - true => SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: reactionPicker, - ), - }, - ), - ); } } diff --git a/packages/stream_chat_flutter/lib/src/reactions/picker/reaction_picker_bubble_overlay.dart b/packages/stream_chat_flutter/lib/src/reactions/picker/reaction_picker_bubble_overlay.dart deleted file mode 100644 index d49fee015d..0000000000 --- a/packages/stream_chat_flutter/lib/src/reactions/picker/reaction_picker_bubble_overlay.dart +++ /dev/null @@ -1,74 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/src/reactions/picker/reaction_picker.dart'; -import 'package:stream_chat_flutter/src/reactions/reaction_bubble_overlay.dart'; -import 'package:stream_chat_flutter/src/theme/stream_chat_theme.dart'; -import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; - -/// {@template reactionPickerBubbleOverlay} -/// A widget that displays a reaction picker bubble overlay attached to a -/// [child] widget. Typically used with the [MessageWidget] as the child. -/// -/// It positions the reaction picker relative to the provided [child] widget, -/// using the given [anchorOffset] and [childSizeDelta] for fine-tuned placement -/// {@endtemplate} -class ReactionPickerBubbleOverlay extends StatelessWidget { - /// {@macro reactionPickerBubbleOverlay} - const ReactionPickerBubbleOverlay({ - super.key, - required this.message, - required this.child, - this.onReactionPicked, - this.visible = true, - this.reverse = false, - this.anchorOffset = Offset.zero, - this.childSizeDelta = Offset.zero, - this.reactionPickerBuilder = StreamReactionPicker.builder, - }); - - /// Whether the overlay should be visible. - final bool visible; - - /// Whether to reverse the alignment of the overlay. - final bool reverse; - - /// The widget to which the overlay is anchored. - final Widget child; - - /// The message to attach the reaction to. - final Message message; - - /// Callback triggered when a reaction is picked. - final OnReactionPicked? onReactionPicked; - - /// Builder for the reaction picker widget. - final ReactionPickerBuilder reactionPickerBuilder; - - /// The offset to apply to the anchor position. - final Offset anchorOffset; - - /// The additional size delta to apply to the child widget for positioning. - final Offset childSizeDelta; - - @override - Widget build(BuildContext context) { - final theme = StreamChatTheme.of(context); - final colorTheme = theme.colorTheme; - - return ReactionBubbleOverlay( - visible: visible, - childSizeDelta: childSizeDelta, - config: ReactionBubbleConfig( - fillColor: colorTheme.barsBg, - maskColor: Colors.transparent, - borderColor: Colors.transparent, - ), - anchor: ReactionBubbleAnchor( - offset: anchorOffset, - follower: AlignmentDirectional.bottomCenter, - target: AlignmentDirectional(reverse ? -1 : 1, -1), - ), - reaction: reactionPickerBuilder.call(context, message, onReactionPicked), - child: child, - ); - } -} diff --git a/packages/stream_chat_flutter/lib/src/reactions/picker/reaction_picker_icon_list.dart b/packages/stream_chat_flutter/lib/src/reactions/picker/reaction_picker_icon_list.dart deleted file mode 100644 index 9a7bfc7f6e..0000000000 --- a/packages/stream_chat_flutter/lib/src/reactions/picker/reaction_picker_icon_list.dart +++ /dev/null @@ -1,265 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:ezanimation/ezanimation.dart'; -import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/src/misc/reaction_icon.dart'; - -/// {@template onReactionIconPicked} -/// Callback called when a reaction icon is pressed. -/// {@endtemplate} -typedef OnReactionIconPicked = ValueSetter; - -/// {@template onReactionPickerIconPressed} -/// Callback called when a reaction picker icon is pressed. -/// {@endtemplate} -typedef OnReactionPickerIconPressed = ValueSetter; - -/// {@template reactionPickerIconBuilder} -/// Function signature for building a custom reaction icon widget. -/// -/// This is used to customize how each reaction icon is displayed in the -/// [ReactionPickerIconList]. -/// -/// Parameters: -/// - [context]: The build context. -/// - [icon]: The reaction icon data containing type and selection state. -/// - [onPressed]: Callback when the reaction icon is pressed. -/// {@endtemplate} -typedef ReactionPickerIconBuilder = Widget Function( - BuildContext context, - ReactionPickerIcon icon, - VoidCallback? onPressed, -); - -/// {@template reactionPickerIconList} -/// A widget that displays a list of reactionIcons that can be picked by a user. -/// -/// This widget shows a row of reaction icons with animated entry. When a user -/// taps on a reaction icon, the [onIconPicked] callback is invoked with the -/// selected reaction. -/// -/// The reactions displayed are configured via [reactionIcons]. -/// -/// See also: -/// - [StreamReactionPicker], which is a higher-level widget that uses this -/// widget to display a reaction picker in a modal or inline. -/// {@endtemplate} -class ReactionPickerIconList extends StatefulWidget { - /// {@macro reactionPickerIconList} - const ReactionPickerIconList({ - super.key, - this.onIconPicked, - required this.reactionIcons, - ReactionPickerIconBuilder? iconBuilder, - }) : iconBuilder = iconBuilder ?? _defaultIconBuilder; - - /// The list of available reaction picker icons. - final List reactionIcons; - - /// The builder used to create the reaction picker icons. - final ReactionPickerIconBuilder iconBuilder; - - /// {@macro onReactionIconPicked} - final OnReactionIconPicked? onIconPicked; - - static Widget _defaultIconBuilder( - BuildContext context, - ReactionPickerIcon icon, - VoidCallback? onPressed, - ) { - return ReactionIconButton( - icon: icon, - onPressed: onPressed, - ); - } - - @override - State createState() => _ReactionPickerIconListState(); -} - -class _ReactionPickerIconListState extends State { - List _iconAnimations = []; - - void _triggerAnimations() async { - for (final animation in _iconAnimations) { - if (mounted) animation.start(); - // Add a small delay between the start of each animation. - await Future.delayed(const Duration(milliseconds: 100)); - } - } - - void _dismissAnimations() { - for (final animation in _iconAnimations) { - animation.stop(); - } - } - - void _disposeAnimations() { - for (final animation in _iconAnimations) { - animation.dispose(); - } - } - - @override - void initState() { - super.initState(); - _iconAnimations = List.generate( - widget.reactionIcons.length, - (index) => EzAnimation.tween( - Tween(begin: 0.0, end: 1.0), - kThemeAnimationDuration, - curve: Curves.easeInOutBack, - ), - ); - - // Trigger animations at the end of the frame to avoid jank. - WidgetsBinding.instance.endOfFrame.then((_) => _triggerAnimations()); - } - - @override - void didUpdateWidget(covariant ReactionPickerIconList oldWidget) { - super.didUpdateWidget(oldWidget); - if (oldWidget.reactionIcons.length != widget.reactionIcons.length) { - // Dismiss and dispose old animations. - _dismissAnimations(); - _disposeAnimations(); - - // Initialize new animations. - _iconAnimations = List.generate( - widget.reactionIcons.length, - (index) => EzAnimation.tween( - Tween(begin: 0.0, end: 1.0), - kThemeAnimationDuration, - curve: Curves.easeInOutBack, - ), - ); - - // Trigger animations at the end of the frame to avoid jank. - WidgetsBinding.instance.endOfFrame.then((_) => _triggerAnimations()); - } - } - - @override - void dispose() { - _dismissAnimations(); - _disposeAnimations(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - final child = Wrap( - spacing: 4, - runSpacing: 4, - runAlignment: WrapAlignment.center, - alignment: WrapAlignment.spaceAround, - crossAxisAlignment: WrapCrossAlignment.center, - children: [ - ...widget.reactionIcons.mapIndexed((index, icon) { - final animation = _iconAnimations[index]; - return AnimatedBuilder( - animation: animation, - builder: (context, child) => Transform.scale( - scale: animation.value, - child: child, - ), - child: Builder( - builder: (context) { - final icon = widget.reactionIcons[index]; - final onPressed = switch (widget.onIconPicked) { - final onPicked? => () => onPicked(icon), - _ => null, - }; - - return widget.iconBuilder(context, icon, onPressed); - }, - ), - ); - }), - ], - ); - - return TweenAnimationBuilder( - tween: Tween(begin: 0, end: 1), - curve: Curves.easeOutBack, - duration: const Duration(milliseconds: 335), - builder: (context, scale, child) { - return Transform.scale(scale: scale, child: child); - }, - child: child, - ); - } -} - -/// {@template reactionPickerIcon} -/// A data class that represents a reaction icon within the reaction picker. -/// -/// This class holds information about a specific reaction, such as its type, -/// whether it's currently selected by the user, and a builder function -/// to construct its visual representation. -/// {@endtemplate} -class ReactionPickerIcon { - /// {@macro reactionPickerIcon} - const ReactionPickerIcon({ - required this.type, - this.emojiCode, - this.isSelected = false, - this.iconSize = 24, - required ReactionIconBuilder builder, - }) : _builder = builder; - - /// The unique identifier for the reaction type (e.g., "like", "love"). - final String type; - - /// Optional emoji code for the reaction. - /// - /// Used to display a custom emoji in the notification. - final String? emojiCode; - - /// A boolean indicating whether this reaction is currently selected by the - /// user. - final bool isSelected; - - /// The size of the reaction icon. - final double iconSize; - - /// Builds the actual widget for this reaction icon using the provided - /// [context], selection state, and icon size. - Widget build(BuildContext context) => _builder(context, isSelected, iconSize); - final ReactionIconBuilder _builder; -} - -/// {@template reactionIconButton} -/// A button that displays a reaction icon. -/// -/// This button is used in the reaction picker to display individual reaction -/// options. -/// {@endtemplate} -class ReactionIconButton extends StatelessWidget { - /// {@macro reactionIconButton} - const ReactionIconButton({ - super.key, - required this.icon, - this.onPressed, - }); - - /// The reaction icon to display. - final ReactionPickerIcon icon; - - /// Callback triggered when the reaction picker icon is pressed. - final VoidCallback? onPressed; - - @override - Widget build(BuildContext context) { - return IconButton( - key: Key(icon.type), - iconSize: icon.iconSize, - onPressed: onPressed, - icon: icon.build(context), - padding: const EdgeInsets.all(4), - style: IconButton.styleFrom( - tapTargetSize: MaterialTapTargetSize.shrinkWrap, - minimumSize: Size.square(icon.iconSize), - ), - ); - } -} diff --git a/packages/stream_chat_flutter/lib/src/reactions/reaction_bubble.dart b/packages/stream_chat_flutter/lib/src/reactions/reaction_bubble.dart deleted file mode 100644 index 88386c9d09..0000000000 --- a/packages/stream_chat_flutter/lib/src/reactions/reaction_bubble.dart +++ /dev/null @@ -1,308 +0,0 @@ -import 'dart:math'; - -import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; - -/// {@template streamReactionBubble} -/// Creates a reaction bubble that displays over messages. -/// {@endtemplate} -class StreamReactionBubble extends StatelessWidget { - /// {@macro streamReactionBubble} - const StreamReactionBubble({ - super.key, - required this.reactions, - required this.borderColor, - required this.backgroundColor, - required this.maskColor, - this.reverse = false, - this.flipTail = false, - this.highlightOwnReactions = true, - this.tailCirclesSpacing = 0, - }); - - /// Reactions to show - final List reactions; - - /// Border color of bubble - final Color borderColor; - - /// Background color of bubble - final Color backgroundColor; - - /// Mask color - final Color maskColor; - - /// Reverse for other side - final bool reverse; - - /// Reverse tail for other side - final bool flipTail; - - /// Flag for highlighting own reactions - final bool highlightOwnReactions; - - /// Spacing for tail circles - final double tailCirclesSpacing; - - @override - Widget build(BuildContext context) { - final reactionIcons = StreamChatConfiguration.of(context).reactionIcons; - final totalReactions = reactions.length; - final offset = - totalReactions > 1 ? 16.0.mirrorConditionally(flipTail) : 2.0; - return Stack( - alignment: Alignment.center, - children: [ - Transform.translate( - offset: Offset(-offset, 0), - child: Container( - padding: const EdgeInsets.all(2), - decoration: BoxDecoration( - color: maskColor, - borderRadius: const BorderRadius.all(Radius.circular(26)), - ), - child: Container( - padding: EdgeInsets.symmetric( - vertical: 8, - horizontal: totalReactions > 1 ? 12.0 : 8.0, - ), - decoration: BoxDecoration( - color: backgroundColor, - border: Border.all(color: borderColor), - borderRadius: const BorderRadius.all(Radius.circular(24)), - ), - child: Wrap( - spacing: 8, - runSpacing: 4, - runAlignment: WrapAlignment.center, - alignment: WrapAlignment.spaceAround, - crossAxisAlignment: WrapCrossAlignment.center, - children: [ - ...reactions.map( - (reaction) => _buildReaction( - context, - reaction, - reactionIcons, - ), - ) - ], - ), - ), - ), - ), - Positioned( - bottom: 2, - left: reverse ? null : 13, - right: reverse ? 13 : null, - child: _buildReactionsTail(context), - ), - ], - ); - } - - Widget _buildReaction( - BuildContext context, - Reaction reaction, - List reactionIcons, - ) { - final reactionIcon = reactionIcons.firstWhere( - (it) => it.type == reaction.type, - orElse: () => const StreamReactionIcon.unknown(), - ); - - final currentUser = StreamChat.of(context).currentUser; - final isMyReaction = reaction.userId == currentUser?.id; - final isHighlighted = highlightOwnReactions && isMyReaction; - - return reactionIcon.builder(context, isHighlighted, 16); - } - - Widget _buildReactionsTail(BuildContext context) { - final tail = CustomPaint( - painter: ReactionBubblePainter( - backgroundColor, - borderColor, - maskColor, - tailCirclesSpace: tailCirclesSpacing, - flipTail: !flipTail, - numberOfReactions: reactions.length, - ), - ); - return tail; - } -} - -/// Painter widget for a reaction bubble -class ReactionBubblePainter extends CustomPainter { - /// Constructor for creating a [ReactionBubblePainter] - ReactionBubblePainter( - this.color, - this.borderColor, - this.maskColor, { - this.tailCirclesSpace = 0, - this.flipTail = false, - this.numberOfReactions = 0, - }); - - /// Color of bubble - final Color color; - - /// Border color of bubble - final Color borderColor; - - /// Mask color - final Color maskColor; - - /// Tail circle space - final double tailCirclesSpace; - - /// Flip tail - final bool flipTail; - - /// Number of reactions on the page - final int numberOfReactions; - - @override - void paint(Canvas canvas, Size size) { - _drawOvalMask(size, canvas); - - _drawMask(size, canvas); - - _drawOval(size, canvas); - - _drawOvalBorder(size, canvas); - - _drawArc(size, canvas); - - _drawBorder(size, canvas); - } - - void _drawOvalMask(Size size, Canvas canvas) { - final paint = Paint() - ..color = maskColor - ..style = PaintingStyle.fill; - - final path = Path() - ..addOval( - Rect.fromCircle( - center: const Offset(4, 3).mirrorConditionally(flipTail) + - Offset(tailCirclesSpace, tailCirclesSpace) - .mirrorConditionally(flipTail), - radius: 4, - ), - ); - canvas.drawPath(path, paint); - } - - void _drawOvalBorder(Size size, Canvas canvas) { - final paint = Paint() - ..color = borderColor - ..strokeWidth = 1 - ..style = PaintingStyle.stroke; - - final path = Path() - ..addOval( - Rect.fromCircle( - center: const Offset(4, 3).mirrorConditionally(flipTail) + - Offset(tailCirclesSpace, tailCirclesSpace) - .mirrorConditionally(flipTail), - radius: 2, - ), - ); - canvas.drawPath(path, paint); - } - - void _drawOval(Size size, Canvas canvas) { - final paint = Paint() - ..color = color - ..strokeWidth = 1; - - final path = Path() - ..addOval(Rect.fromCircle( - center: const Offset(4, 3).mirrorConditionally(flipTail) + - Offset(tailCirclesSpace, tailCirclesSpace) - .mirrorConditionally(flipTail), - radius: 2, - )); - canvas.drawPath(path, paint); - } - - void _drawBorder(Size size, Canvas canvas) { - final paint = Paint() - ..color = borderColor - ..strokeWidth = 1 - ..style = PaintingStyle.stroke; - - const dy = -2.2; - final startAngle = flipTail ? -0.1 : 1.1; - final sweepAngle = flipTail ? -1.2 : (numberOfReactions > 1 ? 1.2 : 0.9); - final path = Path() - ..addArc( - Rect.fromCircle( - center: const Offset(1, dy).mirrorConditionally(flipTail), - radius: 4, - ), - -pi * startAngle, - -pi / sweepAngle, - ); - canvas.drawPath(path, paint); - } - - void _drawArc(Size size, Canvas canvas) { - final paint = Paint() - ..color = color - ..strokeWidth = 1; - - const dy = -2.2; - final startAngle = flipTail ? -0.0 : 1.0; - final sweepAngle = flipTail ? -1.3 : 1.3; - final path = Path() - ..addArc( - Rect.fromCircle( - center: const Offset(1, dy).mirrorConditionally(flipTail), - radius: 4, - ), - -pi * startAngle, - -pi * sweepAngle, - ); - canvas.drawPath(path, paint); - } - - void _drawMask(Size size, Canvas canvas) { - final paint = Paint() - ..color = maskColor - ..strokeWidth = 1 - ..style = PaintingStyle.fill; - - const dy = -2.2; - final startAngle = flipTail ? -0.1 : 1.1; - final sweepAngle = flipTail ? -1.2 : 1.2; - final path = Path() - ..addArc( - Rect.fromCircle( - center: const Offset(1, dy).mirrorConditionally(flipTail), - radius: 6, - ), - -pi * startAngle, - -pi / sweepAngle, - ); - canvas.drawPath(path, paint); - } - - @override - bool shouldRepaint(CustomPainter oldDelegate) => true; -} - -/// Extension on [Offset] -extension YTransformer on Offset { - /// Flips x coordinate when flip is true - // ignore: avoid_positional_boolean_parameters - Offset mirrorConditionally(bool flip) => Offset(flip ? -dx : dx, dy); -} - -/// Extension on [Offset] -extension IntTransformer on double { - /// Flips x coordinate when flip is true - // ignore: avoid_positional_boolean_parameters - double mirrorConditionally(bool flip) => flip ? -this : this; -} diff --git a/packages/stream_chat_flutter/lib/src/reactions/reaction_bubble_overlay.dart b/packages/stream_chat_flutter/lib/src/reactions/reaction_bubble_overlay.dart deleted file mode 100644 index bd1b50034e..0000000000 --- a/packages/stream_chat_flutter/lib/src/reactions/reaction_bubble_overlay.dart +++ /dev/null @@ -1,373 +0,0 @@ -// ignore_for_file: cascade_invocations - -import 'dart:math' as math; - -import 'package:flutter/material.dart'; -import 'package:flutter_portal/flutter_portal.dart'; -import 'package:stream_chat_flutter/src/misc/size_change_listener.dart'; - -/// Signature for building a custom ReactionBubble widget. -typedef ReactionBubbleBuilder = Widget Function( - BuildContext context, - ReactionBubbleConfig config, - Widget child, -); - -/// Defines the anchor settings for positioning a ReactionBubble relative to a -/// target widget. -class ReactionBubbleAnchor { - /// Creates an anchor with custom alignment and offset. - const ReactionBubbleAnchor({ - this.offset = Offset.zero, - required this.target, - required this.follower, - this.shiftToWithinBound = const AxisFlag(x: true), - }); - - /// Creates an anchor that positions the bubble at the top-end of the - /// target widget. - const ReactionBubbleAnchor.topEnd({ - this.offset = Offset.zero, - this.shiftToWithinBound = const AxisFlag(x: true), - }) : target = AlignmentDirectional.topEnd, - follower = AlignmentDirectional.bottomCenter; - - /// Creates an anchor that positions the bubble at the top-start of the - /// target widget. - const ReactionBubbleAnchor.topStart({ - this.offset = Offset.zero, - this.shiftToWithinBound = const AxisFlag(x: true), - }) : target = AlignmentDirectional.topStart, - follower = AlignmentDirectional.bottomCenter; - - /// Additional offset applied to the bubble position. - final Offset offset; - - /// Target alignment relative to the target widget. - final AlignmentDirectional target; - - /// Alignment of the bubble follower relative to the target alignment. - final AlignmentDirectional follower; - - /// Whether to shift the bubble within the visible screen bounds along each - /// axis if it exceeds the screen size. - final AxisFlag shiftToWithinBound; -} - -/// An overlay widget that displays a reaction bubble near a child widget. -class ReactionBubbleOverlay extends StatefulWidget { - /// Creates a new instance of [ReactionBubbleOverlay]. - const ReactionBubbleOverlay({ - super.key, - this.visible = true, - required this.child, - required this.reaction, - this.childSizeDelta = Offset.zero, - this.builder = _defaultBuilder, - this.config = const ReactionBubbleConfig(), - this.anchor = const ReactionBubbleAnchor.topEnd(), - }); - - /// The target child widget to anchor the reaction bubble to. - final Widget child; - - /// The reaction widget to display inside the bubble. - final Widget reaction; - - /// Whether the reaction bubble is visible. - final bool visible; - - /// Optional adjustment to the child's reported size. - final Offset childSizeDelta; - - /// The configuration used for rendering the reaction bubble. - final ReactionBubbleConfig config; - - /// The anchor configuration to control bubble positioning. - final ReactionBubbleAnchor anchor; - - /// The builder used to create the bubble appearance. - final ReactionBubbleBuilder builder; - - static Widget _defaultBuilder( - BuildContext context, - ReactionBubbleConfig config, - Widget child, - ) { - return RepaintBoundary( - child: CustomPaint( - painter: ReactionBubblePainter(config: config), - child: child, - ), - ); - } - - @override - State createState() => _ReactionBubbleOverlayState(); -} - -class _ReactionBubbleOverlayState extends State { - Size? _childSize; - - /// Calculates the alignment for the bubble tail relative to the bubble rect. - AlignmentGeometry _calculateTailAlignment({ - required Size childSize, - required Rect bubbleRect, - required Size availableSpace, - bool reverse = false, - }) { - final childEdgeX = switch (reverse) { - true => availableSpace.width - childSize.width, - false => childSize.width - }; - - final idealBubbleLeft = childEdgeX - (bubbleRect.width / 2); - final maxLeft = availableSpace.width - bubbleRect.width; - final actualBubbleLeft = idealBubbleLeft.clamp(0, math.max(0, maxLeft)); - final tailOffset = childEdgeX - actualBubbleLeft; - - if (tailOffset == 0) return AlignmentDirectional.bottomCenter; - return AlignmentDirectional((tailOffset * 2 / bubbleRect.width) - 1, 1); - } - - @override - Widget build(BuildContext context) { - final child = SizeChangeListener( - onSizeChanged: (size) => setState(() => _childSize = size), - child: widget.child, - ); - - final childSize = _childSize; - // If the child size is not available or the overlay should not be visible, - // return the child without any overlay. - if (childSize == null || !widget.visible) return child; - - final alignment = widget.anchor; - final direction = Directionality.maybeOf(context); - final targetAlignment = alignment.target.resolve(direction); - final followerAlignment = alignment.follower.resolve(direction); - final availableSpace = MediaQuery.sizeOf(context); - - final reverse = targetAlignment.x < 0; - final config = widget.config.copyWith( - flipTail: reverse, - tailAlignment: (bubbleRect) { - final alignment = _calculateTailAlignment( - reverse: reverse, - bubbleRect: bubbleRect, - availableSpace: availableSpace, - childSize: childSize + widget.childSizeDelta, - ); - - return alignment.resolve(direction); - }, - ); - - return PortalTarget( - anchor: Aligned( - target: targetAlignment, - follower: followerAlignment, - offset: widget.anchor.offset, - shiftToWithinBound: widget.anchor.shiftToWithinBound, - ), - portalFollower: widget.builder(context, config, widget.reaction), - child: child, - ); - } -} - -/// Defines the visual configuration of a ReactionBubble. -class ReactionBubbleConfig { - /// Creates a new instance of [ReactionBubbleConfig] with default values. - const ReactionBubbleConfig({ - this.flipTail = false, - this.fillColor, - this.maskColor, - this.borderColor, - this.maskWidth = 2.0, - this.borderWidth = 1.0, - this.bigTailCircleRadius = 4.0, - this.smallTailCircleRadius = 2.0, - this.tailAlignment = _defaultTailAlignment, - }); - - /// Whether to flip the tail horizontally. - final bool flipTail; - - /// Fill color of the bubble. - final Color? fillColor; - - /// Mask color of the bubble (used for visual masking). - final Color? maskColor; - - /// Border color of the bubble. - final Color? borderColor; - - /// Width of the mask stroke. - final double maskWidth; - - /// Width of the border stroke. - final double borderWidth; - - /// Radius of the larger circle at the bubble tail. - final double bigTailCircleRadius; - - /// Radius of the smaller circle at the bubble tail. - final double smallTailCircleRadius; - - /// Function that defines the alignment of the tail within the bubble rect. - final Alignment Function(Rect) tailAlignment; - - static Alignment _defaultTailAlignment(Rect rect) => Alignment.bottomCenter; - - /// The total height contribution of the bubble tail. - double get tailHeight => bigTailCircleRadius * 2 + smallTailCircleRadius * 2; - - /// Returns a copy of this config with optional overrides. - ReactionBubbleConfig copyWith({ - bool? flipTail, - Color? fillColor, - Color? maskColor, - Color? borderColor, - double? maskWidth, - double? borderWidth, - double? bigTailCircleRadius, - double? smallTailCircleRadius, - Alignment Function(Rect)? tailAlignment, - }) { - return ReactionBubbleConfig( - flipTail: flipTail ?? this.flipTail, - fillColor: fillColor ?? this.fillColor, - maskColor: maskColor ?? this.maskColor, - borderColor: borderColor ?? this.borderColor, - maskWidth: maskWidth ?? this.maskWidth, - borderWidth: borderWidth ?? this.borderWidth, - bigTailCircleRadius: bigTailCircleRadius ?? this.bigTailCircleRadius, - smallTailCircleRadius: - smallTailCircleRadius ?? this.smallTailCircleRadius, - tailAlignment: tailAlignment ?? this.tailAlignment, - ); - } - - @override - bool operator ==(Object other) { - if (identical(this, other)) return true; - return other is ReactionBubbleConfig && - runtimeType == other.runtimeType && - flipTail == other.flipTail && - fillColor == other.fillColor && - maskColor == other.maskColor && - borderColor == other.borderColor && - maskWidth == other.maskWidth && - borderWidth == other.borderWidth && - bigTailCircleRadius == other.bigTailCircleRadius && - smallTailCircleRadius == other.smallTailCircleRadius && - tailAlignment == other.tailAlignment; - } - - @override - int get hashCode => - flipTail.hashCode ^ - fillColor.hashCode ^ - maskColor.hashCode ^ - borderColor.hashCode ^ - maskWidth.hashCode ^ - borderWidth.hashCode ^ - bigTailCircleRadius.hashCode ^ - smallTailCircleRadius.hashCode ^ - tailAlignment.hashCode; -} - -/// A CustomPainter that draws a ReactionBubble based on a ReactionBubbleConfig. -class ReactionBubblePainter extends CustomPainter { - /// Creates a [ReactionBubblePainter] with the specified configuration. - ReactionBubblePainter({ - this.config = const ReactionBubbleConfig(), - }) : _fillPaint = Paint() - ..color = config.fillColor ?? Colors.white - ..style = PaintingStyle.fill, - _maskPaint = Paint() - ..color = config.maskColor ?? Colors.white - ..style = PaintingStyle.fill, - _borderPaint = Paint() - ..color = config.borderColor ?? Colors.black - ..style = PaintingStyle.stroke - ..strokeWidth = config.borderWidth; - - /// Configuration used to style the bubble. - final ReactionBubbleConfig config; - - final Paint _fillPaint; - final Paint _borderPaint; - final Paint _maskPaint; - - @override - void paint(Canvas canvas, Size size) { - final tailHeight = config.tailHeight; - final fullHeight = size.height + tailHeight; - final bubbleHeight = fullHeight - tailHeight; - final bubbleWidth = size.width; - - final bubbleRect = RRect.fromRectAndRadius( - Rect.fromLTRB(0, 0, bubbleWidth, bubbleHeight), - Radius.circular(bubbleHeight / 2), - ); - - final alignment = config.tailAlignment.call(bubbleRect.outerRect); - final bigTailCircleCenter = alignment.withinRect(bubbleRect.tallMiddleRect); - - final bigTailCircleRect = Rect.fromCircle( - center: bigTailCircleCenter, - radius: config.bigTailCircleRadius, - ); - - final smallTailCircleOffset = Offset( - config.flipTail ? bigTailCircleRect.right : bigTailCircleRect.left, - bigTailCircleRect.bottom + config.smallTailCircleRadius, - ); - - final smallTailCircleRect = Rect.fromCircle( - center: smallTailCircleOffset, - radius: config.smallTailCircleRadius, - ); - - final reactionBubbleMaskPath = _buildCombinedPath( - bubbleRect.inflate(config.maskWidth), - bigTailCircleRect.inflate(config.maskWidth), - smallTailCircleRect.inflate(config.maskWidth), - ); - - canvas.drawPath(reactionBubbleMaskPath, _maskPaint); - - final reactionBubblePath = _buildCombinedPath( - bubbleRect, - bigTailCircleRect, - smallTailCircleRect, - ); - - canvas.drawPath(reactionBubblePath, _borderPaint); - canvas.drawPath(reactionBubblePath, _fillPaint); - } - - /// Builds a combined path of the bubble and tail circles. - Path _buildCombinedPath( - RRect bubble, - Rect bigCircle, - Rect smallCircle, - ) { - final bubblePath = Path()..addRRect(bubble); - final bigTailPath = Path()..addOval(bigCircle); - final smallTailPath = Path()..addOval(smallCircle); - - return Path.combine( - PathOperation.union, - Path.combine(PathOperation.union, bubblePath, bigTailPath), - smallTailPath, - ); - } - - @override - bool shouldRepaint(covariant ReactionBubblePainter oldDelegate) { - return true; - } -} diff --git a/packages/stream_chat_flutter/lib/src/reactions/user_reactions.dart b/packages/stream_chat_flutter/lib/src/reactions/user_reactions.dart index 68065e8afc..654ca829e9 100644 --- a/packages/stream_chat_flutter/lib/src/reactions/user_reactions.dart +++ b/packages/stream_chat_flutter/lib/src/reactions/user_reactions.dart @@ -1,13 +1,11 @@ import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/src/avatars/user_avatar.dart'; +import 'package:stream_chat_flutter/src/components/avatar/stream_user_avatar.dart'; import 'package:stream_chat_flutter/src/misc/empty_widget.dart'; -import 'package:stream_chat_flutter/src/misc/reaction_icon.dart'; -import 'package:stream_chat_flutter/src/reactions/indicator/reaction_indicator_icon_list.dart'; -import 'package:stream_chat_flutter/src/reactions/reaction_bubble_overlay.dart'; import 'package:stream_chat_flutter/src/stream_chat_configuration.dart'; import 'package:stream_chat_flutter/src/theme/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/utils/extensions.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; /// {@template streamUserReactions} /// A widget that displays the reactions of a user to a message. @@ -100,13 +98,7 @@ class _UserReactionItem extends StatelessWidget { final theme = StreamChatTheme.of(context); final messageTheme = theme.getMessageTheme(reverse: isCurrentUserReaction); - final config = StreamChatConfiguration.of(context); - final reactionIcons = config.reactionIcons; - - final reactionIcon = reactionIcons.firstWhere( - (it) => it.type == reaction.type, - orElse: () => const StreamReactionIcon.unknown(), - ); + final resolver = StreamChatConfiguration.of(context).reactionIconResolver; return Column( mainAxisSize: MainAxisSize.min, @@ -115,40 +107,39 @@ class _UserReactionItem extends StatelessWidget { Stack( clipBehavior: Clip.none, children: [ - StreamUserAvatar( - onTap: onTap, - user: reactionUser, - showOnlineStatus: false, - borderRadius: BorderRadius.circular(32), - constraints: const BoxConstraints.tightFor(height: 64, width: 64), + GestureDetector( + onTap: switch (onTap) { + final onTap? => () => onTap(reactionUser), + _ => null, + }, + child: StreamUserAvatar( + size: .xl, + user: reactionUser, + showOnlineIndicator: false, + ), ), PositionedDirectional( bottom: 8, end: isCurrentUserReaction ? null : 0, start: isCurrentUserReaction ? 0 : null, - child: IgnorePointer( - child: RepaintBoundary( - child: CustomPaint( - painter: ReactionBubblePainter( - config: ReactionBubbleConfig( - flipTail: isCurrentUserReaction, - fillColor: messageTheme.reactionsBackgroundColor, - borderColor: messageTheme.reactionsBorderColor, - maskColor: messageTheme.reactionsMaskColor, - ), - ), - child: Padding( - padding: const EdgeInsets.all(8), - child: ReactionIndicatorIconList( - indicatorIcons: [ - ReactionIndicatorIcon( - type: reactionIcon.type, - isSelected: isCurrentUserReaction, - builder: reactionIcon.builder, - ), - ], - ), + child: Container( + padding: const EdgeInsets.all(2), + decoration: BoxDecoration( + color: messageTheme.reactionsMaskColor, + borderRadius: const BorderRadius.all(Radius.circular(26)), + ), + child: Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: messageTheme.reactionsBackgroundColor, + border: Border.all( + color: messageTheme.reactionsBorderColor ?? Colors.transparent, ), + borderRadius: const BorderRadius.all(Radius.circular(24)), + ), + child: StreamEmoji( + size: StreamEmojiSize.sm, + emoji: resolver.resolve(context, reaction.type), ), ), ), diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_grid_tile.dart b/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_grid_tile.dart index dc685d09ae..fae5ad9bca 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_grid_tile.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_grid_tile.dart @@ -46,31 +46,23 @@ class StreamChannelGridTile extends StatelessWidget { Widget? footer, GestureTapCallback? onTap, GestureLongPressCallback? onLongPress, - }) => - StreamChannelGridTile( - key: key ?? this.key, - channel: channel ?? this.channel, - footer: footer ?? this.footer, - onTap: onTap ?? this.onTap, - onLongPress: onLongPress ?? this.onLongPress, - child: child ?? this.child, - ); + }) => StreamChannelGridTile( + key: key ?? this.key, + channel: channel ?? this.channel, + footer: footer ?? this.footer, + onTap: onTap ?? this.onTap, + onLongPress: onLongPress ?? this.onLongPress, + child: child ?? this.child, + ); @override Widget build(BuildContext context) { final channelPreviewTheme = StreamChannelPreviewTheme.of(context); - final child = this.child ?? - StreamChannelAvatar( - channel: channel, - borderRadius: BorderRadius.circular(32), - constraints: const BoxConstraints.tightFor( - height: 64, - width: 64, - ), - ); + final child = this.child ?? StreamChannelAvatar(size: .xl, channel: channel); - final footer = this.footer ?? + final footer = + this.footer ?? StreamChannelName( channel: channel, textStyle: channelPreviewTheme.titleStyle, diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_grid_view.dart b/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_grid_view.dart index e391451647..0b970ebb6c 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_grid_view.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_grid_view.dart @@ -7,13 +7,12 @@ import 'package:stream_chat_flutter/src/scroll_view/stream_scroll_view_loading_w import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// Default grid delegate for [StreamChannelGridView]. -const defaultChannelGridViewDelegate = - SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 4); +const defaultChannelGridViewDelegate = SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 4); /// Signature for the item builder that creates the children of the /// [StreamChannelGridView]. -typedef StreamChannelGridViewIndexedWidgetBuilder - = StreamScrollViewIndexedWidgetBuilder; +typedef StreamChannelGridViewIndexedWidgetBuilder = + StreamScrollViewIndexedWidgetBuilder; /// A [GridView] that shows a grid of [User]s, /// it uses [StreamChannelGridTile] as a default item. @@ -351,9 +350,9 @@ class StreamChannelGridView extends StatelessWidget { child: Padding( padding: const EdgeInsets.all(8), child: StreamScrollViewEmptyWidget( - emptyIcon: StreamSvgIcon( + emptyIcon: Icon( + context.streamIcons.bubble3ChatMessage, size: 148, - icon: StreamSvgIcons.message, color: chatThemeData.colorTheme.disabled, ), emptyTitle: Text( @@ -364,8 +363,7 @@ class StreamChannelGridView extends StatelessWidget { ), ); }, - loadMoreErrorBuilder: (context, error) => - StreamScrollViewLoadMoreError.grid( + loadMoreErrorBuilder: (context, error) => StreamScrollViewLoadMoreError.grid( onTap: controller.retry, error: Text( context.translations.loadingChannelsError, diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_item.dart b/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_item.dart new file mode 100644 index 0000000000..8df6f3c196 --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_item.dart @@ -0,0 +1,788 @@ +import 'package:collection/collection.dart'; +import 'package:flutter/material.dart'; +import 'package:rxdart/rxdart.dart'; +import 'package:stream_chat_flutter/src/misc/empty_widget.dart'; +import 'package:stream_chat_flutter/src/misc/timestamp.dart'; +import 'package:stream_chat_flutter/src/utils/date_formatter.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; + +/// A widget that displays a channel preview. +/// +/// This widget is intended to be used as a Tile in [StreamChannelListView]. +/// +/// It shows the last message of the channel, the last message time, the unread +/// message count, the typing indicator, the sending indicator and the channel +/// avatar. +/// +/// Internally uses [StreamListTileContainer] from the core design system for +/// consistent visual presentation. +/// +/// See also: +/// * [StreamChannelAvatar] +/// * [StreamChannelName] +class StreamChannelListItem extends StatelessWidget { + /// Creates a new instance of [StreamChannelListItem] widget. + StreamChannelListItem({ + super.key, + required Channel channel, + GestureTapCallback? onTap, + GestureLongPressCallback? onLongPress, + bool selected = false, + }) : assert( + channel.state != null, + 'Channel ${channel.id} is not initialized', + ), + props = .new( + channel: channel, + onTap: onTap, + onLongPress: onLongPress, + selected: selected, + ); + + /// The properties for the channel list item. + final StreamChannelListItemProps props; + + /// Creates a copy of this tile but with the given fields replaced with + /// the new values. + StreamChannelListItem copyWith({ + Key? key, + Channel? channel, + VoidCallback? onTap, + VoidCallback? onLongPress, + bool? selected, + }) { + return StreamChannelListItem( + key: key ?? this.key, + channel: channel ?? props.channel, + onTap: onTap ?? props.onTap, + onLongPress: onLongPress ?? props.onLongPress, + selected: selected ?? props.selected, + ); + } + + @override + Widget build(BuildContext context) { + final builder = context.chatComponentBuilder(); + return builder?.call(context, props) ?? _DefaultStreamChannelListItem(props: props); + } +} + +/// Properties for configuring a [StreamChannelListItem]. +/// +/// This class holds all the configuration options for a channel list item, +/// allowing them to be passed through the [StreamComponentFactory]. +/// +/// See also: +/// +/// * [StreamChannelListItem], which uses these properties. +/// * [DefaultStreamChannelListItem], the default implementation. +class StreamChannelListItemProps { + /// Creates properties for a channel list item. + const StreamChannelListItemProps({ + required this.channel, + this.leading, + this.title, + this.subtitle, + this.trailing, + this.onTap, + this.onLongPress, + this.sendingIndicatorBuilder, + this.selected = false, + }); + + /// The channel to display. + final Channel channel; + + /// A widget to display as the avatar. + /// + /// Defaults to [StreamChannelAvatar]. + final Widget? leading; + + /// The primary content of the list tile. + /// + /// Defaults to [StreamChannelName]. + final Widget? title; + + /// Additional content displayed below the title. + /// + /// Defaults to [ChannelListTileSubtitle] which shows typing indicators, + /// draft messages, or the last message preview. + final Widget? subtitle; + + /// A widget to display as the timestamp. + /// + /// Defaults to [ChannelLastMessageDate]. + final Widget? trailing; + + /// Called when the user taps this list tile. + final GestureTapCallback? onTap; + + /// Called when the user long-presses on this list tile. + final GestureLongPressCallback? onLongPress; + + /// The widget builder for the sending indicator. + /// + /// `Message` is the last message in the channel. Use it to determine the + /// status using [Message.state]. + final Widget Function(BuildContext, Message)? sendingIndicatorBuilder; + + /// True if the tile is in a selected state. + final bool selected; +} + +class _DefaultStreamChannelListItem extends StatelessWidget { + const _DefaultStreamChannelListItem({ + required this.props, + }); + + final StreamChannelListItemProps props; + + @override + Widget build(BuildContext context) { + final channelState = props.channel.state!; + final textTheme = context.streamTextTheme; + + final avatar = props.leading ?? StreamChannelAvatar(channel: props.channel, size: StreamAvatarGroupSize.xl); + final titleWidget = + props.title ?? + StreamChannelName( + channel: props.channel, + textStyle: textTheme.headingSm.copyWith(height: 1), + ); + final subtitleWidget = + props.subtitle ?? + ChannelListTileSubtitle( + channel: props.channel, + sendingIndicatorBuilder: props.sendingIndicatorBuilder, + ); + final timestampWidget = props.trailing ?? ChannelLastMessageDate(channel: props.channel); + + return BetterStreamBuilder( + stream: props.channel.isMutedStream, + initialData: props.channel.isMuted, + builder: (context, isMuted) => BetterStreamBuilder( + stream: channelState.unreadCountStream, + initialData: channelState.unreadCount, + builder: (context, unreadCount) { + return StreamChannelListTile( + avatar: avatar, + title: titleWidget, + subtitle: subtitleWidget, + timestamp: timestampWidget, + unreadCount: unreadCount, + isMuted: isMuted, + onTap: props.onTap, + onLongPress: props.onLongPress, + selected: props.selected, + ); + }, + ), + ); + } +} + +/// A widget that displays a channel list tile. +/// It's the basic component for [StreamChannelListItem] without any of the logic. +/// It can be used to fully customize the list tile data being shown. +class StreamChannelListTile extends StatelessWidget { + /// Creates a new instance of [StreamChannelListTile] widget. + const StreamChannelListTile({ + super.key, + required this.avatar, + required this.title, + this.subtitle, + this.timestamp, + this.unreadCount = 0, + this.isMuted = false, + this.onTap, + this.onLongPress, + this.selected = false, + }); + + /// The avatar widget displayed at the leading edge. + /// + /// Typically a [StreamAvatar], [StreamAvatarGroup], or an avatar wrapped + /// in a [StreamOnlineIndicator]. + final Widget avatar; + + /// The channel title widget. + /// + /// Typically a [Text] widget with the channel name. The default text style + /// is provided by the theme's title style via [DefaultTextStyle]. + final Widget title; + + /// The message preview widget displayed below the title. + /// + /// Typically a [Text] widget with the last message, but can be any widget + /// for richer content (e.g., icons, read receipts, sender prefix). + final Widget? subtitle; + + /// The timestamp widget displayed in the trailing section of the title row. + /// + /// Typically a [Text] widget with a formatted date string. The default text + /// style is provided by the theme's timestamp style via [DefaultTextStyle]. + final Widget? timestamp; + + /// The number of unread messages. + /// + /// When greater than zero, a [StreamBadgeNotification] is displayed. + final int unreadCount; + + /// Whether the channel is muted. + /// + /// When true, a mute icon is displayed in the title or subtitle. + final bool isMuted; + + /// Called when the list item is tapped. + final VoidCallback? onTap; + + /// Called when the list item is long-pressed. + final VoidCallback? onLongPress; + + /// Whether the list item is in a selected state. + final bool selected; + + @override + Widget build(BuildContext context) { + final spacing = context.streamSpacing; + final channelListItemTheme = StreamChannelListItemTheme.of(context); + final defaults = _StreamChannelListItemThemeDefaults(context); + + final effectiveTitleStyle = channelListItemTheme.titleStyle ?? defaults.titleStyle; + final effectiveSubtitleStyle = channelListItemTheme.subtitleStyle ?? defaults.subtitleStyle; + final effectiveTimestampStyle = channelListItemTheme.timestampStyle ?? defaults.timestampStyle; + final effectiveMuteIconPosition = channelListItemTheme.muteIconPosition ?? defaults.muteIconPosition; + + final muteIcon = isMuted + ? Icon( + context.streamIcons.mute, + size: 20, + color: context.streamColorScheme.textTertiary, + ) + : null; + + final hasMuteIconInSubtitle = effectiveMuteIconPosition == MuteIconPosition.subtitle && isMuted; + + return StreamListTileTheme( + data: context.streamListTileTheme.copyWith( + contentPadding: EdgeInsets.all(spacing.md - 4), + backgroundColor: channelListItemTheme.backgroundColor, + ), + child: Padding( + padding: const EdgeInsets.all(4), + child: Material( + type: MaterialType.transparency, + child: StreamListTileContainer( + enabled: true, + selected: selected, + onTap: onTap, + onLongPress: onLongPress, + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: spacing.md, + children: [ + avatar, + Expanded( + child: Padding( + padding: EdgeInsets.symmetric(vertical: spacing.xxxs), + child: Column( + mainAxisSize: MainAxisSize.min, + spacing: spacing.xxs, + children: [ + _TitleRow( + title: title, + titleTrailing: effectiveMuteIconPosition == MuteIconPosition.title ? muteIcon : null, + timestamp: timestamp, + unreadCount: unreadCount, + titleStyle: effectiveTitleStyle, + timestampStyle: effectiveTimestampStyle, + spacing: spacing, + ), + if (subtitle != null || hasMuteIconInSubtitle) + _SubtitleRow( + subtitle: subtitle, + subtitleTrailing: effectiveMuteIconPosition == MuteIconPosition.subtitle ? muteIcon : null, + subtitleStyle: effectiveSubtitleStyle, + ), + ], + ), + ), + ), + ], + ), + ), + ), + ), + ); + } +} + +class _TitleRow extends StatelessWidget { + const _TitleRow({ + required this.title, + this.titleTrailing, + this.timestamp, + required this.unreadCount, + required this.titleStyle, + required this.timestampStyle, + required this.spacing, + }); + + final Widget title; + final Widget? titleTrailing; + final Widget? timestamp; + final int unreadCount; + final TextStyle titleStyle; + final TextStyle timestampStyle; + final StreamSpacing spacing; + + @override + Widget build(BuildContext context) { + return Row( + spacing: spacing.md, + children: [ + Expanded( + child: Row( + spacing: spacing.xxs, + children: [ + Flexible( + child: ConstrainedBox( + constraints: BoxConstraints(minHeight: StreamBadgeNotificationSize.sm.value), + child: Align( + alignment: AlignmentDirectional.centerStart, + widthFactor: 1, + child: DefaultTextStyle.merge( + style: titleStyle, + maxLines: 1, + overflow: TextOverflow.ellipsis, + child: title, + ), + ), + ), + ), + ?titleTrailing, + ], + ), + ), + if (timestamp != null || unreadCount > 0) + Row( + mainAxisSize: MainAxisSize.min, + spacing: spacing.xs, + children: [ + if (timestamp case final timestamp?) + DefaultTextStyle.merge( + style: timestampStyle, + child: timestamp, + ), + if (unreadCount > 0) StreamBadgeNotification(label: '$unreadCount'), + ], + ), + ], + ); + } +} + +class _SubtitleRow extends StatelessWidget { + const _SubtitleRow({ + required this.subtitle, + this.subtitleTrailing, + required this.subtitleStyle, + }); + + final Widget? subtitle; + final Widget? subtitleTrailing; + final TextStyle subtitleStyle; + + @override + Widget build(BuildContext context) { + return DefaultTextStyle( + style: subtitleStyle, + maxLines: 1, + overflow: TextOverflow.ellipsis, + child: Row( + children: [ + Expanded(child: subtitle ?? const SizedBox.shrink()), + ?subtitleTrailing, + ], + ), + ); + } +} + +class _StreamChannelListItemThemeDefaults extends StreamChannelListItemThemeData { + _StreamChannelListItemThemeDefaults(this._context); + + final BuildContext _context; + + late final _colorScheme = _context.streamColorScheme; + late final _textTheme = _context.streamTextTheme; + + @override + TextStyle get titleStyle => _textTheme.headingSm.copyWith(color: _colorScheme.textPrimary); + + @override + TextStyle get subtitleStyle => _textTheme.captionDefault.copyWith(color: _colorScheme.textSecondary); + + @override + TextStyle get timestampStyle => _textTheme.captionDefault.copyWith(color: _colorScheme.textTertiary); + + @override + Color get borderColor => _colorScheme.borderSubtle; + + @override + MuteIconPosition get muteIconPosition => MuteIconPosition.title; +} + +/// Shows the delivery status icon + "You:" prefix for outgoing messages in +/// the channel list. +/// +/// Unlike [StreamSendingIndicator], this widget does not show a read count +/// number. It only shows: +/// - Clock icon + "You:" (sending) +/// - Single check + "You:" (sent) +/// - Double check grey + "You:" (delivered) +/// - Double check blue + "You:" (read) +class _ChannelListDeliveryStatus extends StatelessWidget { + const _ChannelListDeliveryStatus({ + required this.channel, + required this.message, + }); + + final Channel channel; + final Message message; + + @override + Widget build(BuildContext context) { + final colorTheme = context.streamMessageTheme.mergeWithDefaults(context); + final colorScheme = context.streamColorScheme; + + return BetterStreamBuilder>( + stream: channel.state?.readStream, + initialData: channel.state?.read, + builder: (context, data) { + final isRead = data.readsOf(message: message).isNotEmpty; + final isDelivered = data.deliveriesOf(message: message).isNotEmpty; + + final Widget icon; + if (isRead) { + icon = Icon( + context.streamIcons.doupleCheckmark1Small, + size: 16, + color: colorTheme.outgoing?.textReadColor ?? colorScheme.accentPrimary, + ); + } else if (isDelivered) { + icon = Icon( + context.streamIcons.doupleCheckmark1Small, + size: 16, + color: colorTheme.outgoing?.textTimestampColor ?? colorScheme.textTertiary, + ); + } else if (message.state.isCompleted) { + icon = Icon( + context.streamIcons.checkmark1Small, + size: 16, + color: colorTheme.outgoing?.textTimestampColor ?? colorScheme.textTertiary, + ); + } else if (message.state.isOutgoing) { + icon = Icon( + context.streamIcons.clock, + size: 16, + color: colorTheme.outgoing?.textTimestampColor ?? colorScheme.textTertiary, + ); + } else { + return const Empty(); + } + + return Padding( + padding: const EdgeInsetsDirectional.only(end: 4), + child: icon, + ); + }, + ); + } +} + +/// A widget that displays the channel last message date. +class ChannelLastMessageDate extends StatelessWidget { + /// Creates a new instance of the [ChannelLastMessageDate] widget. + ChannelLastMessageDate({ + super.key, + required this.channel, + this.textStyle, + this.formatter, + }) : assert( + channel.state != null, + 'Channel ${channel.id} is not initialized', + ); + + /// The channel to display the last message date for. + final Channel channel; + + /// The style of the text displayed + final TextStyle? textStyle; + + /// The formatter to format the date. + final DateFormatter? formatter; + + @override + Widget build(BuildContext context) { + return BetterStreamBuilder( + stream: channel.lastMessageAtStream, + initialData: channel.lastMessageAt, + builder: (context, lastMessageAt) => StreamTimestamp( + date: lastMessageAt.toLocal(), + style: textStyle, + formatter: formatter, + ), + ); + } +} + +/// A widget that displays the subtitle for [StreamChannelListItem]. +/// +/// Shows typing indicators, draft messages, or the last message preview. +/// The delivery status prefix (icon + "You:") is only shown when the subtitle +/// displays an actual sent message from the current user (not for drafts or +/// typing indicators). +class ChannelListTileSubtitle extends StatelessWidget { + /// Creates a new instance of [StreamChannelListTileSubtitle] widget. + ChannelListTileSubtitle({ + super.key, + required this.channel, + this.textStyle, + this.sendingIndicatorBuilder, + }) : assert( + channel.state != null, + 'Channel ${channel.id} is not initialized', + ); + + /// The channel to create the subtitle from. + final Channel channel; + + /// The style of the text displayed + final TextStyle? textStyle; + + /// The widget builder for the sending indicator. + /// + /// `Message` is the last message in the channel. Use it to determine the + /// status using [Message.state]. + final Widget Function(BuildContext, Message)? sendingIndicatorBuilder; + + @override + Widget build(BuildContext context) { + return StreamTypingIndicator( + channel: channel, + style: textStyle, + alternativeWidget: _ChannelLastMessageWithStatus( + channel: channel, + textStyle: textStyle, + sendingIndicatorBuilder: sendingIndicatorBuilder, + ), + ); + } +} + +/// Combines the delivery status prefix with the last message text. +/// +/// Shows the delivery status only when the displayed content is an actual +/// sent message from the current user (not a draft). +class _ChannelLastMessageWithStatus extends StatefulWidget { + const _ChannelLastMessageWithStatus({ + required this.channel, + this.textStyle, + this.sendingIndicatorBuilder, + }); + + final Channel channel; + final TextStyle? textStyle; + final Widget Function(BuildContext, Message)? sendingIndicatorBuilder; + + @override + State<_ChannelLastMessageWithStatus> createState() => _ChannelLastMessageWithStatusState(); +} + +class _ChannelLastMessageWithStatusState extends State<_ChannelLastMessageWithStatus> { + Message? _currentLastMessage; + + static bool _defaultLastMessagePredicate(Message message) { + if (message.isShadowed) return false; + if (message.isDeleted) return false; + if (message.isError) return false; + if (message.isEphemeral) return false; + + return true; + } + + @override + Widget build(BuildContext context) { + final channelState = widget.channel.state; + if (channelState == null) return const Empty(); + + final currentUser = widget.channel.client.state.currentUser; + + return BetterStreamBuilder<(Draft?, List)>( + stream: CombineLatestStream.combine2( + channelState.draftStream, + channelState.messagesStream, + (draft, messages) => (draft, messages), + ), + initialData: (channelState.draft, channelState.messages), + builder: (context, data) { + final (draft, messages) = data; + + // If there's a draft, show only the draft preview (no delivery status). + if (draft?.message case final draftMessage?) { + return StreamDraftMessagePreviewText( + draftMessage: draftMessage, + textStyle: widget.textStyle, + ); + } + + // Find the last valid message. + final message = messages.lastWhereOrNull( + _defaultLastMessagePredicate, + ); + final latestLastMessage = [message, _currentLastMessage].latest; + + if (latestLastMessage == null) { + return Text( + context.translations.emptyMessagesText, + style: widget.textStyle?.copyWith(color: context.streamColorScheme.textTertiary), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ); + } + + final isOwnMessage = currentUser != null && latestLastMessage.user?.id == currentUser.id; + + // Show delivery status prefix only for own messages. + final Widget deliveryPrefix; + if (isOwnMessage) { + deliveryPrefix = + widget.sendingIndicatorBuilder?.call(context, latestLastMessage) ?? + _ChannelListDeliveryStatus( + channel: widget.channel, + message: latestLastMessage, + ); + } else { + deliveryPrefix = const Empty(); + } + + return Row( + children: [ + deliveryPrefix, + Flexible( + child: StreamMessagePreviewText( + message: latestLastMessage, + textStyle: widget.textStyle, + channel: channelState.channelState.channel, + ), + ), + ], + ); + }, + ); + } +} + +/// A widget that displays the last message of a channel. +class ChannelLastMessageText extends StatefulWidget { + /// Creates a new instance of [ChannelLastMessageText] widget. + ChannelLastMessageText({ + super.key, + required this.channel, + this.textStyle, + this.lastMessagePredicate = _defaultLastMessagePredicate, + }) : assert( + channel.state != null, + 'Channel ${channel.id} is not initialized', + ); + + /// The channel to display the last message of. + final Channel channel; + + /// The style of the text displayed + final TextStyle? textStyle; + + /// The predicate to determine if the message should be considered for the + /// last message. + /// + /// This predicate is used to filter out messages that should not be + /// considered for the last message. + final bool Function(Message) lastMessagePredicate; + + // The default predicate to determine if the message should be + // considered for the last message. + static bool _defaultLastMessagePredicate(Message message) { + if (message.isShadowed) return false; + if (message.isDeleted) return false; + if (message.isError) return false; + if (message.isEphemeral) return false; + + return true; + } + + @override + State createState() => _ChannelLastMessageTextState(); +} + +class _ChannelLastMessageTextState extends State { + Message? _currentLastMessage; + + @override + Widget build(BuildContext context) { + final channelState = widget.channel.state; + if (channelState == null) return const Empty(); + + return BetterStreamBuilder<(Draft?, List)>( + stream: CombineLatestStream.combine2( + channelState.draftStream, + channelState.messagesStream, + (draft, messages) => (draft, messages), + ), + initialData: (channelState.draft, channelState.messages), + builder: (context, data) { + final (draft, messages) = data; + + // Prioritize the draft message if it exists. + if (draft?.message case final draftMessage?) { + return StreamDraftMessagePreviewText( + draftMessage: draftMessage, + textStyle: widget.textStyle, + ); + } + + // Otherwise, show the channel last message if it exists. + final message = messages.lastWhereOrNull(widget.lastMessagePredicate); + final latestLastMessage = [message, _currentLastMessage].latest; + + if (latestLastMessage == null) { + return Text( + maxLines: 1, + context.translations.emptyMessagesText, + style: widget.textStyle, + overflow: TextOverflow.ellipsis, + ); + } + + return StreamMessagePreviewText( + message: latestLastMessage, + textStyle: widget.textStyle, + channel: channelState.channelState.channel, + ); + }, + ); + } +} + +extension on Iterable { + Message? get latest { + return reduce((a, b) { + if (a == null) return b; + if (b == null) return a; + + if (a.createdAt.isAfter(b.createdAt)) return a; + return b; + }); + } +} diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_tile.dart b/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_tile.dart deleted file mode 100644 index 1d950ce44f..0000000000 --- a/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_tile.dart +++ /dev/null @@ -1,445 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:flutter/material.dart'; -import 'package:rxdart/rxdart.dart'; -import 'package:stream_chat_flutter/src/message_widget/sending_indicator_builder.dart'; -import 'package:stream_chat_flutter/src/misc/empty_widget.dart'; -import 'package:stream_chat_flutter/src/misc/timestamp.dart'; -import 'package:stream_chat_flutter/src/utils/date_formatter.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; - -/// A widget that displays a channel preview. -/// -/// This widget is intended to be used as a Tile in [StreamChannelListView] -/// -/// It shows the last message of the channel, the last message time, the unread -/// message count, the typing indicator, the sending indicator and the channel -/// avatar. -/// -/// See also: -/// * [StreamChannelAvatar] -/// * [StreamChannelName] -class StreamChannelListTile extends StatelessWidget { - /// Creates a new instance of [StreamChannelListTile] widget. - StreamChannelListTile({ - super.key, - required this.channel, - this.leading, - this.title, - this.subtitle, - this.trailing, - this.onTap, - this.onLongPress, - this.tileColor, - this.visualDensity = VisualDensity.compact, - this.contentPadding = const EdgeInsets.symmetric(horizontal: 8), - this.unreadIndicatorBuilder, - this.sendingIndicatorBuilder, - this.selected = false, - this.selectedTileColor, - }) : assert( - channel.state != null, - 'Channel ${channel.id} is not initialized', - ); - - /// The channel to display. - final Channel channel; - - /// A widget to display before the title. - final Widget? leading; - - /// The primary content of the list tile. - final Widget? title; - - /// Additional content displayed below the title. - final Widget? subtitle; - - /// A widget to display at the end of tile. - final Widget? trailing; - - /// Called when the user taps this list tile. - final GestureTapCallback? onTap; - - /// Called when the user long-presses on this list tile. - final GestureLongPressCallback? onLongPress; - - /// {@template flutter.material.ListTile.tileColor} - /// Defines the background color of `ListTile`. - /// - /// When the value is null, - /// the `tileColor` is set to [ListTileTheme.tileColor] - /// if it's not null and to [Colors.transparent] if it's null. - /// {@endtemplate} - final Color? tileColor; - - /// Defines how compact the list tile's layout will be. - /// - /// {@macro flutter.material.themedata.visualDensity} - /// - /// See also: - /// - /// * [ThemeData.visualDensity], which specifies the [visualDensity] for all - /// widgets within a [Theme]. - final VisualDensity visualDensity; - - /// The tile's internal padding. - /// - /// Insets a [ListTile]'s contents: its [leading], [title], [subtitle], - /// and [trailing] widgets. - /// - /// If null, `EdgeInsets.symmetric(horizontal: 16.0)` is used. - final EdgeInsetsGeometry contentPadding; - - /// The widget builder for the unread indicator. - final WidgetBuilder? unreadIndicatorBuilder; - - /// The widget builder for the sending indicator. - /// - /// `Message` is the last message in the channel, Use it to determine the - /// status using [Message.state]. - final Widget Function(BuildContext, Message)? sendingIndicatorBuilder; - - /// True if the tile is in a selected state. - final bool selected; - - /// The color of the tile in selected state. - final Color? selectedTileColor; - - /// Creates a copy of this tile but with the given fields replaced with - /// the new values. - StreamChannelListTile copyWith({ - Key? key, - Channel? channel, - Widget? leading, - Widget? title, - Widget? subtitle, - VoidCallback? onTap, - VoidCallback? onLongPress, - VisualDensity? visualDensity, - EdgeInsetsGeometry? contentPadding, - bool? selected, - Widget Function(BuildContext, Message)? sendingIndicatorBuilder, - Color? tileColor, - Color? selectedTileColor, - WidgetBuilder? unreadIndicatorBuilder, - Widget? trailing, - }) { - return StreamChannelListTile( - key: key ?? this.key, - channel: channel ?? this.channel, - leading: leading ?? this.leading, - title: title ?? this.title, - subtitle: subtitle ?? this.subtitle, - onTap: onTap ?? this.onTap, - onLongPress: onLongPress ?? this.onLongPress, - visualDensity: visualDensity ?? this.visualDensity, - contentPadding: contentPadding ?? this.contentPadding, - sendingIndicatorBuilder: - sendingIndicatorBuilder ?? this.sendingIndicatorBuilder, - tileColor: tileColor ?? this.tileColor, - trailing: trailing ?? this.trailing, - unreadIndicatorBuilder: - unreadIndicatorBuilder ?? this.unreadIndicatorBuilder, - selected: selected ?? this.selected, - selectedTileColor: selectedTileColor ?? this.selectedTileColor, - ); - } - - @override - Widget build(BuildContext context) { - final channelState = channel.state!; - final currentUser = channel.client.state.currentUser!; - - final channelPreviewTheme = StreamChannelPreviewTheme.of(context); - final streamChatTheme = StreamChatTheme.of(context); - final streamChat = StreamChat.of(context); - - final leading = this.leading ?? - StreamChannelAvatar( - channel: channel, - ); - - final title = this.title ?? - StreamChannelName( - channel: channel, - textStyle: channelPreviewTheme.titleStyle, - ); - - final subtitle = this.subtitle ?? - ChannelListTileSubtitle( - channel: channel, - textStyle: channelPreviewTheme.subtitleStyle, - ); - - final trailing = this.trailing ?? - ChannelLastMessageDate( - channel: channel, - textStyle: channelPreviewTheme.lastMessageAtStyle, - formatter: channelPreviewTheme.lastMessageAtFormatter, - ); - - return BetterStreamBuilder( - stream: channel.isMutedStream, - initialData: channel.isMuted, - builder: (context, isMuted) => AnimatedOpacity( - opacity: isMuted ? 0.5 : 1, - duration: const Duration(milliseconds: 300), - child: ListTile( - onTap: onTap, - onLongPress: onLongPress, - visualDensity: visualDensity, - contentPadding: contentPadding, - leading: leading, - tileColor: tileColor, - selected: selected, - selectedTileColor: selectedTileColor ?? - StreamChatTheme.of(context).colorTheme.borders, - title: Row( - children: [ - Expanded(child: title), - BetterStreamBuilder>( - stream: channelState.membersStream, - initialData: channelState.members, - comparator: const ListEquality().equals, - builder: (context, members) { - if (members.isEmpty) { - return const Empty(); - } - return unreadIndicatorBuilder?.call(context) ?? - StreamUnreadIndicator.channels(cid: channel.cid); - }, - ), - ], - ), - subtitle: Row( - children: [ - Expanded( - child: Align( - alignment: Alignment.centerLeft, - child: subtitle, - ), - ), - BetterStreamBuilder>( - stream: channelState.messagesStream, - initialData: channelState.messages, - comparator: const ListEquality().equals, - builder: (context, messages) { - final lastMessage = messages.lastWhereOrNull( - (m) => !m.shadowed && !m.isDeleted, - ); - - if (lastMessage == null || - (lastMessage.user?.id != currentUser.id)) { - return const Empty(); - } - - final hasNonUrlAttachments = lastMessage.attachments - .any((it) => it.type != AttachmentType.urlPreview); - - return Padding( - padding: const EdgeInsets.only(right: 4), - child: - sendingIndicatorBuilder?.call(context, lastMessage) ?? - SendingIndicatorBuilder( - messageTheme: streamChatTheme.ownMessageTheme, - message: lastMessage, - hasNonUrlAttachments: hasNonUrlAttachments, - streamChat: streamChat, - streamChatTheme: streamChatTheme, - channel: channel, - ), - ); - }, - ), - trailing, - ], - ), - ), - ), - ); - } -} - -/// A widget that displays the channel last message date. -class ChannelLastMessageDate extends StatelessWidget { - /// Creates a new instance of the [ChannelLastMessageDate] widget. - ChannelLastMessageDate({ - super.key, - required this.channel, - this.textStyle, - this.formatter, - }) : assert( - channel.state != null, - 'Channel ${channel.id} is not initialized', - ); - - /// The channel to display the last message date for. - final Channel channel; - - /// The style of the text displayed - final TextStyle? textStyle; - - /// The formatter to format the date. - final DateFormatter? formatter; - - @override - Widget build(BuildContext context) { - return BetterStreamBuilder( - stream: channel.lastMessageAtStream, - initialData: channel.lastMessageAt, - builder: (context, lastMessageAt) => StreamTimestamp( - date: lastMessageAt.toLocal(), - style: textStyle, - formatter: formatter, - ), - ); - } -} - -/// A widget that displays the subtitle for [StreamChannelListTile]. -class ChannelListTileSubtitle extends StatelessWidget { - /// Creates a new instance of [StreamChannelListTileSubtitle] widget. - ChannelListTileSubtitle({ - super.key, - required this.channel, - this.textStyle, - }) : assert( - channel.state != null, - 'Channel ${channel.id} is not initialized', - ); - - /// The channel to create the subtitle from. - final Channel channel; - - /// The style of the text displayed - final TextStyle? textStyle; - - @override - Widget build(BuildContext context) { - if (channel.isMuted) { - return Row( - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - const StreamSvgIcon(size: 16, icon: StreamSvgIcons.mute), - Expanded( - child: Text( - ' ${context.translations.channelIsMutedText}', - style: textStyle, - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - ), - ], - ); - } - return StreamTypingIndicator( - channel: channel, - style: textStyle, - alternativeWidget: ChannelLastMessageText( - channel: channel, - textStyle: textStyle, - ), - ); - } -} - -/// A widget that displays the last message of a channel. -class ChannelLastMessageText extends StatefulWidget { - /// Creates a new instance of [ChannelLastMessageText] widget. - ChannelLastMessageText({ - super.key, - required this.channel, - this.textStyle, - this.lastMessagePredicate = _defaultLastMessagePredicate, - }) : assert( - channel.state != null, - 'Channel ${channel.id} is not initialized', - ); - - /// The channel to display the last message of. - final Channel channel; - - /// The style of the text displayed - final TextStyle? textStyle; - - /// The predicate to determine if the message should be considered for the - /// last message. - /// - /// This predicate is used to filter out messages that should not be - /// considered for the last message. - final bool Function(Message) lastMessagePredicate; - - // The default predicate to determine if the message should be - // considered for the last message. - static bool _defaultLastMessagePredicate(Message message) { - if (message.isShadowed) return false; - if (message.isDeleted) return false; - if (message.isError) return false; - if (message.isEphemeral) return false; - - return true; - } - - @override - State createState() => _ChannelLastMessageTextState(); -} - -class _ChannelLastMessageTextState extends State { - Message? _currentLastMessage; - - @override - Widget build(BuildContext context) { - final channelState = widget.channel.state; - if (channelState == null) return const Empty(); - - return BetterStreamBuilder<(Draft?, List)>( - stream: CombineLatestStream.combine2( - channelState.draftStream, - channelState.messagesStream, - (draft, messages) => (draft, messages), - ), - initialData: (channelState.draft, channelState.messages), - builder: (context, data) { - final (draft, messages) = data; - - // Prioritize the draft message if it exists. - if (draft?.message case final draftMessage?) { - return StreamDraftMessagePreviewText( - draftMessage: draftMessage, - textStyle: widget.textStyle, - ); - } - - // Otherwise, show the channel last message if it exists. - final message = messages.lastWhereOrNull(widget.lastMessagePredicate); - final latestLastMessage = [message, _currentLastMessage].latest; - - if (latestLastMessage == null) { - return Text( - maxLines: 1, - context.translations.emptyMessagesText, - style: widget.textStyle, - overflow: TextOverflow.ellipsis, - ); - } - - return StreamMessagePreviewText( - message: latestLastMessage, - textStyle: widget.textStyle, - channel: channelState.channelState.channel, - ); - }, - ); - } -} - -extension on Iterable { - Message? get latest { - return reduce((a, b) { - if (a == null) return b; - if (b == null) return a; - - if (a.createdAt.isAfter(b.createdAt)) return a; - return b; - }); - } -} diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_view.dart b/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_view.dart index 3541963b3f..1d697dfd6a 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_view.dart @@ -11,16 +11,15 @@ Widget defaultChannelListViewSeparatorBuilder( BuildContext context, List items, int index, -) => - const StreamChannelListSeparator(); +) => const StreamChannelListSeparator(); /// Signature for the item builder that creates the children of the /// [StreamChannelListView]. -typedef StreamChannelListViewIndexedWidgetBuilder - = StreamScrollViewIndexedWidgetBuilder; +typedef StreamChannelListViewIndexedWidgetBuilder = + StreamScrollViewIndexedWidgetBuilder; /// A [ListView] that shows a list of [Channel]s, -/// it uses [StreamChannelListTile] as a default item. +/// it uses [StreamChannelListItem] as a default item. /// /// This is the new version of [StreamChannelListView] that uses /// [StreamChannelListController]. @@ -40,7 +39,7 @@ typedef StreamChannelListViewIndexedWidgetBuilder /// ``` /// /// See also: -/// * [StreamChannelListTile] +/// * [StreamChannelListItem] /// * [StreamChannelListController] class StreamChannelListView extends StatelessWidget { /// Creates a new instance of [StreamChannelListView]. @@ -304,7 +303,7 @@ class StreamChannelListView extends StatelessWidget { final onTap = onChannelTap; final onLongPress = onChannelLongPress; - final streamChannelListTile = StreamChannelListTile( + final streamChannelListTile = StreamChannelListItem( channel: channel, onTap: onTap == null ? null : () => onTap(channel), onLongPress: onLongPress == null ? null : () => onLongPress(channel), @@ -325,9 +324,9 @@ class StreamChannelListView extends StatelessWidget { child: Padding( padding: const EdgeInsets.all(8), child: StreamScrollViewEmptyWidget( - emptyIcon: StreamSvgIcon( + emptyIcon: Icon( + context.streamIcons.bubble3ChatMessage, size: 148, - icon: StreamSvgIcons.message, color: chatThemeData.colorTheme.disabled, ), emptyTitle: Text( @@ -338,8 +337,7 @@ class StreamChannelListView extends StatelessWidget { ), ); }, - loadMoreErrorBuilder: (context, error) => - StreamScrollViewLoadMoreError.list( + loadMoreErrorBuilder: (context, error) => StreamScrollViewLoadMoreError.list( onTap: controller.retry, error: Text(context.translations.loadingChannelsError), ), @@ -367,7 +365,7 @@ class StreamChannelListView extends StatelessWidget { } /// A widget that is used to display a separator between -/// [StreamChannelListTile] items. +/// [StreamChannelListItem] items. class StreamChannelListSeparator extends StatelessWidget { /// Creates a new instance of [StreamChannelListSeparator]. const StreamChannelListSeparator({super.key}); diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/draft_scroll_view/stream_draft_list_view.dart b/packages/stream_chat_flutter/lib/src/scroll_view/draft_scroll_view/stream_draft_list_view.dart index 1c59029017..8b37be7500 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/draft_scroll_view/stream_draft_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/draft_scroll_view/stream_draft_list_view.dart @@ -11,13 +11,11 @@ Widget defaultDraftListViewSeparatorBuilder( BuildContext context, List drafts, int index, -) => - const StreamDraftListSeparator(); +) => const StreamDraftListSeparator(); /// Signature for the item builder that creates the children of the /// [StreamDraftListView]. -typedef StreamDraftListViewIndexedWidgetBuilder - = StreamScrollViewIndexedWidgetBuilder; +typedef StreamDraftListViewIndexedWidgetBuilder = StreamScrollViewIndexedWidgetBuilder; /// {@template streamDraftListView} /// A [ListView] that shows a list of [Draft]'s. It uses a @@ -318,9 +316,9 @@ class StreamDraftListView extends StatelessWidget { child: Padding( padding: const EdgeInsets.all(8), child: StreamScrollViewEmptyWidget( - emptyIcon: StreamSvgIcon( + emptyIcon: Icon( + context.streamIcons.editBig, size: 148, - icon: StreamSvgIcons.edit, color: chatThemeData.colorTheme.disabled, ), emptyTitle: Text( @@ -331,8 +329,7 @@ class StreamDraftListView extends StatelessWidget { ), ); }, - loadMoreErrorBuilder: (context, error) => - StreamScrollViewLoadMoreError.list( + loadMoreErrorBuilder: (context, error) => StreamScrollViewLoadMoreError.list( onTap: controller.retry, error: Text(context.translations.loadingMessagesError), ), diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/member_scroll_view/stream_member_grid_view.dart b/packages/stream_chat_flutter/lib/src/scroll_view/member_scroll_view/stream_member_grid_view.dart index adf97a7860..b82ec32b99 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/member_scroll_view/stream_member_grid_view.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/member_scroll_view/stream_member_grid_view.dart @@ -7,13 +7,11 @@ import 'package:stream_chat_flutter/src/scroll_view/stream_scroll_view_loading_w import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// Default grid delegate for [StreamMemberGridView]. -const defaultMemberGridViewDelegate = - SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 4); +const defaultMemberGridViewDelegate = SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 4); /// Signature for the item builder that creates the children of the /// [StreamMemberGridView]. -typedef StreamMemberGridViewIndexedWidgetBuilder - = StreamScrollViewIndexedWidgetBuilder; +typedef StreamMemberGridViewIndexedWidgetBuilder = StreamScrollViewIndexedWidgetBuilder; /// Signature for the member grid tile, currently equal to [StreamUserGridTile]. typedef StreamMemberGridTile = StreamUserGridTile; @@ -352,9 +350,9 @@ class StreamMemberGridView extends StatelessWidget { child: Padding( padding: const EdgeInsets.all(8), child: StreamScrollViewEmptyWidget( - emptyIcon: StreamSvgIcon( + emptyIcon: Icon( + context.streamIcons.people, size: 148, - icon: StreamSvgIcons.user, color: chatThemeData.colorTheme.disabled, ), emptyTitle: Text( @@ -365,8 +363,7 @@ class StreamMemberGridView extends StatelessWidget { ), ); }, - loadMoreErrorBuilder: (context, error) => - StreamScrollViewLoadMoreError.grid( + loadMoreErrorBuilder: (context, error) => StreamScrollViewLoadMoreError.grid( onTap: controller.retry, error: Text( context.translations.loadingUsersError, diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/member_scroll_view/stream_member_list_view.dart b/packages/stream_chat_flutter/lib/src/scroll_view/member_scroll_view/stream_member_list_view.dart index fcdad5379e..aa18ec4d47 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/member_scroll_view/stream_member_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/member_scroll_view/stream_member_list_view.dart @@ -11,13 +11,11 @@ Widget defaultMemberListViewSeparatorBuilder( BuildContext context, List members, int index, -) => - const StreamUserListSeparator(); +) => const StreamUserListSeparator(); /// Signature for the item builder that creates the children of the /// [StreamMemberListView]. -typedef StreamMemberListViewIndexedWidgetBuilder - = StreamScrollViewIndexedWidgetBuilder; +typedef StreamMemberListViewIndexedWidgetBuilder = StreamScrollViewIndexedWidgetBuilder; /// Signature for the member grid tile, currently equal to [StreamUserListTile]. typedef StreamMemberListTile = StreamUserListTile; @@ -278,86 +276,85 @@ class StreamMemberListView extends StatelessWidget { @override Widget build(BuildContext context) => PagedValueListView( - scrollDirection: scrollDirection, - padding: padding, - physics: physics, - reverse: reverse, - controller: controller, - scrollController: scrollController, - primary: primary, - shrinkWrap: shrinkWrap, - addAutomaticKeepAlives: addAutomaticKeepAlives, - addRepaintBoundaries: addRepaintBoundaries, - addSemanticIndexes: addSemanticIndexes, - keyboardDismissBehavior: keyboardDismissBehavior, - restorationId: restorationId, - dragStartBehavior: dragStartBehavior, - cacheExtent: cacheExtent, - clipBehavior: clipBehavior, - loadMoreTriggerIndex: loadMoreTriggerIndex, - separatorBuilder: separatorBuilder, - itemBuilder: (context, members, index) { - final member = members[index]; - final onTap = onMemberTap; - final onLongPress = onMemberLongPress; + scrollDirection: scrollDirection, + padding: padding, + physics: physics, + reverse: reverse, + controller: controller, + scrollController: scrollController, + primary: primary, + shrinkWrap: shrinkWrap, + addAutomaticKeepAlives: addAutomaticKeepAlives, + addRepaintBoundaries: addRepaintBoundaries, + addSemanticIndexes: addSemanticIndexes, + keyboardDismissBehavior: keyboardDismissBehavior, + restorationId: restorationId, + dragStartBehavior: dragStartBehavior, + cacheExtent: cacheExtent, + clipBehavior: clipBehavior, + loadMoreTriggerIndex: loadMoreTriggerIndex, + separatorBuilder: separatorBuilder, + itemBuilder: (context, members, index) { + final member = members[index]; + final onTap = onMemberTap; + final onLongPress = onMemberLongPress; - final streamUserListTile = StreamMemberListTile( - user: member.user!, - onTap: onTap == null ? null : () => onTap(member), - onLongPress: onLongPress == null ? null : () => onLongPress(member), - ); + final streamUserListTile = StreamMemberListTile( + user: member.user!, + onTap: onTap == null ? null : () => onTap(member), + onLongPress: onLongPress == null ? null : () => onLongPress(member), + ); - return itemBuilder?.call( - context, - members, - index, - streamUserListTile, - ) ?? - streamUserListTile; - }, - emptyBuilder: (context) { - final chatThemeData = StreamChatTheme.of(context); - return emptyBuilder?.call(context) ?? - Center( - child: Padding( - padding: const EdgeInsets.all(8), - child: StreamScrollViewEmptyWidget( - emptyIcon: StreamSvgIcon( - size: 148, - icon: StreamSvgIcons.user, - color: chatThemeData.colorTheme.disabled, - ), - emptyTitle: Text( - context.translations.noUsersLabel, - style: chatThemeData.textTheme.headline, - ), - ), + return itemBuilder?.call( + context, + members, + index, + streamUserListTile, + ) ?? + streamUserListTile; + }, + emptyBuilder: (context) { + final chatThemeData = StreamChatTheme.of(context); + return emptyBuilder?.call(context) ?? + Center( + child: Padding( + padding: const EdgeInsets.all(8), + child: StreamScrollViewEmptyWidget( + emptyIcon: Icon( + context.streamIcons.people, + size: 148, + color: chatThemeData.colorTheme.disabled, + ), + emptyTitle: Text( + context.translations.noUsersLabel, + style: chatThemeData.textTheme.headline, ), - ); - }, - loadMoreErrorBuilder: (context, error) => - StreamScrollViewLoadMoreError.list( - onTap: controller.retry, - error: Text(context.translations.loadingUsersError), + ), + ), + ); + }, + loadMoreErrorBuilder: (context, error) => StreamScrollViewLoadMoreError.list( + onTap: controller.retry, + error: Text(context.translations.loadingUsersError), + ), + loadMoreIndicatorBuilder: (context) => const Center( + child: Padding( + padding: EdgeInsets.all(16), + child: StreamScrollViewLoadMoreIndicator(), + ), + ), + loadingBuilder: (context) => + loadingBuilder?.call(context) ?? + const Center( + child: StreamScrollViewLoadingWidget(), ), - loadMoreIndicatorBuilder: (context) => const Center( - child: Padding( - padding: EdgeInsets.all(16), - child: StreamScrollViewLoadMoreIndicator(), + errorBuilder: (context, error) => + errorBuilder?.call(context, error) ?? + Center( + child: StreamScrollViewErrorWidget( + errorTitle: Text(context.translations.loadingUsersError), + onRetryPressed: controller.refresh, ), ), - loadingBuilder: (context) => - loadingBuilder?.call(context) ?? - const Center( - child: StreamScrollViewLoadingWidget(), - ), - errorBuilder: (context, error) => - errorBuilder?.call(context, error) ?? - Center( - child: StreamScrollViewErrorWidget( - errorTitle: Text(context.translations.loadingUsersError), - onRetryPressed: controller.refresh, - ), - ), - ); + ); } diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/message_search_scroll_view/stream_message_search_grid_view.dart b/packages/stream_chat_flutter/lib/src/scroll_view/message_search_scroll_view/stream_message_search_grid_view.dart index e7a939f942..1279f3a033 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/message_search_scroll_view/stream_message_search_grid_view.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/message_search_scroll_view/stream_message_search_grid_view.dart @@ -7,13 +7,11 @@ import 'package:stream_chat_flutter/src/scroll_view/stream_scroll_view_loading_w import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// Default grid delegate for [StreamMessageSearchGridView]. -const defaultMessageSearchGridViewDelegate = - SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 4); +const defaultMessageSearchGridViewDelegate = SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 4); /// Signature for the item builder that creates the children of the /// [StreamMessageSearchGridView]. -typedef StreamMessageSearchGridViewIndexedWidgetBuilder - = PagedValueScrollViewIndexedWidgetBuilder; +typedef StreamMessageSearchGridViewIndexedWidgetBuilder = PagedValueScrollViewIndexedWidgetBuilder; /// A [GridView] that shows a grid of [GetMessageResponse]s, /// it uses [StreamMessageSearchGridTile] as a default item. @@ -323,9 +321,9 @@ class StreamMessageSearchGridView extends StatelessWidget { child: Padding( padding: const EdgeInsets.all(8), child: StreamScrollViewEmptyWidget( - emptyIcon: StreamSvgIcon( + emptyIcon: Icon( + context.streamIcons.bubble3ChatMessage, size: 148, - icon: StreamSvgIcons.message, color: chatThemeData.colorTheme.disabled, ), emptyTitle: Text( @@ -336,8 +334,7 @@ class StreamMessageSearchGridView extends StatelessWidget { ), ); }, - loadMoreErrorBuilder: (context, error) => - StreamScrollViewLoadMoreError.grid( + loadMoreErrorBuilder: (context, error) => StreamScrollViewLoadMoreError.grid( onTap: controller.retry, error: Text(context.translations.loadingMessagesError), ), diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/message_search_scroll_view/stream_message_search_list_tile.dart b/packages/stream_chat_flutter/lib/src/scroll_view/message_search_scroll_view/stream_message_search_list_tile.dart index 23575f05bc..3a9be001a3 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/message_search_scroll_view/stream_message_search_list_tile.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/message_search_scroll_view/stream_message_search_list_tile.dart @@ -91,20 +91,19 @@ class StreamMessageSearchListTile extends StatelessWidget { Color? tileColor, VisualDensity? visualDensity, EdgeInsetsGeometry? contentPadding, - }) => - StreamMessageSearchListTile( - key: key ?? this.key, - messageResponse: messageResponse ?? this.messageResponse, - leading: leading ?? this.leading, - title: title ?? this.title, - subtitle: subtitle ?? this.subtitle, - trailing: trailing ?? this.trailing, - onTap: onTap ?? this.onTap, - onLongPress: onLongPress ?? this.onLongPress, - tileColor: tileColor ?? this.tileColor, - visualDensity: visualDensity ?? this.visualDensity, - contentPadding: contentPadding ?? this.contentPadding, - ); + }) => StreamMessageSearchListTile( + key: key ?? this.key, + messageResponse: messageResponse ?? this.messageResponse, + leading: leading ?? this.leading, + title: title ?? this.title, + subtitle: subtitle ?? this.subtitle, + trailing: trailing ?? this.trailing, + onTap: onTap ?? this.onTap, + onLongPress: onLongPress ?? this.onLongPress, + tileColor: tileColor ?? this.tileColor, + visualDensity: visualDensity ?? this.visualDensity, + contentPadding: contentPadding ?? this.contentPadding, + ); @override Widget build(BuildContext context) { @@ -112,23 +111,17 @@ class StreamMessageSearchListTile extends StatelessWidget { final user = message.user!; final channelPreviewTheme = StreamChannelPreviewTheme.of(context); - final leading = this.leading ?? - StreamUserAvatar( - user: user, - constraints: const BoxConstraints.tightFor( - height: 40, - width: 40, - ), - ); + final leading = this.leading ?? StreamUserAvatar(size: .lg, user: user); - final title = this.title ?? + final title = + this.title ?? MessageSearchListTileTitle( messageResponse: messageResponse, - textStyle: channelPreviewTheme.titleStyle - ?.copyWith(overflow: TextOverflow.ellipsis), + textStyle: channelPreviewTheme.titleStyle?.copyWith(overflow: TextOverflow.ellipsis), ); - final subtitle = this.subtitle ?? + final subtitle = + this.subtitle ?? Row( children: [ Expanded( @@ -185,9 +178,7 @@ class MessageSearchListTileTitle extends StatelessWidget { TextSpan( children: [ TextSpan( - text: user.id == StreamChat.of(context).currentUser?.id - ? context.translations.youText - : user.name, + text: user.id == StreamChat.of(context).currentUser?.id ? context.translations.youText : user.name, ), if (channelName != null) ...[ TextSpan( diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/message_search_scroll_view/stream_message_search_list_view.dart b/packages/stream_chat_flutter/lib/src/scroll_view/message_search_scroll_view/stream_message_search_list_view.dart index 2f4c75468f..e9d08d655d 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/message_search_scroll_view/stream_message_search_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/message_search_scroll_view/stream_message_search_list_view.dart @@ -11,14 +11,12 @@ Widget defaultMessageSearchListViewSeparatorBuilder( BuildContext context, List responses, int index, -) => - const StreamMessageSearchListSeparator(); +) => const StreamMessageSearchListSeparator(); /// Signature for the item builder that creates the children of the /// [StreamMessageSearchListView]. -typedef StreamMessageSearchListViewIndexedWidgetBuilder - = StreamScrollViewIndexedWidgetBuilder; +typedef StreamMessageSearchListViewIndexedWidgetBuilder = + StreamScrollViewIndexedWidgetBuilder; /// A [ListView] that shows a list of [GetMessageResponse]s, /// it uses [StreamMessageSearchListTile] as a default item. @@ -81,8 +79,7 @@ class StreamMessageSearchListView extends StatelessWidget { final StreamMessageSearchListViewIndexedWidgetBuilder? itemBuilder; /// A builder that is called to build the list separator. - final PagedValueScrollViewIndexedWidgetBuilder - separatorBuilder; + final PagedValueScrollViewIndexedWidgetBuilder separatorBuilder; /// A builder that is called to build the empty state of the list. final WidgetBuilder? emptyBuilder; @@ -308,8 +305,7 @@ class StreamMessageSearchListView extends StatelessWidget { final streamMessageSearchListTile = StreamMessageSearchListTile( messageResponse: messageResponse, onTap: onTap == null ? null : () => onTap(messageResponse), - onLongPress: - onLongPress == null ? null : () => onLongPress(messageResponse), + onLongPress: onLongPress == null ? null : () => onLongPress(messageResponse), ); return itemBuilder?.call( @@ -327,9 +323,9 @@ class StreamMessageSearchListView extends StatelessWidget { child: Padding( padding: const EdgeInsets.all(8), child: StreamScrollViewEmptyWidget( - emptyIcon: StreamSvgIcon( + emptyIcon: Icon( + context.streamIcons.bubble3ChatMessage, size: 148, - icon: StreamSvgIcons.message, color: chatThemeData.colorTheme.disabled, ), emptyTitle: Text( @@ -340,8 +336,7 @@ class StreamMessageSearchListView extends StatelessWidget { ), ); }, - loadMoreErrorBuilder: (context, error) => - StreamScrollViewLoadMoreError.list( + loadMoreErrorBuilder: (context, error) => StreamScrollViewLoadMoreError.list( onTap: controller.retry, error: Text(context.translations.loadingMessagesError), ), diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/photo_gallery/stream_photo_gallery.dart b/packages/stream_chat_flutter/lib/src/scroll_view/photo_gallery/stream_photo_gallery.dart index dcee2b76a4..fdfd164731 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/photo_gallery/stream_photo_gallery.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/photo_gallery/stream_photo_gallery.dart @@ -1,7 +1,6 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; -import 'package:photo_manager/photo_manager.dart' - show AssetEntity, ThumbnailFormat, ThumbnailSize; +import 'package:photo_manager/photo_manager.dart' show AssetEntity, ThumbnailFormat, ThumbnailSize; import 'package:stream_chat_flutter/src/scroll_view/stream_scroll_view_error_widget.dart'; import 'package:stream_chat_flutter/src/scroll_view/stream_scroll_view_load_more_error.dart'; @@ -10,8 +9,7 @@ import 'package:stream_chat_flutter/src/scroll_view/stream_scroll_view_loading_w import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// Default grid delegate for [StreamPhotoGallery]. -const defaultStreamPhotoGalleryDelegate = - SliverGridDelegateWithFixedCrossAxisCount( +const defaultStreamPhotoGalleryDelegate = SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3, mainAxisSpacing: 2, crossAxisSpacing: 2, @@ -19,8 +17,8 @@ const defaultStreamPhotoGalleryDelegate = /// Signature for the item builder that creates the children of the /// [StreamPhotoGallery]. -typedef StreamPhotoGalleryIndexedWidgetBuilder - = StreamScrollViewIndexedWidgetBuilder; +typedef StreamPhotoGalleryIndexedWidgetBuilder = + StreamScrollViewIndexedWidgetBuilder; /// Widget used to display a gallery of photos in the form of grid. class StreamPhotoGallery extends StatelessWidget { @@ -58,6 +56,7 @@ class StreamPhotoGallery extends StatelessWidget { this.thumbnailFormat = ThumbnailFormat.jpeg, this.thumbnailQuality = 100, this.thumbnailScale = 1, + this.addMoreBuilder, }); /// The [StreamPhotoGalleryController] used to control the grid of users. @@ -309,6 +308,11 @@ class StreamPhotoGallery extends StatelessWidget { /// Scale of the image. final double thumbnailScale; + /// An optional builder for a leading "Add more" tile shown as the first item + /// in the gallery grid. Useful when the user has limited photo library access + /// and needs a way to expand the selection. + final WidgetBuilder? addMoreBuilder; + @override Widget build(BuildContext context) { return PagedValueGridView( @@ -330,6 +334,7 @@ class StreamPhotoGallery extends StatelessWidget { restorationId: restorationId, clipBehavior: clipBehavior, loadMoreTriggerIndex: loadMoreTriggerIndex, + leadingItemBuilder: addMoreBuilder, gridDelegate: gridDelegate, itemBuilder: (context, mediaList, index) { final media = mediaList[index]; @@ -361,9 +366,9 @@ class StreamPhotoGallery extends StatelessWidget { child: Padding( padding: const EdgeInsets.all(8), child: StreamScrollViewEmptyWidget( - emptyIcon: StreamSvgIcon( + emptyIcon: Icon( + context.streamIcons.images1Alt, size: 148, - icon: StreamSvgIcons.pictures, color: chatThemeData.colorTheme.disabled, ), emptyTitle: Text( diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/photo_gallery/stream_photo_gallery_controller.dart b/packages/stream_chat_flutter/lib/src/scroll_view/photo_gallery/stream_photo_gallery_controller.dart index eb9b21bbf9..8d25cb561e 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/photo_gallery/stream_photo_gallery_controller.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/photo_gallery/stream_photo_gallery_controller.dart @@ -3,8 +3,7 @@ import 'package:photo_manager/photo_manager.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; /// -class StreamPhotoGalleryController - extends PagedValueNotifier { +class StreamPhotoGalleryController extends PagedValueNotifier { /// StreamPhotoGalleryController({ this.limit = 50, diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/photo_gallery/stream_photo_gallery_tile.dart b/packages/stream_chat_flutter/lib/src/scroll_view/photo_gallery/stream_photo_gallery_tile.dart index 6b7ef7ebdc..a9edd1a23d 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/photo_gallery/stream_photo_gallery_tile.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/photo_gallery/stream_photo_gallery_tile.dart @@ -3,8 +3,8 @@ import 'dart:ui' as ui; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:photo_manager/photo_manager.dart'; -import 'package:stream_chat_flutter/src/icons/stream_svg_icon.dart'; import 'package:stream_chat_flutter/src/theme/stream_chat_theme.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; /// Widget that displays a photo or video item from the gallery. class StreamPhotoGalleryTile extends StatelessWidget { @@ -60,18 +60,17 @@ class StreamPhotoGalleryTile extends StatelessWidget { ThumbnailFormat? thumbnailFormat, int? thumbnailQuality, double? thumbnailScale, - }) => - StreamPhotoGalleryTile( - key: key ?? this.key, - media: media ?? this.media, - selected: selected ?? this.selected, - onTap: onTap ?? this.onTap, - onLongPress: onLongPress ?? this.onLongPress, - thumbnailSize: thumbnailSize ?? this.thumbnailSize, - thumbnailFormat: thumbnailFormat ?? this.thumbnailFormat, - thumbnailQuality: thumbnailQuality ?? this.thumbnailQuality, - thumbnailScale: thumbnailScale ?? this.thumbnailScale, - ); + }) => StreamPhotoGalleryTile( + key: key ?? this.key, + media: media ?? this.media, + selected: selected ?? this.selected, + onTap: onTap ?? this.onTap, + onLongPress: onLongPress ?? this.onLongPress, + thumbnailSize: thumbnailSize ?? this.thumbnailSize, + thumbnailFormat: thumbnailFormat ?? this.thumbnailFormat, + thumbnailQuality: thumbnailQuality ?? this.thumbnailQuality, + thumbnailScale: thumbnailScale ?? this.thumbnailScale, + ); @override Widget build(BuildContext context) { @@ -101,33 +100,27 @@ class StreamPhotoGalleryTile extends StatelessWidget { child: AnimatedOpacity( duration: const Duration(milliseconds: 300), opacity: selected ? 1.0 : 0.0, - child: Container( - color: - // ignore: deprecated_member_use - chatThemeData.colorTheme.textHighEmphasis.withOpacity(0.5), - alignment: Alignment.topRight, - padding: const EdgeInsets.only( - top: 8, - right: 8, - ), - child: CircleAvatar( - radius: 12, - backgroundColor: chatThemeData.colorTheme.barsBg, - child: StreamSvgIcon( - size: 24, - icon: StreamSvgIcons.check, - color: chatThemeData.colorTheme.textHighEmphasis, - ), + child: DecoratedBox( + decoration: BoxDecoration( + // ignore: deprecated_member_use + color: chatThemeData.colorTheme.textHighEmphasis.withOpacity(0.15), ), ), ), ), ), + Positioned( + top: 8, + right: 8, + child: IgnorePointer( + child: _GallerySelectedIndicator(selected: selected), + ), + ), if (media.type == AssetType.video) ...[ - const Positioned( + Positioned( left: 8, bottom: 10, - child: StreamSvgIcon(icon: StreamSvgIcons.videoCall), + child: Icon(context.streamIcons.videoSolid), ), Positioned( right: 4, @@ -155,6 +148,34 @@ class StreamPhotoGalleryTile extends StatelessWidget { } } +class _GallerySelectedIndicator extends StatelessWidget { + const _GallerySelectedIndicator({required this.selected}); + + final bool selected; + + @override + Widget build(BuildContext context) { + return AnimatedContainer( + duration: const Duration(milliseconds: 200), + width: 24, + height: 24, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: selected ? const Color(0xFF005FFF) : Colors.transparent, + border: Border.all(color: Colors.white, width: 2), + ), + child: selected + ? Icon( + context.streamIcons.checkmark1Small, + fontWeight: FontWeight.w900, + size: 12, + color: Colors.white, + ) + : null, + ); + } +} + extension on Duration { String format() { final s = '$this'.split('.')[0].padLeft(8, '0'); @@ -251,7 +272,8 @@ class MediaThumbnailProvider extends ImageProvider { int get hashCode => Object.hash(media, size, format, quality, scale); @override - String toString() => '$runtimeType(' + String toString() => + '$runtimeType(' 'media: $media, ' 'size: $size, ' 'format: $format, ' diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/poll_vote_scroll_view/stream_poll_vote_list_tile.dart b/packages/stream_chat_flutter/lib/src/scroll_view/poll_vote_scroll_view/stream_poll_vote_list_tile.dart index 82ad280d74..1e9cad5cc8 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/poll_vote_scroll_view/stream_poll_vote_list_tile.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/poll_vote_scroll_view/stream_poll_vote_list_tile.dart @@ -53,17 +53,16 @@ class StreamPollVoteListTile extends StatelessWidget { Color? tileColor, BorderRadiusGeometry? borderRadius, EdgeInsetsGeometry? contentPadding, - }) => - StreamPollVoteListTile( - key: key ?? this.key, - pollVote: pollVote ?? this.pollVote, - showAnswerText: showAnswerText ?? this.showAnswerText, - onTap: onTap ?? this.onTap, - onLongPress: onLongPress ?? this.onLongPress, - tileColor: tileColor ?? this.tileColor, - borderRadius: borderRadius ?? this.borderRadius, - contentPadding: contentPadding ?? this.contentPadding, - ); + }) => StreamPollVoteListTile( + key: key ?? this.key, + pollVote: pollVote ?? this.pollVote, + showAnswerText: showAnswerText ?? this.showAnswerText, + onTap: onTap ?? this.onTap, + onLongPress: onLongPress ?? this.onLongPress, + tileColor: tileColor ?? this.tileColor, + borderRadius: borderRadius ?? this.borderRadius, + contentPadding: contentPadding ?? this.contentPadding, + ); @override Widget build(BuildContext context) { @@ -81,8 +80,7 @@ class StreamPollVoteListTile extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (pollVote.answerText case final answerText? - when showAnswerText) ...[ + if (pollVote.answerText case final answerText? when showAnswerText) ...[ Text( answerText, style: theme.textTheme.headlineBold.copyWith( @@ -95,10 +93,9 @@ class StreamPollVoteListTile extends StatelessWidget { children: [ if (pollVote.user case final user?) ...[ StreamUserAvatar( + size: .xs, user: user, - constraints: - BoxConstraints.tight(const Size.fromRadius(10)), - showOnlineStatus: false, + showOnlineIndicator: false, ), Expanded( child: Padding( @@ -118,7 +115,7 @@ class StreamPollVoteListTile extends StatelessWidget { dateTime: pollVote.updatedAt.toLocal(), ), ], - ) + ), ], ), ), diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/poll_vote_scroll_view/stream_poll_vote_list_view.dart b/packages/stream_chat_flutter/lib/src/scroll_view/poll_vote_scroll_view/stream_poll_vote_list_view.dart index 13aecdb471..c7e99d6751 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/poll_vote_scroll_view/stream_poll_vote_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/poll_vote_scroll_view/stream_poll_vote_list_view.dart @@ -1,6 +1,5 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/src/icons/stream_svg_icon.dart'; import 'package:stream_chat_flutter/src/scroll_view/poll_vote_scroll_view/stream_poll_vote_list_tile.dart'; import 'package:stream_chat_flutter/src/scroll_view/stream_scroll_view_empty_widget.dart'; import 'package:stream_chat_flutter/src/scroll_view/stream_scroll_view_error_widget.dart'; @@ -11,19 +10,19 @@ import 'package:stream_chat_flutter/src/scroll_view/stream_scroll_view_loading_w import 'package:stream_chat_flutter/src/theme/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/utils/extensions.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; /// Default separator builder for [StreamPollVoteListView]. Widget defaultPollVoteListViewSeparatorBuilder( BuildContext context, List pollVotes, int index, -) => - const SizedBox(height: 8); +) => const SizedBox(height: 8); /// Signature for the item builder that creates the children of the /// [StreamPollVoteListView]. -typedef StreamPollVoteListViewIndexedWidgetBuilder - = StreamScrollViewIndexedWidgetBuilder; +typedef StreamPollVoteListViewIndexedWidgetBuilder = + StreamScrollViewIndexedWidgetBuilder; /// {@template streamPollVoteListView} /// A [ListView] that shows a list of [PollVote] for a poll. It uses a @@ -283,87 +282,85 @@ class StreamPollVoteListView extends StatelessWidget { @override Widget build(BuildContext context) => PagedValueListView( - scrollDirection: scrollDirection, - padding: padding, - physics: physics, - reverse: reverse, - controller: controller, - scrollController: scrollController, - primary: primary, - shrinkWrap: shrinkWrap, - addAutomaticKeepAlives: addAutomaticKeepAlives, - addRepaintBoundaries: addRepaintBoundaries, - addSemanticIndexes: addSemanticIndexes, - keyboardDismissBehavior: keyboardDismissBehavior, - restorationId: restorationId, - dragStartBehavior: dragStartBehavior, - cacheExtent: cacheExtent, - clipBehavior: clipBehavior, - loadMoreTriggerIndex: loadMoreTriggerIndex, - separatorBuilder: separatorBuilder, - itemBuilder: (context, pollVotes, index) { - final pollVote = pollVotes[index]; - final onTap = onPollVoteTap; - final onLongPress = onPollVoteLongPress; + scrollDirection: scrollDirection, + padding: padding, + physics: physics, + reverse: reverse, + controller: controller, + scrollController: scrollController, + primary: primary, + shrinkWrap: shrinkWrap, + addAutomaticKeepAlives: addAutomaticKeepAlives, + addRepaintBoundaries: addRepaintBoundaries, + addSemanticIndexes: addSemanticIndexes, + keyboardDismissBehavior: keyboardDismissBehavior, + restorationId: restorationId, + dragStartBehavior: dragStartBehavior, + cacheExtent: cacheExtent, + clipBehavior: clipBehavior, + loadMoreTriggerIndex: loadMoreTriggerIndex, + separatorBuilder: separatorBuilder, + itemBuilder: (context, pollVotes, index) { + final pollVote = pollVotes[index]; + final onTap = onPollVoteTap; + final onLongPress = onPollVoteLongPress; - final streamPollVoteListTile = StreamPollVoteListTile( - pollVote: pollVote, - onTap: onTap == null ? null : () => onTap(pollVote), - onLongPress: - onLongPress == null ? null : () => onLongPress(pollVote), - ); + final streamPollVoteListTile = StreamPollVoteListTile( + pollVote: pollVote, + onTap: onTap == null ? null : () => onTap(pollVote), + onLongPress: onLongPress == null ? null : () => onLongPress(pollVote), + ); - return itemBuilder?.call( - context, - pollVotes, - index, - streamPollVoteListTile, - ) ?? - streamPollVoteListTile; - }, - emptyBuilder: (context) { - final chatThemeData = StreamChatTheme.of(context); - return emptyBuilder?.call(context) ?? - Center( - child: Padding( - padding: const EdgeInsets.all(8), - child: StreamScrollViewEmptyWidget( - emptyIcon: StreamSvgIcon( - size: 148, - icon: StreamSvgIcons.polls, - color: chatThemeData.colorTheme.disabled, - ), - emptyTitle: Text( - context.translations.noPollVotesLabel, - style: chatThemeData.textTheme.headline, - ), - ), + return itemBuilder?.call( + context, + pollVotes, + index, + streamPollVoteListTile, + ) ?? + streamPollVoteListTile; + }, + emptyBuilder: (context) { + final chatThemeData = StreamChatTheme.of(context); + return emptyBuilder?.call(context) ?? + Center( + child: Padding( + padding: const EdgeInsets.all(8), + child: StreamScrollViewEmptyWidget( + emptyIcon: Icon( + context.streamIcons.chart5, + size: 148, + color: chatThemeData.colorTheme.disabled, + ), + emptyTitle: Text( + context.translations.noPollVotesLabel, + style: chatThemeData.textTheme.headline, ), - ); - }, - loadMoreErrorBuilder: (context, error) => - StreamScrollViewLoadMoreError.list( - onTap: controller.retry, - error: Text(context.translations.loadingPollVotesError), + ), + ), + ); + }, + loadMoreErrorBuilder: (context, error) => StreamScrollViewLoadMoreError.list( + onTap: controller.retry, + error: Text(context.translations.loadingPollVotesError), + ), + loadMoreIndicatorBuilder: (context) => const Center( + child: Padding( + padding: EdgeInsets.all(16), + child: StreamScrollViewLoadMoreIndicator(), + ), + ), + loadingBuilder: (context) => + loadingBuilder?.call(context) ?? + const Center( + child: StreamScrollViewLoadingWidget(), ), - loadMoreIndicatorBuilder: (context) => const Center( - child: Padding( - padding: EdgeInsets.all(16), - child: StreamScrollViewLoadMoreIndicator(), + errorBuilder: (context, error) => + errorBuilder?.call(context, error) ?? + Center( + child: StreamScrollViewErrorWidget( + errorTitle: Text(context.translations.loadingPollVotesError), + onRetryPressed: controller.refresh, ), ), - loadingBuilder: (context) => - loadingBuilder?.call(context) ?? - const Center( - child: StreamScrollViewLoadingWidget(), - ), - errorBuilder: (context, error) => - errorBuilder?.call(context, error) ?? - Center( - child: StreamScrollViewErrorWidget( - errorTitle: Text(context.translations.loadingPollVotesError), - onRetryPressed: controller.refresh, - ), - ), - ); + ); } diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/reaction_scroll_view/stream_reaction_list_view.dart b/packages/stream_chat_flutter/lib/src/scroll_view/reaction_scroll_view/stream_reaction_list_view.dart new file mode 100644 index 0000000000..914dac212c --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/scroll_view/reaction_scroll_view/stream_reaction_list_view.dart @@ -0,0 +1,224 @@ +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/src/scroll_view/stream_scroll_view_error_widget.dart'; +import 'package:stream_chat_flutter/src/scroll_view/stream_scroll_view_load_more_error.dart'; +import 'package:stream_chat_flutter/src/scroll_view/stream_scroll_view_load_more_indicator.dart'; +import 'package:stream_chat_flutter/src/scroll_view/stream_scroll_view_loading_widget.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +/// Default separator builder for [StreamReactionListView]. +Widget defaultReactionListViewSeparatorBuilder( + BuildContext context, + List reactions, + int index, +) => const SizedBox.shrink(); + +/// Signature for the item builder that creates the children of the +/// [StreamReactionListView]. +typedef StreamReactionListViewIndexedWidgetBuilder = PagedValueScrollViewIndexedWidgetBuilder; + +/// {@template streamReactionListView} +/// A [ListView] that shows a list of [Reaction]s. It uses a +/// [StreamReactionListController] to load the reactions in paginated form. +/// +/// Example: +/// +/// ```dart +/// StreamReactionListView( +/// controller: controller, +/// itemBuilder: (context, reactions, index) { +/// final reaction = reactions[index]; +/// return ListTile(title: Text(reaction.type)); +/// }, +/// ) +/// ``` +/// +/// See also: +/// * [StreamReactionListController] +/// {@endtemplate} +class StreamReactionListView extends StatelessWidget { + /// Creates a new instance of [StreamReactionListView]. + const StreamReactionListView({ + super.key, + required this.controller, + required this.itemBuilder, + this.separatorBuilder = defaultReactionListViewSeparatorBuilder, + this.emptyBuilder, + this.loadingBuilder, + this.errorBuilder, + this.loadMoreTriggerIndex = 3, + this.scrollDirection = Axis.vertical, + this.reverse = false, + this.scrollController, + this.primary, + this.physics, + this.shrinkWrap = false, + this.padding, + this.addAutomaticKeepAlives = true, + this.addRepaintBoundaries = true, + this.addSemanticIndexes = true, + this.cacheExtent, + this.dragStartBehavior = DragStartBehavior.start, + this.keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual, + this.restorationId, + this.clipBehavior = Clip.hardEdge, + }); + + /// The [StreamReactionListController] used to control the list of reactions. + final StreamReactionListController controller; + + /// A builder that is called to build items in the [ListView]. + final StreamReactionListViewIndexedWidgetBuilder itemBuilder; + + /// A builder that is called to build the list separator. + final PagedValueScrollViewIndexedWidgetBuilder separatorBuilder; + + /// A builder that is called to build the empty state of the list. + final WidgetBuilder? emptyBuilder; + + /// A builder that is called to build the loading state of the list. + final WidgetBuilder? loadingBuilder; + + /// A builder that is called to build the error state of the list. + final Widget Function(BuildContext, StreamChatError)? errorBuilder; + + /// The index to take into account when triggering [controller.loadMore]. + final int loadMoreTriggerIndex; + + /// {@template flutter.widgets.scroll_view.scrollDirection} + /// The axis along which the scroll view scrolls. + /// + /// Defaults to [Axis.vertical]. + /// {@endtemplate} + final Axis scrollDirection; + + /// The amount of space by which to inset the children. + final EdgeInsetsGeometry? padding; + + /// Whether to wrap each child in an [AutomaticKeepAlive]. + /// + /// Defaults to true. + final bool addAutomaticKeepAlives; + + /// Whether to wrap each child in a [RepaintBoundary]. + /// + /// Defaults to true. + final bool addRepaintBoundaries; + + /// Whether to wrap each child in an [IndexedSemantics]. + /// + /// Defaults to true. + final bool addSemanticIndexes; + + /// {@template flutter.widgets.scroll_view.reverse} + /// Whether the scroll view scrolls in the reading direction. + /// + /// Defaults to false. + /// {@endtemplate} + final bool reverse; + + /// {@template flutter.widgets.scroll_view.controller} + /// An object that can be used to control the position to which this scroll + /// view is scrolled. + /// {@endtemplate} + final ScrollController? scrollController; + + /// {@template flutter.widgets.scroll_view.primary} + /// Whether this is the primary scroll view associated with the parent + /// [PrimaryScrollController]. + /// {@endtemplate} + final bool? primary; + + /// {@template flutter.widgets.scroll_view.shrinkWrap} + /// Whether the extent of the scroll view in the [scrollDirection] should be + /// determined by the contents being viewed. + /// + /// Defaults to false. + /// {@endtemplate} + final bool shrinkWrap; + + /// {@template flutter.widgets.scroll_view.physics} + /// How the scroll view should respond to user input. + /// {@endtemplate} + final ScrollPhysics? physics; + + /// {@macro flutter.rendering.RenderViewportBase.cacheExtent} + final double? cacheExtent; + + /// {@macro flutter.widgets.scrollable.dragStartBehavior} + final DragStartBehavior dragStartBehavior; + + /// {@template flutter.widgets.scroll_view.keyboardDismissBehavior} + /// [ScrollViewKeyboardDismissBehavior] the defines how this [ScrollView] will + /// dismiss the keyboard automatically. + /// {@endtemplate} + final ScrollViewKeyboardDismissBehavior keyboardDismissBehavior; + + /// {@macro flutter.widgets.scrollable.restorationId} + final String? restorationId; + + /// {@macro flutter.material.Material.clipBehavior} + /// + /// Defaults to [Clip.hardEdge]. + final Clip clipBehavior; + + @override + Widget build(BuildContext context) => PagedValueListView( + scrollDirection: scrollDirection, + padding: padding, + physics: physics, + reverse: reverse, + controller: controller, + scrollController: scrollController, + primary: primary, + shrinkWrap: shrinkWrap, + addAutomaticKeepAlives: addAutomaticKeepAlives, + addRepaintBoundaries: addRepaintBoundaries, + addSemanticIndexes: addSemanticIndexes, + keyboardDismissBehavior: keyboardDismissBehavior, + restorationId: restorationId, + dragStartBehavior: dragStartBehavior, + cacheExtent: cacheExtent, + clipBehavior: clipBehavior, + loadMoreTriggerIndex: loadMoreTriggerIndex, + separatorBuilder: separatorBuilder, + itemBuilder: itemBuilder, + emptyBuilder: (context) => + emptyBuilder?.call(context) ?? + Center( + child: Padding( + padding: const EdgeInsets.all(8), + child: StreamScrollViewEmptyWidget( + emptyIcon: Icon( + context.streamIcons.emojiSmile, + size: 148, + color: StreamChatTheme.of(context).colorTheme.disabled, + ), + emptyTitle: Text( + 'No reactions yet', + style: StreamChatTheme.of(context).textTheme.headline, + ), + ), + ), + ), + loadMoreErrorBuilder: (context, error) => StreamScrollViewLoadMoreError.list( + onTap: controller.retry, + error: const Text('Error loading reactions'), + ), + loadMoreIndicatorBuilder: (context) => const Center( + child: Padding( + padding: EdgeInsets.all(16), + child: StreamScrollViewLoadMoreIndicator(), + ), + ), + loadingBuilder: (context) => loadingBuilder?.call(context) ?? const Center(child: StreamScrollViewLoadingWidget()), + errorBuilder: (context, error) => + errorBuilder?.call(context, error) ?? + Center( + child: StreamScrollViewErrorWidget( + errorTitle: const Text('Error loading reactions'), + onRetryPressed: controller.refresh, + ), + ), + ); +} diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/stream_scroll_view_error_widget.dart b/packages/stream_chat_flutter/lib/src/scroll_view/stream_scroll_view_error_widget.dart index 3dafd08931..94801f87c0 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/stream_scroll_view_error_widget.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/stream_scroll_view_error_widget.dart @@ -52,7 +52,8 @@ class StreamScrollViewErrorWidget extends StatelessWidget { final errorIcon = AnimatedSwitcher( duration: kThemeChangeDuration, - child: this.errorIcon ?? + child: + this.errorIcon ?? Icon( Icons.error_outline_rounded, size: 148, @@ -67,7 +68,8 @@ class StreamScrollViewErrorWidget extends StatelessWidget { ); final retryButtonText = AnimatedDefaultTextStyle( - style: errorTitleStyle ?? + style: + errorTitleStyle ?? chatThemeData.textTheme.headline.copyWith( color: Colors.white, ), diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/stream_scroll_view_indexed_widget_builder.dart b/packages/stream_chat_flutter/lib/src/scroll_view/stream_scroll_view_indexed_widget_builder.dart index 0305cd1f20..9d669f04d0 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/stream_scroll_view_indexed_widget_builder.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/stream_scroll_view_indexed_widget_builder.dart @@ -5,11 +5,10 @@ import 'package:flutter/material.dart'; /// /// Used by [StreamChannelListView], [StreamMessageSearchListView] /// and [StreamUserListView]. -typedef StreamScrollViewIndexedWidgetBuilder - = Widget Function( - BuildContext context, - List items, - int index, - WidgetType defaultWidget, -); +typedef StreamScrollViewIndexedWidgetBuilder = + Widget Function( + BuildContext context, + List items, + int index, + WidgetType defaultWidget, + ); diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/stream_scroll_view_load_more_error.dart b/packages/stream_chat_flutter/lib/src/scroll_view/stream_scroll_view_load_more_error.dart index 0b32058559..9cef925306 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/stream_scroll_view_load_more_error.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/stream_scroll_view_load_more_error.dart @@ -74,14 +74,16 @@ class StreamScrollViewLoadMoreError extends StatelessWidget { final errorIcon = AnimatedSwitcher( duration: kThemeChangeDuration, - child: this.errorIcon ?? - const StreamSvgIcon( + child: + this.errorIcon ?? + Icon( + context.streamIcons.arrowRotateClockwise, color: Colors.white, - icon: StreamSvgIcons.retry, ), ); - final backgroundColor = this.backgroundColor ?? + final backgroundColor = + this.backgroundColor ?? // ignore: deprecated_member_use theme.colorTheme.textLowEmphasis.withOpacity(0.9); diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/stream_scroll_view_load_more_indicator.dart b/packages/stream_chat_flutter/lib/src/scroll_view/stream_scroll_view_load_more_indicator.dart index 7258522590..ab32318d69 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/stream_scroll_view_load_more_indicator.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/stream_scroll_view_load_more_indicator.dart @@ -18,8 +18,8 @@ class StreamScrollViewLoadMoreIndicator extends StatelessWidget { @override Widget build(BuildContext context) => SizedBox( - height: height, - width: width, - child: const CircularProgressIndicator.adaptive(), - ); + height: height, + width: width, + child: const CircularProgressIndicator.adaptive(), + ); } diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/stream_scroll_view_loading_widget.dart b/packages/stream_chat_flutter/lib/src/scroll_view/stream_scroll_view_loading_widget.dart index 958eb80dc4..b18cab4d55 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/stream_scroll_view_loading_widget.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/stream_scroll_view_loading_widget.dart @@ -17,8 +17,8 @@ class StreamScrollViewLoadingWidget extends StatelessWidget { @override Widget build(BuildContext context) => SizedBox( - height: height, - width: width, - child: const CircularProgressIndicator.adaptive(), - ); + height: height, + width: width, + child: const CircularProgressIndicator.adaptive(), + ); } diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_thread_list_tile.dart b/packages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_thread_list_tile.dart index 0617a81305..1db1e2a69b 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_thread_list_tile.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_thread_list_tile.dart @@ -38,9 +38,7 @@ class StreamThreadListTile extends StatelessWidget { final theme = StreamThreadListTileTheme.of(context); final language = currentUser?.language; - final unreadMessageCount = thread.read - ?.firstWhereOrNull((read) => read.user.id == currentUser?.id) - ?.unreadMessages; + final unreadMessageCount = thread.read?.firstWhereOrNull((read) => read.user.id == currentUser?.id)?.unreadMessages; return Material( color: theme.backgroundColor, @@ -68,8 +66,7 @@ class StreamThreadListTile extends StatelessWidget { parentMessage: parentMessage, ), ), - if (unreadMessageCount case final count? when count > 0) - ThreadUnreadCount(unreadCount: count), + if (unreadMessageCount case final count? when count > 0) ThreadUnreadCount(unreadCount: count), ], ), if (thread.latestReplies.lastOrNull case final latestReply?) @@ -226,7 +223,7 @@ class ThreadLatestReply extends StatelessWidget { return Row( spacing: 8, children: [ - if (latestReply.user case final user?) StreamUserAvatar(user: user), + if (latestReply.user case final user?) StreamUserAvatar(size: .md, user: user), Expanded( child: Column( mainAxisSize: MainAxisSize.min, diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_thread_list_view.dart b/packages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_thread_list_view.dart index b8b2d293e3..6fc1fea02e 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_thread_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_thread_list_view.dart @@ -11,13 +11,11 @@ Widget defaultThreadListViewSeparatorBuilder( BuildContext context, List threads, int index, -) => - const StreamThreadListSeparator(); +) => const StreamThreadListSeparator(); /// Signature for the item builder that creates the children of the /// [StreamThreadListView]. -typedef StreamThreadListViewIndexedWidgetBuilder - = StreamScrollViewIndexedWidgetBuilder; +typedef StreamThreadListViewIndexedWidgetBuilder = StreamScrollViewIndexedWidgetBuilder; /// {@template streamThreadListView} /// A [ListView] that shows a list of [Thread]'s. It uses a @@ -318,9 +316,9 @@ class StreamThreadListView extends StatelessWidget { child: Padding( padding: const EdgeInsets.all(8), child: StreamScrollViewEmptyWidget( - emptyIcon: StreamSvgIcon( + emptyIcon: Icon( + context.streamIcons.bubbleAnnotation2ChatMessage, size: 148, - icon: StreamSvgIcons.threadReply, color: chatThemeData.colorTheme.disabled, ), emptyTitle: Text( @@ -331,8 +329,7 @@ class StreamThreadListView extends StatelessWidget { ), ); }, - loadMoreErrorBuilder: (context, error) => - StreamScrollViewLoadMoreError.list( + loadMoreErrorBuilder: (context, error) => StreamScrollViewLoadMoreError.list( onTap: controller.retry, error: Text(context.translations.loadingMessagesError), ), diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_unread_threads_banner.dart b/packages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_unread_threads_banner.dart index 088203d4fd..5ba12dad9a 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_unread_threads_banner.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_unread_threads_banner.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/src/icons/stream_svg_icon.dart'; import 'package:stream_chat_flutter/src/misc/empty_widget.dart'; import 'package:stream_chat_flutter/src/theme/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/utils/extensions.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; /// {@template unreadThreadsBanner} /// A widget that shows a banner with the number of unread threads. @@ -74,8 +74,8 @@ class StreamUnreadThreadsBanner extends StatelessWidget { ), ), ), - StreamSvgIcon( - icon: StreamSvgIcons.reload, + Icon( + context.streamIcons.arrowRotateRightLeftRepeatRefresh, color: theme.colorTheme.barsBg, ), ], diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/user_scroll_view/stream_user_grid_tile.dart b/packages/stream_chat_flutter/lib/src/scroll_view/user_scroll_view/stream_user_grid_tile.dart index 4d512f05ea..0a3a331365 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/user_scroll_view/stream_user_grid_tile.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/user_scroll_view/stream_user_grid_tile.dart @@ -45,33 +45,21 @@ class StreamUserGridTile extends StatelessWidget { Widget? footer, GestureTapCallback? onTap, GestureLongPressCallback? onLongPress, - }) => - StreamUserGridTile( - key: key ?? this.key, - user: user ?? this.user, - footer: footer ?? this.footer, - onTap: onTap ?? this.onTap, - onLongPress: onLongPress ?? this.onLongPress, - child: child ?? this.child, - ); + }) => StreamUserGridTile( + key: key ?? this.key, + user: user ?? this.user, + footer: footer ?? this.footer, + onTap: onTap ?? this.onTap, + onLongPress: onLongPress ?? this.onLongPress, + child: child ?? this.child, + ); @override Widget build(BuildContext context) { - final child = this.child ?? - StreamUserAvatar( - user: user, - borderRadius: BorderRadius.circular(32), - constraints: const BoxConstraints.tightFor( - height: 64, - width: 64, - ), - onlineIndicatorConstraints: const BoxConstraints.tightFor( - height: 12, - width: 12, - ), - ); + final child = this.child ?? StreamUserAvatar(size: .xl, user: user); - final footer = this.footer ?? + final footer = + this.footer ?? Padding( padding: const EdgeInsets.symmetric(horizontal: 8), child: Text( diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/user_scroll_view/stream_user_grid_view.dart b/packages/stream_chat_flutter/lib/src/scroll_view/user_scroll_view/stream_user_grid_view.dart index 5aac833955..0c5702e3ca 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/user_scroll_view/stream_user_grid_view.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/user_scroll_view/stream_user_grid_view.dart @@ -7,13 +7,11 @@ import 'package:stream_chat_flutter/src/scroll_view/stream_scroll_view_loading_w import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// Default grid delegate for [StreamUserGridView]. -const defaultUserGridViewDelegate = - SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 4); +const defaultUserGridViewDelegate = SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 4); /// Signature for the item builder that creates the children of the /// [StreamUserGridView]. -typedef StreamUserGridViewIndexedWidgetBuilder - = StreamScrollViewIndexedWidgetBuilder; +typedef StreamUserGridViewIndexedWidgetBuilder = StreamScrollViewIndexedWidgetBuilder; /// A [GridView] that shows a grid of [User]s, /// it uses [StreamUserGridTile] as a default item. @@ -349,9 +347,9 @@ class StreamUserGridView extends StatelessWidget { child: Padding( padding: const EdgeInsets.all(8), child: StreamScrollViewEmptyWidget( - emptyIcon: StreamSvgIcon( + emptyIcon: Icon( + context.streamIcons.people, size: 148, - icon: StreamSvgIcons.user, color: chatThemeData.colorTheme.disabled, ), emptyTitle: Text( @@ -362,8 +360,7 @@ class StreamUserGridView extends StatelessWidget { ), ); }, - loadMoreErrorBuilder: (context, error) => - StreamScrollViewLoadMoreError.grid( + loadMoreErrorBuilder: (context, error) => StreamScrollViewLoadMoreError.grid( onTap: controller.retry, error: Text( context.translations.loadingUsersError, diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/user_scroll_view/stream_user_list_tile.dart b/packages/stream_chat_flutter/lib/src/scroll_view/user_scroll_view/stream_user_list_tile.dart index 32129c0110..fafa1e6575 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/user_scroll_view/stream_user_list_tile.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/user_scroll_view/stream_user_list_tile.dart @@ -105,49 +105,44 @@ class StreamUserListTile extends StatelessWidget { Color? tileColor, VisualDensity? visualDensity, EdgeInsetsGeometry? contentPadding, - }) => - StreamUserListTile( - key: key ?? this.key, - user: user ?? this.user, - leading: leading ?? this.leading, - title: title ?? this.title, - subtitle: subtitle ?? this.subtitle, - selectedWidget: selectedWidget ?? this.selectedWidget, - selected: selected ?? this.selected, - onTap: onTap ?? this.onTap, - onLongPress: onLongPress ?? this.onLongPress, - tileColor: tileColor ?? this.tileColor, - visualDensity: visualDensity ?? this.visualDensity, - contentPadding: contentPadding ?? this.contentPadding, - ); + }) => StreamUserListTile( + key: key ?? this.key, + user: user ?? this.user, + leading: leading ?? this.leading, + title: title ?? this.title, + subtitle: subtitle ?? this.subtitle, + selectedWidget: selectedWidget ?? this.selectedWidget, + selected: selected ?? this.selected, + onTap: onTap ?? this.onTap, + onLongPress: onLongPress ?? this.onLongPress, + tileColor: tileColor ?? this.tileColor, + visualDensity: visualDensity ?? this.visualDensity, + contentPadding: contentPadding ?? this.contentPadding, + ); @override Widget build(BuildContext context) { final chatThemeData = StreamChatTheme.of(context); - final leading = this.leading ?? - StreamUserAvatar( - user: user, - constraints: const BoxConstraints.tightFor( - height: 40, - width: 40, - ), - ); + final leading = this.leading ?? StreamUserAvatar(size: .lg, user: user); - final title = this.title ?? + final title = + this.title ?? Text( user.name, style: chatThemeData.textTheme.bodyBold, ); - final subtitle = this.subtitle ?? + final subtitle = + this.subtitle ?? UserLastActive( user: user, ); - final selectedWidget = this.selectedWidget ?? - StreamSvgIcon( - icon: StreamSvgIcons.checkSend, + final selectedWidget = + this.selectedWidget ?? + Icon( + context.streamIcons.circleCheck, color: chatThemeData.colorTheme.accentPrimary, ); @@ -184,7 +179,7 @@ class UserLastActive extends StatelessWidget { user.online ? context.translations.userOnlineText : '${context.translations.userLastOnlineText} ' - '${Jiffy.parseFromDateTime(lastActive).fromNow()}', + '${Jiffy.parseFromDateTime(lastActive).fromNow()}', style: chatTheme.textTheme.footnote.copyWith( // ignore: deprecated_member_use color: chatTheme.colorTheme.textHighEmphasis.withOpacity(0.5), diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/user_scroll_view/stream_user_list_view.dart b/packages/stream_chat_flutter/lib/src/scroll_view/user_scroll_view/stream_user_list_view.dart index df2530aad0..2a0ae29137 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/user_scroll_view/stream_user_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/user_scroll_view/stream_user_list_view.dart @@ -11,13 +11,11 @@ Widget defaultUserListViewSeparatorBuilder( BuildContext context, List users, int index, -) => - const StreamUserListSeparator(); +) => const StreamUserListSeparator(); /// Signature for the item builder that creates the children of the /// [StreamUserListView]. -typedef StreamUserListViewIndexedWidgetBuilder - = StreamScrollViewIndexedWidgetBuilder; +typedef StreamUserListViewIndexedWidgetBuilder = StreamScrollViewIndexedWidgetBuilder; /// A [ListView] that shows a list of [User]s, /// it uses [StreamUserListTile] as a default item. @@ -278,88 +276,87 @@ class StreamUserListView extends StatelessWidget { @override Widget build(BuildContext context) => PagedValueListView( - scrollDirection: scrollDirection, - padding: padding, - physics: physics, - reverse: reverse, - controller: controller, - scrollController: scrollController, - primary: primary, - shrinkWrap: shrinkWrap, - addAutomaticKeepAlives: addAutomaticKeepAlives, - addRepaintBoundaries: addRepaintBoundaries, - addSemanticIndexes: addSemanticIndexes, - keyboardDismissBehavior: keyboardDismissBehavior, - restorationId: restorationId, - dragStartBehavior: dragStartBehavior, - cacheExtent: cacheExtent, - clipBehavior: clipBehavior, - loadMoreTriggerIndex: loadMoreTriggerIndex, - separatorBuilder: separatorBuilder, - itemBuilder: (context, users, index) { - final user = users[index]; - final onTap = onUserTap; - final onLongPress = onUserLongPress; - - final streamUserListTile = StreamUserListTile( - user: user, - onTap: onTap == null ? null : () => onTap(user), - onLongPress: onLongPress == null ? null : () => onLongPress(user), - ); + scrollDirection: scrollDirection, + padding: padding, + physics: physics, + reverse: reverse, + controller: controller, + scrollController: scrollController, + primary: primary, + shrinkWrap: shrinkWrap, + addAutomaticKeepAlives: addAutomaticKeepAlives, + addRepaintBoundaries: addRepaintBoundaries, + addSemanticIndexes: addSemanticIndexes, + keyboardDismissBehavior: keyboardDismissBehavior, + restorationId: restorationId, + dragStartBehavior: dragStartBehavior, + cacheExtent: cacheExtent, + clipBehavior: clipBehavior, + loadMoreTriggerIndex: loadMoreTriggerIndex, + separatorBuilder: separatorBuilder, + itemBuilder: (context, users, index) { + final user = users[index]; + final onTap = onUserTap; + final onLongPress = onUserLongPress; + + final streamUserListTile = StreamUserListTile( + user: user, + onTap: onTap == null ? null : () => onTap(user), + onLongPress: onLongPress == null ? null : () => onLongPress(user), + ); - return itemBuilder?.call( - context, - users, - index, - streamUserListTile, - ) ?? - streamUserListTile; - }, - emptyBuilder: (context) { - final chatThemeData = StreamChatTheme.of(context); - return emptyBuilder?.call(context) ?? - Center( - child: Padding( - padding: const EdgeInsets.all(8), - child: StreamScrollViewEmptyWidget( - emptyIcon: StreamSvgIcon( - size: 148, - icon: StreamSvgIcons.user, - color: chatThemeData.colorTheme.disabled, - ), - emptyTitle: Text( - context.translations.noUsersLabel, - style: chatThemeData.textTheme.headline, - ), - ), + return itemBuilder?.call( + context, + users, + index, + streamUserListTile, + ) ?? + streamUserListTile; + }, + emptyBuilder: (context) { + final chatThemeData = StreamChatTheme.of(context); + return emptyBuilder?.call(context) ?? + Center( + child: Padding( + padding: const EdgeInsets.all(8), + child: StreamScrollViewEmptyWidget( + emptyIcon: Icon( + context.streamIcons.people, + size: 148, + color: chatThemeData.colorTheme.disabled, + ), + emptyTitle: Text( + context.translations.noUsersLabel, + style: chatThemeData.textTheme.headline, ), - ); - }, - loadMoreErrorBuilder: (context, error) => - StreamScrollViewLoadMoreError.list( - onTap: controller.retry, - error: Text(context.translations.loadingUsersError), + ), + ), + ); + }, + loadMoreErrorBuilder: (context, error) => StreamScrollViewLoadMoreError.list( + onTap: controller.retry, + error: Text(context.translations.loadingUsersError), + ), + loadMoreIndicatorBuilder: (context) => const Center( + child: Padding( + padding: EdgeInsets.all(16), + child: StreamScrollViewLoadMoreIndicator(), + ), + ), + loadingBuilder: (context) => + loadingBuilder?.call(context) ?? + const Center( + child: StreamScrollViewLoadingWidget(), ), - loadMoreIndicatorBuilder: (context) => const Center( - child: Padding( - padding: EdgeInsets.all(16), - child: StreamScrollViewLoadMoreIndicator(), + errorBuilder: (context, error) => + errorBuilder?.call(context, error) ?? + Center( + child: StreamScrollViewErrorWidget( + errorTitle: Text(context.translations.loadingUsersError), + onRetryPressed: controller.refresh, ), ), - loadingBuilder: (context) => - loadingBuilder?.call(context) ?? - const Center( - child: StreamScrollViewLoadingWidget(), - ), - errorBuilder: (context, error) => - errorBuilder?.call(context, error) ?? - Center( - child: StreamScrollViewErrorWidget( - errorTitle: Text(context.translations.loadingUsersError), - onRetryPressed: controller.refresh, - ), - ), - ); + ); } /// A widget that is used to display a separator between diff --git a/packages/stream_chat_flutter/lib/src/stream_chat.dart b/packages/stream_chat_flutter/lib/src/stream_chat.dart index 33aef8d964..0ed8077047 100644 --- a/packages/stream_chat_flutter/lib/src/stream_chat.dart +++ b/packages/stream_chat_flutter/lib/src/stream_chat.dart @@ -37,6 +37,7 @@ class StreamChat extends StatefulWidget { required this.child, this.streamChatThemeData, this.streamChatConfigData, + this.componentBuilders, this.onBackgroundEventReceived, this.backgroundKeepAlive = const Duration(minutes: 1), this.connectivityStream, @@ -54,6 +55,36 @@ class StreamChat extends StatefulWidget { /// Non-theme related UI configuration options. final StreamChatConfigurationData? streamChatConfigData; + /// Custom component builders for overriding default UI components. + /// + /// When provided, a [StreamComponentFactory] is inserted into the widget + /// tree below the theme and above [StreamChatCore], allowing all descendant + /// widgets to resolve custom builders. + /// + /// {@tool snippet} + /// + /// Override the default message widget with a custom builder: + /// + /// ```dart + /// StreamChat( + /// client: client, + /// componentBuilders: StreamComponentBuilders( + /// extensions: streamChatComponentBuilders( + /// messageWidget: (context, props) { + /// return DefaultStreamMessage( + /// props: props.copyWith( + /// actionsBuilder: myActionsBuilder, + /// ), + /// ); + /// }, + /// ), + /// ), + /// child: MyApp(), + /// ) + /// ``` + /// {@end-tool} + final StreamComponentBuilders? componentBuilders; + /// The amount of time that will pass before disconnecting the client /// in the background final Duration backgroundKeepAlive; @@ -141,8 +172,7 @@ class StreamChatState extends State { StreamChatClient get client => widget.client; /// Gets configuration options from widget - StreamChatConfigurationData get streamChatConfigData => - widget.streamChatConfigData ?? StreamChatConfigurationData(); + StreamChatConfigurationData get streamChatConfigData => widget.streamChatConfigData ?? StreamChatConfigurationData(); @override void initState() { @@ -156,39 +186,39 @@ class StreamChatState extends State { @override Widget build(BuildContext context) { final theme = _getTheme(context, widget.streamChatThemeData); - return Portal( - child: StreamChatConfiguration( - data: streamChatConfigData, - child: StreamChatTheme( - data: theme, - child: Builder( - builder: (context) { - final materialTheme = Theme.of(context); - final streamTheme = StreamChatTheme.of(context); - return Theme( - data: materialTheme.copyWith( - primaryIconTheme: streamTheme.primaryIconTheme, - colorScheme: materialTheme.colorScheme.copyWith( - secondary: streamTheme.colorTheme.accentPrimary, - ), - ), - child: StreamChatCore( - client: client, - onBackgroundEventReceived: widget.onBackgroundEventReceived, - backgroundKeepAlive: widget.backgroundKeepAlive, - connectivityStream: widget.connectivityStream, - child: Builder( - builder: (context) { - return widget.child ?? const Empty(); - }, - ), - ), - ); - }, - ), - ), + + Widget child = StreamChatTheme( + data: theme, + child: Builder( + builder: (context) { + final materialTheme = Theme.of(context); + final streamTheme = StreamChatTheme.of(context); + return Theme( + data: materialTheme.copyWith( + primaryIconTheme: streamTheme.primaryIconTheme, + colorScheme: materialTheme.colorScheme.copyWith( + secondary: streamTheme.colorTheme.accentPrimary, + ), + ), + child: StreamChatCore( + client: client, + onBackgroundEventReceived: widget.onBackgroundEventReceived, + backgroundKeepAlive: widget.backgroundKeepAlive, + connectivityStream: widget.connectivityStream, + child: widget.child ?? const Empty(), + ), + ); + }, ), ); + + if (widget.componentBuilders case final builders?) { + child = StreamComponentFactory(builders: builders, child: child); + } + + return Portal( + child: StreamChatConfiguration(data: streamChatConfigData, child: child), + ); } StreamChatThemeData _getTheme( @@ -208,8 +238,7 @@ class StreamChatState extends State { @override void didChangeDependencies() { - final currentLocale = - Localizations.localeOf(context).toString().toLowerCase(); + final currentLocale = Localizations.localeOf(context).toString().toLowerCase(); final availableLocales = Jiffy.getSupportedLocales(); if (availableLocales.contains(currentLocale)) { Jiffy.setLocale(currentLocale); diff --git a/packages/stream_chat_flutter/lib/src/stream_chat_configuration.dart b/packages/stream_chat_flutter/lib/src/stream_chat_configuration.dart index 946f41f70e..bc9c5207c5 100644 --- a/packages/stream_chat_flutter/lib/src/stream_chat_configuration.dart +++ b/packages/stream_chat_flutter/lib/src/stream_chat_configuration.dart @@ -18,8 +18,7 @@ class StreamChatConfiguration extends InheritedWidget { final StreamChatConfigurationData data; @override - bool updateShouldNotify(StreamChatConfiguration oldWidget) => - data != oldWidget.data; + bool updateShouldNotify(StreamChatConfiguration oldWidget) => data != oldWidget.data; /// Finds the [StreamChatConfigurationData] from the closest /// [StreamChatConfiguration] ancestor that encloses the given context. @@ -81,8 +80,7 @@ class StreamChatConfiguration extends InheritedWidget { /// See also: /// * [of], which throws if no [StreamChatConfiguration] is found. static StreamChatConfigurationData? maybeOf(BuildContext context) { - final streamChatConfiguration = - context.dependOnInheritedWidgetOfExactType(); + final streamChatConfiguration = context.dependOnInheritedWidgetOfExactType(); return streamChatConfiguration?.data; } } @@ -163,20 +161,27 @@ class StreamChatConfigurationData { Widget loadingIndicator = const StreamLoadingIndicator(), Widget Function(BuildContext, User)? defaultUserImage, Widget Function(BuildContext, User)? placeholderUserImage, - List? reactionIcons, + ReactionIconResolver? reactionIconResolver, bool? enforceUniqueReactions, bool draftMessagesEnabled = false, MessagePreviewFormatter? messagePreviewFormatter, + StreamImageCDN imageCDN = const StreamImageCDN(), + List? attachmentBuilders, + StreamReactionsType? reactionType, + StreamReactionsPosition? reactionPosition, }) { return StreamChatConfigurationData._( loadingIndicator: loadingIndicator, defaultUserImage: defaultUserImage ?? _defaultUserImage, placeholderUserImage: placeholderUserImage, - reactionIcons: reactionIcons ?? StreamReactionIcon.defaultReactions, + reactionIconResolver: reactionIconResolver ?? const DefaultReactionIconResolver(), enforceUniqueReactions: enforceUniqueReactions ?? true, draftMessagesEnabled: draftMessagesEnabled, - messagePreviewFormatter: - messagePreviewFormatter ?? MessagePreviewFormatter(), + messagePreviewFormatter: messagePreviewFormatter ?? MessagePreviewFormatter(), + imageCDN: imageCDN, + attachmentBuilders: attachmentBuilders, + reactionType: reactionType, + reactionPosition: reactionPosition, ); } @@ -184,10 +189,14 @@ class StreamChatConfigurationData { required this.loadingIndicator, required this.defaultUserImage, required this.placeholderUserImage, - required this.reactionIcons, + required this.reactionIconResolver, required this.enforceUniqueReactions, required this.draftMessagesEnabled, required this.messagePreviewFormatter, + required this.imageCDN, + required this.attachmentBuilders, + this.reactionType, + this.reactionPosition, }); /// Copies the configuration options from one [StreamChatConfigurationData] to @@ -196,21 +205,27 @@ class StreamChatConfigurationData { Widget? loadingIndicator, Widget Function(BuildContext, User)? defaultUserImage, Widget Function(BuildContext, User)? placeholderUserImage, - List? reactionIcons, + ReactionIconResolver? reactionIconResolver, bool? enforceUniqueReactions, bool? draftMessagesEnabled, MessagePreviewFormatter? messagePreviewFormatter, + StreamImageCDN? imageCDN, + List? attachmentBuilders, + StreamReactionsType? reactionType, + StreamReactionsPosition? reactionPosition, }) { return StreamChatConfigurationData( - reactionIcons: reactionIcons ?? this.reactionIcons, + reactionIconResolver: reactionIconResolver ?? this.reactionIconResolver, defaultUserImage: defaultUserImage ?? this.defaultUserImage, placeholderUserImage: placeholderUserImage ?? this.placeholderUserImage, loadingIndicator: loadingIndicator ?? this.loadingIndicator, - enforceUniqueReactions: - enforceUniqueReactions ?? this.enforceUniqueReactions, + enforceUniqueReactions: enforceUniqueReactions ?? this.enforceUniqueReactions, draftMessagesEnabled: draftMessagesEnabled ?? this.draftMessagesEnabled, - messagePreviewFormatter: - messagePreviewFormatter ?? this.messagePreviewFormatter, + messagePreviewFormatter: messagePreviewFormatter ?? this.messagePreviewFormatter, + imageCDN: imageCDN ?? this.imageCDN, + attachmentBuilders: attachmentBuilders ?? this.attachmentBuilders, + reactionType: reactionType ?? this.reactionType, + reactionPosition: reactionPosition ?? this.reactionPosition, ); } @@ -228,8 +243,11 @@ class StreamChatConfigurationData { /// The widget that will be built when the user image is loading. final Widget Function(BuildContext, User)? placeholderUserImage; - /// Assets used for rendering reactions. - final List reactionIcons; + /// The resolver used to convert reaction types into widgets and to + /// provide the list of supported/default reaction types. + /// + /// Defaults to [DefaultReactionIconResolver]. + final ReactionIconResolver reactionIconResolver; /// Whether a new reaction should replace the existing one. final bool enforceUniqueReactions; @@ -239,6 +257,33 @@ class StreamChatConfigurationData { /// Defaults to [MessagePreviewFormatter]. final MessagePreviewFormatter messagePreviewFormatter; + /// The image CDN used for generating resized image URLs and stable + /// cache keys. + /// + /// Defaults to [StreamImageCDN], which supports Stream's own CDN. + /// Extend [StreamImageCDN] to customize behavior for a custom CDN. + final StreamImageCDN imageCDN; + + /// Custom attachment builders for rendering attachment widgets in messages. + /// + /// When non-null, these builders are prepended to the default builders + /// based on the [Attachment.type], allowing custom attachment types to be + /// rendered globally across all message widgets. + final List? attachmentBuilders; + + /// The visual type of the reactions display used across all message widgets. + /// + /// When null, the widget resolves its own default + /// ([StreamReactionsType.segmented]). + final StreamReactionsType? reactionType; + + /// Where reactions appear relative to the message bubble across all + /// message widgets. + /// + /// When null, the widget resolves its own default + /// ([StreamReactionsPosition.header]). + final StreamReactionsPosition? reactionPosition; + static Widget _defaultUserImage( BuildContext context, User user, diff --git a/packages/stream_chat_flutter/lib/src/theme/audio_waveform_slider_theme.dart b/packages/stream_chat_flutter/lib/src/theme/audio_waveform_slider_theme.dart deleted file mode 100644 index 0b2b9b5424..0000000000 --- a/packages/stream_chat_flutter/lib/src/theme/audio_waveform_slider_theme.dart +++ /dev/null @@ -1,139 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:flutter/widgets.dart'; -import 'package:stream_chat_flutter/src/theme/audio_waveform_theme.dart'; -import 'package:stream_chat_flutter/src/theme/stream_chat_theme.dart'; - -/// {@template streamAudioWaveformSliderTheme} -/// Overrides the default style of [StreamAudioWaveformSlider] descendants. -/// -/// See also: -/// -/// * [StreamVoiceRecordingAttachmentThemeData], which is used to configure -/// this theme. -/// {@endtemplate} -class StreamAudioWaveformSliderTheme extends InheritedTheme { - /// Creates a [StreamAudioWaveformSliderTheme]. - /// - /// The [data] parameter must not be null. - const StreamAudioWaveformSliderTheme({ - super.key, - required this.data, - required super.child, - }); - - /// The configuration of this theme. - final StreamAudioWaveformSliderThemeData data; - - /// The closest instance of this class that encloses the given context. - /// - /// If there is no enclosing [StreamAudioWaveformSliderTheme] widget, - /// then [StreamAudioWaveformSliderTheme.audioWaveformSliderTheme] is used. - /// - /// Typical usage is as follows: - /// - /// ```dart - /// StreamAudioWaveformSliderTheme theme = - /// StreamAudioWaveformSliderTheme.of(context); - /// ``` - static StreamAudioWaveformSliderThemeData of(BuildContext context) { - final audioWaveformSliderTheme = context - .dependOnInheritedWidgetOfExactType(); - return audioWaveformSliderTheme?.data ?? - StreamChatTheme.of(context).audioWaveformSliderTheme; - } - - @override - Widget wrap(BuildContext context, Widget child) => - StreamAudioWaveformSliderTheme(data: data, child: child); - - @override - bool updateShouldNotify(StreamAudioWaveformSliderTheme oldWidget) => - data != oldWidget.data; -} - -/// {@template streamAudioWaveformSliderThemeData} -/// A style that overrides the default appearance of -/// [StreamAudioWaveformSlider] widgets when used with -/// [StreamAudioWaveformSliderTheme] or with the overall -/// [StreamChatTheme]'s [StreamChatThemeData.audioWaveformSliderTheme]. -/// {@endtemplate} -class StreamAudioWaveformSliderThemeData with Diagnosticable { - /// {@macro streamVoiceRecordingAttachmentThemeData} - const StreamAudioWaveformSliderThemeData({ - this.audioWaveformTheme, - this.thumbColor, - this.thumbBorderColor, - }); - - /// The theme of the audio waveform. - final StreamAudioWaveformThemeData? audioWaveformTheme; - - /// The color of the thumb. - final Color? thumbColor; - - /// The color of the thumb border. - final Color? thumbBorderColor; - - /// A copy of [StreamAudioWaveformSliderThemeData] with specified attributes - /// overridden. - StreamAudioWaveformSliderThemeData copyWith({ - StreamAudioWaveformThemeData? audioWaveformTheme, - Color? thumbColor, - Color? thumbBorderColor, - }) { - return StreamAudioWaveformSliderThemeData( - audioWaveformTheme: audioWaveformTheme ?? this.audioWaveformTheme, - thumbColor: thumbColor ?? this.thumbColor, - thumbBorderColor: thumbBorderColor ?? this.thumbBorderColor, - ); - } - - /// Merges this [StreamPollOptionsDialogThemeData] with the [other]. - StreamAudioWaveformSliderThemeData merge( - StreamAudioWaveformSliderThemeData? other, - ) { - if (other == null) return this; - return copyWith( - audioWaveformTheme: other.audioWaveformTheme, - thumbColor: other.thumbColor, - thumbBorderColor: other.thumbBorderColor, - ); - } - - /// Linearly interpolate between two [StreamPollOptionsDialogThemeData]. - static StreamAudioWaveformSliderThemeData lerp( - StreamAudioWaveformSliderThemeData a, - StreamAudioWaveformSliderThemeData b, - double t, - ) => - StreamAudioWaveformSliderThemeData( - audioWaveformTheme: StreamAudioWaveformThemeData.lerp( - a.audioWaveformTheme!, b.audioWaveformTheme!, t), - thumbColor: Color.lerp(a.thumbColor, b.thumbColor, t), - thumbBorderColor: Color.lerp(a.thumbBorderColor, b.thumbBorderColor, t), - ); - - @override - bool operator ==(Object other) => - identical(this, other) || - other is StreamAudioWaveformSliderThemeData && - other.audioWaveformTheme == audioWaveformTheme && - other.thumbColor == thumbColor && - other.thumbBorderColor == thumbBorderColor; - - @override - int get hashCode => - audioWaveformTheme.hashCode ^ - thumbColor.hashCode ^ - thumbBorderColor.hashCode; - - @override - void debugFillProperties(DiagnosticPropertiesBuilder properties) { - super.debugFillProperties(properties); - properties - ..add(DiagnosticsProperty( - 'audioWaveformTheme', audioWaveformTheme)) - ..add(ColorProperty('thumbColor', thumbColor)) - ..add(ColorProperty('thumbBorderColor', thumbBorderColor)); - } -} diff --git a/packages/stream_chat_flutter/lib/src/theme/audio_waveform_theme.dart b/packages/stream_chat_flutter/lib/src/theme/audio_waveform_theme.dart deleted file mode 100644 index dde8fc78df..0000000000 --- a/packages/stream_chat_flutter/lib/src/theme/audio_waveform_theme.dart +++ /dev/null @@ -1,159 +0,0 @@ -import 'dart:ui'; - -import 'package:flutter/foundation.dart'; -import 'package:flutter/widgets.dart'; -import 'package:stream_chat_flutter/src/theme/stream_chat_theme.dart'; - -/// {@template streamAudioWaveformTheme} -/// Overrides the default style of [StreamAudioWaveform] descendants. -/// -/// See also: -/// -/// * [StreamVoiceRecordingAttachmentThemeData], which is used to configure -/// this theme. -/// {@endtemplate} -class StreamAudioWaveformTheme extends InheritedTheme { - /// Creates a [StreamAudioWaveformTheme]. - /// - /// The [data] parameter must not be null. - const StreamAudioWaveformTheme({ - super.key, - required this.data, - required super.child, - }); - - /// The configuration of this theme. - final StreamAudioWaveformThemeData data; - - /// The closest instance of this class that encloses the given context. - /// - /// If there is no enclosing [StreamAudioWaveformTheme] widget, - /// then [StreamAudioWaveformTheme.audioWaveformSliderTheme] is used. - /// - /// Typical usage is as follows: - /// - /// ```dart - /// StreamAudioWaveformTheme theme = StreamAudioWaveformTheme.of(context); - /// ``` - static StreamAudioWaveformThemeData of(BuildContext context) { - final audioWaveformTheme = - context.dependOnInheritedWidgetOfExactType(); - return audioWaveformTheme?.data ?? - StreamChatTheme.of(context).audioWaveformTheme; - } - - @override - Widget wrap(BuildContext context, Widget child) => - StreamAudioWaveformTheme(data: data, child: child); - - @override - bool updateShouldNotify(StreamAudioWaveformTheme oldWidget) => - data != oldWidget.data; -} - -/// {@template streamVoiceRecordingAttachmentThemeData} -/// A style that overrides the default appearance of -/// [StreamAudioWaveformSlider] widgets when used with -/// [StreamAudioWaveformTheme] or with the overall -/// [StreamChatTheme]'s [StreamChatThemeData.audioWaveformSliderTheme]. -/// {@endtemplate} -class StreamAudioWaveformThemeData with Diagnosticable { - /// {@macro streamAudioWaveformThemeData} - const StreamAudioWaveformThemeData({ - this.color, - this.progressColor, - this.minBarHeight, - this.spacingRatio, - this.heightScale, - }); - - /// The color of the wave bars. - final Color? color; - - /// The color of the progressed wave bars. - final Color? progressColor; - - /// The minimum height of the bars. - final double? minBarHeight; - - /// The ratio of the spacing between the bars. - final double? spacingRatio; - - /// The scale of the height of the bars. - final double? heightScale; - - /// A copy of [StreamAudioWaveformThemeData] with specified attributes - /// overridden. - StreamAudioWaveformThemeData copyWith({ - Color? color, - Color? progressColor, - double? minBarHeight, - double? spacingRatio, - double? heightScale, - }) { - return StreamAudioWaveformThemeData( - color: color ?? this.color, - progressColor: progressColor ?? this.progressColor, - minBarHeight: minBarHeight ?? this.minBarHeight, - spacingRatio: spacingRatio ?? this.spacingRatio, - heightScale: heightScale ?? this.heightScale, - ); - } - - /// Merges this [StreamPollOptionsDialogThemeData] with the [other]. - StreamAudioWaveformThemeData merge( - StreamAudioWaveformThemeData? other, - ) { - if (other == null) return this; - return copyWith( - color: other.color, - progressColor: other.progressColor, - minBarHeight: other.minBarHeight, - spacingRatio: other.spacingRatio, - heightScale: other.heightScale, - ); - } - - /// Linearly interpolate between two [StreamPollOptionsDialogThemeData]. - static StreamAudioWaveformThemeData lerp( - StreamAudioWaveformThemeData a, - StreamAudioWaveformThemeData b, - double t, - ) => - StreamAudioWaveformThemeData( - color: Color.lerp(a.color, b.color, t), - progressColor: Color.lerp(a.progressColor, b.progressColor, t), - minBarHeight: lerpDouble(a.minBarHeight, b.minBarHeight, t), - spacingRatio: lerpDouble(a.spacingRatio, b.spacingRatio, t), - heightScale: lerpDouble(a.heightScale, b.heightScale, t), - ); - - @override - bool operator ==(Object other) => - identical(this, other) || - other is StreamAudioWaveformThemeData && - other.color == color && - other.progressColor == progressColor && - other.minBarHeight == minBarHeight && - other.spacingRatio == spacingRatio && - other.heightScale == heightScale; - - @override - int get hashCode => - color.hashCode ^ - progressColor.hashCode ^ - minBarHeight.hashCode ^ - spacingRatio.hashCode ^ - heightScale.hashCode; - - @override - void debugFillProperties(DiagnosticPropertiesBuilder properties) { - super.debugFillProperties(properties); - properties - ..add(ColorProperty('color', color)) - ..add(ColorProperty('progressColor', progressColor)) - ..add(DoubleProperty('minBarHeight', minBarHeight)) - ..add(DoubleProperty('spacingRatio', spacingRatio)) - ..add(DoubleProperty('heightScale', heightScale)); - } -} diff --git a/packages/stream_chat_flutter/lib/src/theme/avatar_theme.dart b/packages/stream_chat_flutter/lib/src/theme/avatar_theme.dart index dbc36da931..e0e6b24e6a 100644 --- a/packages/stream_chat_flutter/lib/src/theme/avatar_theme.dart +++ b/packages/stream_chat_flutter/lib/src/theme/avatar_theme.dart @@ -10,8 +10,8 @@ class StreamAvatarThemeData with Diagnosticable { const StreamAvatarThemeData({ BoxConstraints? constraints, BorderRadius? borderRadius, - }) : _constraints = constraints, - _borderRadius = borderRadius; + }) : _constraints = constraints, + _borderRadius = borderRadius; final BoxConstraints? _constraints; final BorderRadius? _borderRadius; diff --git a/packages/stream_chat_flutter/lib/src/theme/channel_header_theme.dart b/packages/stream_chat_flutter/lib/src/theme/channel_header_theme.dart index 2df24f685e..d9ea4b009e 100644 --- a/packages/stream_chat_flutter/lib/src/theme/channel_header_theme.dart +++ b/packages/stream_chat_flutter/lib/src/theme/channel_header_theme.dart @@ -35,19 +35,15 @@ class StreamChannelHeaderTheme extends InheritedTheme { /// final theme = ChannelHeaderTheme.of(context); /// ``` static StreamChannelHeaderThemeData of(BuildContext context) { - final channelHeaderTheme = - context.dependOnInheritedWidgetOfExactType(); - return channelHeaderTheme?.data ?? - StreamChatTheme.of(context).channelHeaderTheme; + final channelHeaderTheme = context.dependOnInheritedWidgetOfExactType(); + return channelHeaderTheme?.data ?? StreamChatTheme.of(context).channelHeaderTheme; } @override - Widget wrap(BuildContext context, Widget child) => - StreamChannelHeaderTheme(data: data, child: child); + Widget wrap(BuildContext context, Widget child) => StreamChannelHeaderTheme(data: data, child: child); @override - bool updateShouldNotify(StreamChannelHeaderTheme oldWidget) => - data != oldWidget.data; + bool updateShouldNotify(StreamChannelHeaderTheme oldWidget) => data != oldWidget.data; } /// {@template channel_header_theme_data} @@ -108,8 +104,7 @@ class StreamChannelHeaderThemeData with Diagnosticable { return StreamChannelHeaderThemeData( titleStyle: TextStyle.lerp(a.titleStyle, b.titleStyle, t), subtitleStyle: TextStyle.lerp(a.subtitleStyle, b.subtitleStyle, t), - avatarTheme: - const StreamAvatarThemeData().lerp(a.avatarTheme!, b.avatarTheme!, t), + avatarTheme: const StreamAvatarThemeData().lerp(a.avatarTheme!, b.avatarTheme!, t), color: Color.lerp(a.color, b.color, t), ); } @@ -119,8 +114,7 @@ class StreamChannelHeaderThemeData with Diagnosticable { if (other == null) return this; return copyWith( titleStyle: titleStyle?.merge(other.titleStyle) ?? other.titleStyle, - subtitleStyle: - subtitleStyle?.merge(other.subtitleStyle) ?? other.subtitleStyle, + subtitleStyle: subtitleStyle?.merge(other.subtitleStyle) ?? other.subtitleStyle, avatarTheme: avatarTheme?.merge(other.avatarTheme) ?? other.avatarTheme, color: other.color, ); @@ -137,11 +131,7 @@ class StreamChannelHeaderThemeData with Diagnosticable { color == other.color; @override - int get hashCode => - titleStyle.hashCode ^ - subtitleStyle.hashCode ^ - avatarTheme.hashCode ^ - color.hashCode; + int get hashCode => titleStyle.hashCode ^ subtitleStyle.hashCode ^ avatarTheme.hashCode ^ color.hashCode; @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { diff --git a/packages/stream_chat_flutter/lib/src/theme/channel_list_header_theme.dart b/packages/stream_chat_flutter/lib/src/theme/channel_list_header_theme.dart index 7452065df7..6335400790 100644 --- a/packages/stream_chat_flutter/lib/src/theme/channel_list_header_theme.dart +++ b/packages/stream_chat_flutter/lib/src/theme/channel_list_header_theme.dart @@ -35,19 +35,15 @@ class StreamChannelListHeaderTheme extends InheritedTheme { /// final theme = ChannelListHeaderTheme.of(context); /// ``` static StreamChannelListHeaderThemeData of(BuildContext context) { - final channelListHeaderTheme = context - .dependOnInheritedWidgetOfExactType(); - return channelListHeaderTheme?.data ?? - StreamChatTheme.of(context).channelListHeaderTheme; + final channelListHeaderTheme = context.dependOnInheritedWidgetOfExactType(); + return channelListHeaderTheme?.data ?? StreamChatTheme.of(context).channelListHeaderTheme; } @override - Widget wrap(BuildContext context, Widget child) => - StreamChannelListHeaderTheme(data: data, child: child); + Widget wrap(BuildContext context, Widget child) => StreamChannelListHeaderTheme(data: data, child: child); @override - bool updateShouldNotify(StreamChannelListHeaderTheme oldWidget) => - data != oldWidget.data; + bool updateShouldNotify(StreamChannelListHeaderTheme oldWidget) => data != oldWidget.data; } /// {@template channel_list_header_theme_data} @@ -92,8 +88,7 @@ class StreamChannelListHeaderThemeData with Diagnosticable { double t, ) { return StreamChannelListHeaderThemeData( - avatarTheme: - const StreamAvatarThemeData().lerp(a.avatarTheme!, b.avatarTheme!, t), + avatarTheme: const StreamAvatarThemeData().lerp(a.avatarTheme!, b.avatarTheme!, t), color: Color.lerp(a.color, b.color, t), titleStyle: TextStyle.lerp(a.titleStyle, b.titleStyle, t), ); @@ -121,8 +116,7 @@ class StreamChannelListHeaderThemeData with Diagnosticable { color == other.color; @override - int get hashCode => - titleStyle.hashCode ^ avatarTheme.hashCode ^ color.hashCode; + int get hashCode => titleStyle.hashCode ^ avatarTheme.hashCode ^ color.hashCode; @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { diff --git a/packages/stream_chat_flutter/lib/src/theme/channel_preview_theme.dart b/packages/stream_chat_flutter/lib/src/theme/channel_preview_theme.dart index 3b9a39f658..88ea65595e 100644 --- a/packages/stream_chat_flutter/lib/src/theme/channel_preview_theme.dart +++ b/packages/stream_chat_flutter/lib/src/theme/channel_preview_theme.dart @@ -11,6 +11,9 @@ import 'package:stream_chat_flutter/src/utils/date_formatter.dart'; /// /// * [StreamChannelPreviewThemeData], which is used to configure this theme. /// {@endtemplate} +/// +/// This is deprecated, but currently still used by `StreamChannelInfoBottomSheet`. +@Deprecated('Use StreamChannelListItemTheme instead.') class StreamChannelPreviewTheme extends InheritedTheme { /// Creates a [StreamChannelPreviewTheme]. /// @@ -35,19 +38,15 @@ class StreamChannelPreviewTheme extends InheritedTheme { /// final theme = ChannelPreviewTheme.of(context); /// ``` static StreamChannelPreviewThemeData of(BuildContext context) { - final channelPreviewTheme = - context.dependOnInheritedWidgetOfExactType(); - return channelPreviewTheme?.data ?? - StreamChatTheme.of(context).channelPreviewTheme; + final channelPreviewTheme = context.dependOnInheritedWidgetOfExactType(); + return channelPreviewTheme?.data ?? StreamChatTheme.of(context).channelPreviewTheme; } @override - Widget wrap(BuildContext context, Widget child) => - StreamChannelPreviewTheme(data: data, child: child); + Widget wrap(BuildContext context, Widget child) => StreamChannelPreviewTheme(data: data, child: child); @override - bool updateShouldNotify(StreamChannelPreviewTheme oldWidget) => - data != oldWidget.data; + bool updateShouldNotify(StreamChannelPreviewTheme oldWidget) => data != oldWidget.data; } /// {@template channelPreviewThemeData} @@ -124,8 +123,7 @@ class StreamChannelPreviewThemeData with Diagnosticable { avatarTheme: avatarTheme ?? this.avatarTheme, unreadCounterColor: unreadCounterColor ?? this.unreadCounterColor, indicatorIconSize: indicatorIconSize ?? this.indicatorIconSize, - lastMessageAtFormatter: - lastMessageAtFormatter ?? this.lastMessageAtFormatter, + lastMessageAtFormatter: lastMessageAtFormatter ?? this.lastMessageAtFormatter, ); } @@ -136,17 +134,13 @@ class StreamChannelPreviewThemeData with Diagnosticable { double t, ) { return StreamChannelPreviewThemeData( - avatarTheme: - const StreamAvatarThemeData().lerp(a.avatarTheme!, b.avatarTheme!, t), + avatarTheme: const StreamAvatarThemeData().lerp(a.avatarTheme!, b.avatarTheme!, t), indicatorIconSize: a.indicatorIconSize, - lastMessageAtStyle: - TextStyle.lerp(a.lastMessageAtStyle, b.lastMessageAtStyle, t), + lastMessageAtStyle: TextStyle.lerp(a.lastMessageAtStyle, b.lastMessageAtStyle, t), subtitleStyle: TextStyle.lerp(a.subtitleStyle, b.subtitleStyle, t), titleStyle: TextStyle.lerp(a.titleStyle, b.titleStyle, t), - unreadCounterColor: - Color.lerp(a.unreadCounterColor, b.unreadCounterColor, t), - lastMessageAtFormatter: - t < 0.5 ? a.lastMessageAtFormatter : b.lastMessageAtFormatter, + unreadCounterColor: Color.lerp(a.unreadCounterColor, b.unreadCounterColor, t), + lastMessageAtFormatter: t < 0.5 ? a.lastMessageAtFormatter : b.lastMessageAtFormatter, ); } @@ -155,14 +149,11 @@ class StreamChannelPreviewThemeData with Diagnosticable { if (other == null) return this; return copyWith( titleStyle: titleStyle?.merge(other.titleStyle) ?? other.titleStyle, - subtitleStyle: - subtitleStyle?.merge(other.subtitleStyle) ?? other.subtitleStyle, - lastMessageAtStyle: lastMessageAtStyle?.merge(other.lastMessageAtStyle) ?? - other.lastMessageAtStyle, + subtitleStyle: subtitleStyle?.merge(other.subtitleStyle) ?? other.subtitleStyle, + lastMessageAtStyle: lastMessageAtStyle?.merge(other.lastMessageAtStyle) ?? other.lastMessageAtStyle, avatarTheme: avatarTheme?.merge(other.avatarTheme) ?? other.avatarTheme, unreadCounterColor: other.unreadCounterColor, - lastMessageAtFormatter: - other.lastMessageAtFormatter ?? lastMessageAtFormatter, + lastMessageAtFormatter: other.lastMessageAtFormatter ?? lastMessageAtFormatter, ); } @@ -198,7 +189,6 @@ class StreamChannelPreviewThemeData with Diagnosticable { ..add(DiagnosticsProperty('lastMessageAtStyle', lastMessageAtStyle)) ..add(DiagnosticsProperty('avatarTheme', avatarTheme)) ..add(ColorProperty('unreadCounterColor', unreadCounterColor)) - ..add(DiagnosticsProperty( - 'lastMessageAtFormatter', lastMessageAtFormatter)); + ..add(DiagnosticsProperty('lastMessageAtFormatter', lastMessageAtFormatter)); } } diff --git a/packages/stream_chat_flutter/lib/src/theme/draft_list_tile_theme.dart b/packages/stream_chat_flutter/lib/src/theme/draft_list_tile_theme.dart index 20b8973657..dce3292e6e 100644 --- a/packages/stream_chat_flutter/lib/src/theme/draft_list_tile_theme.dart +++ b/packages/stream_chat_flutter/lib/src/theme/draft_list_tile_theme.dart @@ -29,19 +29,15 @@ class StreamDraftListTileTheme extends InheritedTheme { /// If there is no enclosing [StreamDraftListTileTheme] widget, then /// [StreamChatThemeData.draftListTileTheme] is used. static StreamDraftListTileThemeData of(BuildContext context) { - final draftListTileTheme = - context.dependOnInheritedWidgetOfExactType(); - return draftListTileTheme?.data ?? - StreamChatTheme.of(context).draftListTileTheme; + final draftListTileTheme = context.dependOnInheritedWidgetOfExactType(); + return draftListTileTheme?.data ?? StreamChatTheme.of(context).draftListTileTheme; } @override - Widget wrap(BuildContext context, Widget child) => - StreamDraftListTileTheme(data: data, child: child); + Widget wrap(BuildContext context, Widget child) => StreamDraftListTileTheme(data: data, child: child); @override - bool updateShouldNotify(StreamDraftListTileTheme oldWidget) => - data != oldWidget.data; + bool updateShouldNotify(StreamDraftListTileTheme oldWidget) => data != oldWidget.data; } /// {@template streamDraftListTileThemeData} @@ -101,17 +97,14 @@ class StreamDraftListTileThemeData with Diagnosticable { TextStyle? draftTimestampStyle, DateFormatter? draftTimestampFormatter, Color? draftIconColor, - }) => - StreamDraftListTileThemeData( - padding: padding ?? this.padding, - backgroundColor: backgroundColor ?? this.backgroundColor, - draftChannelNameStyle: - draftChannelNameStyle ?? this.draftChannelNameStyle, - draftMessageStyle: draftMessageStyle ?? this.draftMessageStyle, - draftTimestampStyle: draftTimestampStyle ?? this.draftTimestampStyle, - draftTimestampFormatter: - draftTimestampFormatter ?? this.draftTimestampFormatter, - ); + }) => StreamDraftListTileThemeData( + padding: padding ?? this.padding, + backgroundColor: backgroundColor ?? this.backgroundColor, + draftChannelNameStyle: draftChannelNameStyle ?? this.draftChannelNameStyle, + draftMessageStyle: draftMessageStyle ?? this.draftMessageStyle, + draftTimestampStyle: draftTimestampStyle ?? this.draftTimestampStyle, + draftTimestampFormatter: draftTimestampFormatter ?? this.draftTimestampFormatter, + ); /// Merges this [StreamDraftListTileThemeData] with the [other]. StreamDraftListTileThemeData merge( @@ -133,28 +126,26 @@ class StreamDraftListTileThemeData with Diagnosticable { StreamDraftListTileThemeData? a, StreamDraftListTileThemeData? b, double t, - ) => - StreamDraftListTileThemeData( - padding: EdgeInsetsGeometry.lerp(a?.padding, b?.padding, t), - backgroundColor: Color.lerp(a?.backgroundColor, b?.backgroundColor, t), - draftChannelNameStyle: TextStyle.lerp( - a?.draftChannelNameStyle, - b?.draftChannelNameStyle, - t, - ), - draftMessageStyle: TextStyle.lerp( - a?.draftMessageStyle, - b?.draftMessageStyle, - t, - ), - draftTimestampStyle: TextStyle.lerp( - a?.draftTimestampStyle, - b?.draftTimestampStyle, - t, - ), - draftTimestampFormatter: - t < 0.5 ? a?.draftTimestampFormatter : b?.draftTimestampFormatter, - ); + ) => StreamDraftListTileThemeData( + padding: EdgeInsetsGeometry.lerp(a?.padding, b?.padding, t), + backgroundColor: Color.lerp(a?.backgroundColor, b?.backgroundColor, t), + draftChannelNameStyle: TextStyle.lerp( + a?.draftChannelNameStyle, + b?.draftChannelNameStyle, + t, + ), + draftMessageStyle: TextStyle.lerp( + a?.draftMessageStyle, + b?.draftMessageStyle, + t, + ), + draftTimestampStyle: TextStyle.lerp( + a?.draftTimestampStyle, + b?.draftTimestampStyle, + t, + ), + draftTimestampFormatter: t < 0.5 ? a?.draftTimestampFormatter : b?.draftTimestampFormatter, + ); @override bool operator ==(Object other) => diff --git a/packages/stream_chat_flutter/lib/src/theme/gallery_footer_theme.dart b/packages/stream_chat_flutter/lib/src/theme/gallery_footer_theme.dart index 84e1688a60..48733b8632 100644 --- a/packages/stream_chat_flutter/lib/src/theme/gallery_footer_theme.dart +++ b/packages/stream_chat_flutter/lib/src/theme/gallery_footer_theme.dart @@ -33,19 +33,15 @@ class StreamGalleryFooterTheme extends InheritedTheme { /// ImageFooterTheme theme = ImageFooterTheme.of(context); /// ``` static StreamGalleryFooterThemeData of(BuildContext context) { - final imageFooterTheme = - context.dependOnInheritedWidgetOfExactType(); - return imageFooterTheme?.data ?? - StreamChatTheme.of(context).galleryFooterTheme; + final imageFooterTheme = context.dependOnInheritedWidgetOfExactType(); + return imageFooterTheme?.data ?? StreamChatTheme.of(context).galleryFooterTheme; } @override - Widget wrap(BuildContext context, Widget child) => - StreamGalleryFooterTheme(data: data, child: child); + Widget wrap(BuildContext context, Widget child) => StreamGalleryFooterTheme(data: data, child: child); @override - bool updateShouldNotify(StreamGalleryFooterTheme oldWidget) => - data != oldWidget.data; + bool updateShouldNotify(StreamGalleryFooterTheme oldWidget) => data != oldWidget.data; } /// {@template galleryFooterThemeData} @@ -128,14 +124,10 @@ class StreamGalleryFooterThemeData with Diagnosticable { shareIconColor: shareIconColor ?? this.shareIconColor, titleTextStyle: titleTextStyle ?? this.titleTextStyle, gridIconButtonColor: gridIconButtonColor ?? this.gridIconButtonColor, - bottomSheetBarrierColor: - bottomSheetBarrierColor ?? this.bottomSheetBarrierColor, - bottomSheetBackgroundColor: - bottomSheetBackgroundColor ?? this.bottomSheetBackgroundColor, - bottomSheetPhotosTextStyle: - bottomSheetPhotosTextStyle ?? this.bottomSheetPhotosTextStyle, - bottomSheetCloseIconColor: - bottomSheetCloseIconColor ?? this.bottomSheetCloseIconColor, + bottomSheetBarrierColor: bottomSheetBarrierColor ?? this.bottomSheetBarrierColor, + bottomSheetBackgroundColor: bottomSheetBackgroundColor ?? this.bottomSheetBackgroundColor, + bottomSheetPhotosTextStyle: bottomSheetPhotosTextStyle ?? this.bottomSheetPhotosTextStyle, + bottomSheetCloseIconColor: bottomSheetCloseIconColor ?? this.bottomSheetCloseIconColor, ); } @@ -151,10 +143,8 @@ class StreamGalleryFooterThemeData with Diagnosticable { backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t), shareIconColor: Color.lerp(a.shareIconColor, b.shareIconColor, t), titleTextStyle: TextStyle.lerp(a.titleTextStyle, b.titleTextStyle, t), - gridIconButtonColor: - Color.lerp(a.gridIconButtonColor, b.gridIconButtonColor, t), - bottomSheetBarrierColor: - Color.lerp(a.bottomSheetBarrierColor, b.bottomSheetBarrierColor, t), + gridIconButtonColor: Color.lerp(a.gridIconButtonColor, b.gridIconButtonColor, t), + bottomSheetBarrierColor: Color.lerp(a.bottomSheetBarrierColor, b.bottomSheetBarrierColor, t), bottomSheetBackgroundColor: Color.lerp( a.bottomSheetBackgroundColor, b.bottomSheetBackgroundColor, @@ -222,17 +212,23 @@ class StreamGalleryFooterThemeData with Diagnosticable { ..add(DiagnosticsProperty('titleTextStyle', titleTextStyle)) ..add(ColorProperty('gridIconButtonColor', gridIconButtonColor)) ..add(ColorProperty('bottomSheetBarrierColor', bottomSheetBarrierColor)) - ..add(ColorProperty( - 'bottomSheetBackgroundColor', - bottomSheetBackgroundColor, - )) - ..add(DiagnosticsProperty( - 'bottomSheetPhotosTextStyle', - bottomSheetPhotosTextStyle, - )) - ..add(ColorProperty( - 'bottomSheetCloseIconColor', - bottomSheetCloseIconColor, - )); + ..add( + ColorProperty( + 'bottomSheetBackgroundColor', + bottomSheetBackgroundColor, + ), + ) + ..add( + DiagnosticsProperty( + 'bottomSheetPhotosTextStyle', + bottomSheetPhotosTextStyle, + ), + ) + ..add( + ColorProperty( + 'bottomSheetCloseIconColor', + bottomSheetCloseIconColor, + ), + ); } } diff --git a/packages/stream_chat_flutter/lib/src/theme/gallery_header_theme.dart b/packages/stream_chat_flutter/lib/src/theme/gallery_header_theme.dart index 90977d4c9f..8add8e4775 100644 --- a/packages/stream_chat_flutter/lib/src/theme/gallery_header_theme.dart +++ b/packages/stream_chat_flutter/lib/src/theme/gallery_header_theme.dart @@ -33,19 +33,15 @@ class StreamGalleryHeaderTheme extends InheritedTheme { /// ImageHeaderTheme theme = ImageHeaderTheme.of(context); /// ``` static StreamGalleryHeaderThemeData of(BuildContext context) { - final galleryHeaderTheme = - context.dependOnInheritedWidgetOfExactType(); - return galleryHeaderTheme?.data ?? - StreamChatTheme.of(context).galleryHeaderTheme; + final galleryHeaderTheme = context.dependOnInheritedWidgetOfExactType(); + return galleryHeaderTheme?.data ?? StreamChatTheme.of(context).galleryHeaderTheme; } @override - Widget wrap(BuildContext context, Widget child) => - StreamGalleryHeaderTheme(data: data, child: child); + Widget wrap(BuildContext context, Widget child) => StreamGalleryHeaderTheme(data: data, child: child); @override - bool updateShouldNotify(StreamGalleryHeaderTheme oldWidget) => - data != oldWidget.data; + bool updateShouldNotify(StreamGalleryHeaderTheme oldWidget) => data != oldWidget.data; } /// {@template galleryHeaderThemeData} @@ -111,8 +107,7 @@ class StreamGalleryHeaderThemeData with Diagnosticable { iconMenuPointColor: iconMenuPointColor ?? this.iconMenuPointColor, titleTextStyle: titleTextStyle ?? this.titleTextStyle, subtitleTextStyle: subtitleTextStyle ?? this.subtitleTextStyle, - bottomSheetBarrierColor: - bottomSheetBarrierColor ?? this.bottomSheetBarrierColor, + bottomSheetBarrierColor: bottomSheetBarrierColor ?? this.bottomSheetBarrierColor, ); } @@ -127,13 +122,10 @@ class StreamGalleryHeaderThemeData with Diagnosticable { return StreamGalleryHeaderThemeData( closeButtonColor: Color.lerp(a.closeButtonColor, b.closeButtonColor, t), backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t), - iconMenuPointColor: - Color.lerp(a.iconMenuPointColor, b.iconMenuPointColor, t), + iconMenuPointColor: Color.lerp(a.iconMenuPointColor, b.iconMenuPointColor, t), titleTextStyle: TextStyle.lerp(a.titleTextStyle, b.titleTextStyle, t), - subtitleTextStyle: - TextStyle.lerp(a.subtitleTextStyle, b.subtitleTextStyle, t), - bottomSheetBarrierColor: - Color.lerp(a.bottomSheetBarrierColor, b.bottomSheetBarrierColor, t), + subtitleTextStyle: TextStyle.lerp(a.subtitleTextStyle, b.subtitleTextStyle, t), + bottomSheetBarrierColor: Color.lerp(a.bottomSheetBarrierColor, b.bottomSheetBarrierColor, t), ); } diff --git a/packages/stream_chat_flutter/lib/src/theme/message_input_theme.dart b/packages/stream_chat_flutter/lib/src/theme/message_input_theme.dart index a97df00b1e..728ebd2cfb 100644 --- a/packages/stream_chat_flutter/lib/src/theme/message_input_theme.dart +++ b/packages/stream_chat_flutter/lib/src/theme/message_input_theme.dart @@ -35,19 +35,15 @@ class StreamMessageInputTheme extends InheritedTheme { /// final theme = MessageInputTheme.of(context); /// ``` static StreamMessageInputThemeData of(BuildContext context) { - final messageInputTheme = - context.dependOnInheritedWidgetOfExactType(); - return messageInputTheme?.data ?? - StreamChatTheme.of(context).messageInputTheme; + final messageInputTheme = context.dependOnInheritedWidgetOfExactType(); + return messageInputTheme?.data ?? StreamChatTheme.of(context).messageInputTheme; } @override - Widget wrap(BuildContext context, Widget child) => - StreamMessageInputTheme(data: data, child: child); + Widget wrap(BuildContext context, Widget child) => StreamMessageInputTheme(data: data, child: child); @override - bool updateShouldNotify(StreamMessageInputTheme oldWidget) => - data != oldWidget.data; + bool updateShouldNotify(StreamMessageInputTheme oldWidget) => data != oldWidget.data; } /// {@template messageInputThemeData} @@ -160,13 +156,11 @@ class StreamMessageInputThemeData with Diagnosticable { bool? useSystemAttachmentPicker, }) { return StreamMessageInputThemeData( - sendAnimationDuration: - sendAnimationDuration ?? this.sendAnimationDuration, + sendAnimationDuration: sendAnimationDuration ?? this.sendAnimationDuration, inputBackgroundColor: inputBackgroundColor ?? this.inputBackgroundColor, actionButtonColor: actionButtonColor ?? this.actionButtonColor, sendButtonColor: sendButtonColor ?? this.sendButtonColor, - actionButtonIdleColor: - actionButtonIdleColor ?? this.actionButtonIdleColor, + actionButtonIdleColor: actionButtonIdleColor ?? this.actionButtonIdleColor, linkHighlightColor: linkHighlightColor ?? this.linkHighlightColor, expandButtonColor: expandButtonColor ?? this.expandButtonColor, inputTextStyle: inputTextStyle ?? this.inputTextStyle, @@ -178,8 +172,7 @@ class StreamMessageInputThemeData with Diagnosticable { enableSafeArea: enableSafeArea ?? this.enableSafeArea, elevation: elevation ?? this.elevation, shadow: shadow ?? this.shadow, - useSystemAttachmentPicker: - useSystemAttachmentPicker ?? this.useSystemAttachmentPicker, + useSystemAttachmentPicker: useSystemAttachmentPicker ?? this.useSystemAttachmentPicker, ); } @@ -190,25 +183,17 @@ class StreamMessageInputThemeData with Diagnosticable { double t, ) { return StreamMessageInputThemeData( - actionButtonColor: - Color.lerp(a.actionButtonColor, b.actionButtonColor, t), - actionButtonIdleColor: - Color.lerp(a.actionButtonIdleColor, b.actionButtonIdleColor, t), - activeBorderGradient: - Gradient.lerp(a.activeBorderGradient, b.activeBorderGradient, t), + actionButtonColor: Color.lerp(a.actionButtonColor, b.actionButtonColor, t), + actionButtonIdleColor: Color.lerp(a.actionButtonIdleColor, b.actionButtonIdleColor, t), + activeBorderGradient: Gradient.lerp(a.activeBorderGradient, b.activeBorderGradient, t), borderRadius: BorderRadius.lerp(a.borderRadius, b.borderRadius, t), - expandButtonColor: - Color.lerp(a.expandButtonColor, b.expandButtonColor, t), - linkHighlightColor: - Color.lerp(a.linkHighlightColor, b.linkHighlightColor, t), - idleBorderGradient: - Gradient.lerp(a.idleBorderGradient, b.idleBorderGradient, t), - inputBackgroundColor: - Color.lerp(a.inputBackgroundColor, b.inputBackgroundColor, t), + expandButtonColor: Color.lerp(a.expandButtonColor, b.expandButtonColor, t), + linkHighlightColor: Color.lerp(a.linkHighlightColor, b.linkHighlightColor, t), + idleBorderGradient: Gradient.lerp(a.idleBorderGradient, b.idleBorderGradient, t), + inputBackgroundColor: Color.lerp(a.inputBackgroundColor, b.inputBackgroundColor, t), inputTextStyle: TextStyle.lerp(a.inputTextStyle, b.inputTextStyle, t), sendButtonColor: Color.lerp(a.sendButtonColor, b.sendButtonColor, t), - sendButtonIdleColor: - Color.lerp(a.sendButtonIdleColor, b.sendButtonIdleColor, t), + sendButtonIdleColor: Color.lerp(a.sendButtonIdleColor, b.sendButtonIdleColor, t), sendAnimationDuration: a.sendAnimationDuration, inputDecoration: a.inputDecoration, enableSafeArea: a.enableSafeArea, @@ -228,10 +213,8 @@ class StreamMessageInputThemeData with Diagnosticable { actionButtonIdleColor: other.actionButtonIdleColor, sendButtonColor: other.sendButtonColor, sendButtonIdleColor: other.sendButtonIdleColor, - inputTextStyle: - inputTextStyle?.merge(other.inputTextStyle) ?? other.inputTextStyle, - inputDecoration: inputDecoration?.merge(other.inputDecoration) ?? - other.inputDecoration, + inputTextStyle: inputTextStyle?.merge(other.inputTextStyle) ?? other.inputTextStyle, + inputDecoration: inputDecoration?.merge(other.inputDecoration) ?? other.inputDecoration, activeBorderGradient: other.activeBorderGradient, idleBorderGradient: other.idleBorderGradient, borderRadius: other.borderRadius, @@ -307,7 +290,6 @@ class StreamMessageInputThemeData with Diagnosticable { ..add(DiagnosticsProperty('elevation', elevation)) ..add(DiagnosticsProperty('shadow', shadow)) ..add(DiagnosticsProperty('enableSafeArea', enableSafeArea)) - ..add(DiagnosticsProperty( - 'useSystemAttachmentPicker', useSystemAttachmentPicker)); + ..add(DiagnosticsProperty('useSystemAttachmentPicker', useSystemAttachmentPicker)); } } diff --git a/packages/stream_chat_flutter/lib/src/theme/message_list_view_theme.dart b/packages/stream_chat_flutter/lib/src/theme/message_list_view_theme.dart index d21b3725e9..66eb1b9644 100644 --- a/packages/stream_chat_flutter/lib/src/theme/message_list_view_theme.dart +++ b/packages/stream_chat_flutter/lib/src/theme/message_list_view_theme.dart @@ -33,19 +33,15 @@ class StreamMessageListViewTheme extends InheritedTheme { /// MessageListViewTheme theme = MessageListViewTheme.of(context); /// ``` static StreamMessageListViewThemeData of(BuildContext context) { - final messageListViewTheme = context - .dependOnInheritedWidgetOfExactType(); - return messageListViewTheme?.data ?? - StreamChatTheme.of(context).messageListViewTheme; + final messageListViewTheme = context.dependOnInheritedWidgetOfExactType(); + return messageListViewTheme?.data ?? StreamChatTheme.of(context).messageListViewTheme; } @override - Widget wrap(BuildContext context, Widget child) => - StreamMessageListViewTheme(data: data, child: child); + Widget wrap(BuildContext context, Widget child) => StreamMessageListViewTheme(data: data, child: child); @override - bool updateShouldNotify(StreamMessageListViewTheme oldWidget) => - data != oldWidget.data; + bool updateShouldNotify(StreamMessageListViewTheme oldWidget) => data != oldWidget.data; } /// {@template messageListViewThemeData} diff --git a/packages/stream_chat_flutter/lib/src/theme/message_theme.dart b/packages/stream_chat_flutter/lib/src/theme/message_theme.dart index 62b38d4edd..1ad66765e0 100644 --- a/packages/stream_chat_flutter/lib/src/theme/message_theme.dart +++ b/packages/stream_chat_flutter/lib/src/theme/message_theme.dart @@ -140,29 +140,20 @@ class StreamMessageThemeData with Diagnosticable { createdAtStyle: createdAtStyle ?? this.createdAtStyle, createdAtFormatter: createdAtFormatter ?? this.createdAtFormatter, messageDeletedStyle: messageDeletedStyle ?? this.messageDeletedStyle, - messageBackgroundColor: - messageBackgroundColor ?? this.messageBackgroundColor, - messageBackgroundGradient: - messageBackgroundGradient ?? this.messageBackgroundGradient, + messageBackgroundColor: messageBackgroundColor ?? this.messageBackgroundColor, + messageBackgroundGradient: messageBackgroundGradient ?? this.messageBackgroundGradient, messageBorderColor: messageBorderColor ?? this.messageBorderColor, avatarTheme: avatarTheme ?? this.avatarTheme, repliesStyle: repliesStyle ?? this.repliesStyle, - reactionsBackgroundColor: - reactionsBackgroundColor ?? this.reactionsBackgroundColor, + reactionsBackgroundColor: reactionsBackgroundColor ?? this.reactionsBackgroundColor, reactionsBorderColor: reactionsBorderColor ?? this.reactionsBorderColor, reactionsMaskColor: reactionsMaskColor ?? this.reactionsMaskColor, - urlAttachmentBackgroundColor: - urlAttachmentBackgroundColor ?? this.urlAttachmentBackgroundColor, - urlAttachmentHostStyle: - urlAttachmentHostStyle ?? this.urlAttachmentHostStyle, - urlAttachmentTitleStyle: - urlAttachmentTitleStyle ?? this.urlAttachmentTitleStyle, - urlAttachmentTextStyle: - urlAttachmentTextStyle ?? this.urlAttachmentTextStyle, - urlAttachmentTitleMaxLine: - urlAttachmentTitleMaxLine ?? this.urlAttachmentTitleMaxLine, - urlAttachmentTextMaxLine: - urlAttachmentTextMaxLine ?? this.urlAttachmentTextMaxLine, + urlAttachmentBackgroundColor: urlAttachmentBackgroundColor ?? this.urlAttachmentBackgroundColor, + urlAttachmentHostStyle: urlAttachmentHostStyle ?? this.urlAttachmentHostStyle, + urlAttachmentTitleStyle: urlAttachmentTitleStyle ?? this.urlAttachmentTitleStyle, + urlAttachmentTextStyle: urlAttachmentTextStyle ?? this.urlAttachmentTextStyle, + urlAttachmentTitleMaxLine: urlAttachmentTitleMaxLine ?? this.urlAttachmentTitleMaxLine, + urlAttachmentTextMaxLine: urlAttachmentTextMaxLine ?? this.urlAttachmentTextMaxLine, ); } @@ -173,41 +164,30 @@ class StreamMessageThemeData with Diagnosticable { double t, ) { return StreamMessageThemeData( - avatarTheme: - const StreamAvatarThemeData().lerp(a.avatarTheme!, b.avatarTheme!, t), - messageAuthorStyle: - TextStyle.lerp(a.messageAuthorStyle, b.messageAuthorStyle, t), + avatarTheme: const StreamAvatarThemeData().lerp(a.avatarTheme!, b.avatarTheme!, t), + messageAuthorStyle: TextStyle.lerp(a.messageAuthorStyle, b.messageAuthorStyle, t), createdAtStyle: TextStyle.lerp(a.createdAtStyle, b.createdAtStyle, t), createdAtFormatter: t < 0.5 ? a.createdAtFormatter : b.createdAtFormatter, - messageDeletedStyle: - TextStyle.lerp(a.messageDeletedStyle, b.messageDeletedStyle, t), - messageBackgroundColor: - Color.lerp(a.messageBackgroundColor, b.messageBackgroundColor, t), - messageBackgroundGradient: - t < 0.5 ? a.messageBackgroundGradient : b.messageBackgroundGradient, - messageBorderColor: - Color.lerp(a.messageBorderColor, b.messageBorderColor, t), - messageLinksStyle: - TextStyle.lerp(a.messageLinksStyle, b.messageLinksStyle, t), - messageTextStyle: - TextStyle.lerp(a.messageTextStyle, b.messageTextStyle, t), + messageDeletedStyle: TextStyle.lerp(a.messageDeletedStyle, b.messageDeletedStyle, t), + messageBackgroundColor: Color.lerp(a.messageBackgroundColor, b.messageBackgroundColor, t), + messageBackgroundGradient: t < 0.5 ? a.messageBackgroundGradient : b.messageBackgroundGradient, + messageBorderColor: Color.lerp(a.messageBorderColor, b.messageBorderColor, t), + messageLinksStyle: TextStyle.lerp(a.messageLinksStyle, b.messageLinksStyle, t), + messageTextStyle: TextStyle.lerp(a.messageTextStyle, b.messageTextStyle, t), reactionsBackgroundColor: Color.lerp( a.reactionsBackgroundColor, b.reactionsBackgroundColor, t, ), - reactionsBorderColor: - Color.lerp(a.messageBorderColor, b.reactionsBorderColor, t), - reactionsMaskColor: - Color.lerp(a.reactionsMaskColor, b.reactionsMaskColor, t), + reactionsBorderColor: Color.lerp(a.messageBorderColor, b.reactionsBorderColor, t), + reactionsMaskColor: Color.lerp(a.reactionsMaskColor, b.reactionsMaskColor, t), repliesStyle: TextStyle.lerp(a.repliesStyle, b.repliesStyle, t), urlAttachmentBackgroundColor: Color.lerp( a.urlAttachmentBackgroundColor, b.urlAttachmentBackgroundColor, t, ), - urlAttachmentHostStyle: - TextStyle.lerp(a.urlAttachmentHostStyle, b.urlAttachmentHostStyle, t), + urlAttachmentHostStyle: TextStyle.lerp(a.urlAttachmentHostStyle, b.urlAttachmentHostStyle, t), urlAttachmentTextStyle: TextStyle.lerp( a.urlAttachmentTextStyle, b.urlAttachmentTextStyle, @@ -235,20 +215,13 @@ class StreamMessageThemeData with Diagnosticable { StreamMessageThemeData merge(StreamMessageThemeData? other) { if (other == null) return this; return copyWith( - messageTextStyle: messageTextStyle?.merge(other.messageTextStyle) ?? - other.messageTextStyle, - messageAuthorStyle: messageAuthorStyle?.merge(other.messageAuthorStyle) ?? - other.messageAuthorStyle, - messageLinksStyle: messageLinksStyle?.merge(other.messageLinksStyle) ?? - other.messageLinksStyle, - createdAtStyle: - createdAtStyle?.merge(other.createdAtStyle) ?? other.createdAtStyle, + messageTextStyle: messageTextStyle?.merge(other.messageTextStyle) ?? other.messageTextStyle, + messageAuthorStyle: messageAuthorStyle?.merge(other.messageAuthorStyle) ?? other.messageAuthorStyle, + messageLinksStyle: messageLinksStyle?.merge(other.messageLinksStyle) ?? other.messageLinksStyle, + createdAtStyle: createdAtStyle?.merge(other.createdAtStyle) ?? other.createdAtStyle, createdAtFormatter: other.createdAtFormatter ?? createdAtFormatter, - messageDeletedStyle: - messageDeletedStyle?.merge(other.messageDeletedStyle) ?? - other.messageDeletedStyle, - repliesStyle: - repliesStyle?.merge(other.repliesStyle) ?? other.repliesStyle, + messageDeletedStyle: messageDeletedStyle?.merge(other.messageDeletedStyle) ?? other.messageDeletedStyle, + repliesStyle: repliesStyle?.merge(other.repliesStyle) ?? other.repliesStyle, messageBackgroundColor: other.messageBackgroundColor, messageBackgroundGradient: other.messageBackgroundGradient, messageBorderColor: other.messageBorderColor, @@ -326,36 +299,47 @@ class StreamMessageThemeData with Diagnosticable { ..add(DiagnosticsProperty('messageDeletedStyle', messageDeletedStyle)) ..add(DiagnosticsProperty('repliesStyle', repliesStyle)) ..add(ColorProperty('messageBackgroundColor', messageBackgroundColor)) - ..add(DiagnosticsProperty( - 'messageBackgroundGradient', messageBackgroundGradient)) + ..add(DiagnosticsProperty('messageBackgroundGradient', messageBackgroundGradient)) ..add(ColorProperty('messageBorderColor', messageBorderColor)) ..add(DiagnosticsProperty('avatarTheme', avatarTheme)) ..add(ColorProperty('reactionsBackgroundColor', reactionsBackgroundColor)) ..add(ColorProperty('reactionsBorderColor', reactionsBorderColor)) ..add(ColorProperty('reactionsMaskColor', reactionsMaskColor)) - ..add(ColorProperty( - 'urlAttachmentBackgroundColor', - urlAttachmentBackgroundColor, - )) - ..add(DiagnosticsProperty( - 'urlAttachmentHostStyle', - urlAttachmentHostStyle, - )) - ..add(DiagnosticsProperty( - 'urlAttachmentTitleStyle', - urlAttachmentTitleStyle, - )) - ..add(DiagnosticsProperty( - 'urlAttachmentTextStyle', - urlAttachmentTextStyle, - )) - ..add(DiagnosticsProperty( - 'urlAttachmentTitleMaxLine', - urlAttachmentTitleMaxLine, - )) - ..add(DiagnosticsProperty( - 'urlAttachmentTextMaxLine', - urlAttachmentTextMaxLine, - )); + ..add( + ColorProperty( + 'urlAttachmentBackgroundColor', + urlAttachmentBackgroundColor, + ), + ) + ..add( + DiagnosticsProperty( + 'urlAttachmentHostStyle', + urlAttachmentHostStyle, + ), + ) + ..add( + DiagnosticsProperty( + 'urlAttachmentTitleStyle', + urlAttachmentTitleStyle, + ), + ) + ..add( + DiagnosticsProperty( + 'urlAttachmentTextStyle', + urlAttachmentTextStyle, + ), + ) + ..add( + DiagnosticsProperty( + 'urlAttachmentTitleMaxLine', + urlAttachmentTitleMaxLine, + ), + ) + ..add( + DiagnosticsProperty( + 'urlAttachmentTextMaxLine', + urlAttachmentTextMaxLine, + ), + ); } } diff --git a/packages/stream_chat_flutter/lib/src/theme/poll_comments_dialog_theme.dart b/packages/stream_chat_flutter/lib/src/theme/poll_comments_dialog_theme.dart index 9ad433546e..f404fc6c36 100644 --- a/packages/stream_chat_flutter/lib/src/theme/poll_comments_dialog_theme.dart +++ b/packages/stream_chat_flutter/lib/src/theme/poll_comments_dialog_theme.dart @@ -30,19 +30,15 @@ class StreamPollCommentsDialogTheme extends InheritedTheme { /// If there is no enclosing [StreamPollCommentsDialogTheme] widget, then /// [StreamChatThemeData.pollCommentsDialogTheme] is used. static StreamPollCommentsDialogThemeData of(BuildContext context) { - final pollCommentsDialogTheme = context - .dependOnInheritedWidgetOfExactType(); - return pollCommentsDialogTheme?.data ?? - StreamChatTheme.of(context).pollCommentsDialogTheme; + final pollCommentsDialogTheme = context.dependOnInheritedWidgetOfExactType(); + return pollCommentsDialogTheme?.data ?? StreamChatTheme.of(context).pollCommentsDialogTheme; } @override - Widget wrap(BuildContext context, Widget child) => - StreamPollCommentsDialogTheme(data: data, child: child); + Widget wrap(BuildContext context, Widget child) => StreamPollCommentsDialogTheme(data: data, child: child); @override - bool updateShouldNotify(StreamPollCommentsDialogTheme oldWidget) => - data != oldWidget.data; + bool updateShouldNotify(StreamPollCommentsDialogTheme oldWidget) => data != oldWidget.data; } /// {@template streamPollCommentsDialogThemeData} @@ -97,22 +93,16 @@ class StreamPollCommentsDialogThemeData with Diagnosticable { Color? pollCommentItemBackgroundColor, BorderRadius? pollCommentItemBorderRadius, ButtonStyle? updateYourCommentButtonStyle, - }) => - StreamPollCommentsDialogThemeData( - backgroundColor: backgroundColor ?? this.backgroundColor, - appBarElevation: appBarElevation ?? this.appBarElevation, - appBarBackgroundColor: - appBarBackgroundColor ?? this.appBarBackgroundColor, - appBarForegroundColor: - appBarForegroundColor ?? this.appBarForegroundColor, - appBarTitleTextStyle: appBarTitleTextStyle ?? this.appBarTitleTextStyle, - pollCommentItemBackgroundColor: pollCommentItemBackgroundColor ?? - this.pollCommentItemBackgroundColor, - pollCommentItemBorderRadius: - pollCommentItemBorderRadius ?? this.pollCommentItemBorderRadius, - updateYourCommentButtonStyle: - updateYourCommentButtonStyle ?? this.updateYourCommentButtonStyle, - ); + }) => StreamPollCommentsDialogThemeData( + backgroundColor: backgroundColor ?? this.backgroundColor, + appBarElevation: appBarElevation ?? this.appBarElevation, + appBarBackgroundColor: appBarBackgroundColor ?? this.appBarBackgroundColor, + appBarForegroundColor: appBarForegroundColor ?? this.appBarForegroundColor, + appBarTitleTextStyle: appBarTitleTextStyle ?? this.appBarTitleTextStyle, + pollCommentItemBackgroundColor: pollCommentItemBackgroundColor ?? this.pollCommentItemBackgroundColor, + pollCommentItemBorderRadius: pollCommentItemBorderRadius ?? this.pollCommentItemBorderRadius, + updateYourCommentButtonStyle: updateYourCommentButtonStyle ?? this.updateYourCommentButtonStyle, + ); /// Merges this [StreamPollCommentsDialogThemeData] with the [other]. StreamPollCommentsDialogThemeData merge( @@ -140,12 +130,9 @@ class StreamPollCommentsDialogThemeData with Diagnosticable { return StreamPollCommentsDialogThemeData( backgroundColor: Color.lerp(a?.backgroundColor, b?.backgroundColor, t), appBarElevation: lerpDouble(a?.appBarElevation, b?.appBarElevation, t), - appBarBackgroundColor: - Color.lerp(a?.appBarBackgroundColor, b?.appBarBackgroundColor, t), - appBarForegroundColor: - Color.lerp(a?.appBarForegroundColor, b?.appBarForegroundColor, t), - appBarTitleTextStyle: - TextStyle.lerp(a?.appBarTitleTextStyle, b?.appBarTitleTextStyle, t), + appBarBackgroundColor: Color.lerp(a?.appBarBackgroundColor, b?.appBarBackgroundColor, t), + appBarForegroundColor: Color.lerp(a?.appBarForegroundColor, b?.appBarForegroundColor, t), + appBarTitleTextStyle: TextStyle.lerp(a?.appBarTitleTextStyle, b?.appBarTitleTextStyle, t), pollCommentItemBackgroundColor: Color.lerp( a?.pollCommentItemBackgroundColor, b?.pollCommentItemBackgroundColor, @@ -173,8 +160,7 @@ class StreamPollCommentsDialogThemeData with Diagnosticable { other.appBarBackgroundColor == appBarBackgroundColor && other.appBarForegroundColor == appBarForegroundColor && other.appBarTitleTextStyle == appBarTitleTextStyle && - other.pollCommentItemBackgroundColor == - pollCommentItemBackgroundColor && + other.pollCommentItemBackgroundColor == pollCommentItemBackgroundColor && other.pollCommentItemBorderRadius == pollCommentItemBorderRadius && other.updateYourCommentButtonStyle == updateYourCommentButtonStyle; diff --git a/packages/stream_chat_flutter/lib/src/theme/poll_creator_theme.dart b/packages/stream_chat_flutter/lib/src/theme/poll_creator_theme.dart index 5287547603..940fdbb4e2 100644 --- a/packages/stream_chat_flutter/lib/src/theme/poll_creator_theme.dart +++ b/packages/stream_chat_flutter/lib/src/theme/poll_creator_theme.dart @@ -35,19 +35,15 @@ class StreamPollCreatorTheme extends InheritedTheme { /// StreamPollCreatorTheme theme = StreamPollCreatorTheme.of(context); /// ``` static StreamPollCreatorThemeData of(BuildContext context) { - final pollCreatorTheme = - context.dependOnInheritedWidgetOfExactType(); - return pollCreatorTheme?.data ?? - StreamChatTheme.of(context).pollCreatorTheme; + final pollCreatorTheme = context.dependOnInheritedWidgetOfExactType(); + return pollCreatorTheme?.data ?? StreamChatTheme.of(context).pollCreatorTheme; } @override - Widget wrap(BuildContext context, Widget child) => - StreamPollCreatorTheme(data: data, child: child); + Widget wrap(BuildContext context, Widget child) => StreamPollCreatorTheme(data: data, child: child); @override - bool updateShouldNotify(StreamPollCreatorTheme oldWidget) => - data != oldWidget.data; + bool updateShouldNotify(StreamPollCreatorTheme oldWidget) => data != oldWidget.data; } /// {@template streamPollCreatorThemeData} @@ -173,40 +169,24 @@ class StreamPollCreatorThemeData with Diagnosticable { backgroundColor: backgroundColor ?? this.backgroundColor, appBarTitleStyle: appBarTitleStyle ?? this.appBarTitleStyle, appBarElevation: appBarElevation ?? this.appBarElevation, - appBarBackgroundColor: - appBarBackgroundColor ?? this.appBarBackgroundColor, - appBarForegroundColor: - appBarForegroundColor ?? this.appBarForegroundColor, - questionTextFieldFillColor: - questionTextFieldFillColor ?? this.questionTextFieldFillColor, + appBarBackgroundColor: appBarBackgroundColor ?? this.appBarBackgroundColor, + appBarForegroundColor: appBarForegroundColor ?? this.appBarForegroundColor, + questionTextFieldFillColor: questionTextFieldFillColor ?? this.questionTextFieldFillColor, questionHeaderStyle: questionHeaderStyle ?? this.questionHeaderStyle, - questionTextFieldStyle: - questionTextFieldStyle ?? this.questionTextFieldStyle, - questionTextFieldErrorStyle: - questionTextFieldErrorStyle ?? this.questionTextFieldErrorStyle, - questionTextFieldBorderRadius: - questionTextFieldBorderRadius ?? this.questionTextFieldBorderRadius, - optionsTextFieldFillColor: - optionsTextFieldFillColor ?? this.optionsTextFieldFillColor, + questionTextFieldStyle: questionTextFieldStyle ?? this.questionTextFieldStyle, + questionTextFieldErrorStyle: questionTextFieldErrorStyle ?? this.questionTextFieldErrorStyle, + questionTextFieldBorderRadius: questionTextFieldBorderRadius ?? this.questionTextFieldBorderRadius, + optionsTextFieldFillColor: optionsTextFieldFillColor ?? this.optionsTextFieldFillColor, optionsHeaderStyle: optionsHeaderStyle ?? this.optionsHeaderStyle, - optionsTextFieldStyle: - optionsTextFieldStyle ?? this.optionsTextFieldStyle, - optionsTextFieldErrorStyle: - optionsTextFieldErrorStyle ?? this.optionsTextFieldErrorStyle, - optionsTextFieldBorderRadius: - optionsTextFieldBorderRadius ?? this.optionsTextFieldBorderRadius, - switchListTileFillColor: - switchListTileFillColor ?? this.switchListTileFillColor, - switchListTileTitleStyle: - switchListTileTitleStyle ?? this.switchListTileTitleStyle, - switchListTileErrorStyle: - switchListTileErrorStyle ?? this.switchListTileErrorStyle, - switchListTileBorderRadius: - switchListTileBorderRadius ?? this.switchListTileBorderRadius, - actionDialogTitleStyle: - actionDialogTitleStyle ?? this.actionDialogTitleStyle, - actionDialogContentStyle: - actionDialogContentStyle ?? this.actionDialogContentStyle, + optionsTextFieldStyle: optionsTextFieldStyle ?? this.optionsTextFieldStyle, + optionsTextFieldErrorStyle: optionsTextFieldErrorStyle ?? this.optionsTextFieldErrorStyle, + optionsTextFieldBorderRadius: optionsTextFieldBorderRadius ?? this.optionsTextFieldBorderRadius, + switchListTileFillColor: switchListTileFillColor ?? this.switchListTileFillColor, + switchListTileTitleStyle: switchListTileTitleStyle ?? this.switchListTileTitleStyle, + switchListTileErrorStyle: switchListTileErrorStyle ?? this.switchListTileErrorStyle, + switchListTileBorderRadius: switchListTileBorderRadius ?? this.switchListTileBorderRadius, + actionDialogTitleStyle: actionDialogTitleStyle ?? this.actionDialogTitleStyle, + actionDialogContentStyle: actionDialogContentStyle ?? this.actionDialogContentStyle, ); } @@ -217,40 +197,24 @@ class StreamPollCreatorThemeData with Diagnosticable { backgroundColor: other.backgroundColor ?? backgroundColor, appBarTitleStyle: other.appBarTitleStyle ?? appBarTitleStyle, appBarElevation: other.appBarElevation ?? appBarElevation, - appBarBackgroundColor: - other.appBarBackgroundColor ?? appBarBackgroundColor, - appBarForegroundColor: - other.appBarForegroundColor ?? appBarForegroundColor, - questionTextFieldFillColor: - other.questionTextFieldFillColor ?? questionTextFieldFillColor, + appBarBackgroundColor: other.appBarBackgroundColor ?? appBarBackgroundColor, + appBarForegroundColor: other.appBarForegroundColor ?? appBarForegroundColor, + questionTextFieldFillColor: other.questionTextFieldFillColor ?? questionTextFieldFillColor, questionHeaderStyle: other.questionHeaderStyle ?? questionHeaderStyle, - questionTextFieldStyle: - other.questionTextFieldStyle ?? questionTextFieldStyle, - questionTextFieldErrorStyle: - other.questionTextFieldErrorStyle ?? questionTextFieldErrorStyle, - questionTextFieldBorderRadius: - other.questionTextFieldBorderRadius ?? questionTextFieldBorderRadius, - optionsTextFieldFillColor: - other.optionsTextFieldFillColor ?? optionsTextFieldFillColor, + questionTextFieldStyle: other.questionTextFieldStyle ?? questionTextFieldStyle, + questionTextFieldErrorStyle: other.questionTextFieldErrorStyle ?? questionTextFieldErrorStyle, + questionTextFieldBorderRadius: other.questionTextFieldBorderRadius ?? questionTextFieldBorderRadius, + optionsTextFieldFillColor: other.optionsTextFieldFillColor ?? optionsTextFieldFillColor, optionsHeaderStyle: other.optionsHeaderStyle ?? optionsHeaderStyle, - optionsTextFieldStyle: - other.optionsTextFieldStyle ?? optionsTextFieldStyle, - optionsTextFieldErrorStyle: - other.optionsTextFieldErrorStyle ?? optionsTextFieldErrorStyle, - optionsTextFieldBorderRadius: - other.optionsTextFieldBorderRadius ?? optionsTextFieldBorderRadius, - switchListTileFillColor: - other.switchListTileFillColor ?? switchListTileFillColor, - switchListTileTitleStyle: - other.switchListTileTitleStyle ?? switchListTileTitleStyle, - switchListTileErrorStyle: - other.switchListTileErrorStyle ?? switchListTileErrorStyle, - switchListTileBorderRadius: - other.switchListTileBorderRadius ?? switchListTileBorderRadius, - actionDialogTitleStyle: - other.actionDialogTitleStyle ?? actionDialogTitleStyle, - actionDialogContentStyle: - other.actionDialogContentStyle ?? actionDialogContentStyle, + optionsTextFieldStyle: other.optionsTextFieldStyle ?? optionsTextFieldStyle, + optionsTextFieldErrorStyle: other.optionsTextFieldErrorStyle ?? optionsTextFieldErrorStyle, + optionsTextFieldBorderRadius: other.optionsTextFieldBorderRadius ?? optionsTextFieldBorderRadius, + switchListTileFillColor: other.switchListTileFillColor ?? switchListTileFillColor, + switchListTileTitleStyle: other.switchListTileTitleStyle ?? switchListTileTitleStyle, + switchListTileErrorStyle: other.switchListTileErrorStyle ?? switchListTileErrorStyle, + switchListTileBorderRadius: other.switchListTileBorderRadius ?? switchListTileBorderRadius, + actionDialogTitleStyle: other.actionDialogTitleStyle ?? actionDialogTitleStyle, + actionDialogContentStyle: other.actionDialogContentStyle ?? actionDialogContentStyle, ); } @@ -262,45 +226,34 @@ class StreamPollCreatorThemeData with Diagnosticable { ) { return StreamPollCreatorThemeData( backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t), - appBarTitleStyle: - TextStyle.lerp(a.appBarTitleStyle, b.appBarTitleStyle, t), + appBarTitleStyle: TextStyle.lerp(a.appBarTitleStyle, b.appBarTitleStyle, t), appBarElevation: lerpDouble(a.appBarElevation, b.appBarElevation, t), - appBarBackgroundColor: - Color.lerp(a.appBarBackgroundColor, b.appBarBackgroundColor, t), - appBarForegroundColor: - Color.lerp(a.appBarForegroundColor, b.appBarForegroundColor, t), - questionTextFieldFillColor: Color.lerp( - a.questionTextFieldFillColor, b.questionTextFieldFillColor, t), - questionHeaderStyle: - TextStyle.lerp(a.questionHeaderStyle, b.questionHeaderStyle, t), - questionTextFieldStyle: - TextStyle.lerp(a.questionTextFieldStyle, b.questionTextFieldStyle, t), - questionTextFieldErrorStyle: TextStyle.lerp( - a.questionTextFieldErrorStyle, b.questionTextFieldErrorStyle, t), + appBarBackgroundColor: Color.lerp(a.appBarBackgroundColor, b.appBarBackgroundColor, t), + appBarForegroundColor: Color.lerp(a.appBarForegroundColor, b.appBarForegroundColor, t), + questionTextFieldFillColor: Color.lerp(a.questionTextFieldFillColor, b.questionTextFieldFillColor, t), + questionHeaderStyle: TextStyle.lerp(a.questionHeaderStyle, b.questionHeaderStyle, t), + questionTextFieldStyle: TextStyle.lerp(a.questionTextFieldStyle, b.questionTextFieldStyle, t), + questionTextFieldErrorStyle: TextStyle.lerp(a.questionTextFieldErrorStyle, b.questionTextFieldErrorStyle, t), questionTextFieldBorderRadius: BorderRadius.lerp( - a.questionTextFieldBorderRadius, b.questionTextFieldBorderRadius, t), - optionsTextFieldFillColor: Color.lerp( - a.optionsTextFieldFillColor, b.optionsTextFieldFillColor, t), - optionsHeaderStyle: - TextStyle.lerp(a.optionsHeaderStyle, b.optionsHeaderStyle, t), - optionsTextFieldStyle: - TextStyle.lerp(a.optionsTextFieldStyle, b.optionsTextFieldStyle, t), - optionsTextFieldErrorStyle: TextStyle.lerp( - a.optionsTextFieldErrorStyle, b.optionsTextFieldErrorStyle, t), + a.questionTextFieldBorderRadius, + b.questionTextFieldBorderRadius, + t, + ), + optionsTextFieldFillColor: Color.lerp(a.optionsTextFieldFillColor, b.optionsTextFieldFillColor, t), + optionsHeaderStyle: TextStyle.lerp(a.optionsHeaderStyle, b.optionsHeaderStyle, t), + optionsTextFieldStyle: TextStyle.lerp(a.optionsTextFieldStyle, b.optionsTextFieldStyle, t), + optionsTextFieldErrorStyle: TextStyle.lerp(a.optionsTextFieldErrorStyle, b.optionsTextFieldErrorStyle, t), optionsTextFieldBorderRadius: BorderRadius.lerp( - a.optionsTextFieldBorderRadius, b.optionsTextFieldBorderRadius, t), - switchListTileFillColor: - Color.lerp(a.switchListTileFillColor, b.switchListTileFillColor, t), - switchListTileTitleStyle: TextStyle.lerp( - a.switchListTileTitleStyle, b.switchListTileTitleStyle, t), - switchListTileErrorStyle: TextStyle.lerp( - a.switchListTileErrorStyle, b.switchListTileErrorStyle, t), - switchListTileBorderRadius: BorderRadius.lerp( - a.switchListTileBorderRadius, b.switchListTileBorderRadius, t), - actionDialogTitleStyle: - TextStyle.lerp(a.actionDialogTitleStyle, b.actionDialogTitleStyle, t), - actionDialogContentStyle: TextStyle.lerp( - a.actionDialogContentStyle, b.actionDialogContentStyle, t), + a.optionsTextFieldBorderRadius, + b.optionsTextFieldBorderRadius, + t, + ), + switchListTileFillColor: Color.lerp(a.switchListTileFillColor, b.switchListTileFillColor, t), + switchListTileTitleStyle: TextStyle.lerp(a.switchListTileTitleStyle, b.switchListTileTitleStyle, t), + switchListTileErrorStyle: TextStyle.lerp(a.switchListTileErrorStyle, b.switchListTileErrorStyle, t), + switchListTileBorderRadius: BorderRadius.lerp(a.switchListTileBorderRadius, b.switchListTileBorderRadius, t), + actionDialogTitleStyle: TextStyle.lerp(a.actionDialogTitleStyle, b.actionDialogTitleStyle, t), + actionDialogContentStyle: TextStyle.lerp(a.actionDialogContentStyle, b.actionDialogContentStyle, t), ); } @@ -317,8 +270,7 @@ class StreamPollCreatorThemeData with Diagnosticable { other.questionHeaderStyle == questionHeaderStyle && other.questionTextFieldStyle == questionTextFieldStyle && other.questionTextFieldErrorStyle == questionTextFieldErrorStyle && - other.questionTextFieldBorderRadius == - questionTextFieldBorderRadius && + other.questionTextFieldBorderRadius == questionTextFieldBorderRadius && other.optionsTextFieldFillColor == optionsTextFieldFillColor && other.optionsHeaderStyle == optionsHeaderStyle && other.optionsTextFieldStyle == optionsTextFieldStyle && diff --git a/packages/stream_chat_flutter/lib/src/theme/poll_interactor_theme.dart b/packages/stream_chat_flutter/lib/src/theme/poll_interactor_theme.dart index 1880ad5ce7..5eba4be0ac 100644 --- a/packages/stream_chat_flutter/lib/src/theme/poll_interactor_theme.dart +++ b/packages/stream_chat_flutter/lib/src/theme/poll_interactor_theme.dart @@ -37,19 +37,15 @@ class StreamPollInteractorTheme extends InheritedTheme { /// StreamPollInteractorTheme theme = StreamPollInteractorTheme.of(context); /// ``` static StreamPollInteractorThemeData of(BuildContext context) { - final pollInteractorTheme = - context.dependOnInheritedWidgetOfExactType(); - return pollInteractorTheme?.data ?? - StreamChatTheme.of(context).pollInteractorTheme; + final pollInteractorTheme = context.dependOnInheritedWidgetOfExactType(); + return pollInteractorTheme?.data ?? StreamChatTheme.of(context).pollInteractorTheme; } @override - Widget wrap(BuildContext context, Widget child) => - StreamPollInteractorTheme(data: data, child: child); + Widget wrap(BuildContext context, Widget child) => StreamPollInteractorTheme(data: data, child: child); @override - bool updateShouldNotify(StreamPollInteractorTheme oldWidget) => - data != oldWidget.data; + bool updateShouldNotify(StreamPollInteractorTheme oldWidget) => data != oldWidget.data; } /// {@template streamPollInteractorThemeData} @@ -160,42 +156,27 @@ class StreamPollInteractorThemeData with Diagnosticable { pollTitleStyle: pollTitleStyle ?? this.pollTitleStyle, pollSubtitleStyle: pollSubtitleStyle ?? this.pollSubtitleStyle, pollOptionTextStyle: pollOptionTextStyle ?? this.pollOptionTextStyle, - pollOptionVoteCountTextStyle: - pollOptionVoteCountTextStyle ?? this.pollOptionVoteCountTextStyle, - pollOptionCheckboxShape: - pollOptionCheckboxShape ?? this.pollOptionCheckboxShape, - pollOptionCheckboxCheckColor: - pollOptionCheckboxCheckColor ?? this.pollOptionCheckboxCheckColor, - pollOptionCheckboxActiveColor: - pollOptionCheckboxActiveColor ?? this.pollOptionCheckboxActiveColor, - pollOptionCheckboxBorderSide: - pollOptionCheckboxBorderSide ?? this.pollOptionCheckboxBorderSide, + pollOptionVoteCountTextStyle: pollOptionVoteCountTextStyle ?? this.pollOptionVoteCountTextStyle, + pollOptionCheckboxShape: pollOptionCheckboxShape ?? this.pollOptionCheckboxShape, + pollOptionCheckboxCheckColor: pollOptionCheckboxCheckColor ?? this.pollOptionCheckboxCheckColor, + pollOptionCheckboxActiveColor: pollOptionCheckboxActiveColor ?? this.pollOptionCheckboxActiveColor, + pollOptionCheckboxBorderSide: pollOptionCheckboxBorderSide ?? this.pollOptionCheckboxBorderSide, pollOptionVotesProgressBarMinHeight: - pollOptionVotesProgressBarMinHeight ?? - this.pollOptionVotesProgressBarMinHeight, + pollOptionVotesProgressBarMinHeight ?? this.pollOptionVotesProgressBarMinHeight, pollOptionVotesProgressBarTrackColor: - pollOptionVotesProgressBarTrackColor ?? - this.pollOptionVotesProgressBarTrackColor, + pollOptionVotesProgressBarTrackColor ?? this.pollOptionVotesProgressBarTrackColor, pollOptionVotesProgressBarValueColor: - pollOptionVotesProgressBarValueColor ?? - this.pollOptionVotesProgressBarValueColor, + pollOptionVotesProgressBarValueColor ?? this.pollOptionVotesProgressBarValueColor, pollOptionVotesProgressBarWinnerColor: - pollOptionVotesProgressBarWinnerColor ?? - this.pollOptionVotesProgressBarWinnerColor, + pollOptionVotesProgressBarWinnerColor ?? this.pollOptionVotesProgressBarWinnerColor, pollOptionVotesProgressBarBorderRadius: - pollOptionVotesProgressBarBorderRadius ?? - this.pollOptionVotesProgressBarBorderRadius, - pollActionButtonStyle: - pollActionButtonStyle ?? this.pollActionButtonStyle, - pollActionDialogTitleStyle: - pollActionDialogTitleStyle ?? this.pollActionDialogTitleStyle, - pollActionDialogTextFieldStyle: - pollActionDialogTextFieldStyle ?? this.pollActionDialogTextFieldStyle, - pollActionDialogTextFieldFillColor: pollActionDialogTextFieldFillColor ?? - this.pollActionDialogTextFieldFillColor, + pollOptionVotesProgressBarBorderRadius ?? this.pollOptionVotesProgressBarBorderRadius, + pollActionButtonStyle: pollActionButtonStyle ?? this.pollActionButtonStyle, + pollActionDialogTitleStyle: pollActionDialogTitleStyle ?? this.pollActionDialogTitleStyle, + pollActionDialogTextFieldStyle: pollActionDialogTextFieldStyle ?? this.pollActionDialogTextFieldStyle, + pollActionDialogTextFieldFillColor: pollActionDialogTextFieldFillColor ?? this.pollActionDialogTextFieldFillColor, pollActionDialogTextFieldBorderRadius: - pollActionDialogTextFieldBorderRadius ?? - this.pollActionDialogTextFieldBorderRadius, + pollActionDialogTextFieldBorderRadius ?? this.pollActionDialogTextFieldBorderRadius, ); } @@ -206,43 +187,28 @@ class StreamPollInteractorThemeData with Diagnosticable { pollTitleStyle: other.pollTitleStyle ?? pollTitleStyle, pollSubtitleStyle: other.pollSubtitleStyle ?? pollSubtitleStyle, pollOptionTextStyle: other.pollOptionTextStyle ?? pollOptionTextStyle, - pollOptionVoteCountTextStyle: - other.pollOptionVoteCountTextStyle ?? pollOptionVoteCountTextStyle, - pollOptionCheckboxShape: - other.pollOptionCheckboxShape ?? pollOptionCheckboxShape, - pollOptionCheckboxCheckColor: - other.pollOptionCheckboxCheckColor ?? pollOptionCheckboxCheckColor, - pollOptionCheckboxActiveColor: - other.pollOptionCheckboxActiveColor ?? pollOptionCheckboxActiveColor, - pollOptionCheckboxBorderSide: - other.pollOptionCheckboxBorderSide ?? pollOptionCheckboxBorderSide, + pollOptionVoteCountTextStyle: other.pollOptionVoteCountTextStyle ?? pollOptionVoteCountTextStyle, + pollOptionCheckboxShape: other.pollOptionCheckboxShape ?? pollOptionCheckboxShape, + pollOptionCheckboxCheckColor: other.pollOptionCheckboxCheckColor ?? pollOptionCheckboxCheckColor, + pollOptionCheckboxActiveColor: other.pollOptionCheckboxActiveColor ?? pollOptionCheckboxActiveColor, + pollOptionCheckboxBorderSide: other.pollOptionCheckboxBorderSide ?? pollOptionCheckboxBorderSide, pollOptionVotesProgressBarMinHeight: - other.pollOptionVotesProgressBarMinHeight ?? - pollOptionVotesProgressBarMinHeight, + other.pollOptionVotesProgressBarMinHeight ?? pollOptionVotesProgressBarMinHeight, pollOptionVotesProgressBarTrackColor: - other.pollOptionVotesProgressBarTrackColor ?? - pollOptionVotesProgressBarTrackColor, + other.pollOptionVotesProgressBarTrackColor ?? pollOptionVotesProgressBarTrackColor, pollOptionVotesProgressBarValueColor: - other.pollOptionVotesProgressBarValueColor ?? - pollOptionVotesProgressBarValueColor, + other.pollOptionVotesProgressBarValueColor ?? pollOptionVotesProgressBarValueColor, pollOptionVotesProgressBarWinnerColor: - other.pollOptionVotesProgressBarWinnerColor ?? - pollOptionVotesProgressBarWinnerColor, + other.pollOptionVotesProgressBarWinnerColor ?? pollOptionVotesProgressBarWinnerColor, pollOptionVotesProgressBarBorderRadius: - other.pollOptionVotesProgressBarBorderRadius ?? - pollOptionVotesProgressBarBorderRadius, - pollActionButtonStyle: - other.pollActionButtonStyle ?? pollActionButtonStyle, - pollActionDialogTitleStyle: - other.pollActionDialogTitleStyle ?? pollActionDialogTitleStyle, - pollActionDialogTextFieldStyle: other.pollActionDialogTextFieldStyle ?? - pollActionDialogTextFieldStyle, + other.pollOptionVotesProgressBarBorderRadius ?? pollOptionVotesProgressBarBorderRadius, + pollActionButtonStyle: other.pollActionButtonStyle ?? pollActionButtonStyle, + pollActionDialogTitleStyle: other.pollActionDialogTitleStyle ?? pollActionDialogTitleStyle, + pollActionDialogTextFieldStyle: other.pollActionDialogTextFieldStyle ?? pollActionDialogTextFieldStyle, pollActionDialogTextFieldFillColor: - other.pollActionDialogTextFieldFillColor ?? - pollActionDialogTextFieldFillColor, + other.pollActionDialogTextFieldFillColor ?? pollActionDialogTextFieldFillColor, pollActionDialogTextFieldBorderRadius: - other.pollActionDialogTextFieldBorderRadius ?? - pollActionDialogTextFieldBorderRadius, + other.pollActionDialogTextFieldBorderRadius ?? pollActionDialogTextFieldBorderRadius, ); } @@ -254,56 +220,55 @@ class StreamPollInteractorThemeData with Diagnosticable { ) { return StreamPollInteractorThemeData( pollTitleStyle: TextStyle.lerp(a.pollTitleStyle, b.pollTitleStyle, t), - pollSubtitleStyle: - TextStyle.lerp(a.pollSubtitleStyle, b.pollSubtitleStyle, t), - pollOptionTextStyle: - TextStyle.lerp(a.pollOptionTextStyle, b.pollOptionTextStyle, t), - pollOptionVoteCountTextStyle: TextStyle.lerp( - a.pollOptionVoteCountTextStyle, b.pollOptionVoteCountTextStyle, t), - pollOptionCheckboxShape: OutlinedBorder.lerp( - a.pollOptionCheckboxShape, b.pollOptionCheckboxShape, t), - pollOptionCheckboxCheckColor: Color.lerp( - a.pollOptionCheckboxCheckColor, b.pollOptionCheckboxCheckColor, t), - pollOptionCheckboxActiveColor: Color.lerp( - a.pollOptionCheckboxActiveColor, b.pollOptionCheckboxActiveColor, t), - pollOptionCheckboxBorderSide: _lerpSides( - a.pollOptionCheckboxBorderSide, b.pollOptionCheckboxBorderSide, t), + pollSubtitleStyle: TextStyle.lerp(a.pollSubtitleStyle, b.pollSubtitleStyle, t), + pollOptionTextStyle: TextStyle.lerp(a.pollOptionTextStyle, b.pollOptionTextStyle, t), + pollOptionVoteCountTextStyle: TextStyle.lerp(a.pollOptionVoteCountTextStyle, b.pollOptionVoteCountTextStyle, t), + pollOptionCheckboxShape: OutlinedBorder.lerp(a.pollOptionCheckboxShape, b.pollOptionCheckboxShape, t), + pollOptionCheckboxCheckColor: Color.lerp(a.pollOptionCheckboxCheckColor, b.pollOptionCheckboxCheckColor, t), + pollOptionCheckboxActiveColor: Color.lerp(a.pollOptionCheckboxActiveColor, b.pollOptionCheckboxActiveColor, t), + pollOptionCheckboxBorderSide: _lerpSides(a.pollOptionCheckboxBorderSide, b.pollOptionCheckboxBorderSide, t), pollOptionVotesProgressBarMinHeight: lerpDouble( - a.pollOptionVotesProgressBarMinHeight, - b.pollOptionVotesProgressBarMinHeight, - t), + a.pollOptionVotesProgressBarMinHeight, + b.pollOptionVotesProgressBarMinHeight, + t, + ), pollOptionVotesProgressBarTrackColor: Color.lerp( - a.pollOptionVotesProgressBarTrackColor, - b.pollOptionVotesProgressBarTrackColor, - t), + a.pollOptionVotesProgressBarTrackColor, + b.pollOptionVotesProgressBarTrackColor, + t, + ), pollOptionVotesProgressBarValueColor: Color.lerp( - a.pollOptionVotesProgressBarValueColor, - b.pollOptionVotesProgressBarValueColor, - t), + a.pollOptionVotesProgressBarValueColor, + b.pollOptionVotesProgressBarValueColor, + t, + ), pollOptionVotesProgressBarWinnerColor: Color.lerp( - a.pollOptionVotesProgressBarWinnerColor, - b.pollOptionVotesProgressBarWinnerColor, - t), + a.pollOptionVotesProgressBarWinnerColor, + b.pollOptionVotesProgressBarWinnerColor, + t, + ), pollOptionVotesProgressBarBorderRadius: BorderRadius.lerp( - a.pollOptionVotesProgressBarBorderRadius, - b.pollOptionVotesProgressBarBorderRadius, - t), - pollActionButtonStyle: - ButtonStyle.lerp(a.pollActionButtonStyle, b.pollActionButtonStyle, t), - pollActionDialogTitleStyle: TextStyle.lerp( - a.pollActionDialogTitleStyle, b.pollActionDialogTitleStyle, t), + a.pollOptionVotesProgressBarBorderRadius, + b.pollOptionVotesProgressBarBorderRadius, + t, + ), + pollActionButtonStyle: ButtonStyle.lerp(a.pollActionButtonStyle, b.pollActionButtonStyle, t), + pollActionDialogTitleStyle: TextStyle.lerp(a.pollActionDialogTitleStyle, b.pollActionDialogTitleStyle, t), pollActionDialogTextFieldStyle: TextStyle.lerp( - a.pollActionDialogTextFieldStyle, - b.pollActionDialogTextFieldStyle, - t), + a.pollActionDialogTextFieldStyle, + b.pollActionDialogTextFieldStyle, + t, + ), pollActionDialogTextFieldFillColor: Color.lerp( - a.pollActionDialogTextFieldFillColor, - b.pollActionDialogTextFieldFillColor, - t), + a.pollActionDialogTextFieldFillColor, + b.pollActionDialogTextFieldFillColor, + t, + ), pollActionDialogTextFieldBorderRadius: BorderRadius.lerp( - a.pollActionDialogTextFieldBorderRadius, - b.pollActionDialogTextFieldBorderRadius, - t), + a.pollActionDialogTextFieldBorderRadius, + b.pollActionDialogTextFieldBorderRadius, + t, + ), ); } @@ -332,27 +297,18 @@ class StreamPollInteractorThemeData with Diagnosticable { other.pollOptionVoteCountTextStyle == pollOptionVoteCountTextStyle && other.pollOptionCheckboxShape == pollOptionCheckboxShape && other.pollOptionCheckboxCheckColor == pollOptionCheckboxCheckColor && - other.pollOptionCheckboxActiveColor == - pollOptionCheckboxActiveColor && + other.pollOptionCheckboxActiveColor == pollOptionCheckboxActiveColor && other.pollOptionCheckboxBorderSide == pollOptionCheckboxBorderSide && - other.pollOptionVotesProgressBarMinHeight == - pollOptionVotesProgressBarMinHeight && - other.pollOptionVotesProgressBarTrackColor == - pollOptionVotesProgressBarTrackColor && - other.pollOptionVotesProgressBarValueColor == - pollOptionVotesProgressBarValueColor && - other.pollOptionVotesProgressBarWinnerColor == - pollOptionVotesProgressBarWinnerColor && - other.pollOptionVotesProgressBarBorderRadius == - pollOptionVotesProgressBarBorderRadius && + other.pollOptionVotesProgressBarMinHeight == pollOptionVotesProgressBarMinHeight && + other.pollOptionVotesProgressBarTrackColor == pollOptionVotesProgressBarTrackColor && + other.pollOptionVotesProgressBarValueColor == pollOptionVotesProgressBarValueColor && + other.pollOptionVotesProgressBarWinnerColor == pollOptionVotesProgressBarWinnerColor && + other.pollOptionVotesProgressBarBorderRadius == pollOptionVotesProgressBarBorderRadius && other.pollActionButtonStyle == pollActionButtonStyle && other.pollActionDialogTitleStyle == pollActionDialogTitleStyle && - other.pollActionDialogTextFieldStyle == - pollActionDialogTextFieldStyle && - other.pollActionDialogTextFieldFillColor == - pollActionDialogTextFieldFillColor && - other.pollActionDialogTextFieldBorderRadius == - pollActionDialogTextFieldBorderRadius; + other.pollActionDialogTextFieldStyle == pollActionDialogTextFieldStyle && + other.pollActionDialogTextFieldFillColor == pollActionDialogTextFieldFillColor && + other.pollActionDialogTextFieldBorderRadius == pollActionDialogTextFieldBorderRadius; @override int get hashCode => diff --git a/packages/stream_chat_flutter/lib/src/theme/poll_option_votes_dialog_theme.dart b/packages/stream_chat_flutter/lib/src/theme/poll_option_votes_dialog_theme.dart index 72ee25dc4d..e3b5307426 100644 --- a/packages/stream_chat_flutter/lib/src/theme/poll_option_votes_dialog_theme.dart +++ b/packages/stream_chat_flutter/lib/src/theme/poll_option_votes_dialog_theme.dart @@ -30,19 +30,15 @@ class StreamPollOptionVotesDialogTheme extends InheritedTheme { /// If there is no enclosing [StreamPollOptionVotesDialogTheme] widget, then /// [StreamChatThemeData.pollOptionVotesDialogTheme] is used. static StreamPollOptionVotesDialogThemeData of(BuildContext context) { - final pollCommentsDialogTheme = context - .dependOnInheritedWidgetOfExactType(); - return pollCommentsDialogTheme?.data ?? - StreamChatTheme.of(context).pollOptionVotesDialogTheme; + final pollCommentsDialogTheme = context.dependOnInheritedWidgetOfExactType(); + return pollCommentsDialogTheme?.data ?? StreamChatTheme.of(context).pollOptionVotesDialogTheme; } @override - Widget wrap(BuildContext context, Widget child) => - StreamPollOptionVotesDialogTheme(data: data, child: child); + Widget wrap(BuildContext context, Widget child) => StreamPollOptionVotesDialogTheme(data: data, child: child); @override - bool updateShouldNotify(StreamPollOptionVotesDialogTheme oldWidget) => - data != oldWidget.data; + bool updateShouldNotify(StreamPollOptionVotesDialogTheme oldWidget) => data != oldWidget.data; } /// {@template streamPollOptionVotesDialogThemeData} @@ -103,25 +99,17 @@ class StreamPollOptionVotesDialogThemeData with Diagnosticable { TextStyle? pollOptionWinnerVoteCountTextStyle, Color? pollOptionVoteItemBackgroundColor, BorderRadius? pollOptionVoteItemBorderRadius, - }) => - StreamPollOptionVotesDialogThemeData( - backgroundColor: backgroundColor ?? this.backgroundColor, - appBarElevation: appBarElevation ?? this.appBarElevation, - appBarBackgroundColor: - appBarBackgroundColor ?? this.appBarBackgroundColor, - appBarForegroundColor: - appBarForegroundColor ?? this.appBarForegroundColor, - appBarTitleTextStyle: appBarTitleTextStyle ?? this.appBarTitleTextStyle, - pollOptionVoteCountTextStyle: - pollOptionVoteCountTextStyle ?? this.pollOptionVoteCountTextStyle, - pollOptionWinnerVoteCountTextStyle: - pollOptionWinnerVoteCountTextStyle ?? - this.pollOptionWinnerVoteCountTextStyle, - pollOptionVoteItemBackgroundColor: pollOptionVoteItemBackgroundColor ?? - this.pollOptionVoteItemBackgroundColor, - pollOptionVoteItemBorderRadius: pollOptionVoteItemBorderRadius ?? - this.pollOptionVoteItemBorderRadius, - ); + }) => StreamPollOptionVotesDialogThemeData( + backgroundColor: backgroundColor ?? this.backgroundColor, + appBarElevation: appBarElevation ?? this.appBarElevation, + appBarBackgroundColor: appBarBackgroundColor ?? this.appBarBackgroundColor, + appBarForegroundColor: appBarForegroundColor ?? this.appBarForegroundColor, + appBarTitleTextStyle: appBarTitleTextStyle ?? this.appBarTitleTextStyle, + pollOptionVoteCountTextStyle: pollOptionVoteCountTextStyle ?? this.pollOptionVoteCountTextStyle, + pollOptionWinnerVoteCountTextStyle: pollOptionWinnerVoteCountTextStyle ?? this.pollOptionWinnerVoteCountTextStyle, + pollOptionVoteItemBackgroundColor: pollOptionVoteItemBackgroundColor ?? this.pollOptionVoteItemBackgroundColor, + pollOptionVoteItemBorderRadius: pollOptionVoteItemBorderRadius ?? this.pollOptionVoteItemBorderRadius, + ); /// Merges this [StreamPollOptionVotesDialogThemeData] with the [other]. StreamPollOptionVotesDialogThemeData merge( @@ -135,10 +123,8 @@ class StreamPollOptionVotesDialogThemeData with Diagnosticable { appBarForegroundColor: other.appBarForegroundColor, appBarTitleTextStyle: other.appBarTitleTextStyle, pollOptionVoteCountTextStyle: other.pollOptionVoteCountTextStyle, - pollOptionWinnerVoteCountTextStyle: - other.pollOptionWinnerVoteCountTextStyle, - pollOptionVoteItemBackgroundColor: - other.pollOptionVoteItemBackgroundColor, + pollOptionWinnerVoteCountTextStyle: other.pollOptionWinnerVoteCountTextStyle, + pollOptionVoteItemBackgroundColor: other.pollOptionVoteItemBackgroundColor, pollOptionVoteItemBorderRadius: other.pollOptionVoteItemBorderRadius, ); } @@ -152,12 +138,9 @@ class StreamPollOptionVotesDialogThemeData with Diagnosticable { return StreamPollOptionVotesDialogThemeData( backgroundColor: Color.lerp(a?.backgroundColor, b?.backgroundColor, t), appBarElevation: lerpDouble(a?.appBarElevation, b?.appBarElevation, t), - appBarBackgroundColor: - Color.lerp(a?.appBarBackgroundColor, b?.appBarBackgroundColor, t), - appBarForegroundColor: - Color.lerp(a?.appBarForegroundColor, b?.appBarForegroundColor, t), - appBarTitleTextStyle: - TextStyle.lerp(a?.appBarTitleTextStyle, b?.appBarTitleTextStyle, t), + appBarBackgroundColor: Color.lerp(a?.appBarBackgroundColor, b?.appBarBackgroundColor, t), + appBarForegroundColor: Color.lerp(a?.appBarForegroundColor, b?.appBarForegroundColor, t), + appBarTitleTextStyle: TextStyle.lerp(a?.appBarTitleTextStyle, b?.appBarTitleTextStyle, t), pollOptionVoteCountTextStyle: TextStyle.lerp( a?.pollOptionVoteCountTextStyle, b?.pollOptionVoteCountTextStyle, @@ -173,11 +156,13 @@ class StreamPollOptionVotesDialogThemeData with Diagnosticable { b?.pollOptionVoteItemBackgroundColor, t, ), - pollOptionVoteItemBorderRadius: BorderRadiusGeometry.lerp( - a?.pollOptionVoteItemBorderRadius, - b?.pollOptionVoteItemBorderRadius, - t, - ) as BorderRadius?, + pollOptionVoteItemBorderRadius: + BorderRadiusGeometry.lerp( + a?.pollOptionVoteItemBorderRadius, + b?.pollOptionVoteItemBorderRadius, + t, + ) + as BorderRadius?, ); } @@ -191,12 +176,9 @@ class StreamPollOptionVotesDialogThemeData with Diagnosticable { other.appBarForegroundColor == appBarForegroundColor && other.appBarTitleTextStyle == appBarTitleTextStyle && other.pollOptionVoteCountTextStyle == pollOptionVoteCountTextStyle && - other.pollOptionWinnerVoteCountTextStyle == - pollOptionWinnerVoteCountTextStyle && - other.pollOptionVoteItemBackgroundColor == - pollOptionVoteItemBackgroundColor && - other.pollOptionVoteItemBorderRadius == - pollOptionVoteItemBorderRadius; + other.pollOptionWinnerVoteCountTextStyle == pollOptionWinnerVoteCountTextStyle && + other.pollOptionVoteItemBackgroundColor == pollOptionVoteItemBackgroundColor && + other.pollOptionVoteItemBorderRadius == pollOptionVoteItemBorderRadius; @override int get hashCode => diff --git a/packages/stream_chat_flutter/lib/src/theme/poll_options_dialog_theme.dart b/packages/stream_chat_flutter/lib/src/theme/poll_options_dialog_theme.dart index 8d2e07c8a8..cdf780e512 100644 --- a/packages/stream_chat_flutter/lib/src/theme/poll_options_dialog_theme.dart +++ b/packages/stream_chat_flutter/lib/src/theme/poll_options_dialog_theme.dart @@ -30,19 +30,15 @@ class StreamPollOptionsDialogTheme extends InheritedTheme { /// If there is no enclosing [StreamPollOptionsDialogTheme] widget, then /// [StreamChatThemeData.pollOptionsDialogTheme] is used. static StreamPollOptionsDialogThemeData of(BuildContext context) { - final pollOptionsDialogTheme = context - .dependOnInheritedWidgetOfExactType(); - return pollOptionsDialogTheme?.data ?? - StreamChatTheme.of(context).pollOptionsDialogTheme; + final pollOptionsDialogTheme = context.dependOnInheritedWidgetOfExactType(); + return pollOptionsDialogTheme?.data ?? StreamChatTheme.of(context).pollOptionsDialogTheme; } @override - Widget wrap(BuildContext context, Widget child) => - StreamPollOptionsDialogTheme(data: data, child: child); + Widget wrap(BuildContext context, Widget child) => StreamPollOptionsDialogTheme(data: data, child: child); @override - bool updateShouldNotify(StreamPollOptionsDialogTheme oldWidget) => - data != oldWidget.data; + bool updateShouldNotify(StreamPollOptionsDialogTheme oldWidget) => data != oldWidget.data; } /// {@template streamPollOptionsDialogThemeData} @@ -98,20 +94,16 @@ class StreamPollOptionsDialogThemeData with Diagnosticable { TextStyle? pollTitleTextStyle, Decoration? pollTitleDecoration, Decoration? pollOptionsListViewDecoration, - }) => - StreamPollOptionsDialogThemeData( - backgroundColor: backgroundColor ?? this.backgroundColor, - appBarElevation: appBarElevation ?? this.appBarElevation, - appBarBackgroundColor: - appBarBackgroundColor ?? this.appBarBackgroundColor, - appBarForegroundColor: - appBarForegroundColor ?? this.appBarForegroundColor, - appBarTitleTextStyle: appBarTitleTextStyle ?? this.appBarTitleTextStyle, - pollTitleTextStyle: pollTitleTextStyle ?? this.pollTitleTextStyle, - pollTitleDecoration: pollTitleDecoration ?? this.pollTitleDecoration, - pollOptionsListViewDecoration: - pollOptionsListViewDecoration ?? this.pollOptionsListViewDecoration, - ); + }) => StreamPollOptionsDialogThemeData( + backgroundColor: backgroundColor ?? this.backgroundColor, + appBarElevation: appBarElevation ?? this.appBarElevation, + appBarBackgroundColor: appBarBackgroundColor ?? this.appBarBackgroundColor, + appBarForegroundColor: appBarForegroundColor ?? this.appBarForegroundColor, + appBarTitleTextStyle: appBarTitleTextStyle ?? this.appBarTitleTextStyle, + pollTitleTextStyle: pollTitleTextStyle ?? this.pollTitleTextStyle, + pollTitleDecoration: pollTitleDecoration ?? this.pollTitleDecoration, + pollOptionsListViewDecoration: pollOptionsListViewDecoration ?? this.pollOptionsListViewDecoration, + ); /// Merges this [StreamPollOptionsDialogThemeData] with the [other]. StreamPollOptionsDialogThemeData merge( @@ -135,26 +127,20 @@ class StreamPollOptionsDialogThemeData with Diagnosticable { StreamPollOptionsDialogThemeData a, StreamPollOptionsDialogThemeData b, double t, - ) => - StreamPollOptionsDialogThemeData( - backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t), - appBarElevation: lerpDouble(a.appBarElevation, b.appBarElevation, t), - appBarBackgroundColor: - Color.lerp(a.appBarBackgroundColor, b.appBarBackgroundColor, t), - appBarForegroundColor: - Color.lerp(a.appBarForegroundColor, b.appBarForegroundColor, t), - appBarTitleTextStyle: - TextStyle.lerp(a.appBarTitleTextStyle, b.appBarTitleTextStyle, t), - pollTitleTextStyle: - TextStyle.lerp(a.pollTitleTextStyle, b.pollTitleTextStyle, t), - pollTitleDecoration: - Decoration.lerp(a.pollTitleDecoration, b.pollTitleDecoration, t), - pollOptionsListViewDecoration: Decoration.lerp( - a.pollOptionsListViewDecoration, - b.pollOptionsListViewDecoration, - t, - ), - ); + ) => StreamPollOptionsDialogThemeData( + backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t), + appBarElevation: lerpDouble(a.appBarElevation, b.appBarElevation, t), + appBarBackgroundColor: Color.lerp(a.appBarBackgroundColor, b.appBarBackgroundColor, t), + appBarForegroundColor: Color.lerp(a.appBarForegroundColor, b.appBarForegroundColor, t), + appBarTitleTextStyle: TextStyle.lerp(a.appBarTitleTextStyle, b.appBarTitleTextStyle, t), + pollTitleTextStyle: TextStyle.lerp(a.pollTitleTextStyle, b.pollTitleTextStyle, t), + pollTitleDecoration: Decoration.lerp(a.pollTitleDecoration, b.pollTitleDecoration, t), + pollOptionsListViewDecoration: Decoration.lerp( + a.pollOptionsListViewDecoration, + b.pollOptionsListViewDecoration, + t, + ), + ); @override bool operator ==(Object other) => diff --git a/packages/stream_chat_flutter/lib/src/theme/poll_results_dialog_theme.dart b/packages/stream_chat_flutter/lib/src/theme/poll_results_dialog_theme.dart index bb7d1550a5..7b2dd580dd 100644 --- a/packages/stream_chat_flutter/lib/src/theme/poll_results_dialog_theme.dart +++ b/packages/stream_chat_flutter/lib/src/theme/poll_results_dialog_theme.dart @@ -36,19 +36,15 @@ class StreamPollResultsDialogTheme extends InheritedTheme { /// StreamPollCreatorTheme theme = StreamPollCreatorTheme.of(context); /// ``` static StreamPollResultsDialogThemeData of(BuildContext context) { - final pollResultsDialogTheme = context - .dependOnInheritedWidgetOfExactType(); - return pollResultsDialogTheme?.data ?? - StreamChatTheme.of(context).pollResultsDialogTheme; + final pollResultsDialogTheme = context.dependOnInheritedWidgetOfExactType(); + return pollResultsDialogTheme?.data ?? StreamChatTheme.of(context).pollResultsDialogTheme; } @override - Widget wrap(BuildContext context, Widget child) => - StreamPollResultsDialogTheme(data: data, child: child); + Widget wrap(BuildContext context, Widget child) => StreamPollResultsDialogTheme(data: data, child: child); @override - bool updateShouldNotify(StreamPollResultsDialogTheme oldWidget) => - data != oldWidget.data; + bool updateShouldNotify(StreamPollResultsDialogTheme oldWidget) => data != oldWidget.data; } /// {@template streamPollCreatorThemeData} @@ -138,27 +134,19 @@ class StreamPollResultsDialogThemeData with Diagnosticable { return StreamPollResultsDialogThemeData( backgroundColor: backgroundColor ?? this.backgroundColor, appBarElevation: appBarElevation ?? this.appBarElevation, - appBarBackgroundColor: - appBarBackgroundColor ?? this.appBarBackgroundColor, - appBarForegroundColor: - appBarForegroundColor ?? this.appBarForegroundColor, + appBarBackgroundColor: appBarBackgroundColor ?? this.appBarBackgroundColor, + appBarForegroundColor: appBarForegroundColor ?? this.appBarForegroundColor, appBarTitleTextStyle: appBarTitleTextStyle ?? this.appBarTitleTextStyle, pollTitleTextStyle: pollTitleTextStyle ?? this.pollTitleTextStyle, pollTitleDecoration: pollTitleDecoration ?? this.pollTitleDecoration, - pollOptionsDecoration: - pollOptionsDecoration ?? this.pollOptionsDecoration, - pollOptionsWinnerDecoration: - pollOptionsWinnerDecoration ?? this.pollOptionsWinnerDecoration, + pollOptionsDecoration: pollOptionsDecoration ?? this.pollOptionsDecoration, + pollOptionsWinnerDecoration: pollOptionsWinnerDecoration ?? this.pollOptionsWinnerDecoration, pollOptionsTextStyle: pollOptionsTextStyle ?? this.pollOptionsTextStyle, - pollOptionsWinnerTextStyle: - pollOptionsWinnerTextStyle ?? this.pollOptionsWinnerTextStyle, - pollOptionsVoteCountTextStyle: - pollOptionsVoteCountTextStyle ?? this.pollOptionsVoteCountTextStyle, + pollOptionsWinnerTextStyle: pollOptionsWinnerTextStyle ?? this.pollOptionsWinnerTextStyle, + pollOptionsVoteCountTextStyle: pollOptionsVoteCountTextStyle ?? this.pollOptionsVoteCountTextStyle, pollOptionsWinnerVoteCountTextStyle: - pollOptionsWinnerVoteCountTextStyle ?? - this.pollOptionsWinnerVoteCountTextStyle, - pollOptionsShowAllVotesButtonStyle: pollOptionsShowAllVotesButtonStyle ?? - this.pollOptionsShowAllVotesButtonStyle, + pollOptionsWinnerVoteCountTextStyle ?? this.pollOptionsWinnerVoteCountTextStyle, + pollOptionsShowAllVotesButtonStyle: pollOptionsShowAllVotesButtonStyle ?? this.pollOptionsShowAllVotesButtonStyle, ); } @@ -180,10 +168,8 @@ class StreamPollResultsDialogThemeData with Diagnosticable { pollOptionsTextStyle: other.pollOptionsTextStyle, pollOptionsWinnerTextStyle: other.pollOptionsWinnerTextStyle, pollOptionsVoteCountTextStyle: other.pollOptionsVoteCountTextStyle, - pollOptionsWinnerVoteCountTextStyle: - other.pollOptionsWinnerVoteCountTextStyle, - pollOptionsShowAllVotesButtonStyle: - other.pollOptionsShowAllVotesButtonStyle, + pollOptionsWinnerVoteCountTextStyle: other.pollOptionsWinnerVoteCountTextStyle, + pollOptionsShowAllVotesButtonStyle: other.pollOptionsShowAllVotesButtonStyle, ); } @@ -196,25 +182,18 @@ class StreamPollResultsDialogThemeData with Diagnosticable { return StreamPollResultsDialogThemeData( backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t), appBarElevation: lerpDouble(a.appBarElevation, b.appBarElevation, t), - appBarBackgroundColor: - Color.lerp(a.appBarBackgroundColor, b.appBarBackgroundColor, t), - appBarForegroundColor: - Color.lerp(a.appBarForegroundColor, b.appBarForegroundColor, t), - appBarTitleTextStyle: - TextStyle.lerp(a.appBarTitleTextStyle, b.appBarTitleTextStyle, t), - pollTitleTextStyle: - TextStyle.lerp(a.pollTitleTextStyle, b.pollTitleTextStyle, t), - pollTitleDecoration: - Decoration.lerp(a.pollTitleDecoration, b.pollTitleDecoration, t), - pollOptionsDecoration: - Decoration.lerp(a.pollOptionsDecoration, b.pollOptionsDecoration, t), + appBarBackgroundColor: Color.lerp(a.appBarBackgroundColor, b.appBarBackgroundColor, t), + appBarForegroundColor: Color.lerp(a.appBarForegroundColor, b.appBarForegroundColor, t), + appBarTitleTextStyle: TextStyle.lerp(a.appBarTitleTextStyle, b.appBarTitleTextStyle, t), + pollTitleTextStyle: TextStyle.lerp(a.pollTitleTextStyle, b.pollTitleTextStyle, t), + pollTitleDecoration: Decoration.lerp(a.pollTitleDecoration, b.pollTitleDecoration, t), + pollOptionsDecoration: Decoration.lerp(a.pollOptionsDecoration, b.pollOptionsDecoration, t), pollOptionsWinnerDecoration: Decoration.lerp( a.pollOptionsWinnerDecoration, b.pollOptionsWinnerDecoration, t, ), - pollOptionsTextStyle: - TextStyle.lerp(a.pollOptionsTextStyle, b.pollOptionsTextStyle, t), + pollOptionsTextStyle: TextStyle.lerp(a.pollOptionsTextStyle, b.pollOptionsTextStyle, t), pollOptionsWinnerTextStyle: TextStyle.lerp( a.pollOptionsWinnerTextStyle, b.pollOptionsWinnerTextStyle, @@ -253,12 +232,9 @@ class StreamPollResultsDialogThemeData with Diagnosticable { other.pollOptionsWinnerDecoration == pollOptionsWinnerDecoration && other.pollOptionsTextStyle == pollOptionsTextStyle && other.pollOptionsWinnerTextStyle == pollOptionsWinnerTextStyle && - other.pollOptionsVoteCountTextStyle == - pollOptionsVoteCountTextStyle && - other.pollOptionsWinnerVoteCountTextStyle == - pollOptionsWinnerVoteCountTextStyle && - other.pollOptionsShowAllVotesButtonStyle == - pollOptionsShowAllVotesButtonStyle; + other.pollOptionsVoteCountTextStyle == pollOptionsVoteCountTextStyle && + other.pollOptionsWinnerVoteCountTextStyle == pollOptionsWinnerVoteCountTextStyle && + other.pollOptionsShowAllVotesButtonStyle == pollOptionsShowAllVotesButtonStyle; @override int get hashCode => diff --git a/packages/stream_chat_flutter/lib/src/theme/stream_channel_list_item_theme.dart b/packages/stream_chat_flutter/lib/src/theme/stream_channel_list_item_theme.dart new file mode 100644 index 0000000000..d56174f744 --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/theme/stream_channel_list_item_theme.dart @@ -0,0 +1,144 @@ +import 'package:flutter/widgets.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:theme_extensions_builder_annotation/theme_extensions_builder_annotation.dart'; + +part 'stream_channel_list_item_theme.g.theme.dart'; + +/// Applies a channel list item theme to descendant +/// [StreamChannelListItem] widgets. +/// +/// Wrap a subtree with [StreamChannelListItemTheme] to override styling. +/// Access the merged theme using [BuildContext.streamChannelListItemTheme]. +/// +/// {@tool snippet} +/// +/// Override channel list item colors for a specific section: +/// +/// ```dart +/// StreamChannelListItemTheme( +/// data: StreamChannelListItemThemeData( +/// backgroundColor: Colors.grey.shade50, +/// ), +/// child: StreamChannelListItem( +/// avatar: StreamAvatar(...), +/// title: 'General', +/// ), +/// ) +/// ``` +/// {@end-tool} +/// +/// See also: +/// +/// * [StreamChannelListItemThemeData], which describes the theme. +/// * [StreamChannelListItem], the widget affected by this theme. +class StreamChannelListItemTheme extends InheritedTheme { + /// Creates a channel list item theme that controls descendant widgets. + const StreamChannelListItemTheme({ + super.key, + required this.data, + required super.child, + }); + + /// The channel list item theme data for descendant widgets. + final StreamChannelListItemThemeData data; + + /// Returns the [StreamChannelListItemThemeData] merged from local and + /// global themes. + /// + /// Local values from the nearest [StreamChannelListItemTheme] ancestor + /// take precedence over global values from [StreamTheme.of]. + static StreamChannelListItemThemeData of(BuildContext context) { + final localTheme = context.dependOnInheritedWidgetOfExactType(); + return StreamChatTheme.of(context).channelListItemTheme.merge(localTheme?.data); + } + + @override + Widget wrap(BuildContext context, Widget child) { + return StreamChannelListItemTheme(data: data, child: child); + } + + @override + bool updateShouldNotify(StreamChannelListItemTheme oldWidget) => data != oldWidget.data; +} + +/// Theme data for customizing [StreamChannelListItem] widgets. +/// +/// {@tool snippet} +/// +/// Customize channel list item appearance globally: +/// +/// ```dart +/// StreamTheme( +/// channelListItemTheme: StreamChannelListItemThemeData( +/// backgroundColor: Colors.white, +/// borderColor: Colors.grey.shade200, +/// ), +/// ) +/// ``` +/// {@end-tool} +/// +/// See also: +/// +/// * [StreamChannelListItem], the widget that uses this theme data. +/// * [StreamChannelListItemTheme], for overriding theme in a widget subtree. +@themeGen +@immutable +class StreamChannelListItemThemeData with _$StreamChannelListItemThemeData { + /// Creates a channel list item theme with optional style overrides. + const StreamChannelListItemThemeData({ + this.titleStyle, + this.subtitleStyle, + this.timestampStyle, + this.backgroundColor, + this.borderColor, + this.muteIconPosition, + }); + + /// The text style for the channel title. + /// + /// Falls back to [StreamTextTheme.headingSm] with [StreamColorScheme.textPrimary]. + final TextStyle? titleStyle; + + /// The text style for the message preview subtitle. + /// + /// Falls back to [StreamTextTheme.captionDefault] with [StreamColorScheme.textSecondary]. + final TextStyle? subtitleStyle; + + /// The text style for the timestamp. + /// + /// Falls back to [StreamTextTheme.captionDefault] with [StreamColorScheme.textTertiary]. + final TextStyle? timestampStyle; + + /// Defines the default background color of the tile. + /// + /// This color is resolved from [WidgetState]s. + final WidgetStateProperty? backgroundColor; + + /// The bottom border color of the list item. + /// + /// Falls back to [StreamColorScheme.borderSubtle]. + final Color? borderColor; + + /// The position of the mute icon. + /// + /// Falls back to [MuteIconPosition.title]. + final MuteIconPosition? muteIconPosition; + + /// Linearly interpolate between two [StreamChannelListItemThemeData] objects. + static StreamChannelListItemThemeData? lerp( + StreamChannelListItemThemeData? a, + StreamChannelListItemThemeData? b, + double t, + ) => _$StreamChannelListItemThemeData.lerp(a, b, t); +} + +/// The position of the mute icon. +/// By default the mute icon will be shown directly next to the title. +/// When choosing for subtitle, the mute icon will be shown at the end of the list item. +enum MuteIconPosition { + /// Top row of the list item, next to the title. + title, + + /// Bottom row, at the end of the list item. + subtitle, +} diff --git a/packages/stream_chat_flutter/lib/src/theme/stream_channel_list_item_theme.g.theme.dart b/packages/stream_chat_flutter/lib/src/theme/stream_channel_list_item_theme.g.theme.dart new file mode 100644 index 0000000000..bb1baafe13 --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/theme/stream_channel_list_item_theme.g.theme.dart @@ -0,0 +1,127 @@ +// dart format width=80 +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, unused_element + +part of 'stream_channel_list_item_theme.dart'; + +// ************************************************************************** +// ThemeGenGenerator +// ************************************************************************** + +mixin _$StreamChannelListItemThemeData { + bool get canMerge => true; + + static StreamChannelListItemThemeData? lerp( + StreamChannelListItemThemeData? a, + StreamChannelListItemThemeData? b, + double t, + ) { + if (identical(a, b)) { + return a; + } + + if (a == null) { + return t == 1.0 ? b : null; + } + + if (b == null) { + return t == 0.0 ? a : null; + } + + return StreamChannelListItemThemeData( + titleStyle: TextStyle.lerp(a.titleStyle, b.titleStyle, t), + subtitleStyle: TextStyle.lerp(a.subtitleStyle, b.subtitleStyle, t), + timestampStyle: TextStyle.lerp(a.timestampStyle, b.timestampStyle, t), + backgroundColor: WidgetStateProperty.lerp( + a.backgroundColor, + b.backgroundColor, + t, + Color.lerp, + ), + borderColor: Color.lerp(a.borderColor, b.borderColor, t), + muteIconPosition: t < 0.5 ? a.muteIconPosition : b.muteIconPosition, + ); + } + + StreamChannelListItemThemeData copyWith({ + TextStyle? titleStyle, + TextStyle? subtitleStyle, + TextStyle? timestampStyle, + WidgetStateProperty? backgroundColor, + Color? borderColor, + MuteIconPosition? muteIconPosition, + }) { + final _this = (this as StreamChannelListItemThemeData); + + return StreamChannelListItemThemeData( + titleStyle: titleStyle ?? _this.titleStyle, + subtitleStyle: subtitleStyle ?? _this.subtitleStyle, + timestampStyle: timestampStyle ?? _this.timestampStyle, + backgroundColor: backgroundColor ?? _this.backgroundColor, + borderColor: borderColor ?? _this.borderColor, + muteIconPosition: muteIconPosition ?? _this.muteIconPosition, + ); + } + + StreamChannelListItemThemeData merge(StreamChannelListItemThemeData? other) { + final _this = (this as StreamChannelListItemThemeData); + + if (other == null || identical(_this, other)) { + return _this; + } + + if (!other.canMerge) { + return other; + } + + return copyWith( + titleStyle: _this.titleStyle?.merge(other.titleStyle) ?? other.titleStyle, + subtitleStyle: + _this.subtitleStyle?.merge(other.subtitleStyle) ?? + other.subtitleStyle, + timestampStyle: + _this.timestampStyle?.merge(other.timestampStyle) ?? + other.timestampStyle, + backgroundColor: other.backgroundColor, + borderColor: other.borderColor, + muteIconPosition: other.muteIconPosition, + ); + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + + if (other.runtimeType != runtimeType) { + return false; + } + + final _this = (this as StreamChannelListItemThemeData); + final _other = (other as StreamChannelListItemThemeData); + + return _other.titleStyle == _this.titleStyle && + _other.subtitleStyle == _this.subtitleStyle && + _other.timestampStyle == _this.timestampStyle && + _other.backgroundColor == _this.backgroundColor && + _other.borderColor == _this.borderColor && + _other.muteIconPosition == _this.muteIconPosition; + } + + @override + int get hashCode { + final _this = (this as StreamChannelListItemThemeData); + + return Object.hash( + runtimeType, + _this.titleStyle, + _this.subtitleStyle, + _this.timestampStyle, + _this.backgroundColor, + _this.borderColor, + _this.muteIconPosition, + ); + } +} diff --git a/packages/stream_chat_flutter/lib/src/theme/stream_chat_theme.dart b/packages/stream_chat_flutter/lib/src/theme/stream_chat_theme.dart index 489b4a03c5..5cd19d8a0f 100644 --- a/packages/stream_chat_flutter/lib/src/theme/stream_chat_theme.dart +++ b/packages/stream_chat_flutter/lib/src/theme/stream_chat_theme.dart @@ -1,7 +1,6 @@ // ignore_for_file: deprecated_member_use_from_same_package import 'package:flutter/material.dart' hide TextTheme; -import 'package:stream_chat_flutter/src/misc/audio_waveform.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// {@template streamChatTheme} @@ -23,8 +22,7 @@ class StreamChatTheme extends InheritedWidget { /// Use this method to get the current [StreamChatThemeData] instance static StreamChatThemeData of(BuildContext context) { - final streamChatTheme = - context.dependOnInheritedWidgetOfExactType(); + final streamChatTheme = context.dependOnInheritedWidgetOfExactType(); assert( streamChatTheme != null, @@ -64,9 +62,8 @@ class StreamChatThemeData { StreamPollOptionVotesDialogThemeData? pollOptionVotesDialogTheme, StreamThreadListTileThemeData? threadListTileTheme, StreamDraftListTileThemeData? draftListTileTheme, - StreamAudioWaveformThemeData? audioWaveformTheme, - StreamAudioWaveformSliderThemeData? audioWaveformSliderTheme, StreamVoiceRecordingAttachmentThemeData? voiceRecordingAttachmentTheme, + StreamChannelListItemThemeData? channelListItemTheme, }) { brightness ??= colorTheme?.brightness ?? Brightness.light; textTheme ??= StreamTextTheme(brightness: brightness); @@ -98,21 +95,18 @@ class StreamChatThemeData { pollOptionVotesDialogTheme: pollOptionVotesDialogTheme, threadListTileTheme: threadListTileTheme, draftListTileTheme: draftListTileTheme, - audioWaveformTheme: audioWaveformTheme, - audioWaveformSliderTheme: audioWaveformSliderTheme, voiceRecordingAttachmentTheme: voiceRecordingAttachmentTheme, + channelListItemTheme: channelListItemTheme, ); return defaultData.merge(customizedData); } /// Theme initialized with light - factory StreamChatThemeData.light() => - StreamChatThemeData(brightness: Brightness.light); + factory StreamChatThemeData.light() => StreamChatThemeData(brightness: Brightness.light); /// Theme initialized with dark - factory StreamChatThemeData.dark() => - StreamChatThemeData(brightness: Brightness.dark); + factory StreamChatThemeData.dark() => StreamChatThemeData(brightness: Brightness.dark); /// Raw theme initialization const StreamChatThemeData.raw({ @@ -136,9 +130,8 @@ class StreamChatThemeData { required this.pollOptionVotesDialogTheme, required this.threadListTileTheme, required this.draftListTileTheme, - required this.audioWaveformTheme, - required this.audioWaveformSliderTheme, required this.voiceRecordingAttachmentTheme, + required this.channelListItemTheme, }); /// Creates a theme from a Material [Theme] @@ -169,10 +162,6 @@ class StreamChatThemeData { ), ), color: colorTheme.barsBg, - titleStyle: textTheme.headlineBold, - subtitleStyle: textTheme.footnote.copyWith( - color: colorTheme.textLowEmphasis, - ), ); final channelPreviewTheme = StreamChannelPreviewThemeData( unreadCounterColor: colorTheme.accentError, @@ -194,20 +183,6 @@ class StreamChatThemeData { indicatorIconSize: 16, ); - final audioWaveformTheme = StreamAudioWaveformThemeData( - color: colorTheme.textLowEmphasis, - progressColor: colorTheme.accentPrimary, - minBarHeight: 2, - spacingRatio: 0.3, - heightScale: 1, - ); - - final audioWaveformSliderTheme = StreamAudioWaveformSliderThemeData( - audioWaveformTheme: audioWaveformTheme, - thumbColor: Colors.white, - thumbBorderColor: colorTheme.borders, - ); - return StreamChatThemeData.raw( textTheme: textTheme, colorTheme: colorTheme, @@ -226,18 +201,15 @@ class StreamChatThemeData { ), channelHeaderTheme: channelHeaderTheme, ownMessageTheme: StreamMessageThemeData( - messageAuthorStyle: - textTheme.footnote.copyWith(color: colorTheme.textLowEmphasis), + messageAuthorStyle: textTheme.footnote.copyWith(color: colorTheme.textLowEmphasis), messageTextStyle: textTheme.body, messageDeletedStyle: textTheme.body.copyWith( color: colorTheme.textLowEmphasis, fontStyle: FontStyle.italic, ), - createdAtStyle: - textTheme.footnote.copyWith(color: colorTheme.textLowEmphasis), + createdAtStyle: textTheme.footnote.copyWith(color: colorTheme.textLowEmphasis), repliesStyle: textTheme.footnoteBold.copyWith(color: accentColor), messageBackgroundColor: colorTheme.inputBg, - messageBorderColor: colorTheme.borders, reactionsBackgroundColor: colorTheme.barsBg, reactionsBorderColor: colorTheme.borders, reactionsMaskColor: colorTheme.appBg, @@ -265,14 +237,11 @@ class StreamChatThemeData { color: colorTheme.textLowEmphasis, fontStyle: FontStyle.italic, ), - createdAtStyle: - textTheme.footnote.copyWith(color: colorTheme.textLowEmphasis), - messageAuthorStyle: - textTheme.footnote.copyWith(color: colorTheme.textLowEmphasis), + createdAtStyle: textTheme.footnote.copyWith(color: colorTheme.textLowEmphasis), + messageAuthorStyle: textTheme.footnote.copyWith(color: colorTheme.textLowEmphasis), repliesStyle: textTheme.footnoteBold.copyWith(color: accentColor), messageLinksStyle: TextStyle(color: accentColor), messageBackgroundColor: colorTheme.barsBg, - messageBorderColor: colorTheme.borders, avatarTheme: StreamAvatarThemeData( borderRadius: BorderRadius.circular(20), constraints: const BoxConstraints.tightFor( @@ -522,8 +491,7 @@ class StreamChatThemeData { threadUnreadMessageCountStyle: textTheme.footnoteBold.copyWith( color: Colors.white, ), - threadUnreadMessageCountBackgroundColor: - channelPreviewTheme.unreadCounterColor, + threadUnreadMessageCountBackgroundColor: channelPreviewTheme.unreadCounterColor, threadChannelNameStyle: textTheme.bodyBold.copyWith( color: colorTheme.textHighEmphasis, ), @@ -553,47 +521,8 @@ class StreamChatThemeData { color: colorTheme.textLowEmphasis, ), ), - audioWaveformTheme: audioWaveformTheme, - audioWaveformSliderTheme: audioWaveformSliderTheme, - voiceRecordingAttachmentTheme: StreamVoiceRecordingAttachmentThemeData( - backgroundColor: colorTheme.barsBg, - playIcon: const StreamSvgIcon(icon: StreamSvgIcons.play), - pauseIcon: const StreamSvgIcon(icon: StreamSvgIcons.pause), - loadingIndicator: SizedBox.fromSize( - size: const Size.square(24 - /* Padding */ 2), - child: Center( - child: CircularProgressIndicator.adaptive( - valueColor: AlwaysStoppedAnimation(colorTheme.accentPrimary), - ), - ), - ), - audioControlButtonStyle: ElevatedButton.styleFrom( - elevation: 2, - iconColor: Colors.black, - padding: const EdgeInsets.symmetric(horizontal: 6), - backgroundColor: Colors.white, - shape: const CircleBorder(), - tapTargetSize: MaterialTapTargetSize.shrinkWrap, - minimumSize: const Size(36, 36), - ), - titleTextStyle: textTheme.bodyBold.copyWith( - color: colorTheme.textHighEmphasis, - ), - durationTextStyle: textTheme.footnote.copyWith( - color: colorTheme.textLowEmphasis, - ), - speedControlButtonStyle: ElevatedButton.styleFrom( - elevation: 2, - textStyle: textTheme.footnote, - foregroundColor: Colors.black, - padding: const EdgeInsets.symmetric(horizontal: 8), - backgroundColor: Colors.white, - shape: const StadiumBorder(), - tapTargetSize: MaterialTapTargetSize.shrinkWrap, - minimumSize: const Size(40, 28), - ), - audioWaveformSliderTheme: audioWaveformSliderTheme, - ), + voiceRecordingAttachmentTheme: const StreamVoiceRecordingAttachmentThemeData(), + channelListItemTheme: const StreamChannelListItemThemeData(), ); } @@ -656,15 +585,12 @@ class StreamChatThemeData { /// Theme configuration for the [StreamThreadListTile] widget. final StreamThreadListTileThemeData threadListTileTheme; - /// Theme configuration for the [StreamAudioWaveform] widget. - final StreamAudioWaveformThemeData audioWaveformTheme; - - /// Theme configuration for the [StreamAudioWaveformSlider] widget. - final StreamAudioWaveformSliderThemeData audioWaveformSliderTheme; - /// Theme configuration for the [StreamVoiceRecordingAttachment] widget. final StreamVoiceRecordingAttachmentThemeData voiceRecordingAttachmentTheme; + /// Theme configuration for the [StreamChannelListItem] widget. + final StreamChannelListItemThemeData channelListItemTheme; + /// Theme configuration for the [StreamDraftListTile] widget. final StreamDraftListTileThemeData draftListTileTheme; @@ -702,50 +628,38 @@ class StreamChatThemeData { StreamPollOptionVotesDialogThemeData? pollOptionVotesDialogTheme, StreamThreadListTileThemeData? threadListTileTheme, StreamDraftListTileThemeData? draftListTileTheme, - StreamAudioWaveformThemeData? audioWaveformTheme, - StreamAudioWaveformSliderThemeData? audioWaveformSliderTheme, StreamVoiceRecordingAttachmentThemeData? voiceRecordingAttachmentTheme, - }) => - StreamChatThemeData.raw( - channelListHeaderTheme: - this.channelListHeaderTheme.merge(channelListHeaderTheme), - textTheme: this.textTheme.merge(textTheme), - colorTheme: this.colorTheme.merge(colorTheme), - primaryIconTheme: this.primaryIconTheme.merge(primaryIconTheme), - channelPreviewTheme: - this.channelPreviewTheme.merge(channelPreviewTheme), - channelHeaderTheme: this.channelHeaderTheme.merge(channelHeaderTheme), - ownMessageTheme: this.ownMessageTheme.merge(ownMessageTheme), - otherMessageTheme: this.otherMessageTheme.merge(otherMessageTheme), - messageInputTheme: this.messageInputTheme.merge(messageInputTheme), - galleryHeaderTheme: galleryHeaderTheme ?? this.galleryHeaderTheme, - galleryFooterTheme: galleryFooterTheme ?? this.galleryFooterTheme, - messageListViewTheme: messageListViewTheme ?? this.messageListViewTheme, - pollCreatorTheme: pollCreatorTheme ?? this.pollCreatorTheme, - pollInteractorTheme: pollInteractorTheme ?? this.pollInteractorTheme, - pollResultsDialogTheme: - pollResultsDialogTheme ?? this.pollResultsDialogTheme, - pollOptionsDialogTheme: - pollOptionsDialogTheme ?? this.pollOptionsDialogTheme, - pollCommentsDialogTheme: - pollCommentsDialogTheme ?? this.pollCommentsDialogTheme, - pollOptionVotesDialogTheme: - pollOptionVotesDialogTheme ?? this.pollOptionVotesDialogTheme, - threadListTileTheme: threadListTileTheme ?? this.threadListTileTheme, - draftListTileTheme: draftListTileTheme ?? this.draftListTileTheme, - audioWaveformTheme: audioWaveformTheme ?? this.audioWaveformTheme, - audioWaveformSliderTheme: - audioWaveformSliderTheme ?? this.audioWaveformSliderTheme, - voiceRecordingAttachmentTheme: - voiceRecordingAttachmentTheme ?? this.voiceRecordingAttachmentTheme, - ); + StreamChannelListItemThemeData? channelListItemTheme, + }) => StreamChatThemeData.raw( + channelListHeaderTheme: this.channelListHeaderTheme.merge(channelListHeaderTheme), + textTheme: this.textTheme.merge(textTheme), + colorTheme: this.colorTheme.merge(colorTheme), + primaryIconTheme: this.primaryIconTheme.merge(primaryIconTheme), + channelPreviewTheme: this.channelPreviewTheme.merge(channelPreviewTheme), + channelHeaderTheme: this.channelHeaderTheme.merge(channelHeaderTheme), + ownMessageTheme: this.ownMessageTheme.merge(ownMessageTheme), + otherMessageTheme: this.otherMessageTheme.merge(otherMessageTheme), + messageInputTheme: this.messageInputTheme.merge(messageInputTheme), + galleryHeaderTheme: galleryHeaderTheme ?? this.galleryHeaderTheme, + galleryFooterTheme: galleryFooterTheme ?? this.galleryFooterTheme, + messageListViewTheme: messageListViewTheme ?? this.messageListViewTheme, + pollCreatorTheme: pollCreatorTheme ?? this.pollCreatorTheme, + pollInteractorTheme: pollInteractorTheme ?? this.pollInteractorTheme, + pollResultsDialogTheme: pollResultsDialogTheme ?? this.pollResultsDialogTheme, + pollOptionsDialogTheme: pollOptionsDialogTheme ?? this.pollOptionsDialogTheme, + pollCommentsDialogTheme: pollCommentsDialogTheme ?? this.pollCommentsDialogTheme, + pollOptionVotesDialogTheme: pollOptionVotesDialogTheme ?? this.pollOptionVotesDialogTheme, + threadListTileTheme: threadListTileTheme ?? this.threadListTileTheme, + draftListTileTheme: draftListTileTheme ?? this.draftListTileTheme, + voiceRecordingAttachmentTheme: voiceRecordingAttachmentTheme ?? this.voiceRecordingAttachmentTheme, + channelListItemTheme: channelListItemTheme ?? this.channelListItemTheme, + ); /// Merge themes StreamChatThemeData merge(StreamChatThemeData? other) { if (other == null) return this; return copyWith( - channelListHeaderTheme: - channelListHeaderTheme.merge(other.channelListHeaderTheme), + channelListHeaderTheme: channelListHeaderTheme.merge(other.channelListHeaderTheme), textTheme: textTheme.merge(other.textTheme), colorTheme: colorTheme.merge(other.colorTheme), primaryIconTheme: other.primaryIconTheme, @@ -756,25 +670,17 @@ class StreamChatThemeData { messageInputTheme: messageInputTheme.merge(other.messageInputTheme), galleryHeaderTheme: galleryHeaderTheme.merge(other.galleryHeaderTheme), galleryFooterTheme: galleryFooterTheme.merge(other.galleryFooterTheme), - messageListViewTheme: - messageListViewTheme.merge(other.messageListViewTheme), + messageListViewTheme: messageListViewTheme.merge(other.messageListViewTheme), pollCreatorTheme: pollCreatorTheme.merge(other.pollCreatorTheme), pollInteractorTheme: pollInteractorTheme.merge(other.pollInteractorTheme), - pollResultsDialogTheme: - pollResultsDialogTheme.merge(other.pollResultsDialogTheme), - pollOptionsDialogTheme: - pollOptionsDialogTheme.merge(other.pollOptionsDialogTheme), - pollCommentsDialogTheme: - pollCommentsDialogTheme.merge(other.pollCommentsDialogTheme), - pollOptionVotesDialogTheme: - pollOptionVotesDialogTheme.merge(other.pollOptionVotesDialogTheme), + pollResultsDialogTheme: pollResultsDialogTheme.merge(other.pollResultsDialogTheme), + pollOptionsDialogTheme: pollOptionsDialogTheme.merge(other.pollOptionsDialogTheme), + pollCommentsDialogTheme: pollCommentsDialogTheme.merge(other.pollCommentsDialogTheme), + pollOptionVotesDialogTheme: pollOptionVotesDialogTheme.merge(other.pollOptionVotesDialogTheme), threadListTileTheme: threadListTileTheme.merge(other.threadListTileTheme), draftListTileTheme: draftListTileTheme.merge(other.draftListTileTheme), - audioWaveformTheme: audioWaveformTheme.merge(other.audioWaveformTheme), - audioWaveformSliderTheme: - audioWaveformSliderTheme.merge(other.audioWaveformSliderTheme), - voiceRecordingAttachmentTheme: voiceRecordingAttachmentTheme - .merge(other.voiceRecordingAttachmentTheme), + voiceRecordingAttachmentTheme: voiceRecordingAttachmentTheme.merge(other.voiceRecordingAttachmentTheme), + channelListItemTheme: channelListItemTheme.merge(other.channelListItemTheme), ); } } diff --git a/packages/stream_chat_flutter/lib/src/theme/text_theme.dart b/packages/stream_chat_flutter/lib/src/theme/text_theme.dart index 62a30d1245..113e67f8f6 100644 --- a/packages/stream_chat_flutter/lib/src/theme/text_theme.dart +++ b/packages/stream_chat_flutter/lib/src/theme/text_theme.dart @@ -140,28 +140,27 @@ class StreamTextTheme { TextStyle? footnoteBold, TextStyle? footnote, TextStyle? captionBold, - }) => - brightness == Brightness.light - ? StreamTextTheme.light( - body: body ?? this.body, - title: title ?? this.title, - headlineBold: headlineBold ?? this.headlineBold, - headline: headline ?? this.headline, - bodyBold: bodyBold ?? this.bodyBold, - footnoteBold: footnoteBold ?? this.footnoteBold, - footnote: footnote ?? this.footnote, - captionBold: captionBold ?? this.captionBold, - ) - : StreamTextTheme.dark( - body: body ?? this.body, - title: title ?? this.title, - headlineBold: headlineBold ?? this.headlineBold, - headline: headline ?? this.headline, - bodyBold: bodyBold ?? this.bodyBold, - footnoteBold: footnoteBold ?? this.footnoteBold, - footnote: footnote ?? this.footnote, - captionBold: captionBold ?? this.captionBold, - ); + }) => brightness == Brightness.light + ? StreamTextTheme.light( + body: body ?? this.body, + title: title ?? this.title, + headlineBold: headlineBold ?? this.headlineBold, + headline: headline ?? this.headline, + bodyBold: bodyBold ?? this.bodyBold, + footnoteBold: footnoteBold ?? this.footnoteBold, + footnote: footnote ?? this.footnote, + captionBold: captionBold ?? this.captionBold, + ) + : StreamTextTheme.dark( + body: body ?? this.body, + title: title ?? this.title, + headlineBold: headlineBold ?? this.headlineBold, + headline: headline ?? this.headline, + bodyBold: bodyBold ?? this.bodyBold, + footnoteBold: footnoteBold ?? this.footnoteBold, + footnote: footnote ?? this.footnote, + captionBold: captionBold ?? this.captionBold, + ); /// Merge text theme StreamTextTheme merge(StreamTextTheme? other) { diff --git a/packages/stream_chat_flutter/lib/src/theme/themes.dart b/packages/stream_chat_flutter/lib/src/theme/themes.dart index d24070676d..7bcf5a75e6 100644 --- a/packages/stream_chat_flutter/lib/src/theme/themes.dart +++ b/packages/stream_chat_flutter/lib/src/theme/themes.dart @@ -1,5 +1,3 @@ -export 'audio_waveform_slider_theme.dart'; -export 'audio_waveform_theme.dart'; export 'avatar_theme.dart'; export 'channel_header_theme.dart'; export 'channel_list_header_theme.dart'; @@ -17,6 +15,7 @@ export 'poll_interactor_theme.dart'; export 'poll_option_votes_dialog_theme.dart'; export 'poll_options_dialog_theme.dart'; export 'poll_results_dialog_theme.dart'; +export 'stream_channel_list_item_theme.dart'; export 'text_theme.dart'; export 'thread_list_tile_theme.dart'; export 'voice_recording_attachment_theme.dart'; diff --git a/packages/stream_chat_flutter/lib/src/theme/thread_list_tile_theme.dart b/packages/stream_chat_flutter/lib/src/theme/thread_list_tile_theme.dart index 6becfc2a27..aada8337d5 100644 --- a/packages/stream_chat_flutter/lib/src/theme/thread_list_tile_theme.dart +++ b/packages/stream_chat_flutter/lib/src/theme/thread_list_tile_theme.dart @@ -29,19 +29,15 @@ class StreamThreadListTileTheme extends InheritedTheme { /// If there is no enclosing [StreamThreadListTileTheme] widget, then /// [StreamChatThemeData.pollOptionVotesDialogTheme] is used. static StreamThreadListTileThemeData of(BuildContext context) { - final threadListTileTheme = - context.dependOnInheritedWidgetOfExactType(); - return threadListTileTheme?.data ?? - StreamChatTheme.of(context).threadListTileTheme; + final threadListTileTheme = context.dependOnInheritedWidgetOfExactType(); + return threadListTileTheme?.data ?? StreamChatTheme.of(context).threadListTileTheme; } @override - Widget wrap(BuildContext context, Widget child) => - StreamThreadListTileTheme(data: data, child: child); + Widget wrap(BuildContext context, Widget child) => StreamThreadListTileTheme(data: data, child: child); @override - bool updateShouldNotify(StreamThreadListTileTheme oldWidget) => - data != oldWidget.data; + bool updateShouldNotify(StreamThreadListTileTheme oldWidget) => data != oldWidget.data; } /// {@template streamThreadListTileThemeData} @@ -124,29 +120,20 @@ class StreamThreadListTileThemeData with Diagnosticable { DateFormatter? threadLatestReplyTimestampFormatter, TextStyle? threadUnreadMessageCountStyle, Color? threadUnreadMessageCountBackgroundColor, - }) => - StreamThreadListTileThemeData( - padding: padding ?? this.padding, - backgroundColor: backgroundColor ?? this.backgroundColor, - threadChannelNameStyle: - threadChannelNameStyle ?? this.threadChannelNameStyle, - threadReplyToMessageStyle: - threadReplyToMessageStyle ?? this.threadReplyToMessageStyle, - threadLatestReplyUsernameStyle: threadLatestReplyUsernameStyle ?? - this.threadLatestReplyUsernameStyle, - threadLatestReplyMessageStyle: - threadLatestReplyMessageStyle ?? this.threadLatestReplyMessageStyle, - threadLatestReplyTimestampStyle: threadLatestReplyTimestampStyle ?? - this.threadLatestReplyTimestampStyle, - threadLatestReplyTimestampFormatter: - threadLatestReplyTimestampFormatter ?? - this.threadLatestReplyTimestampFormatter, - threadUnreadMessageCountStyle: - threadUnreadMessageCountStyle ?? this.threadUnreadMessageCountStyle, - threadUnreadMessageCountBackgroundColor: - threadUnreadMessageCountBackgroundColor ?? - this.threadUnreadMessageCountBackgroundColor, - ); + }) => StreamThreadListTileThemeData( + padding: padding ?? this.padding, + backgroundColor: backgroundColor ?? this.backgroundColor, + threadChannelNameStyle: threadChannelNameStyle ?? this.threadChannelNameStyle, + threadReplyToMessageStyle: threadReplyToMessageStyle ?? this.threadReplyToMessageStyle, + threadLatestReplyUsernameStyle: threadLatestReplyUsernameStyle ?? this.threadLatestReplyUsernameStyle, + threadLatestReplyMessageStyle: threadLatestReplyMessageStyle ?? this.threadLatestReplyMessageStyle, + threadLatestReplyTimestampStyle: threadLatestReplyTimestampStyle ?? this.threadLatestReplyTimestampStyle, + threadLatestReplyTimestampFormatter: + threadLatestReplyTimestampFormatter ?? this.threadLatestReplyTimestampFormatter, + threadUnreadMessageCountStyle: threadUnreadMessageCountStyle ?? this.threadUnreadMessageCountStyle, + threadUnreadMessageCountBackgroundColor: + threadUnreadMessageCountBackgroundColor ?? this.threadUnreadMessageCountBackgroundColor, + ); /// Merges this [StreamThreadListTileThemeData] with the [other]. StreamThreadListTileThemeData merge( @@ -161,11 +148,9 @@ class StreamThreadListTileThemeData with Diagnosticable { threadLatestReplyUsernameStyle: other.threadLatestReplyUsernameStyle, threadLatestReplyMessageStyle: other.threadLatestReplyMessageStyle, threadLatestReplyTimestampStyle: other.threadLatestReplyTimestampStyle, - threadLatestReplyTimestampFormatter: - other.threadLatestReplyTimestampFormatter, + threadLatestReplyTimestampFormatter: other.threadLatestReplyTimestampFormatter, threadUnreadMessageCountStyle: other.threadUnreadMessageCountStyle, - threadUnreadMessageCountBackgroundColor: - other.threadUnreadMessageCountBackgroundColor, + threadUnreadMessageCountBackgroundColor: other.threadUnreadMessageCountBackgroundColor, ); } @@ -174,49 +159,48 @@ class StreamThreadListTileThemeData with Diagnosticable { StreamThreadListTileThemeData? a, StreamThreadListTileThemeData? b, double t, - ) => - StreamThreadListTileThemeData( - padding: EdgeInsetsGeometry.lerp(a?.padding, b?.padding, t), - backgroundColor: Color.lerp(a?.backgroundColor, b?.backgroundColor, t), - threadChannelNameStyle: TextStyle.lerp( - a?.threadChannelNameStyle, - b?.threadChannelNameStyle, - t, - ), - threadReplyToMessageStyle: TextStyle.lerp( - a?.threadReplyToMessageStyle, - b?.threadReplyToMessageStyle, - t, - ), - threadLatestReplyUsernameStyle: TextStyle.lerp( - a?.threadLatestReplyUsernameStyle, - b?.threadLatestReplyUsernameStyle, - t, - ), - threadLatestReplyMessageStyle: TextStyle.lerp( - a?.threadLatestReplyMessageStyle, - b?.threadLatestReplyMessageStyle, - t, - ), - threadLatestReplyTimestampStyle: TextStyle.lerp( - a?.threadLatestReplyTimestampStyle, - b?.threadLatestReplyTimestampStyle, - t, - ), - threadLatestReplyTimestampFormatter: t < 0.5 - ? a?.threadLatestReplyTimestampFormatter - : b?.threadLatestReplyTimestampFormatter, - threadUnreadMessageCountStyle: TextStyle.lerp( - a?.threadUnreadMessageCountStyle, - b?.threadUnreadMessageCountStyle, - t, - ), - threadUnreadMessageCountBackgroundColor: Color.lerp( - a?.threadUnreadMessageCountBackgroundColor, - b?.threadUnreadMessageCountBackgroundColor, - t, - ), - ); + ) => StreamThreadListTileThemeData( + padding: EdgeInsetsGeometry.lerp(a?.padding, b?.padding, t), + backgroundColor: Color.lerp(a?.backgroundColor, b?.backgroundColor, t), + threadChannelNameStyle: TextStyle.lerp( + a?.threadChannelNameStyle, + b?.threadChannelNameStyle, + t, + ), + threadReplyToMessageStyle: TextStyle.lerp( + a?.threadReplyToMessageStyle, + b?.threadReplyToMessageStyle, + t, + ), + threadLatestReplyUsernameStyle: TextStyle.lerp( + a?.threadLatestReplyUsernameStyle, + b?.threadLatestReplyUsernameStyle, + t, + ), + threadLatestReplyMessageStyle: TextStyle.lerp( + a?.threadLatestReplyMessageStyle, + b?.threadLatestReplyMessageStyle, + t, + ), + threadLatestReplyTimestampStyle: TextStyle.lerp( + a?.threadLatestReplyTimestampStyle, + b?.threadLatestReplyTimestampStyle, + t, + ), + threadLatestReplyTimestampFormatter: t < 0.5 + ? a?.threadLatestReplyTimestampFormatter + : b?.threadLatestReplyTimestampFormatter, + threadUnreadMessageCountStyle: TextStyle.lerp( + a?.threadUnreadMessageCountStyle, + b?.threadUnreadMessageCountStyle, + t, + ), + threadUnreadMessageCountBackgroundColor: Color.lerp( + a?.threadUnreadMessageCountBackgroundColor, + b?.threadUnreadMessageCountBackgroundColor, + t, + ), + ); @override bool operator ==(Object other) => @@ -226,18 +210,12 @@ class StreamThreadListTileThemeData with Diagnosticable { other.backgroundColor == backgroundColor && other.threadChannelNameStyle == threadChannelNameStyle && other.threadReplyToMessageStyle == threadReplyToMessageStyle && - other.threadLatestReplyUsernameStyle == - threadLatestReplyUsernameStyle && - other.threadLatestReplyMessageStyle == - threadLatestReplyMessageStyle && - other.threadLatestReplyTimestampStyle == - threadLatestReplyTimestampStyle && - other.threadLatestReplyTimestampFormatter == - threadLatestReplyTimestampFormatter && - other.threadUnreadMessageCountStyle == - threadUnreadMessageCountStyle && - other.threadUnreadMessageCountBackgroundColor == - threadUnreadMessageCountBackgroundColor; + other.threadLatestReplyUsernameStyle == threadLatestReplyUsernameStyle && + other.threadLatestReplyMessageStyle == threadLatestReplyMessageStyle && + other.threadLatestReplyTimestampStyle == threadLatestReplyTimestampStyle && + other.threadLatestReplyTimestampFormatter == threadLatestReplyTimestampFormatter && + other.threadUnreadMessageCountStyle == threadUnreadMessageCountStyle && + other.threadUnreadMessageCountBackgroundColor == threadUnreadMessageCountBackgroundColor; @override int get hashCode => diff --git a/packages/stream_chat_flutter/lib/src/theme/voice_recording_attachment_theme.dart b/packages/stream_chat_flutter/lib/src/theme/voice_recording_attachment_theme.dart index 6103156c04..64867c902f 100644 --- a/packages/stream_chat_flutter/lib/src/theme/voice_recording_attachment_theme.dart +++ b/packages/stream_chat_flutter/lib/src/theme/voice_recording_attachment_theme.dart @@ -1,6 +1,5 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/src/theme/audio_waveform_slider_theme.dart'; import 'package:stream_chat_flutter/src/theme/stream_chat_theme.dart'; /// {@template streamVoiceRecordingAttachmentTheme} @@ -36,19 +35,15 @@ class StreamVoiceRecordingAttachmentTheme extends InheritedTheme { /// StreamVoiceRecordingAttachmentTheme.of(context); /// ``` static StreamVoiceRecordingAttachmentThemeData of(BuildContext context) { - final voiceRecordingTheme = context.dependOnInheritedWidgetOfExactType< - StreamVoiceRecordingAttachmentTheme>(); - return voiceRecordingTheme?.data ?? - StreamChatTheme.of(context).voiceRecordingAttachmentTheme; + final voiceRecordingTheme = context.dependOnInheritedWidgetOfExactType(); + return voiceRecordingTheme?.data ?? StreamChatTheme.of(context).voiceRecordingAttachmentTheme; } @override - Widget wrap(BuildContext context, Widget child) => - StreamVoiceRecordingAttachmentTheme(data: data, child: child); + Widget wrap(BuildContext context, Widget child) => StreamVoiceRecordingAttachmentTheme(data: data, child: child); @override - bool updateShouldNotify(StreamVoiceRecordingAttachmentTheme oldWidget) => - data != oldWidget.data; + bool updateShouldNotify(StreamVoiceRecordingAttachmentTheme oldWidget) => data != oldWidget.data; } /// {@template streamVoiceRecordingAttachmentThemeData} @@ -61,31 +56,14 @@ class StreamVoiceRecordingAttachmentThemeData with Diagnosticable { /// {@macro streamVoiceRecordingAttachmentThemeData} const StreamVoiceRecordingAttachmentThemeData({ this.backgroundColor, - this.playIcon, - this.pauseIcon, - this.loadingIndicator, - this.audioControlButtonStyle, this.titleTextStyle, this.durationTextStyle, this.speedControlButtonStyle, - this.audioWaveformSliderTheme, }); /// The background color of the attachment. final Color? backgroundColor; - /// The icon widget to show when the recording is playing. - final Widget? playIcon; - - /// The icon widget to show when the recording is paused. - final Widget? pauseIcon; - - /// The widget to show when the recording is loading. - final Widget? loadingIndicator; - - /// The style for the audio control button. - final ButtonStyle? audioControlButtonStyle; - /// The text style for the title. final TextStyle? titleTextStyle; @@ -95,36 +73,19 @@ class StreamVoiceRecordingAttachmentThemeData with Diagnosticable { /// The style for the speed control button. final ButtonStyle? speedControlButtonStyle; - /// The theme for the audio waveform slider. - final StreamAudioWaveformSliderThemeData? audioWaveformSliderTheme; - /// A copy of [StreamVoiceRecordingAttachmentThemeData] with specified /// attributes overridden. StreamVoiceRecordingAttachmentThemeData copyWith({ Color? backgroundColor, - Widget? playIcon, - Widget? pauseIcon, - Widget? loadingIndicator, - ButtonStyle? audioControlButtonStyle, TextStyle? titleTextStyle, TextStyle? durationTextStyle, ButtonStyle? speedControlButtonStyle, - StreamAudioWaveformSliderThemeData? audioWaveformSliderTheme, - }) => - StreamVoiceRecordingAttachmentThemeData( - backgroundColor: backgroundColor ?? this.backgroundColor, - playIcon: playIcon ?? this.playIcon, - pauseIcon: pauseIcon ?? this.pauseIcon, - loadingIndicator: loadingIndicator ?? this.loadingIndicator, - audioControlButtonStyle: - audioControlButtonStyle ?? this.audioControlButtonStyle, - titleTextStyle: titleTextStyle ?? this.titleTextStyle, - durationTextStyle: durationTextStyle ?? this.durationTextStyle, - speedControlButtonStyle: - speedControlButtonStyle ?? this.speedControlButtonStyle, - audioWaveformSliderTheme: - audioWaveformSliderTheme ?? this.audioWaveformSliderTheme, - ); + }) => StreamVoiceRecordingAttachmentThemeData( + backgroundColor: backgroundColor ?? this.backgroundColor, + titleTextStyle: titleTextStyle ?? this.titleTextStyle, + durationTextStyle: durationTextStyle ?? this.durationTextStyle, + speedControlButtonStyle: speedControlButtonStyle ?? this.speedControlButtonStyle, + ); /// Merges this [StreamVoiceRecordingAttachmentThemeData] with the [other]. StreamVoiceRecordingAttachmentThemeData merge( @@ -133,16 +94,9 @@ class StreamVoiceRecordingAttachmentThemeData with Diagnosticable { if (other == null) return this; return copyWith( backgroundColor: other.backgroundColor, - playIcon: other.playIcon, - pauseIcon: other.pauseIcon, - loadingIndicator: other.loadingIndicator, - audioControlButtonStyle: other.audioControlButtonStyle, titleTextStyle: other.titleTextStyle, durationTextStyle: other.durationTextStyle, speedControlButtonStyle: other.speedControlButtonStyle, - audioWaveformSliderTheme: audioWaveformSliderTheme?.merge( - other.audioWaveformSliderTheme, - ), ); } @@ -155,18 +109,9 @@ class StreamVoiceRecordingAttachmentThemeData with Diagnosticable { ) { return StreamVoiceRecordingAttachmentThemeData( backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t), - playIcon: t < 0.5 ? a.playIcon : b.playIcon, - pauseIcon: t < 0.5 ? a.pauseIcon : b.pauseIcon, - loadingIndicator: t < 0.5 ? a.loadingIndicator : b.loadingIndicator, - audioControlButtonStyle: ButtonStyle.lerp( - a.audioControlButtonStyle, b.audioControlButtonStyle, t), titleTextStyle: TextStyle.lerp(a.titleTextStyle, b.titleTextStyle, t), - durationTextStyle: - TextStyle.lerp(a.durationTextStyle, b.durationTextStyle, t), - speedControlButtonStyle: ButtonStyle.lerp( - a.speedControlButtonStyle, b.speedControlButtonStyle, t), - audioWaveformSliderTheme: StreamAudioWaveformSliderThemeData.lerp( - a.audioWaveformSliderTheme!, b.audioWaveformSliderTheme!, t), + durationTextStyle: TextStyle.lerp(a.durationTextStyle, b.durationTextStyle, t), + speedControlButtonStyle: ButtonStyle.lerp(a.speedControlButtonStyle, b.speedControlButtonStyle, t), ); } @@ -175,24 +120,14 @@ class StreamVoiceRecordingAttachmentThemeData with Diagnosticable { identical(this, other) || other is StreamVoiceRecordingAttachmentThemeData && other.backgroundColor == backgroundColor && - other.playIcon == playIcon && - other.pauseIcon == pauseIcon && - other.loadingIndicator == loadingIndicator && - other.audioControlButtonStyle == audioControlButtonStyle && other.titleTextStyle == titleTextStyle && other.durationTextStyle == durationTextStyle && - other.speedControlButtonStyle == speedControlButtonStyle && - other.audioWaveformSliderTheme == audioWaveformSliderTheme; + other.speedControlButtonStyle == speedControlButtonStyle; @override int get hashCode => backgroundColor.hashCode ^ - playIcon.hashCode ^ - pauseIcon.hashCode ^ - loadingIndicator.hashCode ^ - audioControlButtonStyle.hashCode ^ titleTextStyle.hashCode ^ durationTextStyle.hashCode ^ - speedControlButtonStyle.hashCode ^ - audioWaveformSliderTheme.hashCode; + speedControlButtonStyle.hashCode; } diff --git a/packages/stream_chat_flutter/lib/src/user/user_mention_tile.dart b/packages/stream_chat_flutter/lib/src/user/user_mention_tile.dart index f7f9ffa42d..02648fe2a7 100644 --- a/packages/stream_chat_flutter/lib/src/user/user_mention_tile.dart +++ b/packages/stream_chat_flutter/lib/src/user/user_mention_tile.dart @@ -43,11 +43,7 @@ class StreamUserMentionTile extends StatelessWidget { const SizedBox( width: 16, ), - leading ?? - StreamUserAvatar( - user: user, - constraints: BoxConstraints.tight(const Size(40, 40)), - ), + leading ?? StreamUserAvatar(size: .lg, user: user), const SizedBox(width: 8), Expanded( child: Align( @@ -83,8 +79,8 @@ class StreamUserMentionTile extends StatelessWidget { right: 18, left: 8, ), - child: StreamSvgIcon( - icon: StreamSvgIcons.mentions, + child: Icon( + context.streamIcons.at, color: chatThemeData.colorTheme.accentPrimary, ), ), diff --git a/packages/stream_chat_flutter/lib/src/utils/date_formatter.dart b/packages/stream_chat_flutter/lib/src/utils/date_formatter.dart index 1b41cf6d3f..0b0f22c2c4 100644 --- a/packages/stream_chat_flutter/lib/src/utils/date_formatter.dart +++ b/packages/stream_chat_flutter/lib/src/utils/date_formatter.dart @@ -3,10 +3,7 @@ import 'package:jiffy/jiffy.dart'; import 'package:stream_chat_flutter/src/utils/extensions.dart'; /// Represents a function type that formats a date. -typedef DateFormatter = String Function( - BuildContext context, - DateTime date, -); +typedef DateFormatter = String Function(BuildContext context, DateTime date); /// Formats the given [date] as a String. String formatDate(BuildContext context, DateTime date) { diff --git a/packages/stream_chat_flutter/lib/src/utils/device_segmentation.dart b/packages/stream_chat_flutter/lib/src/utils/device_segmentation.dart index bd65820c5e..334e4cfe30 100644 --- a/packages/stream_chat_flutter/lib/src/utils/device_segmentation.dart +++ b/packages/stream_chat_flutter/lib/src/utils/device_segmentation.dart @@ -7,16 +7,12 @@ bool get isWeb => CurrentPlatform.isWeb; bool get isMobileDevice => CurrentPlatform.isIos || CurrentPlatform.isAndroid; /// Returns true if the app is running in a desktop device. -bool get isDesktopDevice => - CurrentPlatform.isMacOS || - CurrentPlatform.isWindows || - CurrentPlatform.isLinux; +bool get isDesktopDevice => CurrentPlatform.isMacOS || CurrentPlatform.isWindows || CurrentPlatform.isLinux; /// Returns true if the app is running on windows or linux platform. bool get isDesktopVideoPlayerSupported => // Dart VLC is not supported on MacOS. - !CurrentPlatform.isMacOS && - (CurrentPlatform.isWindows || CurrentPlatform.isLinux); + !CurrentPlatform.isMacOS && (CurrentPlatform.isWindows || CurrentPlatform.isLinux); /// Returns true if the app is running in a mobile or web. bool get isMobileDeviceOrWeb => isWeb || isMobileDevice; diff --git a/packages/stream_chat_flutter/lib/src/utils/extensions.dart b/packages/stream_chat_flutter/lib/src/utils/extensions.dart index 4fbb58a52a..2ae338eb00 100644 --- a/packages/stream_chat_flutter/lib/src/utils/extensions.dart +++ b/packages/stream_chat_flutter/lib/src/utils/extensions.dart @@ -23,8 +23,7 @@ extension IntExtension on int { if (this <= 0) return '0 B'; const suffixes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; final i = (log(this) / log(_byteUnitConversionFactor)).floor(); - final numberValue = - (this / pow(_byteUnitConversionFactor, i)).toStringAsFixed(2); + final numberValue = (this / pow(_byteUnitConversionFactor, i)).toStringAsFixed(2); final suffix = suffixes[i]; return '$numberValue $suffix'; } @@ -62,8 +61,7 @@ extension StringExtension on String { /// Returns the biggest line of a text. String biggestLine() { if (contains('\n')) { - return split('\n') - .reduce((curr, next) => curr.length > next.length ? curr : next); + return split('\n').reduce((curr, next) => curr.length > next.length ? curr : next); } else { return this; } @@ -103,55 +101,15 @@ extension StringExtension on String { /// Levenshtein distance between this and [t]. int levenshteinDistance(String t) => levenshtein(this, t); - - /// Returns a resized imageUrl with the given [width], [height], [resize] - /// and [crop] if it is from Stream CDN or Dashboard. - /// - /// Read more at https://getstream.io/chat/docs/flutter-dart/file_uploads/?language=dart#image-resizing - String getResizedImageUrl({ - // TODO: Are these sizes optimal? Consider web/desktop - double width = 400, - double height = 400, - String /*clip|crop|scale|fill*/ resize = 'clip', - String /*center|top|bottom|left|right*/ crop = 'center', - }) { - final uri = Uri.parse(this); - final host = uri.host; - - final fromStreamCDN = host.endsWith('stream-io-cdn.com'); - final fromStreamDashboard = host.endsWith('stream-cloud-uploads.imgix.net'); - - if (!fromStreamCDN && !fromStreamDashboard) return this; - - final queryParameters = {...uri.queryParameters}; - - if (fromStreamCDN) { - if (queryParameters['h'].isNullOrMatches('*') && - queryParameters['w'].isNullOrMatches('*') && - queryParameters['crop'].isNullOrMatches('*') && - queryParameters['resize'].isNullOrMatches('*')) { - queryParameters['h'] = height.floor().toString(); - queryParameters['w'] = width.floor().toString(); - queryParameters['crop'] = crop; - queryParameters['resize'] = resize; - } - } else if (fromStreamDashboard) { - queryParameters['height'] = height.floor().toString(); - queryParameters['width'] = width.floor().toString(); - queryParameters['fit'] = crop; - } - - return uri.replace(queryParameters: queryParameters).toString(); - } } /// List extension extension IterableExtension on Iterable { /// Insert any item inBetween the list items List insertBetween(T item) => expand((e) sync* { - yield item; - yield e; - }).skip(1).toList(growable: false); + yield item; + yield e; + }).skip(1).toList(growable: false); } /// Useful extension for [PlatformFile] @@ -263,8 +221,7 @@ extension InputDecorationX on InputDecoration { suffixIconConstraints: other.suffixIconConstraints, counter: other.counter, counterText: other.counterText, - counterStyle: - counterStyle?.merge(other.counterStyle) ?? other.counterStyle, + counterStyle: counterStyle?.merge(other.counterStyle) ?? other.counterStyle, filled: other.filled, fillColor: other.fillColor, focusColor: other.focusColor, @@ -291,8 +248,7 @@ extension BuildContextX on BuildContext { /// Retrieves current translations according to locale /// Defaults to [DefaultTranslations] - Translations get translations => - StreamChatLocalizations.of(this) ?? DefaultTranslations.instance; + Translations get translations => StreamChatLocalizations.of(this) ?? DefaultTranslations.instance; } /// Extension on [BorderRadius] @@ -384,8 +340,7 @@ extension UserListX on List { final entries = matchingUsers.entries.toList(growable: false) ..sort((prev, curr) { bool containsQuery(User user) => - normalize(user.id).contains(normalizedQuery) || - normalize(user.name).contains(normalizedQuery); + normalize(user.id).contains(normalizedQuery) || normalize(user.name).contains(normalizedQuery); final containsInPrev = containsQuery(prev.key); final containsInCurr = containsQuery(curr.key); @@ -425,8 +380,7 @@ extension MessageX on Message { var messageTextLength = min(text?.biggestLine().length ?? 0, 65); if (quotedMessage != null) { - var quotedMessageLength = - (min(quotedMessage!.text?.biggestLine().length ?? 0, 65)) + 8; + var quotedMessageLength = (min(quotedMessage!.text?.biggestLine().length ?? 0, 65)) + 8; if (quotedMessage!.attachments.isNotEmpty) { quotedMessageLength += 8; @@ -448,8 +402,7 @@ extension MessageX on Message { } /// It returns the message with the translated text if available locally - Message translate(String language) => - copyWith(text: i18n?['${language}_text'] ?? text); + Message translate(String language) => copyWith(text: i18n?['${language}_text'] ?? text); /// It returns the message replacing the mentioned user names with /// the respective user ids @@ -510,9 +463,9 @@ extension AttachmentPickerTypeX on AttachmentPickerType { AudiosPickerType() => FileType.audio, FilesPickerType() => FileType.any, _ => throw Exception( - 'Unsupported AttachmentPickerType: $this. ' - 'Only Images, Videos, Audios and Files are supported.', - ), + 'Unsupported AttachmentPickerType: $this. ' + 'Only Images, Videos, Audios and Files are supported.', + ), }; } } @@ -605,16 +558,11 @@ extension ChannelModelX on ChannelModel { // Otherwise, we return the names of the first `maxMembers` members sorted // alphabetically, followed by the number of remaining members if there are // more than `maxMembers` members. - final memberNames = otherMembers - .map((it) => it.user?.name) - .whereType() - .take(maxMembers) - .sorted(); + final memberNames = otherMembers.map((it) => it.user?.name).whereType().take(maxMembers).sorted(); return switch (otherMembers.length <= maxMembers) { true => memberNames.join(', '), - false => - '${memberNames.join(', ')} + ${otherMembers.length - maxMembers}', + false => '${memberNames.join(', ')} + ${otherMembers.length - maxMembers}', }; } } @@ -643,6 +591,15 @@ extension VoiceRecordingAttachmentExtension on Attachment { } } +/// {@template singleAttachmentPlaylistExtension} +/// Extension on [Attachment] to provide the playlist specific +/// properties. +/// {@endtemplate} +extension SingleAttachmentPlaylistExtension on Attachment { + /// Converts the attachment to a list of [PlaylistTrack]. + List toPlaylist() => [this].toPlaylist(); +} + /// {@template attachmentPlaylistExtension} /// Extension on [Iterable] to provide the playlist specific /// properties. @@ -654,25 +611,25 @@ extension AttachmentPlaylistExtension on Iterable { ...map((it) { final uri = switch (it.uploadState) { Preparing() || InProgress() || Failed() => () { - if (CurrentPlatform.isWeb) { - final bytes = it.file?.bytes; - final mimeType = it.file?.mediaType?.mimeType; - if (bytes == null || mimeType == null) return null; + if (CurrentPlatform.isWeb) { + final bytes = it.file?.bytes; + final mimeType = it.file?.mediaType?.mimeType; + if (bytes == null || mimeType == null) return null; - return Uri.dataFromBytes(bytes, mimeType: mimeType); - } + return Uri.dataFromBytes(bytes, mimeType: mimeType); + } - final path = it.file?.path; - if (path == null) return null; + final path = it.file?.path; + if (path == null) return null; - return Uri.file(path, windows: CurrentPlatform.isWindows); - }(), + return Uri.file(path, windows: CurrentPlatform.isWindows); + }(), Success() => () { - final url = it.assetUrl; - if (url == null) return null; + final url = it.assetUrl; + if (url == null) return null; - return Uri.tryParse(url); - }(), + return Uri.tryParse(url); + }(), }; if (uri == null) return null; @@ -682,6 +639,7 @@ extension AttachmentPlaylistExtension on Iterable { title: it.title, waveform: it.waveform, duration: it.duration, + key: it, ); }).nonNulls, ]; diff --git a/packages/stream_chat_flutter/lib/src/utils/helpers.dart b/packages/stream_chat_flutter/lib/src/utils/helpers.dart index 1e732bc40d..3f6226489f 100644 --- a/packages/stream_chat_flutter/lib/src/utils/helpers.dart +++ b/packages/stream_chat_flutter/lib/src/utils/helpers.dart @@ -113,10 +113,9 @@ Future showConfirmationBottomSheet( onPressed: () => Navigator.of(context).pop(false), style: TextButton.styleFrom( textStyle: chatThemeData.textTheme.bodyBold, - foregroundColor: - chatThemeData.colorTheme.textHighEmphasis - // ignore: deprecated_member_use - .withOpacity(0.5), + foregroundColor: chatThemeData.colorTheme.textHighEmphasis + // ignore: deprecated_member_use + .withOpacity(0.5), ), child: Text(cancelText), ), @@ -155,8 +154,7 @@ Future showInfoBottomSheet( }) { final chatThemeData = StreamChatTheme.of(context); return showModalBottomSheet( - backgroundColor: - theme?.colorTheme.barsBg ?? chatThemeData.colorTheme.barsBg, + backgroundColor: theme?.colorTheme.barsBg ?? chatThemeData.colorTheme.barsBg, context: context, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.only( @@ -177,8 +175,7 @@ Future showInfoBottomSheet( ), Text( title, - style: theme?.textTheme.headlineBold ?? - chatThemeData.textTheme.headlineBold, + style: theme?.textTheme.headlineBold ?? chatThemeData.textTheme.headlineBold, ), const SizedBox( height: 7, @@ -188,10 +185,9 @@ Future showInfoBottomSheet( height: 36, ), Container( - // ignore: deprecated_member_use - color: theme?.colorTheme.textHighEmphasis.withOpacity(0.08) ?? - // ignore: deprecated_member_use - chatThemeData.colorTheme.textHighEmphasis.withOpacity(0.08), + color: + theme?.colorTheme.textHighEmphasis.withValues(alpha: 0.08) ?? + chatThemeData.colorTheme.textHighEmphasis.withValues(alpha: 0.08), height: 1, ), Center( @@ -203,8 +199,7 @@ Future showInfoBottomSheet( okText, style: TextStyle( // ignore: deprecated_member_use - color: theme?.colorTheme.textHighEmphasis.withOpacity(0.5) ?? - chatThemeData.colorTheme.accentPrimary, + color: theme?.colorTheme.textHighEmphasis.withOpacity(0.5) ?? chatThemeData.colorTheme.accentPrimary, fontWeight: FontWeight.w400, ), ), @@ -217,8 +212,7 @@ Future showInfoBottomSheet( } /// Get random png with initials -String getRandomPicUrl(User user) => - 'https://getstream.io/random_png/?id=${user.id}&name=${user.name}'; +String getRandomPicUrl(User user) => 'https://getstream.io/random_png/?id=${user.id}&name=${user.name}'; /// Get websiteName from [hostName] String? getWebsiteName(String hostName) { @@ -308,8 +302,7 @@ String fileSize(dynamic size, [int round = 2]) { return '${(_size / divider / divider / divider).toStringAsFixed(round)} GB'; } - if (_size < divider * divider * divider * divider * divider && - _size % divider == 0) { + if (_size < divider * divider * divider * divider * divider && _size % divider == 0) { final num r = _size / divider / divider / divider / divider; return '${r.toStringAsFixed(0)} TB'; } @@ -319,8 +312,7 @@ String fileSize(dynamic size, [int round = 2]) { return '${r.toStringAsFixed(round)} TB'; } - if (_size < divider * divider * divider * divider * divider * divider && - _size % divider == 0) { + if (_size < divider * divider * divider * divider * divider * divider && _size % divider == 0) { final num r = _size / divider / divider / divider / divider / divider; return '${r.toStringAsFixed(0)} PB'; } else { @@ -346,8 +338,7 @@ StreamSvgIcon getFileTypeImage([String? mimeType]) { 'application/zip' => StreamSvgIcons.filetypeCompressionZip, 'application/x-7z-compressed' => StreamSvgIcons.filetypeCompression7z, 'application/x-arj' => StreamSvgIcons.filetypeCompressionArj, - 'application/vnd.debian.binary-package' => - StreamSvgIcons.filetypeCompressionDeb, + 'application/vnd.debian.binary-package' => StreamSvgIcons.filetypeCompressionDeb, 'application/x-apple-diskimage' => StreamSvgIcons.filetypeCompressionPkg, 'application/x-rar-compressed' => StreamSvgIcons.filetypeCompressionRar, 'application/x-rpm' => StreamSvgIcons.filetypeCompressionRpm, @@ -357,20 +348,14 @@ StreamSvgIcon getFileTypeImage([String? mimeType]) { 'application/vnd.openxmlformats-officedocument.presentationml.presentation' => StreamSvgIcons.filetypePresentationPptx, 'application/vnd.apple.keynote' => StreamSvgIcons.filetypePresentationKey, - 'application/vnd.oasis.opendocument.presentation' => - StreamSvgIcons.filetypePresentationOdp, + 'application/vnd.oasis.opendocument.presentation' => StreamSvgIcons.filetypePresentationOdp, 'application/vnd.ms-excel' => StreamSvgIcons.filetypeSpreadsheetXls, - 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => - StreamSvgIcons.filetypeSpreadsheetXlsx, - 'application/vnd.ms-excel.sheet.macroEnabled.12' => - StreamSvgIcons.filetypeSpreadsheetXlsm, - 'application/vnd.oasis.opendocument.spreadsheet' => - StreamSvgIcons.filetypeSpreadsheetOds, + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => StreamSvgIcons.filetypeSpreadsheetXlsx, + 'application/vnd.ms-excel.sheet.macroEnabled.12' => StreamSvgIcons.filetypeSpreadsheetXlsm, + 'application/vnd.oasis.opendocument.spreadsheet' => StreamSvgIcons.filetypeSpreadsheetOds, 'application/msword' => StreamSvgIcons.filetypeTextDoc, - 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => - StreamSvgIcons.filetypeTextDocx, - 'application/vnd.oasis.opendocument.text' => - StreamSvgIcons.filetypeTextOdt, + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => StreamSvgIcons.filetypeTextDocx, + 'application/vnd.oasis.opendocument.text' => StreamSvgIcons.filetypeTextOdt, 'text/plain' => StreamSvgIcons.filetypeTextTxt, 'application/rtf' => StreamSvgIcons.filetypeTextRtf, 'application/x-tex' => StreamSvgIcons.filetypeTextTex, diff --git a/packages/stream_chat_flutter/lib/src/utils/message_preview_formatter.dart b/packages/stream_chat_flutter/lib/src/utils/message_preview_formatter.dart index d7d0909f06..88b178ce18 100644 --- a/packages/stream_chat_flutter/lib/src/utils/message_preview_formatter.dart +++ b/packages/stream_chat_flutter/lib/src/utils/message_preview_formatter.dart @@ -1,7 +1,5 @@ import 'package:flutter/widgets.dart'; -import 'package:stream_chat_flutter/src/theme/stream_chat_theme.dart'; -import 'package:stream_chat_flutter/src/utils/extensions.dart'; -import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// {@template messagePreviewFormatter} /// Formats message previews for display. @@ -46,13 +44,14 @@ import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; /// ```dart /// class CustomFormatter extends StreamMessagePreviewFormatter { /// @override -/// String formatGroupMessage( +/// TextSpan? formatGroupMessage( /// BuildContext context, /// User? messageAuthor, -/// String messageText, +/// User? currentUser, /// ) { -/// if (messageAuthor == null) return messageText; -/// return '${messageAuthor.name} says: $messageText'; +/// final name = messageAuthor?.name; +/// if (name == null || name.isEmpty) return null; +/// return TextSpan(text: '$name says: '); /// } /// } /// ``` @@ -93,25 +92,29 @@ abstract interface class MessagePreviewFormatter { /// /// This formatter applies context-aware formatting based on message type, /// sender identity, and channel configuration. It handles various message -/// types including regular text, attachments, polls, system messages, and -/// deleted messages. +/// types including regular text, attachments, polls, locations, system +/// messages, and deleted messages. /// /// ## Message Type Handling /// /// The formatter handles messages differently based on their type: /// -/// * **Deleted messages** - Shows "Message deleted" +/// * **Deleted messages** - Shows a ban icon with localized deleted label /// * **System messages** - Shows the message text directly -/// * **Poll messages** - Shows poll emoji with voter/creator info +/// * **Poll messages** - Shows a poll icon with the poll name +/// * **Location messages** - Shows a map pin icon with location label or +/// message caption /// * **Regular messages** - Shows text with optional attachment previews /// /// ## Sender Context /// -/// The formatting adapts based on who sent the message: +/// In group channels (member count > 2), [formatGroupMessage] prepends +/// a sender prefix: +/// +/// * **Current user** - Adds bold "You:" prefix +/// * **Other users** - Adds bold first-name prefix /// -/// * **Current user** - Adds "You:" prefix -/// * **Direct messages (1-on-1)** - No prefix -/// * **Group messages** - Adds sender name prefix +/// In direct (1-on-1) channels, no sender prefix is added. /// /// ## Customization /// @@ -120,19 +123,22 @@ abstract interface class MessagePreviewFormatter { /// ```dart /// class ShortFormatter extends StreamMessagePreviewFormatter { /// @override -/// String formatCurrentUserMessage(BuildContext context, String text) { -/// // Remove "You:" prefix for cleaner display. -/// return text; +/// TextSpan? formatGroupMessage( +/// BuildContext context, +/// User? messageAuthor, +/// User? currentUser, +/// ) { +/// // Remove sender prefix for cleaner display. +/// return null; /// } /// /// @override -/// String formatPollMessage( +/// TextSpan formatPollMessage( /// BuildContext context, /// Poll poll, /// User? currentUser, /// ) { -/// // Always show just the poll name. -/// return poll.name.isEmpty ? '📊 Poll' : '📊 ${poll.name}'; +/// return TextSpan(text: poll.name.isEmpty ? 'Poll' : poll.name); /// } /// } /// ``` @@ -143,18 +149,17 @@ abstract interface class MessagePreviewFormatter { /// /// **Content Extraction:** /// * [formatRegularMessage] - Extracts message content (text + attachments) -/// * [formatMessageAttachments] - Formats attachment previews +/// * [formatMessageAttachments] - Formats attachment previews with icons /// /// **Message Types:** /// * [formatDeletedMessage] - Formats deleted messages /// * [formatSystemMessage] - Formats system messages /// * [formatEmptyMessage] - Formats empty messages /// * [formatPollMessage] - Formats poll messages +/// * [formatLocationMessage] - Formats shared location messages /// /// **Sender Context:** -/// * [formatCurrentUserMessage] - Formats messages from current user -/// * [formatDirectMessage] - Formats messages in 1-on-1 channels -/// * [formatGroupMessage] - Formats messages in group channels +/// * [formatGroupMessage] - Adds sender prefix in group channels /// /// **Draft Messages:** /// * [getDraftPrefix] - Returns the draft message prefix text @@ -167,6 +172,7 @@ class StreamMessagePreviewFormatter implements MessagePreviewFormatter { TextSpan formatMessage( BuildContext context, Message message, { + bool showCaption = true, ChannelModel? channel, User? currentUser, TextStyle? textStyle, @@ -176,41 +182,19 @@ class StreamMessagePreviewFormatter implements MessagePreviewFormatter { message, channel, currentUser, + showCaption: showCaption, ); - final mentionedUsers = message.mentionedUsers; - if (mentionedUsers.isEmpty) { - return TextSpan(text: previewText, style: textStyle); - } - - final mentionedUsersRegex = RegExp( - mentionedUsers.map((it) => '@${RegExp.escape(it.name)}').join('|'), - ); - - final children = [ - ...previewText.splitByRegExp(mentionedUsersRegex).map( - (text) { - if (mentionedUsers.any((it) => '@${it.name}' == text)) { - return TextSpan( - text: text, - style: textStyle?.copyWith(fontWeight: FontWeight.bold), - ); - } - - return TextSpan(text: text, style: textStyle); - }, - ) - ]; - - return TextSpan(children: children); + return TextSpan(children: [previewText], style: textStyle); } - String _buildPreviewText( + TextSpan _buildPreviewText( BuildContext context, Message message, ChannelModel? channel, - User? currentUser, - ) { + User? currentUser, { + bool showCaption = true, + }) { if (message.isDeleted) { return formatDeletedMessage(context, message); } @@ -223,285 +207,420 @@ class StreamMessagePreviewFormatter implements MessagePreviewFormatter { return formatPollMessage(context, poll, currentUser); } + TextSpan? messageSpan; + if (message.sharedLocation case final location?) { - return formatLocationMessage(context, location); + messageSpan = formatLocationMessage( + context, + message, + location, + showCaption: showCaption, + ); + } else { + messageSpan = formatRegularMessage( + context, + message, + showCaption: showCaption, + ); } - final messagePreviewText = formatRegularMessage(context, message); - if (messagePreviewText == null) return formatEmptyMessage(context, message); + if (messageSpan == null) return formatEmptyMessage(context, message); - if (channel == null) return messagePreviewText; + if (channel == null) return messageSpan; - if (message.user?.id == currentUser?.id) { - return formatCurrentUserMessage(context, messagePreviewText); - } + return TextSpan( + children: [ + if (channel.memberCount > 2) ?formatGroupMessage(context, message.user, currentUser), + messageSpan, + ], + ); + } - if (channel.memberCount > 2) { - return formatGroupMessage(context, message.user, messagePreviewText); - } + TextSpan _textSpanWithMentions(String text, List mentionedUsers) { + if (mentionedUsers.isEmpty) return TextSpan(text: text); + + final mentionRegex = RegExp( + mentionedUsers.map((it) => '@${RegExp.escape(it.name)}').join('|'), + ); - return formatDirectMessage(context, messagePreviewText); + final parts = text.splitByRegExp(mentionRegex); + if (parts.length <= 1) return TextSpan(text: text); + + return TextSpan( + children: parts.map((part) { + if (mentionedUsers.any((it) => '@${it.name}' == part)) { + return TextSpan( + text: part, + style: const TextStyle(fontWeight: FontWeight.bold), + ); + } + return TextSpan(text: part); + }).toList(), + ); } - /// The text content of a regular [message], including attachment previews. + /// The content of a regular [message] as a [TextSpan], including attachment + /// previews. /// /// Extracts the message text and formats any attachments using - /// [formatMessageAttachments]. Returns `null` if the message has no text - /// or attachments. + /// [formatMessageAttachments]. Mentions within the text are bolded. + /// Returns `null` if the message has no text or attachments. + /// + /// When [showCaption] is `true` and the message has both text and + /// attachments, the text is shown alongside the attachment icon. /// /// Override to customize how message content is extracted: /// /// ```dart /// @override - /// String? formatRegularMessage(BuildContext context, Message message) { - /// // Only show text, ignore attachments - /// return message.text; + /// TextSpan? formatRegularMessage( + /// BuildContext context, + /// Message message, { + /// bool showCaption = true, + /// }) { + /// final text = message.text; + /// if (text == null || text.isEmpty) return null; + /// return TextSpan(text: text); /// } /// ``` @protected - String? formatRegularMessage(BuildContext context, Message message) { + TextSpan? formatRegularMessage( + BuildContext context, + Message message, { + bool showCaption = true, + }) { final messageText = switch (message.text?.trim()) { final text? when text.isNotEmpty => text, _ => null, }; final attachments = message.attachments; - if (attachments.isEmpty) return messageText; - - return formatMessageAttachments(context, messageText, message.attachments); - } + final mentionedUsers = message.mentionedUsers; - /// The preview text for a deleted [message]. - @protected - String formatDeletedMessage(BuildContext context, Message message) { - return context.translations.messageDeletedLabel; - } + if (attachments.isEmpty) { + return messageText != null ? _textSpanWithMentions(messageText, mentionedUsers) : null; + } - /// The preview text for a system [message]. - @protected - String formatSystemMessage(BuildContext context, Message message) { - if (message.text case final text? when text.isNotEmpty) return text; - return context.translations.systemMessageLabel; + return formatMessageAttachments( + context, + messageText, + message.attachments, + mentionedUsers: mentionedUsers, + showCaption: showCaption, + ); } - /// The preview text for an empty [message]. + /// The preview [TextSpan] for a deleted [message]. + /// + /// Shows a ban icon followed by the localized deleted message label, + /// both styled with the tertiary text color. @protected - String formatEmptyMessage(BuildContext context, Message message) { - return context.translations.emptyMessagePreviewText; + TextSpan formatDeletedMessage(BuildContext context, Message message) { + return TextSpan( + children: [ + WidgetSpan( + child: Icon( + context.streamIcons.circleBanSign, + size: 16, + color: context.streamColorScheme.textTertiary, + ), + ), + WidgetSpan( + child: SizedBox(width: context.streamSpacing.xxs), + ), + TextSpan( + text: context.translations.messageDeletedLabel, + style: TextStyle(color: context.streamColorScheme.textTertiary), + ), + ], + ); } - /// The formatted [messageText] with "You:" prefix for the current user. + /// The preview [TextSpan] for a system [message]. /// - /// Override this to customize how messages from the current user are - /// displayed: - /// - /// ```dart - /// @override - /// String formatCurrentUserMessage( - /// BuildContext context, - /// String messageText, - /// ) { - /// return messageText; // Remove prefix - /// } - /// ``` + /// Returns the message text if available, otherwise a localized + /// system message label. @protected - String formatCurrentUserMessage(BuildContext context, String messageText) { - return '${context.translations.youText}: $messageText'; + TextSpan formatSystemMessage(BuildContext context, Message message) { + if (message.text case final text? when text.isNotEmpty) return TextSpan(text: text); + return TextSpan(text: context.translations.systemMessageLabel); } - /// The [messageText] without prefix for 1-on-1 channels. - /// - /// No prefix is added since the other user's identity is clear from the - /// channel itself. Override to add context if needed: + /// The preview [TextSpan] for an empty [message]. /// - /// ```dart - /// @override - /// String formatDirectMessage(BuildContext context, String messageText) { - /// return '💬 $messageText'; - /// } - /// ``` + /// Returns the localized empty message preview text, styled with the + /// tertiary text color. @protected - String formatDirectMessage(BuildContext context, String messageText) { - return messageText; + TextSpan formatEmptyMessage(BuildContext context, Message message) { + return TextSpan( + text: context.translations.emptyMessagePreviewText, + style: TextStyle(color: context.streamColorScheme.textTertiary), + ); } - /// The formatted [messageText] with [messageAuthor] name prefix for groups. + /// A bold sender prefix [TextSpan] for group channel previews. /// - /// Adds the author's name as a prefix. Returns [messageText] without - /// prefix if [messageAuthor] is `null`. + /// Returns a "You: " prefix when [messageAuthor] matches [currentUser], + /// or the author's first name followed by ": " for other users. Returns + /// `null` if the author name is unavailable. /// - /// Override to customize author name formatting: + /// Override to customize the sender prefix: /// /// ```dart /// @override - /// String formatGroupMessage( + /// TextSpan? formatGroupMessage( /// BuildContext context, /// User? messageAuthor, - /// String messageText, + /// User? currentUser, /// ) { - /// if (messageAuthor == null) return messageText; - /// return '${messageAuthor.name} says: $messageText'; + /// final name = messageAuthor?.name; + /// if (name == null || name.isEmpty) return null; + /// return TextSpan(text: '$name: '); /// } /// ``` @protected - String formatGroupMessage( + TextSpan? formatGroupMessage( BuildContext context, User? messageAuthor, - String messageText, + User? currentUser, ) { - final authorName = messageAuthor?.name; - if (authorName == null || authorName.isEmpty) return messageText; + if (messageAuthor?.id == currentUser?.id) { + return TextSpan( + text: '${context.translations.youText}: ', + style: TextStyle( + fontWeight: FontWeight.bold, + color: context.streamColorScheme.textTertiary, + ), + ); + } - return '$authorName: $messageText'; + final authorName = messageAuthor?.name.split(' ')[0]; + if (authorName == null || authorName.isEmpty) return null; + + return TextSpan( + text: '$authorName: ', + style: TextStyle( + fontWeight: FontWeight.bold, + color: context.streamColorScheme.textTertiary, + ), + ); } - /// The formatted preview for the first attachment in [attachments]. + /// The formatted preview [TextSpan] for [attachments]. + /// + /// Renders an icon prefix based on the attachment type, followed by either + /// the [messageText] (when [showCaption] is `true`) or a descriptive suffix + /// (attachment name, count, or duration). [mentionedUsers] in the message + /// text are bolded. /// - /// Formats each attachment type with an emoji icon and title. The - /// [messageText] is used as fallback for certain types. Returns - /// [messageText] if no attachments are present or the type is unsupported. + /// Returns `null` if [attachments] is empty and [messageText] is `null`. /// - /// Supported types: Audio (🎧), File (📄), Image (📷), Video (📹), - /// Giphy (/giphy), and Voice Recording (🎤). + /// When attachments have mixed types, a generic file icon is used with the + /// total file count. For uniform types, a type-specific icon is shown: + /// Audio/Voice Recording (microphone), Image (camera), Video (video), + /// Giphy (/giphy), and File (file). /// /// Override to handle custom attachment types: /// /// ```dart /// @override - /// String? formatMessageAttachments( + /// TextSpan? formatMessageAttachments( /// BuildContext context, /// String? messageText, - /// Iterable attachments, - /// ) { + /// Iterable attachments, { + /// List mentionedUsers = const [], + /// bool showCaption = true, + /// }) { /// final attachment = attachments.firstOrNull; /// if (attachment?.type == 'product') { - /// return '🛍️ ${attachment?.extraData['title'] ?? "Product"}'; + /// return TextSpan(text: '🛍️ Product'); /// } /// return super.formatMessageAttachments( /// context, /// messageText, /// attachments, + /// mentionedUsers: mentionedUsers, + /// showCaption: showCaption, /// ); /// } /// ``` @protected - String? formatMessageAttachments( + TextSpan? formatMessageAttachments( BuildContext context, String? messageText, - Iterable attachments, - ) { - final translations = context.translations; + Iterable attachments, { + List mentionedUsers = const [], + bool showCaption = true, + }) { final attachment = attachments.firstOrNull; - if (attachment == null) return messageText; - - // If the message contains some attachments, we will show the first one - // and the text if it exists. - final attachmentIcon = switch (attachment.type) { - AttachmentType.audio => '🎧', - AttachmentType.file => '📄', - AttachmentType.image => '📷', - AttachmentType.video => '📹', - AttachmentType.giphy => '/giphy', - AttachmentType.voiceRecording => '🎤', - _ => null, - }; + if (attachment == null) { + return messageText != null ? _textSpanWithMentions(messageText, mentionedUsers) : null; + } - final attachmentTitle = switch (attachment.type) { - AttachmentType.audio => messageText ?? translations.audioAttachmentText, - AttachmentType.file => attachment.title ?? messageText, - AttachmentType.image => messageText ?? translations.imageAttachmentText, - AttachmentType.video => messageText ?? translations.videoAttachmentText, - AttachmentType.giphy => messageText, - AttachmentType.voiceRecording => translations.voiceRecordingText, - _ => null, + final mixedTypes = attachments.any((it) => it.type != attachment.type); + final prefix = _attachmentPrefix(context, mixedTypes ? null : attachment.type); + + if (showCaption && messageText != null) { + return TextSpan( + children: [ + prefix, + WidgetSpan(child: SizedBox(width: context.streamSpacing.xxs)), + _textSpanWithMentions(messageText, mentionedUsers), + ], + ); + } + + final suffix = _attachmentSuffix( + context, + attachment, + count: attachments.length, + isMixed: mixedTypes, + ); + + return TextSpan( + children: [ + prefix, + WidgetSpan(child: SizedBox(width: context.streamSpacing.xxs)), + ?suffix, + ], + ); + } + + InlineSpan _attachmentPrefix(BuildContext context, String? type) { + final icons = context.streamIcons; + return switch (type) { + AttachmentType.audio || AttachmentType.voiceRecording => WidgetSpan(child: Icon(icons.microphone, size: 16)), + AttachmentType.image => WidgetSpan(child: Icon(icons.camera1, size: 16)), + AttachmentType.video => WidgetSpan(child: Icon(icons.video, size: 16)), + AttachmentType.giphy => const TextSpan(text: '/giphy'), + _ => WidgetSpan(child: Icon(icons.fileBend, size: 16)), }; + } - if (attachmentIcon != null || attachmentTitle != null) { - return [attachmentIcon, attachmentTitle].nonNulls.join(' '); - } + TextSpan? _attachmentSuffix( + BuildContext context, + Attachment attachment, { + required int count, + required bool isMixed, + }) { + final translations = context.translations; - return messageText; + if (isMixed) return TextSpan(text: translations.filesAttachmentCountText(count)); + + return switch (attachment.type) { + AttachmentType.audio => TextSpan(text: translations.audioAttachmentText), + AttachmentType.voiceRecording => TextSpan( + text: '${translations.voiceRecordingText} (${attachment.duration.toMinutesAndSeconds()})', + ), + AttachmentType.file => TextSpan( + text: (count == 1 ? attachment.file?.name : null) ?? translations.filesAttachmentCountText(count), + ), + AttachmentType.image => TextSpan(text: translations.photosAttachmentCountText(count)), + AttachmentType.video => TextSpan(text: translations.videosAttachmentCountText(count)), + _ => null, + }; } - /// The formatted preview for a [poll] message with voter or creator info. + /// The formatted preview [TextSpan] for a [poll] message. + /// + /// Shows a poll chart icon followed by the latest vote activity when + /// available, or the poll name as a fallback. Specifically: /// - /// Shows the latest voter and poll name if the poll has votes, otherwise - /// shows the creator and poll name. If the poll has no votes or creator, - /// shows just the poll name. Actions by [currentUser] show as "You", - /// while actions by other users show their name. + /// - If the [currentUser] cast the latest vote, shows "You voted: {answer}". + /// - If another user cast the latest vote, shows "{name} voted: {answer}". + /// - Otherwise, falls back to displaying the [poll] name (trimmed). If the + /// name is empty, only the icon is shown. /// /// Override to customize poll formatting: /// /// ```dart /// @override - /// String formatPollMessage( + /// TextSpan formatPollMessage( /// BuildContext context, /// Poll poll, /// User? currentUser, /// ) { - /// return poll.name.isEmpty ? '📊 Poll' : '📊 ${poll.name}'; + /// return TextSpan( + /// text: poll.name.isEmpty ? 'Poll' : poll.name, + /// ); /// } /// ``` @protected - String formatPollMessage( + TextSpan formatPollMessage( BuildContext context, Poll poll, User? currentUser, ) { final translations = context.translations; + TextSpan? latestVoterSpan; - // If the poll already contains some votes, we will preview the latest voter - // and the poll name - if (poll.latestVotes.firstOrNull?.user case final latestVoter?) { - if (latestVoter.id == currentUser?.id) { + if (poll.latestVotes.firstOrNull case final latestVote?) { + if (latestVote.user?.id == currentUser?.id) { final youVoted = translations.pollYouVotedText; - return '📊 $youVoted: "${poll.name}"'; - } - - final someoneVoted = translations.pollSomeoneVotedText(latestVoter.name); - return '📊 $someoneVoted: "${poll.name}"'; - } - - // Otherwise, we will show the creator of the poll and the poll name - if (poll.createdBy case final creator?) { - if (creator.id == currentUser?.id) { - final youCreated = translations.pollYouCreatedText; - return '📊 $youCreated: "${poll.name}"'; + latestVoterSpan = TextSpan(text: '$youVoted: ${latestVote.answerText}'); + } else if (latestVote.user case final latestVoter?) { + if (latestVote.answerText != null) { + final someoneVoted = translations.pollSomeoneVotedText(latestVoter.name.split(' ')[0]); + latestVoterSpan = TextSpan(text: '$someoneVoted: ${latestVote.answerText}'); + } } - - final someoneCreated = translations.pollSomeoneCreatedText(creator.name); - return '📊 $someoneCreated: "${poll.name}"'; } - // Otherwise, we will show the poll name if it exists. - if (poll.name.trim() case final pollName when pollName.isNotEmpty) { - return '📊 $pollName'; - } - - // If nothing else, we will show the default poll emoji. - return '📊'; + return TextSpan( + children: [ + WidgetSpan(child: Icon(context.streamIcons.chart5, size: 16)), + if (latestVoterSpan case final latestVoterSpan?) ...[ + WidgetSpan(child: SizedBox(width: context.streamSpacing.xxs)), + latestVoterSpan, + ] else if (poll.name.trim() case final pollName when pollName.isNotEmpty) ...[ + WidgetSpan(child: SizedBox(width: context.streamSpacing.xxs)), + TextSpan(text: pollName), + ], + ], + ); } - /// The formatted preview for a shared [location] message. + /// The formatted preview [TextSpan] for a shared [location] message. + /// + /// Shows a map pin icon followed by the [message] text (when [showCaption] + /// is `true` and text is available) or a localized location label. Live + /// locations use a distinct label from static ones. /// /// Override to customize shared location formatting: /// /// ```dart /// @override - /// String formatLocationMessage( + /// TextSpan formatLocationMessage( /// BuildContext context, - /// Location location, - /// ) { - /// return '📍 (${location.latitude}, ${location.longitude})'; + /// Message message, + /// Location location, { + /// bool showCaption = true, + /// }) { + /// return TextSpan( + /// text: '📍 (${location.latitude}, ${location.longitude})', + /// ); /// } /// ``` @protected - String formatLocationMessage( + TextSpan formatLocationMessage( BuildContext context, - Location location, - ) { - final translations = context.translations; - return translations.locationLabel(isLive: location.isLive); + Message message, + Location location, { + bool showCaption = true, + }) { + return TextSpan( + children: [ + WidgetSpan(child: Icon(context.streamIcons.mapPin, size: 16)), + WidgetSpan( + child: SizedBox(width: context.streamSpacing.xxs), + ), + if (message.text?.trim() case final messageText? when messageText.isNotEmpty && showCaption) ...[ + _textSpanWithMentions(messageText, message.mentionedUsers), + ] else ...[ + TextSpan(text: context.translations.locationLabel(isLive: location.isLive)), + ], + ], + ); } @override @@ -510,16 +629,16 @@ class StreamMessagePreviewFormatter implements MessagePreviewFormatter { DraftMessage draftMessage, { TextStyle? textStyle, }) { - final theme = StreamChatTheme.of(context); - final colorTheme = theme.colorTheme; + final colorScheme = context.streamColorScheme; return TextSpan( - text: getDraftPrefix(context), - style: textStyle?.copyWith( - fontWeight: FontWeight.bold, - color: colorTheme.accentPrimary, - ), children: [ + TextSpan( + text: getDraftPrefix(context), + style: (textStyle ?? context.streamTextTheme.captionEmphasis).copyWith( + color: colorScheme.accentPrimary, + ), + ), const TextSpan(text: ' '), // Space between prefix and message TextSpan(text: draftMessage.text, style: textStyle), ], diff --git a/packages/stream_chat_flutter/lib/src/utils/stream_image_cdn.dart b/packages/stream_chat_flutter/lib/src/utils/stream_image_cdn.dart new file mode 100644 index 0000000000..2b9b569b78 --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/utils/stream_image_cdn.dart @@ -0,0 +1,175 @@ +/// Resize mode for CDN image transformations. +/// +/// See the [Stream Image Resizing docs](https://getstream.io/chat/docs/flutter-dart/file_uploads/?language=dart#image-resizing) +/// for more information. +enum ResizeMode { + /// Resizes the image to fit within the given dimensions, preserving the + /// aspect ratio. The image may be smaller than the requested size. + clip('clip'), + + /// Resizes and crops the image to exactly fill the given dimensions. + crop('crop'), + + /// Stretches the image to exactly fill the given dimensions, + /// ignoring the aspect ratio. + scale('scale'), + + /// Resizes the image to fill the given dimensions, preserving the + /// aspect ratio. Parts of the image may be cropped. + fill('fill') + ; + + const ResizeMode(this.value); + + /// The raw string value used as a CDN query parameter. + final String value; +} + +/// Crop alignment for CDN image transformations. +/// +/// This determines which part of the image is preserved when cropping. +enum CropMode { + /// Crop from the center of the image. + center('center'), + + /// Crop from the top of the image. + top('top'), + + /// Crop from the bottom of the image. + bottom('bottom'), + + /// Crop from the left of the image. + left('left'), + + /// Crop from the right of the image. + right('right') + ; + + const CropMode(this.value); + + /// The raw string value used as a CDN query parameter. + final String value; +} + +/// Configuration for resizing an image via a CDN. +/// +/// When passed to [StreamImageCDN.resolveUrl], the CDN will resize the image +/// to the given [width] and [height] using the specified [mode] and [crop]. +class ImageResize { + /// Creates a new [ImageResize] configuration. + const ImageResize({ + required this.width, + required this.height, + this.mode = .clip, + this.crop = .center, + }); + + /// The target width in logical pixels. + final double width; + + /// The target height in logical pixels. + final double height; + + /// The resize mode to use. + /// + /// Defaults to [ResizeMode.clip]. + final ResizeMode mode; + + /// The crop alignment when the resize mode requires cropping. + /// + /// Defaults to [CropMode.center]. + final CropMode crop; +} + +/// Handles CDN URL resolution and cache key generation for Stream Chat images. +/// +/// The default implementation supports Stream's own CDN +/// (`stream-io-cdn.com`). +/// +/// To customize behavior for a custom CDN, extend this class and override +/// [resolveUrl] and/or [cacheKey]: +/// +/// ```dart +/// class MyImageCDN extends StreamImageCDN { +/// @override +/// String cacheKey(String imageUrl) { +/// // Custom cache key logic for your CDN. +/// return Uri.parse(imageUrl).path; +/// } +/// } +/// ``` +/// +/// Then inject it via [StreamChatConfigurationData]: +/// +/// ```dart +/// StreamChat( +/// client: client, +/// config: StreamChatConfigurationData( +/// imageCDN: MyImageCDN(), +/// ), +/// child: ..., +/// ) +/// ``` +class StreamImageCDN { + /// Creates a new [StreamImageCDN] instance. + const StreamImageCDN(); + + // The host suffix for Stream's image CDN. + static const _streamCDNHost = 'stream-io-cdn.com'; + + // Query parameter names that are preserved in cache keys. + // + // These are the image-transformation parameters that affect + // which rendition of the image is returned. All other parameters + // (e.g. signed URL tokens) are stripped. + static const _persistedParameters = {'w', 'h', 'resize', 'crop'}; + + /// Resolves the [sourceUrl] by appending resize/transform parameters + /// appropriate for the CDN. + /// + /// When [resize] is null, no resizing parameters are added and the + /// [sourceUrl] is returned unchanged. + /// + /// For non-Stream CDN URLs, returns [sourceUrl] unchanged regardless + /// of [resize]. + /// + /// Override this to customize URL rewriting for a custom CDN. + String resolveUrl(String sourceUrl, {ImageResize? resize}) { + final uri = Uri.tryParse(sourceUrl); + if (uri == null || !uri.host.contains(_streamCDNHost)) return sourceUrl; + if (resize == null) return sourceUrl; + + final queryParameters = { + ...uri.queryParameters, + 'w': resize.width == 0 ? '*' : resize.width.floor().toString(), + 'h': resize.height == 0 ? '*' : resize.height.floor().toString(), + 'resize': resize.mode.value, + 'ro': '0', + if (resize.mode == ResizeMode.crop) 'crop': resize.crop.value, + }; + + return uri.replace(queryParameters: queryParameters).toString(); + } + + /// Returns a stable cache key for [imageUrl], stripping volatile + /// authentication parameters (e.g. CloudFront signed URL tokens) + /// while preserving those that identify distinct image renditions. + /// + /// This uses an allowlist approach, keeping only the parameters in + /// [_persistedParameters] for Stream CDN URLs. + /// + /// For non-Stream CDN URLs, returns the full URL string unchanged. + /// + /// Override this to customize cache key generation for a custom CDN. + String cacheKey(String imageUrl) { + final uri = Uri.tryParse(imageUrl); + if (uri == null || !uri.host.contains(_streamCDNHost)) return imageUrl; + + final filteredParams = { + for (final MapEntry(:key, :value) in uri.queryParameters.entries) + if (_persistedParameters.contains(key)) key: value, + }; + + return uri.replace(queryParameters: filteredParams).toString(); + } +} diff --git a/packages/stream_chat_flutter/lib/src/utils/typedefs.dart b/packages/stream_chat_flutter/lib/src/utils/typedefs.dart index ab6a9d318a..99c2b4a37f 100644 --- a/packages/stream_chat_flutter/lib/src/utils/typedefs.dart +++ b/packages/stream_chat_flutter/lib/src/utils/typedefs.dart @@ -42,10 +42,11 @@ typedef ReplyMessageCallback = void Function(Message message); /// The action to perform when a specific image attachment in an [ImageGroup] /// is tapped or clicked. /// {@endtemplate} -typedef OnImageGroupAttachmentTap = void Function( - Message message, - Attachment attachment, -); +typedef OnImageGroupAttachmentTap = + void Function( + Message message, + Attachment attachment, + ); /// {@template onUserAvatarPress} /// The action to perform when a user's avatar is tapped, clicked, or @@ -68,11 +69,12 @@ typedef EditMessageInputBuilder = Widget Function(BuildContext, Message); /// {@template channelListHeaderTitleBuilder} /// A widget builder for custom [ChannelListHeader] title widgets. /// {@endtemplate} -typedef ChannelListHeaderTitleBuilder = Widget Function( - BuildContext context, - ConnectionStatus status, - StreamChatClient client, -); +typedef ChannelListHeaderTitleBuilder = + Widget Function( + BuildContext context, + ConnectionStatus status, + StreamChatClient client, + ); /// {@template channelTapCallback} /// The action to perform when a channel is tapped or clicked. @@ -97,11 +99,12 @@ typedef ViewInfoCallback = void Function(Channel); /// [defaultActionsModal] is the default [AttachmentActionsModal] configuration. /// Use [defaultActionsModal.copyWith] to easily customize it /// {@endtemplate} -typedef AttachmentActionsBuilder = Widget Function( - BuildContext context, - Attachment attachment, - AttachmentActionsModal defaultActionsModal, -); +typedef AttachmentActionsBuilder = + Widget Function( + BuildContext context, + Attachment attachment, + AttachmentActionsModal defaultActionsModal, + ); /// {@template errorListener} /// A callback that can be passed to [StreamMessageInput.onError]. @@ -110,10 +113,11 @@ typedef AttachmentActionsBuilder = Widget Function( /// /// It exists merely for error reporting, and should not be used otherwise. /// {@endtemplate} -typedef ErrorListener = void Function( - Object error, - StackTrace? stackTrace, -); +typedef ErrorListener = + void Function( + Object error, + StackTrace? stackTrace, + ); /// {@template attachmentLimitExceededListener} /// A callback that can be passed to @@ -123,45 +127,50 @@ typedef ErrorListener = void Function( /// /// It exists merely for showing custom error, and should not be used otherwise. /// {@endtemplate} -typedef AttachmentLimitExceedListener = void Function( - int limit, - String error, -); +typedef AttachmentLimitExceedListener = + void Function( + int limit, + String error, + ); /// {@template attachmentThumbnailBuilder} /// A widget builder for representing attachment thumbnails. /// {@endtemplate} -typedef AttachmentThumbnailBuilder = Widget Function( - BuildContext, - Attachment, -); +typedef AttachmentThumbnailBuilder = + Widget Function( + BuildContext, + Attachment, + ); /// {@template mentionTileBuilder} /// A widget builder for representing a custom mention tile. /// {@endtemplate} -typedef MentionTileBuilder = Widget Function( - BuildContext context, - Member member, -); +typedef MentionTileBuilder = + Widget Function( + BuildContext context, + Member member, + ); /// {@template mentionTileOverlayBuilder} /// A widget builder for representing a custom mention tile within a /// [UserMentionsOverlay]. /// {@endtemplate} -typedef MentionTileOverlayBuilder = Widget Function( - BuildContext context, - User user, -); +typedef MentionTileOverlayBuilder = + Widget Function( + BuildContext context, + User user, + ); /// {@template userMentionTileBuilder} /// A builder function for representing a custom user mention tile. /// /// Use [UserMentionTile] for the default implementation. /// {@endtemplate} -typedef UserMentionTileBuilder = Widget Function( - BuildContext context, - User user, -); +typedef UserMentionTileBuilder = + Widget Function( + BuildContext context, + User user, + ); /// {@template actionButtonBuilder} /// A widget builder for building a custom command button. @@ -169,10 +178,11 @@ typedef UserMentionTileBuilder = Widget Function( /// [commandButton] is the default [CommandButton] configuration, /// use [commandButton.copyWith] to easily customize it. /// {@endtemplate} -typedef CommandButtonBuilder = Widget Function( - BuildContext context, - CommandButton commandButton, -); +typedef CommandButtonBuilder = + Widget Function( + BuildContext context, + CommandButton commandButton, + ); /// {@template actionButtonBuilder} /// A widget builder for building a custom action button. @@ -180,27 +190,30 @@ typedef CommandButtonBuilder = Widget Function( /// [attachmentButton] is the default [AttachmentButton] configuration, /// use [attachmentButton.copyWith] to easily customize it. /// {@endtemplate} -typedef AttachmentButtonBuilder = Widget Function( - BuildContext context, - AttachmentButton attachmentButton, -); +typedef AttachmentButtonBuilder = + Widget Function( + BuildContext context, + AttachmentButton attachmentButton, + ); /// {@template quotedMessageAttachmentThumbnailBuilder} /// A widget builder for building a custom quoted message attachment thumbnail. /// {@endtemplate} -typedef QuotedMessageAttachmentThumbnailBuilder = Widget Function( - BuildContext, - Attachment, -); +typedef QuotedMessageAttachmentThumbnailBuilder = + Widget Function( + BuildContext, + Attachment, + ); /// {@template attachmentBuilder} /// A widget builder for representing attachments. /// {@endtemplate} -typedef AttachmentBuilder = Widget Function( - BuildContext, - Message, - List, -); +typedef AttachmentBuilder = + Widget Function( + BuildContext, + Message, + List, + ); /// {@template onQuotedMessageTap} /// The action to perform when a quoted message is tapped. @@ -237,59 +250,41 @@ typedef MessageSearchItemTapCallback = void Function(GetMessageResponse); /// {@template messageSearchItemBuilder} /// A widget builder used to create a custom [ListUserItem] from a [User]. /// {@endtemplate} -typedef MessageSearchItemBuilder = Widget Function( - BuildContext, - GetMessageResponse, -); +typedef MessageSearchItemBuilder = + Widget Function( + BuildContext, + GetMessageResponse, + ); -/// {@template messageBuilder} -/// A widget builder for creating custom message UI. -/// -/// [defaultMessageWidget] is the default [StreamMessageWidget] configuration. -/// Use [defaultMessageWidget.copyWith] to customize it. -/// {@endtemplate} -typedef MessageBuilder = Widget Function( - BuildContext, - MessageDetails, - List, - StreamMessageWidget defaultMessageWidget, -); - -/// {@template parentMessageBuilder} -/// A widget builder for creating custom parent message UI. -/// -/// [defaultMessageWidget] is the default [StreamMessageWidget] configuration. -/// Use [defaultMessageWidget.copyWith] to customize it. -/// {@endtemplate} -typedef ParentMessageBuilder = Widget Function( - BuildContext, - Message?, - StreamMessageWidget defaultMessageWidget, -); +// Legacy MessageBuilder and ParentMessageBuilder typedefs removed. +// Use StreamMessageWidgetBuilder from message_list_view.dart instead. /// {@template systemMessageBuilder} /// A widget builder for creating custom system messages. /// {@endtemplate} -typedef SystemMessageBuilder = Widget Function( - BuildContext, - Message, -); +typedef SystemMessageBuilder = + Widget Function( + BuildContext, + Message, + ); /// {@template ephemeralMessageBuilder} /// A widget builder for creating custom ephemeral messages. /// {@endtemplate} -typedef EphemeralMessageBuilder = Widget Function( - BuildContext, - Message, -); +typedef EphemeralMessageBuilder = + Widget Function( + BuildContext, + Message, + ); /// {@template moderatedMessageBuilder} /// A widget builder for creating custom moderated messages. /// {@endtemplate} -typedef ModeratedMessageBuilder = Widget Function( - BuildContext, - Message, -); +typedef ModeratedMessageBuilder = + Widget Function( + BuildContext, + Message, + ); /// {@template threadBuilder} /// A widget builder for creating custom thread UI. @@ -323,23 +318,25 @@ typedef ThreadTapCallback = void Function(Message, Widget?); /// ), /// ```dart /// {@endtemplate} -typedef SpacingWidgetBuilder = Widget Function( - BuildContext context, - List spacingTypes, -); +typedef SpacingWidgetBuilder = + Widget Function( + BuildContext context, + List spacingTypes, + ); /// {@template attachmentDownloader} /// A callback for downloading an attachment asset. /// {@endtemplate} /// Callback to download an attachment asset -typedef AttachmentDownloader = Future Function( - Attachment attachment, { - ProgressCallback? onReceiveProgress, - Map? queryParameters, - CancelToken? cancelToken, - bool deleteOnError, - Options? options, -}); +typedef AttachmentDownloader = + Future Function( + Attachment attachment, { + ProgressCallback? onReceiveProgress, + Map? queryParameters, + CancelToken? cancelToken, + bool deleteOnError, + Options? options, + }); /// Callback to receive the path once the attachment asset is downloaded typedef DownloadedPathCallback = void Function(String? path); @@ -366,10 +363,11 @@ typedef OnScrollToBottom = Function(int unreadCount); /// Widget builder for widgets that may require data from the /// [MessageInputController]. -typedef MessageRelatedBuilder = Widget Function( - BuildContext context, - StreamMessageInputController messageInputController, -); +typedef MessageRelatedBuilder = + Widget Function( + BuildContext context, + StreamMessageInputController messageInputController, + ); /// A function that returns true if the message is valid and can be sent. typedef MessageValidator = bool Function(Message message); diff --git a/packages/stream_chat_flutter/lib/src/video/video_thumbnail_image.dart b/packages/stream_chat_flutter/lib/src/video/video_thumbnail_image.dart index b53f6ebe86..8134b62191 100644 --- a/packages/stream_chat_flutter/lib/src/video/video_thumbnail_image.dart +++ b/packages/stream_chat_flutter/lib/src/video/video_thumbnail_image.dart @@ -41,8 +41,7 @@ import 'package:stream_chat_flutter/src/video/video_service.dart'; /// ``` /// {@end-tool} /// {@endtemplate} -class StreamVideoThumbnailImage - extends ImageProvider { +class StreamVideoThumbnailImage extends ImageProvider { /// {@macro video_thumbnail_image} const StreamVideoThumbnailImage({ required this.video, @@ -131,9 +130,7 @@ class StreamVideoThumbnailImage if (other.runtimeType != runtimeType) { return false; } - return other is StreamVideoThumbnailImage && - other.video == video && - other.scale == scale; + return other is StreamVideoThumbnailImage && other.video == video && other.scale == scale; } @override diff --git a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart index 86bcc75d83..f44bc63231 100644 --- a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart +++ b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart @@ -1,7 +1,40 @@ export 'package:jiffy/jiffy.dart'; -export 'package:photo_manager/photo_manager.dart' - show ThumbnailSize, ThumbnailFormat; +export 'package:photo_manager/photo_manager.dart' show ThumbnailSize, ThumbnailFormat; export 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; +export 'package:stream_core_flutter/stream_core_flutter.dart' + show + StreamAvatarGroupSize, + StreamAvatarSize, + StreamAudioWaveformSlider, + StreamAudioWaveform, + StreamTheme, + StreamIcons, + StreamThemeExtension, + StreamComponentFactory, + StreamComponentBuilder, + StreamComponentBuilders, + StreamComponentBuilderExtension, + StreamContextMenu, + StreamContextMenuAction, + StreamContextMenuSeparator, + StreamEmoji, + StreamEmojiButton, + StreamEmojiChipBar, + StreamEmojiChipItem, + StreamEmojiSize, + StreamEmojiData, + StreamEmojiPickerSheet, + StreamMessageAlignment, + StreamMessagePlacement, + StreamMessageStackPosition, + StreamReactionPicker, + StreamReactionPickerItem, + StreamReactionPickerProps, + StreamReactionPickerTheme, + StreamReactionPickerThemeData, + StreamReactionsPosition, + StreamReactionsType, + streamSupportedEmojis; export 'src/ai_assistant/ai_typing_indicator_view.dart'; export 'src/ai_assistant/stream_typewriter_builder.dart'; @@ -24,8 +57,6 @@ export 'src/attachment/voice_recording_attachment_playlist.dart'; export 'src/attachment_actions_modal/attachment_actions_modal.dart'; export 'src/autocomplete/stream_autocomplete.dart'; export 'src/avatars/gradient_avatar.dart'; -export 'src/avatars/group_avatar.dart'; -export 'src/avatars/user_avatar.dart'; export 'src/bottom_sheets/attachment_modal_sheet.dart'; export 'src/bottom_sheets/edit_message_sheet.dart'; export 'src/bottom_sheets/error_alert_sheet.dart'; @@ -34,10 +65,17 @@ export 'src/channel/channel_header.dart'; export 'src/channel/channel_info.dart'; export 'src/channel/channel_list_header.dart'; export 'src/channel/channel_name.dart'; -export 'src/channel/stream_channel_avatar.dart'; export 'src/channel/stream_channel_name.dart'; export 'src/channel/stream_draft_message_preview_text.dart'; export 'src/channel/stream_message_preview_text.dart'; +// region SDK Design Refresh Components +export 'src/components/avatar/stream_channel_avatar.dart'; +export 'src/components/avatar/stream_user_avatar.dart'; +export 'src/components/avatar/stream_user_avatar_group.dart'; +export 'src/components/avatar/stream_user_avatar_stack.dart'; +export 'src/components/message_composer/message_composer.dart'; +export 'src/components/stream_chat_component_builders.dart'; +// endregion export 'src/fullscreen_media/full_screen_media.dart'; export 'src/fullscreen_media/full_screen_media_builder.dart'; export 'src/gallery/gallery_footer.dart'; @@ -51,7 +89,6 @@ export 'src/keyboard_shortcuts/keyboard_shortcut_runner.dart'; export 'src/localization/stream_chat_localizations.dart'; export 'src/localization/translations.dart' show DefaultTranslations; export 'src/message_action/message_action.dart'; -export 'src/message_action/message_action_item.dart'; export 'src/message_action/message_actions_builder.dart'; export 'src/message_input/attachment_picker/stream_attachment_picker.dart'; export 'src/message_input/attachment_picker/stream_attachment_picker_bottom_sheet.dart'; @@ -71,18 +108,14 @@ export 'src/message_input/stream_message_send_button.dart'; export 'src/message_input/stream_message_text_field.dart'; export 'src/message_list_view/message_details.dart'; export 'src/message_list_view/message_list_view.dart'; +export 'src/message_list_view/unread_indicator_button.dart'; export 'src/message_modal/message_action_confirmation_modal.dart'; export 'src/message_modal/message_actions_modal.dart'; export 'src/message_modal/message_modal.dart'; -export 'src/message_modal/message_reactions_modal.dart'; export 'src/message_modal/moderated_message_actions_modal.dart'; -export 'src/message_widget/deleted_message.dart'; -export 'src/message_widget/message_text.dart'; export 'src/message_widget/message_widget.dart'; -export 'src/message_widget/message_widget_content_components.dart'; export 'src/message_widget/moderated_message.dart'; export 'src/message_widget/system_message.dart'; -export 'src/message_widget/text_bubble.dart'; export 'src/misc/adaptive_dialog_action.dart'; export 'src/misc/animated_circle_border_painter.dart'; export 'src/misc/back_button.dart'; @@ -91,7 +124,7 @@ export 'src/misc/date_divider.dart'; export 'src/misc/info_tile.dart'; export 'src/misc/markdown_message.dart'; export 'src/misc/option_list_tile.dart'; -export 'src/misc/reaction_icon.dart'; +export 'src/misc/reaction_icon_resolver.dart'; export 'src/misc/stream_modal.dart'; export 'src/misc/stream_neumorphic_button.dart'; export 'src/misc/swipeable.dart'; @@ -105,15 +138,12 @@ export 'src/poll/stream_poll_option_votes_dialog.dart'; export 'src/poll/stream_poll_options_dialog.dart'; export 'src/poll/stream_poll_results_dialog.dart'; export 'src/poll/stream_poll_text_field.dart'; -export 'src/reactions/indicator/reaction_indicator.dart'; -export 'src/reactions/indicator/reaction_indicator_icon_list.dart'; +export 'src/reactions/detail/reaction_detail_sheet.dart'; export 'src/reactions/picker/reaction_picker.dart'; -export 'src/reactions/picker/reaction_picker_icon_list.dart'; -export 'src/reactions/reaction_bubble.dart'; export 'src/reactions/user_reactions.dart'; export 'src/scroll_view/channel_scroll_view/stream_channel_grid_tile.dart'; export 'src/scroll_view/channel_scroll_view/stream_channel_grid_view.dart'; -export 'src/scroll_view/channel_scroll_view/stream_channel_list_tile.dart'; +export 'src/scroll_view/channel_scroll_view/stream_channel_list_item.dart'; export 'src/scroll_view/channel_scroll_view/stream_channel_list_view.dart'; export 'src/scroll_view/draft_scroll_view/stream_draft_list_tile.dart'; export 'src/scroll_view/draft_scroll_view/stream_draft_list_view.dart'; @@ -127,6 +157,7 @@ export 'src/scroll_view/photo_gallery/stream_photo_gallery_controller.dart'; export 'src/scroll_view/photo_gallery/stream_photo_gallery_tile.dart'; export 'src/scroll_view/poll_vote_scroll_view/stream_poll_vote_list_tile.dart'; export 'src/scroll_view/poll_vote_scroll_view/stream_poll_vote_list_view.dart'; +export 'src/scroll_view/reaction_scroll_view/stream_reaction_list_view.dart'; export 'src/scroll_view/stream_scroll_view_empty_widget.dart'; export 'src/scroll_view/stream_scroll_view_indexed_widget_builder.dart'; export 'src/scroll_view/thread_scroll_view/stream_thread_list_tile.dart'; @@ -146,4 +177,5 @@ export 'src/utils/device_segmentation.dart'; export 'src/utils/extensions.dart'; export 'src/utils/helpers.dart'; export 'src/utils/message_preview_formatter.dart'; +export 'src/utils/stream_image_cdn.dart'; export 'src/utils/typedefs.dart'; diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index f1c1436724..779f8109e6 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -5,6 +5,9 @@ version: 10.0.0-beta.12 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues +# TODO: Remove this once stream_core_flutter is published to pub.dev +publish_to: none + # Note: The environment configuration and dependency versions are managed by Melos. # # Do not edit them manually. @@ -18,8 +21,8 @@ issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues # 2. Add it to the melos.yaml file for future updates. environment: - sdk: ^3.6.2 - flutter: ">=3.27.4" + sdk: ^3.10.0 + flutter: ">=3.38.1" dependencies: cached_network_image: ^3.3.1 @@ -48,7 +51,7 @@ dependencies: media_kit_video: ^2.0.0 meta: ^1.9.1 path_provider: ^2.1.3 - photo_manager: ^3.2.0 + photo_manager: ^3.8.3 photo_view: ^0.15.0 rate_limiter: ^1.0.0 record: ">=5.2.0 <7.0.0" @@ -56,14 +59,20 @@ dependencies: share_plus: ">=11.0.0 <13.0.0" shimmer: ^3.0.0 stream_chat_flutter_core: ^10.0.0-beta.12 + stream_core_flutter: + git: + url: https://github.com/GetStream/stream-core-flutter.git + ref: 8057a775c2ed764dbd5cbabd2dd60d3cd68d2f08 + path: packages/stream_core_flutter svg_icon_widget: ^0.0.1 synchronized: ^3.1.0+1 + theme_extensions_builder_annotation: ^7.1.0 thumblr: ^0.0.4 url_launcher: ^6.3.0 video_player: ^2.8.7 dev_dependencies: - alchemist: ">=0.11.0 <0.14.0" + alchemist: ^0.13.0 connectivity_plus_platform_interface: ^2.0.0 faker_dart: ^0.2.1 flutter_test: diff --git a/packages/stream_chat_flutter/test/conditional_parent_builder/conditional_parent_builder_test.dart b/packages/stream_chat_flutter/test/conditional_parent_builder/conditional_parent_builder_test.dart index c52c28ab4b..b029f97917 100644 --- a/packages/stream_chat_flutter/test/conditional_parent_builder/conditional_parent_builder_test.dart +++ b/packages/stream_chat_flutter/test/conditional_parent_builder/conditional_parent_builder_test.dart @@ -3,8 +3,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:stream_chat_flutter/conditional_parent_builder/conditional_parent_builder.dart'; void main() { - testWidgets('ConditionalParentBuilder builds the parent widget', - (tester) async { + testWidgets('ConditionalParentBuilder builds the parent widget', (tester) async { await tester.pumpWidget( MaterialApp( home: Scaffold( @@ -26,8 +25,7 @@ void main() { expect(find.byType(Text), findsOneWidget); }); - testWidgets('ConditionalParentBuilder does not build the parent widget', - (tester) async { + testWidgets('ConditionalParentBuilder does not build the parent widget', (tester) async { await tester.pumpWidget( MaterialApp( home: Scaffold( diff --git a/packages/stream_chat_flutter/test/flutter_test_config.dart b/packages/stream_chat_flutter/test/flutter_test_config.dart index 7b00df9385..5bc88f9814 100644 --- a/packages/stream_chat_flutter/test/flutter_test_config.dart +++ b/packages/stream_chat_flutter/test/flutter_test_config.dart @@ -4,14 +4,12 @@ import 'dart:io'; import 'package:alchemist/alchemist.dart'; Future testExecutable(FutureOr Function() testMain) async { - final isRunningInCi = Platform.environment.containsKey('CI') || - Platform.environment.containsKey('GITHUB_ACTIONS'); + final isRunningInCi = Platform.environment.containsKey('CI') || Platform.environment.containsKey('GITHUB_ACTIONS'); return AlchemistConfig.runWithConfig( config: AlchemistConfig( - platformGoldensConfig: PlatformGoldensConfig( - enabled: !isRunningInCi, - ), + ciGoldensConfig: CiGoldensConfig(enabled: isRunningInCi), + platformGoldensConfig: PlatformGoldensConfig(enabled: !isRunningInCi), ), run: testMain, ); diff --git a/packages/stream_chat_flutter/test/platform_widget_builder/desktop_widget_builder_test.dart b/packages/stream_chat_flutter/test/platform_widget_builder/desktop_widget_builder_test.dart index a7b867ce38..1136d8a5d1 100644 --- a/packages/stream_chat_flutter/test/platform_widget_builder/desktop_widget_builder_test.dart +++ b/packages/stream_chat_flutter/test/platform_widget_builder/desktop_widget_builder_test.dart @@ -1,5 +1,4 @@ -import 'package:flutter/foundation.dart' - show debugDefaultTargetPlatformOverride; +import 'package:flutter/foundation.dart' show debugDefaultTargetPlatformOverride; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:stream_chat_flutter/platform_widget_builder/platform_widget_builder.dart'; diff --git a/packages/stream_chat_flutter/test/platform_widget_builder/platform_widget_builder_test.dart b/packages/stream_chat_flutter/test/platform_widget_builder/platform_widget_builder_test.dart index cfb7e774f5..854eacf3f4 100644 --- a/packages/stream_chat_flutter/test/platform_widget_builder/platform_widget_builder_test.dart +++ b/packages/stream_chat_flutter/test/platform_widget_builder/platform_widget_builder_test.dart @@ -1,5 +1,4 @@ -import 'package:flutter/foundation.dart' - show debugDefaultTargetPlatformOverride; +import 'package:flutter/foundation.dart' show debugDefaultTargetPlatformOverride; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:stream_chat_flutter/platform_widget_builder/platform_widget_builder.dart'; diff --git a/packages/stream_chat_flutter/test/scrollable_positioned_list/horizontal_scrollable_positioned_list_test.dart b/packages/stream_chat_flutter/test/scrollable_positioned_list/horizontal_scrollable_positioned_list_test.dart index 6e851ada5c..8bceed8ffe 100644 --- a/packages/stream_chat_flutter/test/scrollable_positioned_list/horizontal_scrollable_positioned_list_test.dart +++ b/packages/stream_chat_flutter/test/scrollable_positioned_list/horizontal_scrollable_positioned_list_test.dart @@ -58,119 +58,94 @@ void main() { expect(tester.getBottomRight(find.text('Item 9')).dx, screenWidth); expect(find.text('Item 10'), findsNothing); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 0).itemLeadingEdge, 0); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 0) - .itemLeadingEdge, - 0); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 0) - .itemTrailingEdge, - 1 / 10); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 9) - .itemTrailingEdge, - 1); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 0).itemTrailingEdge, + 1 / 10, + ); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 9).itemTrailingEdge, 1); }); testWidgets('List positioned with 0 at right', (WidgetTester tester) async { final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemPositionsListener: itemPositionsListener, reverse: true); + await setUpWidgetTest(tester, itemPositionsListener: itemPositionsListener, reverse: true); expect(tester.getBottomRight(find.text('Item 0')).dx, screenWidth); expect(tester.getTopLeft(find.text('Item 9')).dx, 0); expect(find.text('Item 10'), findsNothing); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 0).itemLeadingEdge, 0); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 0) - .itemLeadingEdge, - 0); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 0) - .itemTrailingEdge, - 1 / 10); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 9) - .itemTrailingEdge, - 1); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 0).itemTrailingEdge, + 1 / 10, + ); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 9).itemTrailingEdge, 1); }); testWidgets('Scroll to 2 (already on screen)', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, - itemPositionsListener: itemPositionsListener); + await setUpWidgetTest( + tester, + itemScrollController: itemScrollController, + itemPositionsListener: itemPositionsListener, + ); - unawaited( - itemScrollController.scrollTo(index: 2, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 2, duration: scrollDuration)); await tester.pump(); await tester.pump(scrollDuration); expect(find.text('Item 1'), findsNothing); expect(tester.getTopLeft(find.text('Item 2')).dx, 0); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 2).itemLeadingEdge, 0); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 2) - .itemLeadingEdge, - 0); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 2) - .itemTrailingEdge, - 1 / 10); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 2).itemTrailingEdge, + 1 / 10, + ); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 11) - .itemTrailingEdge, - 1); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 11).itemTrailingEdge, + 1, + ); }); - testWidgets('Scroll to 100 (not already on screen)', - (WidgetTester tester) async { + testWidgets('Scroll to 100 (not already on screen)', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, - itemPositionsListener: itemPositionsListener); + await setUpWidgetTest( + tester, + itemScrollController: itemScrollController, + itemPositionsListener: itemPositionsListener, + ); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pumpAndSettle(); expect(find.text('Item 99'), findsNothing); expect(find.text('Item 100'), findsOneWidget); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 100) - .itemLeadingEdge, - 0); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 100).itemLeadingEdge, + 0, + ); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 100) - .itemTrailingEdge, - 1 / 10); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 100).itemTrailingEdge, + 1 / 10, + ); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 109) - .itemTrailingEdge, - 1); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 109).itemTrailingEdge, + 1, + ); }); testWidgets('Jump to 100', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, - itemPositionsListener: itemPositionsListener); + await setUpWidgetTest( + tester, + itemScrollController: itemScrollController, + itemPositionsListener: itemPositionsListener, + ); itemScrollController.jumpTo(index: 100); await tester.pumpAndSettle(); @@ -179,39 +154,36 @@ void main() { expect(tester.getBottomRight(find.text('Item 109')).dy, screenHeight); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 100) - .itemLeadingEdge, - 0); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 100).itemLeadingEdge, + 0, + ); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 100) - .itemTrailingEdge, - 1 / 10); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 100).itemTrailingEdge, + 1 / 10, + ); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 109) - .itemLeadingEdge, - 9 / 10); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 109).itemLeadingEdge, + 9 / 10, + ); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 109) - .itemTrailingEdge, - 1); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 109).itemTrailingEdge, + 1, + ); }); testWidgets('Scroll to 20 without fading', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, - itemPositionsListener: itemPositionsListener); + await setUpWidgetTest( + tester, + itemScrollController: itemScrollController, + itemPositionsListener: itemPositionsListener, + ); var fadeTransition = tester.widget(fadeTransitionFinder); final initialOpacity = fadeTransition.opacity; - unawaited( - itemScrollController.scrollTo(index: 20, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 20, duration: scrollDuration)); await tester.pump(); await tester.pump(); await tester.pump(scrollDuration ~/ 2); @@ -225,8 +197,7 @@ void main() { expect(find.text('Item 20'), findsOneWidget); }); - testWidgets('padding test - centered sliver at left', - (WidgetTester tester) async { + testWidgets('padding test - centered sliver at left', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); await setUpWidgetTest( tester, @@ -235,25 +206,19 @@ void main() { ); expect(tester.getTopLeft(find.text('Item 0')), const Offset(10, 10)); - expect(tester.getTopLeft(find.text('Item 1')), - const Offset(itemWidth + 10, 10)); - expect(tester.getBottomRight(find.text('Item 1')), - const Offset(10 + itemWidth * 2, screenHeight - 10)); + expect(tester.getTopLeft(find.text('Item 1')), const Offset(itemWidth + 10, 10)); + expect(tester.getBottomRight(find.text('Item 1')), const Offset(10 + itemWidth * 2, screenHeight - 10)); - unawaited( - itemScrollController.scrollTo(index: 490, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 490, duration: scrollDuration)); await tester.pumpAndSettle(); - await tester.drag( - find.byType(ScrollablePositionedList), const Offset(-100, 0)); + await tester.drag(find.byType(ScrollablePositionedList), const Offset(-100, 0)); await tester.pumpAndSettle(); - expect(tester.getBottomRight(find.text('Item 499')), - const Offset(screenWidth - 10, screenHeight - 10)); + expect(tester.getBottomRight(find.text('Item 499')), const Offset(screenWidth - 10, screenHeight - 10)); }); - testWidgets('padding test - centered sliver not at left', - (WidgetTester tester) async { + testWidgets('padding test - centered sliver not at left', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); await setUpWidgetTest( tester, @@ -262,19 +227,15 @@ void main() { padding: const EdgeInsets.all(10), ); - await tester.drag( - find.byType(ScrollablePositionedList), const Offset(200, 0)); + await tester.drag(find.byType(ScrollablePositionedList), const Offset(200, 0)); await tester.pumpAndSettle(); expect(tester.getTopLeft(find.text('Item 0')), const Offset(10, 10)); - expect(tester.getTopLeft(find.text('Item 2')), - const Offset(10 + itemWidth * 2, 10)); - expect(tester.getTopLeft(find.text('Item 3')), - const Offset(10 + itemWidth * 3, 10)); + expect(tester.getTopLeft(find.text('Item 2')), const Offset(10 + itemWidth * 2, 10)); + expect(tester.getTopLeft(find.text('Item 3')), const Offset(10 + itemWidth * 3, 10)); }); - testWidgets('padding test - reversed - centered sliver at right', - (WidgetTester tester) async { + testWidgets('padding test - reversed - centered sliver at right', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); await setUpWidgetTest( tester, @@ -283,26 +244,23 @@ void main() { reverse: true, ); - expect(tester.getTopRight(find.text('Item 0')), - const Offset(screenWidth - 10, 10)); - expect(tester.getTopRight(find.text('Item 1')), - const Offset(screenWidth - (itemWidth + 10), 10)); - expect(tester.getBottomLeft(find.text('Item 1')), - const Offset(screenWidth - (10 + itemWidth * 2), screenHeight - 10)); + expect(tester.getTopRight(find.text('Item 0')), const Offset(screenWidth - 10, 10)); + expect(tester.getTopRight(find.text('Item 1')), const Offset(screenWidth - (itemWidth + 10), 10)); + expect( + tester.getBottomLeft(find.text('Item 1')), + const Offset(screenWidth - (10 + itemWidth * 2), screenHeight - 10), + ); - unawaited( - itemScrollController.scrollTo(index: 490, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 490, duration: scrollDuration)); await tester.pumpAndSettle(); - await tester.drag( - find.byType(ScrollablePositionedList), const Offset(100, 0)); + await tester.drag(find.byType(ScrollablePositionedList), const Offset(100, 0)); await tester.pumpAndSettle(); expect(tester.getTopLeft(find.text('Item 499')), const Offset(10, 10)); }); - testWidgets('padding test - reversed - centered sliver not at right', - (WidgetTester tester) async { + testWidgets('padding test - reversed - centered sliver not at right', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); await setUpWidgetTest( tester, @@ -312,15 +270,11 @@ void main() { reverse: true, ); - await tester.drag( - find.byType(ScrollablePositionedList), const Offset(-200, 0)); + await tester.drag(find.byType(ScrollablePositionedList), const Offset(-200, 0)); await tester.pumpAndSettle(); - expect(tester.getTopRight(find.text('Item 0')), - const Offset(screenWidth - 10, 10)); - expect(tester.getTopRight(find.text('Item 2')), - const Offset(screenWidth - (10 + itemWidth * 2), 10)); - expect(tester.getTopRight(find.text('Item 3')), - const Offset(screenWidth - (10 + itemWidth * 3), 10)); + expect(tester.getTopRight(find.text('Item 0')), const Offset(screenWidth - 10, 10)); + expect(tester.getTopRight(find.text('Item 2')), const Offset(screenWidth - (10 + itemWidth * 2), 10)); + expect(tester.getTopRight(find.text('Item 3')), const Offset(screenWidth - (10 + itemWidth * 3), 10)); }); } diff --git a/packages/stream_chat_flutter/test/scrollable_positioned_list/positioned_list_test.dart b/packages/stream_chat_flutter/test/scrollable_positioned_list/positioned_list_test.dart index 887165a512..c43fea6fa9 100644 --- a/packages/stream_chat_flutter/test/scrollable_positioned_list/positioned_list_test.dart +++ b/packages/stream_chat_flutter/test/scrollable_positioned_list/positioned_list_test.dart @@ -53,16 +53,11 @@ void main() { expect(find.text('Item 4'), findsOneWidget); expect(find.text('Item 5'), findsNothing); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 0).itemLeadingEdge, 0); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 0) - .itemLeadingEdge, - 0); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 4) - .itemTrailingEdge, - 1 / 2); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 4).itemTrailingEdge, + 1 / 2, + ); }); testWidgets('List positioned with 0 at top', (WidgetTester tester) async { @@ -73,26 +68,13 @@ void main() { expect(find.text('Item 9'), findsOneWidget); expect(find.text('Item 10'), findsNothing); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 0).itemLeadingEdge, 0); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 9).itemTrailingEdge, 1); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 10).itemLeadingEdge, 1); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 0) - .itemLeadingEdge, - 0); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 9) - .itemTrailingEdge, - 1); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 10) - .itemLeadingEdge, - 1); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 10) - .itemTrailingEdge, - 11 / 10); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 10).itemTrailingEdge, + 11 / 10, + ); }); testWidgets('List positioned with 5 at top', (WidgetTester tester) async { @@ -105,25 +87,15 @@ void main() { expect(find.text('Item 15'), findsNothing); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 4) - .itemLeadingEdge, - -1 / 10); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 4) - .itemTrailingEdge, - 0); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 5) - .itemLeadingEdge, - 0); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 4).itemLeadingEdge, + -1 / 10, + ); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 4).itemTrailingEdge, 0); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 5).itemLeadingEdge, 0); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 14) - .itemTrailingEdge, - 1); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 14).itemTrailingEdge, + 1, + ); }); testWidgets('List positioned with 20 at bottom', (WidgetTester tester) async { @@ -134,69 +106,50 @@ void main() { expect(find.text('Item 19'), findsOneWidget); expect(find.text('Item 10'), findsOneWidget); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 10).itemLeadingEdge, 0); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 10) - .itemLeadingEdge, - 0); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 19) - .itemLeadingEdge, - 9 / 10); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 19) - .itemTrailingEdge, - 1); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 19).itemLeadingEdge, + 9 / 10, + ); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 20) - .itemLeadingEdge, - 1); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 19).itemTrailingEdge, + 1, + ); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 20).itemLeadingEdge, 1); }); - testWidgets('List positioned with 20 at halfway', - (WidgetTester tester) async { + testWidgets('List positioned with 20 at halfway', (WidgetTester tester) async { await setUpWidgetTest(tester, topItem: 20, anchor: 0.5); await tester.pump(); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 20) - .itemLeadingEdge, - 0.5); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 20).itemLeadingEdge, + 0.5, + ); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 20) - .itemTrailingEdge, - 0.5 + itemHeight / screenHeight); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 20).itemTrailingEdge, + 0.5 + itemHeight / screenHeight, + ); }); - testWidgets('List positioned with 20 half off top of screen', - (WidgetTester tester) async { - await setUpWidgetTest(tester, - topItem: 20, anchor: -(itemHeight / screenHeight) / 2); + testWidgets('List positioned with 20 half off top of screen', (WidgetTester tester) async { + await setUpWidgetTest(tester, topItem: 20, anchor: -(itemHeight / screenHeight) / 2); await tester.pump(); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 20) - .itemLeadingEdge, - -(itemHeight / screenHeight) / 2); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 20).itemLeadingEdge, + -(itemHeight / screenHeight) / 2, + ); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 20) - .itemTrailingEdge, - (itemHeight / screenHeight) / 2); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 20).itemTrailingEdge, + (itemHeight / screenHeight) / 2, + ); }); - testWidgets('List positioned with 5 at top then scroll up 2', - (WidgetTester tester) async { + testWidgets('List positioned with 5 at top then scroll up 2', (WidgetTester tester) async { await setUpWidgetTest(tester, topItem: 5); - await tester.drag( - find.byType(PositionedList), const Offset(0, itemHeight * 2)); + await tester.drag(find.byType(PositionedList), const Offset(0, itemHeight * 2)); await tester.pump(); expect(find.text('Item 2'), findsNothing); @@ -205,44 +158,33 @@ void main() { expect(find.text('Item 13'), findsNothing); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 2) - .itemLeadingEdge, - -1 / 10); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 3) - .itemLeadingEdge, - 0); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 2).itemLeadingEdge, + -1 / 10, + ); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 3).itemLeadingEdge, 0); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 12) - .itemTrailingEdge, - 1); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 12).itemTrailingEdge, + 1, + ); }); - testWidgets('List positioned with 5 at top then scroll down 1/2', - (WidgetTester tester) async { + testWidgets('List positioned with 5 at top then scroll down 1/2', (WidgetTester tester) async { await setUpWidgetTest(tester, topItem: 5); - await tester.drag( - find.byType(PositionedList), const Offset(0, -1 / 2 * itemHeight)); + await tester.drag(find.byType(PositionedList), const Offset(0, -1 / 2 * itemHeight)); await tester.pump(); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 5) - .itemTrailingEdge, - 1 / 20); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 5).itemTrailingEdge, + 1 / 20, + ); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 14) - .itemLeadingEdge, - 17 / 20); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 14).itemLeadingEdge, + 17 / 20, + ); }); - testWidgets('List positioned with 0 at top scroll up 5', - (WidgetTester tester) async { + testWidgets('List positioned with 0 at top scroll up 5', (WidgetTester tester) async { final scrollController = ScrollController(); await setUpWidgetTest(tester, scrollController: scrollController); await tester.pump(); @@ -256,23 +198,16 @@ void main() { expect(find.text('Item 14'), findsOneWidget); expect(find.text('Item 15'), findsNothing); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 5).itemLeadingEdge, 0); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 5) - .itemLeadingEdge, - 0); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 4) - .itemLeadingEdge, - -1 / 10); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 4).itemLeadingEdge, + -1 / 10, + ); }); - testWidgets('List positioned with 5 at top then scroll up 2 programatically', - (WidgetTester tester) async { + testWidgets('List positioned with 5 at top then scroll up 2 programatically', (WidgetTester tester) async { final scrollController = ScrollController(); - await setUpWidgetTest(tester, - topItem: 5, scrollController: scrollController); + await setUpWidgetTest(tester, topItem: 5, scrollController: scrollController); scrollController.jumpTo(-2 * itemHeight); await tester.pump(); @@ -283,92 +218,67 @@ void main() { expect(find.text('Item 13'), findsNothing); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 2) - .itemLeadingEdge, - -1 / 10); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 3) - .itemLeadingEdge, - 0); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 2).itemLeadingEdge, + -1 / 10, + ); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 3).itemLeadingEdge, 0); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 12) - .itemTrailingEdge, - 1); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 12).itemTrailingEdge, + 1, + ); }); - testWidgets( - 'List positioned with 5 at top then scroll down 20 programatically', - (WidgetTester tester) async { + testWidgets('List positioned with 5 at top then scroll down 20 programatically', (WidgetTester tester) async { final scrollController = ScrollController(); - await setUpWidgetTest(tester, - topItem: 5, scrollController: scrollController); + await setUpWidgetTest(tester, topItem: 5, scrollController: scrollController); scrollController.jumpTo(itemHeight * 20); await tester.pump(); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 23) - .itemLeadingEdge, - -2 / 10); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 24) - .itemLeadingEdge, - -1 / 10); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 23).itemLeadingEdge, + -2 / 10, + ); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 25) - .itemLeadingEdge, - 0); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 24).itemLeadingEdge, + -1 / 10, + ); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 25).itemLeadingEdge, 0); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 4) - .itemLeadingEdge, - -21 / 10); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 4).itemLeadingEdge, + -21 / 10, + ); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 5) - .itemLeadingEdge, - -20 / 10); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 5).itemLeadingEdge, + -20 / 10, + ); }); - testWidgets('List positioned with 5 at top and initial scroll offset', - (WidgetTester tester) async { - final scrollController = - ScrollController(initialScrollOffset: -2 * itemHeight); - await setUpWidgetTest(tester, - topItem: 5, scrollController: scrollController); + testWidgets('List positioned with 5 at top and initial scroll offset', (WidgetTester tester) async { + final scrollController = ScrollController(initialScrollOffset: -2 * itemHeight); + await setUpWidgetTest(tester, topItem: 5, scrollController: scrollController); expect(find.text('Item 2'), findsNothing); expect(find.text('Item 3'), findsOneWidget); expect(find.text('Item 12'), findsOneWidget); expect(find.text('Item 13'), findsNothing); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 3).itemLeadingEdge, 0); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 3) - .itemLeadingEdge, - 0); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 12) - .itemTrailingEdge, - 1); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 12).itemTrailingEdge, + 1, + ); }); - testWidgets('Does not crash when updated offscreen', - (WidgetTester tester) async { + testWidgets('Does not crash when updated offscreen', (WidgetTester tester) async { late StateSetter setState; var updated = false; // There's 0 relayout boundaries in this subtree. - final widget = StatefulBuilder(builder: (context, stateSetter) { - setState = stateSetter; - return Positioned( + final widget = StatefulBuilder( + builder: (context, stateSetter) { + setState = stateSetter; + return Positioned( left: 0, right: 0, child: PositionedList( @@ -378,17 +288,21 @@ void main() { // RenderIndexedSemantics to the render tree. addSemanticIndexes: updated, itemBuilder: (context, index) => const SizedBox(height: itemHeight), - )); - }); + ), + ); + }, + ); - await tester.pumpWidget(Directionality( - textDirection: TextDirection.ltr, - child: Overlay( - initialEntries: [ - OverlayEntry(builder: (context) => widget, maintainState: true), - ], + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: Overlay( + initialEntries: [ + OverlayEntry(builder: (context) => widget, maintainState: true), + ], + ), ), - )); + ); // Insert a new opaque OverlayEntry that would prevent the first // OverlayEntry from doing re-layout. Since there's no relayout boundaries diff --git a/packages/stream_chat_flutter/test/scrollable_positioned_list/reversed_positioned_list_test.dart b/packages/stream_chat_flutter/test/scrollable_positioned_list/reversed_positioned_list_test.dart index 828f17e390..3077126080 100644 --- a/packages/stream_chat_flutter/test/scrollable_positioned_list/reversed_positioned_list_test.dart +++ b/packages/stream_chat_flutter/test/scrollable_positioned_list/reversed_positioned_list_test.dart @@ -52,16 +52,11 @@ void main() { expect(find.text('Item 4'), findsOneWidget); expect(find.text('Item 5'), findsNothing); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 0).itemLeadingEdge, 0); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 0) - .itemLeadingEdge, - 0); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 4) - .itemTrailingEdge, - 1 / 2); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 4).itemTrailingEdge, + 1 / 2, + ); }); testWidgets('List positioned with 0 at bottom', (WidgetTester tester) async { @@ -72,16 +67,8 @@ void main() { expect(tester.getTopLeft(find.text('Item 9')).dy, 0); expect(find.text('Item 10'), findsNothing); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 0) - .itemLeadingEdge, - 0); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 9) - .itemTrailingEdge, - 1); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 0).itemLeadingEdge, 0); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 9).itemTrailingEdge, 1); }); testWidgets('List positioned with 5 at bottom', (WidgetTester tester) async { @@ -94,25 +81,15 @@ void main() { expect(find.text('Item 15'), findsNothing); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 4) - .itemLeadingEdge, - -1 / 10); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 4) - .itemTrailingEdge, - 0); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 5) - .itemLeadingEdge, - 0); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 4).itemLeadingEdge, + -1 / 10, + ); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 4).itemTrailingEdge, 0); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 5).itemLeadingEdge, 0); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 14) - .itemTrailingEdge, - 1); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 14).itemTrailingEdge, + 1, + ); }); testWidgets('List positioned with 15 at bottom', (WidgetTester tester) async { @@ -134,53 +111,35 @@ void main() { expect(find.text('Item 5'), findsOneWidget); expect(find.text('Item 4'), findsNothing); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 15).itemLeadingEdge, 1); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 15) - .itemLeadingEdge, - 1); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 14) - .itemTrailingEdge, - 1); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 14) - .itemLeadingEdge, - 9 / 10); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 14).itemTrailingEdge, + 1, + ); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 5) - .itemLeadingEdge, - 0); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 14).itemLeadingEdge, + 9 / 10, + ); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 5).itemLeadingEdge, 0); }); - testWidgets('List positioned with 5 at bottom then scroll up 2', - (WidgetTester tester) async { + testWidgets('List positioned with 5 at bottom then scroll up 2', (WidgetTester tester) async { await setUpWidgetTest(tester, topItem: 5); - await tester.drag( - find.byType(PositionedList), const Offset(0, itemHeight * 2)); + await tester.drag(find.byType(PositionedList), const Offset(0, itemHeight * 2)); await tester.pump(); expect(find.text('Item 6'), findsNothing); expect(find.text('Item 7'), findsOneWidget); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 7).itemLeadingEdge, 0); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 7) - .itemLeadingEdge, - 0); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 7) - .itemTrailingEdge, - 1 / 10); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 7).itemTrailingEdge, + 1 / 10, + ); }); - testWidgets('List positioned with 0 at bottom scroll to item 5', - (WidgetTester tester) async { + testWidgets('List positioned with 0 at bottom scroll to item 5', (WidgetTester tester) async { final scrollController = ScrollController(); await setUpWidgetTest(tester, scrollController: scrollController); await tester.pump(); @@ -194,24 +153,16 @@ void main() { expect(find.text('Item 14'), findsOneWidget); expect(find.text('Item 15'), findsNothing); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 5).itemLeadingEdge, 0); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 5) - .itemLeadingEdge, - 0); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 4) - .itemLeadingEdge, - -1 / 10); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 4).itemLeadingEdge, + -1 / 10, + ); }); - testWidgets( - 'List positioned with 5 at bottom then scroll up 2 programatically', - (WidgetTester tester) async { + testWidgets('List positioned with 5 at bottom then scroll up 2 programatically', (WidgetTester tester) async { final scrollController = ScrollController(); - await setUpWidgetTest(tester, - topItem: 5, scrollController: scrollController); + await setUpWidgetTest(tester, topItem: 5, scrollController: scrollController); scrollController.jumpTo(itemHeight * 2); await tester.pump(); @@ -222,28 +173,19 @@ void main() { expect(find.text('Item 17'), findsNothing); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 6) - .itemLeadingEdge, - -1 / 10); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 7) - .itemLeadingEdge, - 0); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 6).itemLeadingEdge, + -1 / 10, + ); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 7).itemLeadingEdge, 0); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 16) - .itemTrailingEdge, - 1); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 16).itemTrailingEdge, + 1, + ); }); - testWidgets('List positioned with 5 at bottom and initial scroll offset', - (WidgetTester tester) async { - final scrollController = - ScrollController(initialScrollOffset: itemHeight * 2); - await setUpWidgetTest(tester, - topItem: 5, scrollController: scrollController); + testWidgets('List positioned with 5 at bottom and initial scroll offset', (WidgetTester tester) async { + final scrollController = ScrollController(initialScrollOffset: itemHeight * 2); + await setUpWidgetTest(tester, topItem: 5, scrollController: scrollController); expect(find.text('Item 6'), findsNothing); expect(find.text('Item 7'), findsOneWidget); @@ -251,19 +193,13 @@ void main() { expect(find.text('Item 17'), findsNothing); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 6) - .itemLeadingEdge, - -1 / 10); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 7) - .itemLeadingEdge, - 0); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 6).itemLeadingEdge, + -1 / 10, + ); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 7).itemLeadingEdge, 0); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 16) - .itemTrailingEdge, - 1); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 16).itemTrailingEdge, + 1, + ); }); } diff --git a/packages/stream_chat_flutter/test/scrollable_positioned_list/reversed_scrollable_positioned_list_test.dart b/packages/stream_chat_flutter/test/scrollable_positioned_list/reversed_scrollable_positioned_list_test.dart index e2b16d8a4d..ddd1971659 100644 --- a/packages/stream_chat_flutter/test/scrollable_positioned_list/reversed_scrollable_positioned_list_test.dart +++ b/packages/stream_chat_flutter/test/scrollable_positioned_list/reversed_scrollable_positioned_list_test.dart @@ -51,122 +51,95 @@ void main() { expect(tester.getTopLeft(find.text('Item 9')).dy, 0); expect(find.text('Item 10'), findsNothing); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 0) - .itemLeadingEdge, - 0); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 9) - .itemTrailingEdge, - 1); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 0).itemLeadingEdge, 0); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 9).itemTrailingEdge, 1); }); - testWidgets('Scroll to 1 then 2 (both already on screen)', - (WidgetTester tester) async { + testWidgets('Scroll to 1 then 2 (both already on screen)', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, - itemPositionsListener: itemPositionsListener); + await setUpWidgetTest( + tester, + itemScrollController: itemScrollController, + itemPositionsListener: itemPositionsListener, + ); - unawaited( - itemScrollController.scrollTo(index: 1, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 1, duration: scrollDuration)); await tester.pump(); await tester.pump(scrollDuration); expect(find.text('Item 0'), findsNothing); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 1) - .itemLeadingEdge, - 0); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 1).itemLeadingEdge, 0); expect(tester.getBottomRight(find.text('Item 1')).dy, screenHeight); - unawaited( - itemScrollController.scrollTo(index: 2, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 2, duration: scrollDuration)); await tester.pump(); await tester.pump(scrollDuration); expect(find.text('Item 1'), findsNothing); expect(tester.getBottomRight(find.text('Item 2')).dy, screenHeight); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 2).itemLeadingEdge, 0); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 2) - .itemLeadingEdge, - 0); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 11) - .itemTrailingEdge, - 1); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 11).itemTrailingEdge, + 1, + ); }); - testWidgets('Scroll to 5 (already on screen) and then back to 0', - (WidgetTester tester) async { + testWidgets('Scroll to 5 (already on screen) and then back to 0', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, - itemPositionsListener: itemPositionsListener); + await setUpWidgetTest( + tester, + itemScrollController: itemScrollController, + itemPositionsListener: itemPositionsListener, + ); - unawaited( - itemScrollController.scrollTo(index: 5, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 5, duration: scrollDuration)); await tester.pumpAndSettle(); - unawaited( - itemScrollController.scrollTo(index: 0, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 0, duration: scrollDuration)); await tester.pumpAndSettle(); expect(find.text('Item 0'), findsOneWidget); expect(find.text('Item 9'), findsOneWidget); expect(find.text('Item 10'), findsNothing); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 0) - .itemLeadingEdge, - 0); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 9) - .itemTrailingEdge, - 1); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 0).itemLeadingEdge, 0); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 9).itemTrailingEdge, 1); }); - testWidgets('Scroll to 100 (not already on screen)', - (WidgetTester tester) async { + testWidgets('Scroll to 100 (not already on screen)', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, - itemPositionsListener: itemPositionsListener); + await setUpWidgetTest( + tester, + itemScrollController: itemScrollController, + itemPositionsListener: itemPositionsListener, + ); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pumpAndSettle(); expect(find.text('Item 99'), findsNothing); expect(find.text('Item 100'), findsOneWidget); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 100) - .itemLeadingEdge, - 0); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 100).itemLeadingEdge, + 0, + ); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 109) - .itemTrailingEdge, - 1); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 109).itemTrailingEdge, + 1, + ); }); testWidgets('Jump to 100', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, - itemPositionsListener: itemPositionsListener); + await setUpWidgetTest( + tester, + itemScrollController: itemScrollController, + itemPositionsListener: itemPositionsListener, + ); itemScrollController.jumpTo(index: 100); await tester.pumpAndSettle(); @@ -175,19 +148,16 @@ void main() { expect(tester.getTopLeft(find.text('Item 109')).dy, 0); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 100) - .itemLeadingEdge, - 0); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 100).itemLeadingEdge, + 0, + ); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 109) - .itemTrailingEdge, - 1); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 109).itemTrailingEdge, + 1, + ); }); - testWidgets('padding test - centered sliver at bottom', - (WidgetTester tester) async { + testWidgets('padding test - centered sliver at bottom', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); await setUpWidgetTest( tester, @@ -195,26 +165,23 @@ void main() { padding: const EdgeInsets.all(10), ); - expect(tester.getBottomLeft(find.text('Item 0')), - const Offset(10, screenHeight - 10)); - expect(tester.getBottomLeft(find.text('Item 1')), - const Offset(10, screenHeight - (itemHeight + 10))); - expect(tester.getTopRight(find.text('Item 1')), - const Offset(screenWidth - 10, screenHeight - (10 + itemHeight * 2))); + expect(tester.getBottomLeft(find.text('Item 0')), const Offset(10, screenHeight - 10)); + expect(tester.getBottomLeft(find.text('Item 1')), const Offset(10, screenHeight - (itemHeight + 10))); + expect( + tester.getTopRight(find.text('Item 1')), + const Offset(screenWidth - 10, screenHeight - (10 + itemHeight * 2)), + ); - unawaited( - itemScrollController.scrollTo(index: 490, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 490, duration: scrollDuration)); await tester.pumpAndSettle(); - await tester.drag( - find.byType(ScrollablePositionedList), const Offset(0, 100)); + await tester.drag(find.byType(ScrollablePositionedList), const Offset(0, 100)); await tester.pumpAndSettle(); expect(tester.getTopLeft(find.text('Item 499')), const Offset(10, 10)); }); - testWidgets('padding test - centered sliver not at bottom', - (WidgetTester tester) async { + testWidgets('padding test - centered sliver not at bottom', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); await setUpWidgetTest( tester, @@ -223,15 +190,11 @@ void main() { padding: const EdgeInsets.all(10), ); - await tester.drag( - find.byType(ScrollablePositionedList), const Offset(0, -200)); + await tester.drag(find.byType(ScrollablePositionedList), const Offset(0, -200)); await tester.pumpAndSettle(); - expect(tester.getBottomLeft(find.text('Item 0')), - const Offset(10, screenHeight - 10)); - expect(tester.getBottomLeft(find.text('Item 2')), - const Offset(10, screenHeight - (10 + itemHeight * 2))); - expect(tester.getBottomLeft(find.text('Item 3')), - const Offset(10, screenHeight - (10 + itemHeight * 3))); + expect(tester.getBottomLeft(find.text('Item 0')), const Offset(10, screenHeight - 10)); + expect(tester.getBottomLeft(find.text('Item 2')), const Offset(10, screenHeight - (10 + itemHeight * 2))); + expect(tester.getBottomLeft(find.text('Item 3')), const Offset(10, screenHeight - (10 + itemHeight * 3))); }); } diff --git a/packages/stream_chat_flutter/test/scrollable_positioned_list/scrollable_positioned_list_test.dart b/packages/stream_chat_flutter/test/scrollable_positioned_list/scrollable_positioned_list_test.dart index d3c19b2f0f..baa5196c49 100644 --- a/packages/stream_chat_flutter/test/scrollable_positioned_list/scrollable_positioned_list_test.dart +++ b/packages/stream_chat_flutter/test/scrollable_positioned_list/scrollable_positioned_list_test.dart @@ -52,8 +52,7 @@ void main() { 'index must be in the range of 0 to itemCount - 1', ); return SizedBox( - height: - variableHeight ? (itemHeight + (index % 13) * 5) : itemHeight, + height: variableHeight ? (itemHeight + (index % 13) * 5) : itemHeight, child: Text('Item $index'), ); }, @@ -85,24 +84,12 @@ void main() { expect(find.text('Item 9'), findsOneWidget); expect(find.text('Item 10'), findsNothing); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 0) - .itemLeadingEdge, - 0); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 9) - .itemTrailingEdge, - 1); - expect( - itemPositionsListener.itemPositions.value - .where((position) => position.index == 10), - isEmpty); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 0).itemLeadingEdge, 0); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 9).itemTrailingEdge, 1); + expect(itemPositionsListener.itemPositions.value.where((position) => position.index == 10), isEmpty); }); - testWidgets('List positioned with 0 at top - use default values', - (WidgetTester tester) async { + testWidgets('List positioned with 0 at top - use default values', (WidgetTester tester) async { final itemPositionsListener = ItemPositionsListener.create(); tester.view.devicePixelRatio = 1.0; tester.view.physicalSize = const Size(screenWidth, screenHeight); @@ -124,88 +111,68 @@ void main() { expect(find.text('Item 9'), findsOneWidget); expect(find.text('Item 10'), findsNothing); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 0) - .itemLeadingEdge, - 0); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 9) - .itemTrailingEdge, - 1); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 0).itemLeadingEdge, 0); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 9).itemTrailingEdge, 1); }); testWidgets('List positioned with 5 at top', (WidgetTester tester) async { final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemPositionsListener: itemPositionsListener, initialIndex: 5); + await setUpWidgetTest(tester, itemPositionsListener: itemPositionsListener, initialIndex: 5); expect(find.text('Item 4'), findsNothing); expect(find.text('Item 5'), findsOneWidget); - expect( - itemPositionsListener.itemPositions.value - .where((position) => position.index == 4), - isEmpty); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 5) - .itemLeadingEdge, - 0); + expect(itemPositionsListener.itemPositions.value.where((position) => position.index == 4), isEmpty); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 5).itemLeadingEdge, 0); }); testWidgets('List positioned with 9 at middle', (WidgetTester tester) async { final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemPositionsListener: itemPositionsListener, - initialIndex: 9, - initialAlignment: 0.5); + await setUpWidgetTest(tester, itemPositionsListener: itemPositionsListener, initialIndex: 9, initialAlignment: 0.5); expect(tester.getTopLeft(find.text('Item 9')).dy, screenHeight / 2); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 9) - .itemLeadingEdge, - 0.5); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 9).itemLeadingEdge, + 0.5, + ); }); - testWidgets('List positioned with 9 half way off top', - (WidgetTester tester) async { + testWidgets('List positioned with 9 half way off top', (WidgetTester tester) async { final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemPositionsListener: itemPositionsListener, - initialIndex: 9, - initialAlignment: -(itemHeight / screenHeight) / 2); + await setUpWidgetTest( + tester, + itemPositionsListener: itemPositionsListener, + initialIndex: 9, + initialAlignment: -(itemHeight / screenHeight) / 2, + ); expect(tester.getTopLeft(find.text('Item 9')).dy, -itemHeight / 2); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 9) - .itemLeadingEdge, - -(itemHeight / screenHeight) / 2); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 9).itemLeadingEdge, + -(itemHeight / screenHeight) / 2, + ); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 9) - .itemTrailingEdge, - (itemHeight / screenHeight) / 2); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 9).itemTrailingEdge, + (itemHeight / screenHeight) / 2, + ); }); testWidgets('Scroll to 9 half way off top', (WidgetTester tester) async { final itemPositionsListener = ItemPositionsListener.create(); final itemScrollController = ItemScrollController(); expect(itemScrollController.isAttached, false); - await setUpWidgetTest(tester, - itemPositionsListener: itemPositionsListener, - itemScrollController: itemScrollController); + await setUpWidgetTest( + tester, + itemPositionsListener: itemPositionsListener, + itemScrollController: itemScrollController, + ); expect(itemScrollController.isAttached, true); - unawaited(itemScrollController.scrollTo( - index: 9, - duration: scrollDuration, - alignment: -(itemHeight / screenHeight) / 2)); + unawaited( + itemScrollController.scrollTo(index: 9, duration: scrollDuration, alignment: -(itemHeight / screenHeight) / 2), + ); await tester.pump(); await tester.pump(); await tester.pump(scrollDuration + scrollDurationTolerance); @@ -213,54 +180,51 @@ void main() { expect(tester.getTopLeft(find.text('Item 9')).dy, -itemHeight / 2); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 9) - .itemLeadingEdge, - -(itemHeight / screenHeight) / 2); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 9).itemLeadingEdge, + -(itemHeight / screenHeight) / 2, + ); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 9) - .itemTrailingEdge, - (itemHeight / screenHeight) / 2); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 9).itemTrailingEdge, + (itemHeight / screenHeight) / 2, + ); }); testWidgets('Jump to 9 half way off top', (WidgetTester tester) async { final itemPositionsListener = ItemPositionsListener.create(); final itemScrollController = ItemScrollController(); - await setUpWidgetTest(tester, - itemPositionsListener: itemPositionsListener, - itemScrollController: itemScrollController); + await setUpWidgetTest( + tester, + itemPositionsListener: itemPositionsListener, + itemScrollController: itemScrollController, + ); - itemScrollController.jumpTo( - index: 9, alignment: -(itemHeight / screenHeight) / 2); + itemScrollController.jumpTo(index: 9, alignment: -(itemHeight / screenHeight) / 2); await tester.pump(); expect(tester.getTopLeft(find.text('Item 9')).dy, -itemHeight / 2); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 9) - .itemLeadingEdge, - -(itemHeight / screenHeight) / 2); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 9).itemLeadingEdge, + -(itemHeight / screenHeight) / 2, + ); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 9) - .itemTrailingEdge, - (itemHeight / screenHeight) / 2); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 9).itemTrailingEdge, + (itemHeight / screenHeight) / 2, + ); }); - testWidgets('List positioned with 9 at middle scroll to 15 at bottom', - (WidgetTester tester) async { + testWidgets('List positioned with 9 at middle scroll to 15 at bottom', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, - itemPositionsListener: itemPositionsListener, - initialIndex: 9, - initialAlignment: 0.5); + await setUpWidgetTest( + tester, + itemScrollController: itemScrollController, + itemPositionsListener: itemPositionsListener, + initialIndex: 9, + initialAlignment: 0.5, + ); - unawaited(itemScrollController.scrollTo( - index: 16, duration: scrollDuration, alignment: 1)); + unawaited(itemScrollController.scrollTo(index: 16, duration: scrollDuration, alignment: 1)); await tester.pump(); await tester.pump(); await tester.pump(scrollDuration + scrollDurationTolerance); @@ -268,21 +232,21 @@ void main() { expect(tester.getBottomRight(find.text('Item 15')).dy, screenHeight); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 15) - .itemTrailingEdge, - 1.0); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 15).itemTrailingEdge, + 1.0, + ); }); testWidgets('Scroll to 1 (already on screen)', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, - itemPositionsListener: itemPositionsListener); + await setUpWidgetTest( + tester, + itemScrollController: itemScrollController, + itemPositionsListener: itemPositionsListener, + ); - unawaited( - itemScrollController.scrollTo(index: 1, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 1, duration: scrollDuration)); await tester.pump(); await tester.pump(scrollDuration ~/ 2); @@ -295,83 +259,61 @@ void main() { expect(find.text('Item 1'), findsOneWidget); expect(find.text('Item 10'), findsOneWidget); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 1).itemLeadingEdge, 0); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 1) - .itemLeadingEdge, - 0); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 10) - .itemTrailingEdge, - 1); - expect( - itemPositionsListener.itemPositions.value - .where((position) => position.index == 11), - isEmpty); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 10).itemTrailingEdge, + 1, + ); + expect(itemPositionsListener.itemPositions.value.where((position) => position.index == 11), isEmpty); }); - testWidgets('Scroll to 1 then 2 (both already on screen)', - (WidgetTester tester) async { + testWidgets('Scroll to 1 then 2 (both already on screen)', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, - itemPositionsListener: itemPositionsListener); + await setUpWidgetTest( + tester, + itemScrollController: itemScrollController, + itemPositionsListener: itemPositionsListener, + ); - unawaited( - itemScrollController.scrollTo(index: 1, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 1, duration: scrollDuration)); await tester.pump(); await tester.pump(scrollDuration); expect(find.text('Item 0'), findsNothing); expect(find.text('Item 1'), findsOneWidget); - unawaited( - itemScrollController.scrollTo(index: 2, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 2, duration: scrollDuration)); await tester.pump(); await tester.pump(scrollDuration); expect(find.text('Item 1'), findsNothing); expect(find.text('Item 2'), findsOneWidget); + expect(itemPositionsListener.itemPositions.value.where((position) => position.index == 1), isEmpty); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 2).itemLeadingEdge, 0); expect( - itemPositionsListener.itemPositions.value - .where((position) => position.index == 1), - isEmpty); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 2) - .itemLeadingEdge, - 0); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 11) - .itemTrailingEdge, - 1); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 11).itemTrailingEdge, + 1, + ); }); - testWidgets('Scroll to 5 (already on screen) and then back to 0', - (WidgetTester tester) async { + testWidgets('Scroll to 5 (already on screen) and then back to 0', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, - itemPositionsListener: itemPositionsListener); + await setUpWidgetTest( + tester, + itemScrollController: itemScrollController, + itemPositionsListener: itemPositionsListener, + ); - unawaited( - itemScrollController.scrollTo(index: 5, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 5, duration: scrollDuration)); await tester.pump(); await tester.pump(); await tester.pump(scrollDuration + scrollDurationTolerance); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 5) - .itemLeadingEdge, - 0); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 5).itemLeadingEdge, 0); - unawaited( - itemScrollController.scrollTo(index: 0, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 0, duration: scrollDuration)); await tester.pump(); await tester.pump(); await tester.pump(scrollDuration + scrollDurationTolerance); @@ -380,30 +322,23 @@ void main() { expect(find.text('Item 9'), findsOneWidget); expect(find.text('Item 10'), findsNothing); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 0) - .itemLeadingEdge, - 0); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 9) - .itemTrailingEdge, - 1); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 0).itemLeadingEdge, 0); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 9).itemTrailingEdge, 1); }); testWidgets('Scroll to 20 without fading', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, - itemPositionsListener: itemPositionsListener); + await setUpWidgetTest( + tester, + itemScrollController: itemScrollController, + itemPositionsListener: itemPositionsListener, + ); var fadeTransition = tester.widget(fadeTransitionFinder); final initialOpacity = fadeTransition.opacity; - unawaited( - itemScrollController.scrollTo(index: 20, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 20, duration: scrollDuration)); await tester.pump(); await tester.pump(); await tester.pump(scrollDuration ~/ 2); @@ -417,16 +352,16 @@ void main() { expect(find.text('Item 20'), findsOneWidget); }); - testWidgets('Scroll to 100 (not already on screen)', - (WidgetTester tester) async { + testWidgets('Scroll to 100 (not already on screen)', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, - itemPositionsListener: itemPositionsListener); + await setUpWidgetTest( + tester, + itemScrollController: itemScrollController, + itemPositionsListener: itemPositionsListener, + ); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pump(); await tester.pump(); await tester.pump(scrollDuration + scrollDurationTolerance); @@ -435,26 +370,22 @@ void main() { expect(find.text('Item 100'), findsOneWidget); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 100) - .itemLeadingEdge, - 0); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 100).itemLeadingEdge, + 0, + ); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 109) - .itemTrailingEdge, - 1); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 109).itemTrailingEdge, + 1, + ); await tester.pumpAndSettle(); }); - testWidgets('Scroll to 100 (not already on screen) front scroll view', - (WidgetTester tester) async { + testWidgets('Scroll to 100 (not already on screen) front scroll view', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); await setUpWidgetTest(tester, itemScrollController: itemScrollController); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pump(); await tester.pump(); expect(fadeTransitionFinder.evaluate().length, 2); @@ -489,13 +420,11 @@ void main() { ); }); - testWidgets('Scroll to 100 (not already on screen) back scroll view', - (WidgetTester tester) async { + testWidgets('Scroll to 100 (not already on screen) back scroll view', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); await setUpWidgetTest(tester, itemScrollController: itemScrollController); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pump(); await tester.pump(); await tester.pump(scrollDuration ~/ 2); @@ -506,23 +435,22 @@ void main() { expect(find.text('Item 25', skipOffstage: false), findsNothing); }); - testWidgets('Scroll to 100 (not already on screen) then back to 0', - (WidgetTester tester) async { + testWidgets('Scroll to 100 (not already on screen) then back to 0', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, - itemPositionsListener: itemPositionsListener); + await setUpWidgetTest( + tester, + itemScrollController: itemScrollController, + itemPositionsListener: itemPositionsListener, + ); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pump(); await tester.pump(); await tester.pump(scrollDuration + scrollDurationTolerance); expect(find.text('Item 0'), findsNothing); - unawaited( - itemScrollController.scrollTo(index: 0, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 0, duration: scrollDuration)); await tester.pump(); await tester.pump(); expect( @@ -538,29 +466,18 @@ void main() { expect(find.text('Item 0'), findsOneWidget); expect(tester.getTopLeft(find.text('Item 0')).dy, 0); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 0) - .itemLeadingEdge, - 0); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 9) - .itemTrailingEdge, - 1); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 0).itemLeadingEdge, 0); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 9).itemTrailingEdge, 1); }); - testWidgets('Scroll to 100 then back to 0 back scroll view', - (WidgetTester tester) async { + testWidgets('Scroll to 100 then back to 0 back scroll view', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); await setUpWidgetTest(tester, itemScrollController: itemScrollController); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pumpAndSettle(); - unawaited( - itemScrollController.scrollTo(index: 0, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 0, duration: scrollDuration)); await tester.pump(); await tester.pump(); await tester.pump(scrollDuration ~/ 2); @@ -571,19 +488,16 @@ void main() { await tester.pumpAndSettle(); }); - testWidgets('Scroll to 100 then back to 0 front scroll view', - (WidgetTester tester) async { + testWidgets('Scroll to 100 then back to 0 front scroll view', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); await setUpWidgetTest(tester, itemScrollController: itemScrollController); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pump(); await tester.pump(); await tester.pump(scrollDuration + scrollDurationTolerance); - unawaited( - itemScrollController.scrollTo(index: 0, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 0, duration: scrollDuration)); await tester.pump(); await tester.pump(); await tester.pump(scrollDuration ~/ 2); @@ -602,20 +516,17 @@ void main() { final itemScrollController = ItemScrollController(); await setUpWidgetTest(tester, itemScrollController: itemScrollController); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pump(); await tester.pump(); await tester.pump(scrollDuration + scrollDurationTolerance); - unawaited( - itemScrollController.scrollTo(index: 0, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 0, duration: scrollDuration)); await tester.pump(); await tester.pump(); await tester.pump(scrollDuration + scrollDurationTolerance); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pump(); await tester.pump(); await tester.pump(scrollDuration ~/ 2); @@ -629,9 +540,11 @@ void main() { testWidgets('Jump to 100', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, - itemPositionsListener: itemPositionsListener); + await setUpWidgetTest( + tester, + itemScrollController: itemScrollController, + itemPositionsListener: itemPositionsListener, + ); itemScrollController.jumpTo(index: 100); await tester.pump(); @@ -642,24 +555,23 @@ void main() { expect(tester.getBottomLeft(find.text('Item 109')).dy, screenHeight); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 100) - .itemLeadingEdge, - 0); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 100).itemLeadingEdge, + 0, + ); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 109) - .itemTrailingEdge, - 1); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 109).itemTrailingEdge, + 1, + ); }); - testWidgets('Jump to 100 and position at bottom', - (WidgetTester tester) async { + testWidgets('Jump to 100 and position at bottom', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, - itemPositionsListener: itemPositionsListener); + await setUpWidgetTest( + tester, + itemScrollController: itemScrollController, + itemPositionsListener: itemPositionsListener, + ); itemScrollController.jumpTo(index: 100, alignment: 1); await tester.pump(); @@ -669,23 +581,20 @@ void main() { expect(tester.getBottomLeft(find.text('Item 99')).dy, screenHeight); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 99) - .itemTrailingEdge, - 1.0); - expect( - itemPositionsListener.itemPositions.value - .where((position) => position.index == 100), - isEmpty); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 99).itemTrailingEdge, + 1.0, + ); + expect(itemPositionsListener.itemPositions.value.where((position) => position.index == 100), isEmpty); }); - testWidgets('Jump to 100 and position at middle', - (WidgetTester tester) async { + testWidgets('Jump to 100 and position at middle', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, - itemPositionsListener: itemPositionsListener); + await setUpWidgetTest( + tester, + itemScrollController: itemScrollController, + itemPositionsListener: itemPositionsListener, + ); itemScrollController.jumpTo(index: 100, alignment: 0.5); await tester.pump(); @@ -695,21 +604,21 @@ void main() { expect(tester.getTopLeft(find.text('Item 100')).dy, screenHeight / 2); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 100) - .itemLeadingEdge, - 0.5); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 100).itemLeadingEdge, + 0.5, + ); }); - testWidgets('Manually scroll a significant distance, jump to 100', - (WidgetTester tester) async { + testWidgets('Manually scroll a significant distance, jump to 100', (WidgetTester tester) async { // Test for https://github.com/google/flutter.widgets/issues/144. final itemScrollController = ItemScrollController(); final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, - itemPositionsListener: itemPositionsListener, - variableHeight: true); + await setUpWidgetTest( + tester, + itemScrollController: itemScrollController, + itemPositionsListener: itemPositionsListener, + variableHeight: true, + ); final listFinder = find.byType(ScrollablePositionedList); for (var i = 0; i < 5; i += 1) { @@ -723,16 +632,16 @@ void main() { expect(tester.getTopLeft(find.text('Item 100')).dy, 0); }, skip: true); - testWidgets('Scroll to 100 and position at bottom', - (WidgetTester tester) async { + testWidgets('Scroll to 100 and position at bottom', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, - itemPositionsListener: itemPositionsListener); + await setUpWidgetTest( + tester, + itemScrollController: itemScrollController, + itemPositionsListener: itemPositionsListener, + ); - unawaited(itemScrollController.scrollTo( - index: 100, alignment: 1, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, alignment: 1, duration: scrollDuration)); await tester.pump(); await tester.pump(); await tester.pump(scrollDuration + scrollDurationTolerance); @@ -740,26 +649,22 @@ void main() { expect(tester.getBottomLeft(find.text('Item 99')).dy, screenHeight); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 99) - .itemTrailingEdge, - 1.0); - expect( - itemPositionsListener.itemPositions.value - .where((position) => position.index == 100), - isEmpty); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 99).itemTrailingEdge, + 1.0, + ); + expect(itemPositionsListener.itemPositions.value.where((position) => position.index == 100), isEmpty); }); - testWidgets('Scroll to 100 and position at middle', - (WidgetTester tester) async { + testWidgets('Scroll to 100 and position at middle', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, - itemPositionsListener: itemPositionsListener); + await setUpWidgetTest( + tester, + itemScrollController: itemScrollController, + itemPositionsListener: itemPositionsListener, + ); - unawaited(itemScrollController.scrollTo( - index: 100, alignment: 0.5, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, alignment: 0.5, duration: scrollDuration)); await tester.pump(); await tester.pump(); await tester.pump(scrollDuration + scrollDurationTolerance); @@ -767,22 +672,21 @@ void main() { expect(tester.getTopLeft(find.text('Item 100')).dy, screenHeight / 2); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 100) - .itemLeadingEdge, - 0.5); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 100).itemLeadingEdge, + 0.5, + ); }); - testWidgets('Scroll to 9 and position at middle', - (WidgetTester tester) async { + testWidgets('Scroll to 9 and position at middle', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, - itemPositionsListener: itemPositionsListener); + await setUpWidgetTest( + tester, + itemScrollController: itemScrollController, + itemPositionsListener: itemPositionsListener, + ); - unawaited(itemScrollController.scrollTo( - index: 9, alignment: 0.5, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 9, alignment: 0.5, duration: scrollDuration)); await tester.pump(); await tester.pump(); await tester.pump(scrollDuration + scrollDurationTolerance); @@ -790,22 +694,21 @@ void main() { expect(tester.getTopLeft(find.text('Item 9')).dy, screenHeight / 2); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 9) - .itemLeadingEdge, - 0.5); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 9).itemLeadingEdge, + 0.5, + ); }); - testWidgets('Scroll up a little then jump to 100', - (WidgetTester tester) async { + testWidgets('Scroll up a little then jump to 100', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, - itemPositionsListener: itemPositionsListener); + await setUpWidgetTest( + tester, + itemScrollController: itemScrollController, + itemPositionsListener: itemPositionsListener, + ); - await tester.drag( - find.byType(ScrollablePositionedList), const Offset(0, -10)); + await tester.drag(find.byType(ScrollablePositionedList), const Offset(0, -10)); await tester.pumpAndSettle(); itemScrollController.jumpTo(index: 100); @@ -817,24 +720,20 @@ void main() { expect(tester.getBottomLeft(find.text('Item 109')).dy, screenHeight); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 100) - .itemLeadingEdge, - 0); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 100).itemLeadingEdge, + 0, + ); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 109) - .itemTrailingEdge, - 1); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 109).itemTrailingEdge, + 1, + ); }); - testWidgets('Scroll to 100 Jump to 0 Scroll to 100', - (WidgetTester tester) async { + testWidgets('Scroll to 100 Jump to 0 Scroll to 100', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); await setUpWidgetTest(tester, itemScrollController: itemScrollController); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pump(); await tester.pump(); await tester.pump(scrollDuration + scrollDurationTolerance); @@ -844,8 +743,7 @@ void main() { await tester.pump(); await tester.pump(scrollDuration + scrollDurationTolerance); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pump(); await tester.pump(); await tester.pump(scrollDuration ~/ 2); @@ -859,13 +757,11 @@ void main() { expect(tester.getBottomLeft(find.text('Item 109')).dy, screenHeight); }); - testWidgets('Scroll to 100 stop before half way', - (WidgetTester tester) async { + testWidgets('Scroll to 100 stop before half way', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); await setUpWidgetTest(tester, itemScrollController: itemScrollController); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pump(); await tester.pump(); await tester.pump(scrollDuration ~/ 2 - scrollDuration ~/ 20); @@ -884,8 +780,7 @@ void main() { final itemScrollController = ItemScrollController(); await setUpWidgetTest(tester, itemScrollController: itemScrollController); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pump(); await tester.pump(); await tester.pump(scrollDuration ~/ 2); @@ -904,12 +799,10 @@ void main() { final itemScrollController = ItemScrollController(); await setUpWidgetTest(tester, itemScrollController: itemScrollController); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pumpAndSettle(); - unawaited( - itemScrollController.scrollTo(index: 0, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 0, duration: scrollDuration)); await tester.pump(); await tester.pump(); await tester.pump(scrollDuration ~/ 2 - scrollDuration ~/ 20); @@ -927,21 +820,18 @@ void main() { final itemScrollController = ItemScrollController(); await setUpWidgetTest(tester, itemScrollController: itemScrollController); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pump(); await tester.pump(); await tester.pump(scrollDuration ~/ 2 + scrollDuration ~/ 20); expect(find.text('Item 9', skipOffstage: false), findsOneWidget); - expect(tester.getBottomLeft(find.text('Item 100')).dy, - closeTo(screenHeight, tolerance)); + expect(tester.getBottomLeft(find.text('Item 100')).dy, closeTo(screenHeight, tolerance)); await tester.tap(find.byType(ScrollablePositionedList)); await tester.pump(); - expect(tester.getBottomLeft(find.text('Item 100')).dy, - closeTo(screenHeight, tolerance)); + expect(tester.getBottomLeft(find.text('Item 100')).dy, closeTo(screenHeight, tolerance)); expect(find.text('Item 9', skipOffstage: false), findsNothing); expect(fadeTransitionFinder, findsNWidgets(1)); @@ -952,12 +842,10 @@ void main() { final itemScrollController = ItemScrollController(); await setUpWidgetTest(tester, itemScrollController: itemScrollController); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pumpAndSettle(); - unawaited( - itemScrollController.scrollTo(index: 0, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 0, duration: scrollDuration)); await tester.pump(); await tester.pump(); await tester.pump(scrollDuration ~/ 2 + scrollDuration ~/ 20); @@ -976,12 +864,10 @@ void main() { final itemScrollController = ItemScrollController(); await setUpWidgetTest(tester, itemScrollController: itemScrollController); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pumpAndSettle(); - unawaited( - itemScrollController.scrollTo(index: 0, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 0, duration: scrollDuration)); await tester.pump(); await tester.pump(); await tester.pump(scrollDuration ~/ 2); @@ -995,13 +881,11 @@ void main() { await tester.pumpAndSettle(); }); - testWidgets('Scroll to 100 jump to 250 half way', - (WidgetTester tester) async { + testWidgets('Scroll to 100 jump to 250 half way', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); await setUpWidgetTest(tester, itemScrollController: itemScrollController); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pump(); await tester.pump(); await tester.pump(scrollDuration ~/ 2); @@ -1016,17 +900,14 @@ void main() { await tester.pumpAndSettle(); }); - testWidgets('Scroll to 250, scroll to 100, jump to 0 half way', - (WidgetTester tester) async { + testWidgets('Scroll to 250, scroll to 100, jump to 0 half way', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); await setUpWidgetTest(tester, itemScrollController: itemScrollController); - unawaited( - itemScrollController.scrollTo(index: 250, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 250, duration: scrollDuration)); await tester.pumpAndSettle(); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pump(); await tester.pump(); await tester.pump(scrollDuration ~/ 2); @@ -1040,38 +921,32 @@ void main() { await tester.pumpAndSettle(); }); - testWidgets('Scroll to 100 scroll to 250 half way', - (WidgetTester tester) async { + testWidgets('Scroll to 100 scroll to 250 half way', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); await setUpWidgetTest(tester, itemScrollController: itemScrollController); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pump(); await tester.pump(); await tester.pump(scrollDuration ~/ 2); - unawaited( - itemScrollController.scrollTo(index: 250, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 250, duration: scrollDuration)); await tester.pumpAndSettle(); expect(tester.getTopLeft(find.text('Item 250')).dy, 0); expect(find.text('Item 100'), findsNothing); }); - testWidgets("Second scroll future doesn't complete until scroll is done", - (WidgetTester tester) async { + testWidgets("Second scroll future doesn't complete until scroll is done", (WidgetTester tester) async { final itemScrollController = ItemScrollController(); await setUpWidgetTest(tester, itemScrollController: itemScrollController); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pump(); await tester.pump(); await tester.pump(scrollDuration ~/ 2); - final scrollFuture2 = - itemScrollController.scrollTo(index: 250, duration: scrollDuration); + final scrollFuture2 = itemScrollController.scrollTo(index: 250, duration: scrollDuration); var futureComplete = false; unawaited(scrollFuture2.then((_) => futureComplete = true)); @@ -1087,42 +962,33 @@ void main() { expect(futureComplete, isTrue); }); - testWidgets('Scroll to 250, scroll to 100, scroll to 0 half way', - (WidgetTester tester) async { + testWidgets('Scroll to 250, scroll to 100, scroll to 0 half way', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); await setUpWidgetTest(tester, itemScrollController: itemScrollController); - unawaited( - itemScrollController.scrollTo(index: 250, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 250, duration: scrollDuration)); await tester.pumpAndSettle(); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pump(); await tester.pump(); await tester.pump(scrollDuration ~/ 2); - unawaited( - itemScrollController.scrollTo(index: 0, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 0, duration: scrollDuration)); await tester.pumpAndSettle(); expect(tester.getTopLeft(find.text('Item 0')).dy, 0); expect(find.text('Item 100'), findsNothing); }); - testWidgets( - 'Scroll to 100, scroll to 200, then scroll to 300 without waiting', - (WidgetTester tester) async { + testWidgets('Scroll to 100, scroll to 200, then scroll to 300 without waiting', (WidgetTester tester) async { // Possibly https://github.com/google/flutter.widgets/issues/171. final itemScrollController = ItemScrollController(); await setUpWidgetTest(tester, itemScrollController: itemScrollController); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); - unawaited( - itemScrollController.scrollTo(index: 200, duration: scrollDuration)); - unawaited( - itemScrollController.scrollTo(index: 300, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 200, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 300, duration: scrollDuration)); await tester.pumpAndSettle(); expect(find.text('Item 100'), findsNothing); @@ -1138,9 +1004,11 @@ void main() { (WidgetTester tester) async { final itemScrollController = ItemScrollController(); final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, - itemPositionsListener: itemPositionsListener); + await setUpWidgetTest( + tester, + itemScrollController: itemScrollController, + itemPositionsListener: itemPositionsListener, + ); itemScrollController.jumpTo(index: 400, alignment: 1); await tester.pumpAndSettle(); @@ -1150,12 +1018,10 @@ void main() { await tester.drag(listFinder, const Offset(0, -screenHeight)); await tester.pumpAndSettle(); - unawaited(itemScrollController.scrollTo( - index: 100, alignment: 1, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, alignment: 1, duration: scrollDuration)); await tester.pumpAndSettle(); - unawaited(itemScrollController.scrollTo( - index: 400, alignment: 1, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 400, alignment: 1, duration: scrollDuration)); await tester.pumpAndSettle(); final itemFinder = find.text('Item 399'); @@ -1166,12 +1032,9 @@ void main() { testWidgets('physics', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, - physics: const BouncingScrollPhysics()); + await setUpWidgetTest(tester, itemScrollController: itemScrollController, physics: const BouncingScrollPhysics()); - await tester.drag( - find.byType(ScrollablePositionedList), const Offset(0, 50)); + await tester.drag(find.byType(ScrollablePositionedList), const Offset(0, 50)); await tester.pump(const Duration(milliseconds: 200)); expect(tester.getTopLeft(find.text('Item 0')).dy, greaterThan(0)); @@ -1179,14 +1042,12 @@ void main() { await tester.pumpAndSettle(); expect(tester.getTopLeft(find.text('Item 0')).dy, 0); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pumpAndSettle(); itemScrollController.jumpTo(index: 0); await tester.pumpAndSettle(); - await tester.drag( - find.byType(ScrollablePositionedList), const Offset(0, 50)); + await tester.drag(find.byType(ScrollablePositionedList), const Offset(0, 50)); await tester.pump(const Duration(milliseconds: 200)); expect(tester.getTopLeft(find.text('Item 0')).dy, greaterThan(0)); @@ -1197,47 +1058,51 @@ void main() { testWidgets('correct index sematics', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, initialIndex: 5); + await setUpWidgetTest(tester, itemScrollController: itemScrollController, initialIndex: 5); - await tester.drag( - find.byType(ScrollablePositionedList), const Offset(0, itemHeight * 2)); + await tester.drag(find.byType(ScrollablePositionedList), const Offset(0, itemHeight * 2)); await tester.pumpAndSettle(); - final indexSemantics3 = tester.widget(find.ancestor( - of: find.text('Item 3'), matching: find.byType(IndexedSemantics))); + final indexSemantics3 = tester.widget( + find.ancestor(of: find.text('Item 3'), matching: find.byType(IndexedSemantics)), + ); expect(indexSemantics3.index, 3); - final indexSemantics4 = tester.widget(find.ancestor( - of: find.text('Item 4'), matching: find.byType(IndexedSemantics))); + final indexSemantics4 = tester.widget( + find.ancestor(of: find.text('Item 4'), matching: find.byType(IndexedSemantics)), + ); expect(indexSemantics4.index, 4); - final indexSemantics5 = tester.widget(find.ancestor( - of: find.text('Item 5'), matching: find.byType(IndexedSemantics))); + final indexSemantics5 = tester.widget( + find.ancestor(of: find.text('Item 5'), matching: find.byType(IndexedSemantics)), + ); expect(indexSemantics5.index, 5); - final indexSemantics6 = tester.widget(find.ancestor( - of: find.text('Item 6'), matching: find.byType(IndexedSemantics))); + final indexSemantics6 = tester.widget( + find.ancestor(of: find.text('Item 6'), matching: find.byType(IndexedSemantics)), + ); expect(indexSemantics6.index, 6); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pumpAndSettle(); itemScrollController.jumpTo(index: 0); await tester.pumpAndSettle(); - await tester.drag( - find.byType(ScrollablePositionedList), const Offset(0, itemHeight * 2)); + await tester.drag(find.byType(ScrollablePositionedList), const Offset(0, itemHeight * 2)); await tester.pumpAndSettle(); - final indexSemantics3b = tester.widget(find.ancestor( - of: find.text('Item 3'), matching: find.byType(IndexedSemantics))); + final indexSemantics3b = tester.widget( + find.ancestor(of: find.text('Item 3'), matching: find.byType(IndexedSemantics)), + ); expect(indexSemantics3b.index, 3); - final indexSemantics4b = tester.widget(find.ancestor( - of: find.text('Item 4'), matching: find.byType(IndexedSemantics))); + final indexSemantics4b = tester.widget( + find.ancestor(of: find.text('Item 4'), matching: find.byType(IndexedSemantics)), + ); expect(indexSemantics4b.index, 4); - final indexSemantics5b = tester.widget(find.ancestor( - of: find.text('Item 5'), matching: find.byType(IndexedSemantics))); + final indexSemantics5b = tester.widget( + find.ancestor(of: find.text('Item 5'), matching: find.byType(IndexedSemantics)), + ); expect(indexSemantics5b.index, 5); - final indexSemantics6b = tester.widget(find.ancestor( - of: find.text('Item 6'), matching: find.byType(IndexedSemantics))); + final indexSemantics6b = tester.widget( + find.ancestor(of: find.text('Item 6'), matching: find.byType(IndexedSemantics)), + ); expect(indexSemantics6b.index, 6); }); @@ -1252,8 +1117,7 @@ void main() { expect(find.byType(IndexedSemantics), findsNothing); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pumpAndSettle(); itemScrollController.jumpTo(index: 0); await tester.pumpAndSettle(); @@ -1270,16 +1134,13 @@ void main() { itemScrollController: itemScrollController, ); - final customScrollView = - tester.widget(find.byType(UnboundedCustomScrollView)); + final customScrollView = tester.widget(find.byType(UnboundedCustomScrollView)); expect(customScrollView.semanticChildCount, 30); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pumpAndSettle(); - final customScrollView2 = - tester.widget(find.byType(UnboundedCustomScrollView)); + final customScrollView2 = tester.widget(find.byType(UnboundedCustomScrollView)); expect(customScrollView2.semanticChildCount, 30); }); @@ -1290,16 +1151,13 @@ void main() { itemScrollController: itemScrollController, ); - final customScrollView = - tester.widget(find.byType(UnboundedCustomScrollView)); + final customScrollView = tester.widget(find.byType(UnboundedCustomScrollView)); expect(customScrollView.semanticChildCount, defaultItemCount); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pumpAndSettle(); - final customScrollView2 = - tester.widget(find.byType(UnboundedCustomScrollView)); + final customScrollView2 = tester.widget(find.byType(UnboundedCustomScrollView)); expect(customScrollView2.semanticChildCount, defaultItemCount); }); @@ -1329,25 +1187,19 @@ void main() { ); expect(tester.getTopLeft(find.text('Item 0')), const Offset(10, 10)); - expect(tester.getTopLeft(find.text('Item 1')), - const Offset(10, itemHeight + 10)); - expect(tester.getTopRight(find.text('Item 1')), - const Offset(screenWidth - 10, itemHeight + 10)); + expect(tester.getTopLeft(find.text('Item 1')), const Offset(10, itemHeight + 10)); + expect(tester.getTopRight(find.text('Item 1')), const Offset(screenWidth - 10, itemHeight + 10)); - unawaited( - itemScrollController.scrollTo(index: 490, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 490, duration: scrollDuration)); await tester.pumpAndSettle(); - await tester.drag( - find.byType(ScrollablePositionedList), const Offset(0, -100)); + await tester.drag(find.byType(ScrollablePositionedList), const Offset(0, -100)); await tester.pumpAndSettle(); - expect(tester.getBottomRight(find.text('Item 499')), - const Offset(screenWidth - 10, screenHeight - 10)); + expect(tester.getBottomRight(find.text('Item 499')), const Offset(screenWidth - 10, screenHeight - 10)); }); - testWidgets('padding test - centered not at top', - (WidgetTester tester) async { + testWidgets('padding test - centered not at top', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); await setUpWidgetTest( tester, @@ -1356,19 +1208,15 @@ void main() { padding: const EdgeInsets.all(10), ); - await tester.drag( - find.byType(ScrollablePositionedList), const Offset(0, 200)); + await tester.drag(find.byType(ScrollablePositionedList), const Offset(0, 200)); await tester.pumpAndSettle(); expect(tester.getTopLeft(find.text('Item 0')), const Offset(10, 10)); - expect(tester.getTopLeft(find.text('Item 2')), - const Offset(10, 10 + itemHeight * 2)); - expect(tester.getTopLeft(find.text('Item 3')), - const Offset(10, 10 + itemHeight * 3)); + expect(tester.getTopLeft(find.text('Item 2')), const Offset(10, 10 + itemHeight * 2)); + expect(tester.getTopLeft(find.text('Item 3')), const Offset(10, 10 + itemHeight * 3)); }); - testWidgets('padding - first element centered - scroll up', - (WidgetTester tester) async { + testWidgets('padding - first element centered - scroll up', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); await setUpWidgetTest( tester, @@ -1376,15 +1224,13 @@ void main() { padding: const EdgeInsets.all(10), ); - await tester.drag( - find.byType(ScrollablePositionedList), const Offset(0, 100)); + await tester.drag(find.byType(ScrollablePositionedList), const Offset(0, 100)); await tester.pumpAndSettle(); expect(tester.getTopLeft(find.text('Item 0')), const Offset(10, 10)); }); - testWidgets('padding - last element centered - scroll down', - (WidgetTester tester) async { + testWidgets('padding - last element centered - scroll down', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); await setUpWidgetTest( tester, @@ -1392,12 +1238,10 @@ void main() { padding: const EdgeInsets.all(10), ); - unawaited(itemScrollController.scrollTo( - index: defaultItemCount - 1, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: defaultItemCount - 1, duration: scrollDuration)); await tester.pumpAndSettle(); - await tester.drag( - find.byType(ScrollablePositionedList), const Offset(0, -100)); + await tester.drag(find.byType(ScrollablePositionedList), const Offset(0, -100)); await tester.pumpAndSettle(); expect( @@ -1417,12 +1261,13 @@ void main() { ); expect( - tester - .widgetList(find.descendant( - of: find.byType(ScrollablePositionedList), - matching: find.byType(RepaintBoundary))) - .length, - lessThan(5)); + tester + .widgetList( + find.descendant(of: find.byType(ScrollablePositionedList), matching: find.byType(RepaintBoundary)), + ) + .length, + lessThan(5), + ); }); testWidgets('no automatic keep alives', (WidgetTester tester) async { @@ -1436,10 +1281,9 @@ void main() { ); expect( - find.descendant( - of: find.byType(ScrollablePositionedList), - matching: find.byType(AutomaticKeepAlive)), - findsNothing); + find.descendant(of: find.byType(ScrollablePositionedList), matching: find.byType(AutomaticKeepAlive)), + findsNothing, + ); }); testWidgets('Jump to end of list', (WidgetTester tester) async { @@ -1449,61 +1293,49 @@ void main() { itemScrollController.jumpTo(index: defaultItemCount - 1); await tester.pumpAndSettle(); - expect(tester.getBottomLeft(find.text('Item ${defaultItemCount - 1}')).dy, - screenHeight); + expect(tester.getBottomLeft(find.text('Item ${defaultItemCount - 1}')).dy, screenHeight); }); testWidgets('Scroll to end of list', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); await setUpWidgetTest(tester, itemScrollController: itemScrollController); - unawaited(itemScrollController.scrollTo( - index: defaultItemCount - 1, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: defaultItemCount - 1, duration: scrollDuration)); await tester.pumpAndSettle(); - expect(tester.getBottomLeft(find.text('Item ${defaultItemCount - 1}')).dy, - screenHeight); + expect(tester.getBottomLeft(find.text('Item ${defaultItemCount - 1}')).dy, screenHeight); }); - testWidgets('Scroll to end of list, jump to beginning, jump to end', - (WidgetTester tester) async { + testWidgets('Scroll to end of list, jump to beginning, jump to end', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); await setUpWidgetTest(tester, itemScrollController: itemScrollController); - unawaited(itemScrollController.scrollTo( - index: defaultItemCount - 1, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: defaultItemCount - 1, duration: scrollDuration)); await tester.pumpAndSettle(); itemScrollController.jumpTo(index: 0); await tester.pumpAndSettle(); itemScrollController.jumpTo(index: defaultItemCount - 1); await tester.pumpAndSettle(); - expect(tester.getBottomLeft(find.text('Item ${defaultItemCount - 1}')).dy, - screenHeight); + expect(tester.getBottomLeft(find.text('Item ${defaultItemCount - 1}')).dy, screenHeight); }); - testWidgets('Jump to end of list, scroll to beginning, scroll to end', - (WidgetTester tester) async { + testWidgets('Jump to end of list, scroll to beginning, scroll to end', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); await setUpWidgetTest(tester, itemScrollController: itemScrollController); itemScrollController.jumpTo(index: defaultItemCount - 1); await tester.pumpAndSettle(); - unawaited( - itemScrollController.scrollTo(index: 0, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 0, duration: scrollDuration)); await tester.pumpAndSettle(); - unawaited(itemScrollController.scrollTo( - index: defaultItemCount - 1, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: defaultItemCount - 1, duration: scrollDuration)); await tester.pumpAndSettle(); - expect(tester.getBottomLeft(find.text('Item ${defaultItemCount - 1}')).dy, - screenHeight); + expect(tester.getBottomLeft(find.text('Item ${defaultItemCount - 1}')).dy, screenHeight); }); - testWidgets( - 'Jump to end of list, jump to beginning with alignment not at top', - (WidgetTester tester) async { + testWidgets('Jump to end of list, jump to beginning with alignment not at top', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); await setUpWidgetTest(tester, itemScrollController: itemScrollController); @@ -1518,11 +1350,9 @@ void main() { testWidgets("Short list, can't scroll past end", (WidgetTester tester) async { final itemScrollController = ItemScrollController(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, itemCount: 3); + await setUpWidgetTest(tester, itemScrollController: itemScrollController, itemCount: 3); - await tester.drag( - find.byType(ScrollablePositionedList), const Offset(0, -10)); + await tester.drag(find.byType(ScrollablePositionedList), const Offset(0, -10)); await tester.pumpAndSettle(); expect(tester.getTopLeft(find.text('Item 0')).dy, 0); @@ -1536,9 +1366,7 @@ void main() { expect(find.byKey(key), findsOneWidget); }); - testWidgets( - 'Maintain programmatic position (9 half way off top) in page view', - (WidgetTester tester) async { + testWidgets('Maintain programmatic position (9 half way off top) in page view', (WidgetTester tester) async { final itemPositionsListener = ItemPositionsListener.create(); final itemScrollController = ItemScrollController(); @@ -1563,14 +1391,13 @@ void main() { ), const Center( child: Text('Test'), - ) + ), ], ), ), ); - itemScrollController.jumpTo( - index: 9, alignment: -(itemHeight / screenHeight) / 2); + itemScrollController.jumpTo(index: 9, alignment: -(itemHeight / screenHeight) / 2); await tester.pump(); await tester.drag(find.byType(PageView), const Offset(-500, 0)); @@ -1582,19 +1409,16 @@ void main() { expect(tester.getTopLeft(find.text('Item 9')).dy, -itemHeight / 2); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 9) - .itemLeadingEdge, - -(itemHeight / screenHeight) / 2); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 9).itemLeadingEdge, + -(itemHeight / screenHeight) / 2, + ); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 9) - .itemTrailingEdge, - (itemHeight / screenHeight) / 2); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 9).itemTrailingEdge, + (itemHeight / screenHeight) / 2, + ); }); - testWidgets('Maintain user scroll position (1 half way off top) in page view', - (WidgetTester tester) async { + testWidgets('Maintain user scroll position (1 half way off top) in page view', (WidgetTester tester) async { final itemPositionsListener = ItemPositionsListener.create(); final itemScrollController = ItemScrollController(); @@ -1619,14 +1443,13 @@ void main() { ), const Center( child: Text('Test'), - ) + ), ], ), ), ); - await tester.drag( - find.byType(ScrollablePositionedList), const Offset(0, -itemHeight)); + await tester.drag(find.byType(ScrollablePositionedList), const Offset(0, -itemHeight)); await tester.pumpAndSettle(); final item0Bottom = tester.getBottomRight(find.text('Item 0')).dy; @@ -1641,15 +1464,13 @@ void main() { expect(tester.getBottomRight(find.text('Item 0')).dy, item0Bottom); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 0) - .itemLeadingEdge, - -(itemHeight / screenHeight) / 2); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 0).itemLeadingEdge, + -(itemHeight / screenHeight) / 2, + ); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 0) - .itemTrailingEdge, - (itemHeight / screenHeight) / 2); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 0).itemTrailingEdge, + (itemHeight / screenHeight) / 2, + ); }); testWidgets( @@ -1679,7 +1500,7 @@ void main() { ), const Center( child: Text('Test'), - ) + ), ], ), ), @@ -1690,8 +1511,7 @@ void main() { expect(tester.getBottomRight(find.text('Item 9')).dy, itemHeight); - await tester.drag( - find.byType(ScrollablePositionedList), const Offset(0, -itemHeight)); + await tester.drag(find.byType(ScrollablePositionedList), const Offset(0, -itemHeight)); await tester.pumpAndSettle(); final item9Bottom = tester.getBottomRight(find.text('Item 9')).dy; @@ -1706,28 +1526,24 @@ void main() { expect(tester.getBottomRight(find.text('Item 9')).dy, item9Bottom); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 9) - .itemLeadingEdge, - -(itemHeight / screenHeight) / 2); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 9).itemLeadingEdge, + -(itemHeight / screenHeight) / 2, + ); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 9) - .itemTrailingEdge, - (itemHeight / screenHeight) / 2); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 9).itemTrailingEdge, + (itemHeight / screenHeight) / 2, + ); }, ); testWidgets('List with no items', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, itemCount: 0); + await setUpWidgetTest(tester, itemScrollController: itemScrollController, itemCount: 0); expect(find.text('Item 0'), findsNothing); }); - testWidgets('Jump to 100 then set itemCount to 0', - (WidgetTester tester) async { + testWidgets('Jump to 100 then set itemCount to 0', (WidgetTester tester) async { tester.view.devicePixelRatio = 1.0; tester.view.physicalSize = const Size(screenWidth, screenHeight); @@ -1774,8 +1590,7 @@ void main() { expect(itemPositionsListener.itemPositions.value.isEmpty, isTrue); }); - testWidgets('List positioned with 100 at top then set itemCount to 100', - (WidgetTester tester) async { + testWidgets('List positioned with 100 at top then set itemCount to 100', (WidgetTester tester) async { tester.view.devicePixelRatio = 1.0; tester.view.physicalSize = const Size(screenWidth, screenHeight); @@ -1815,8 +1630,7 @@ void main() { expect(tester.getBottomLeft(find.text('Item 99')).dy, screenHeight); }); - testWidgets('List positioned with 499 at bottom then set itemCount to 100', - (WidgetTester tester) async { + testWidgets('List positioned with 499 at bottom then set itemCount to 100', (WidgetTester tester) async { tester.view.devicePixelRatio = 1.0; tester.view.physicalSize = const Size(screenWidth, screenHeight); @@ -1862,8 +1676,7 @@ void main() { expect(find.text('Item 100', skipOffstage: false), findsOneWidget); }); - testWidgets('Scroll to 20 without fading small minCacheExtent', - (WidgetTester tester) async { + testWidgets('Scroll to 20 without fading small minCacheExtent', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); final itemPositionsListener = ItemPositionsListener.create(); await setUpWidgetTest( @@ -1876,8 +1689,7 @@ void main() { var fadeTransition = tester.widget(fadeTransitionFinder); final initialOpacity = fadeTransition.opacity; - unawaited( - itemScrollController.scrollTo(index: 20, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 20, duration: scrollDuration)); await tester.pump(); await tester.pump(); await tester.pump(scrollDuration ~/ 2); @@ -1891,8 +1703,7 @@ void main() { expect(find.text('Item 20'), findsOneWidget); }); - testWidgets('Scroll to 100 without fading for large minCacheExtent', - (WidgetTester tester) async { + testWidgets('Scroll to 100 without fading for large minCacheExtent', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); await setUpWidgetTest( @@ -1906,8 +1717,7 @@ void main() { ); final initialOpacity = fadeTransition.opacity; - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pump(); await tester.pump(); await tester.pump(scrollDuration ~/ 2); @@ -1920,8 +1730,7 @@ void main() { expect(find.text('Item 100'), findsOneWidget); }); - testWidgets('Position list when not enough above top item to fill viewport', - (WidgetTester tester) async { + testWidgets('Position list when not enough above top item to fill viewport', (WidgetTester tester) async { const alignment = 0.8; await setUpWidgetTest( @@ -1969,15 +1778,13 @@ void main() { key.value = const ValueKey('newKey'); await tester.pumpAndSettle(); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pumpAndSettle(); expect(find.text('Item 100'), findsOneWidget); }); - testWidgets('Double rebuild with scroll controller', - (WidgetTester tester) async { + testWidgets('Double rebuild with scroll controller', (WidgetTester tester) async { tester.view.devicePixelRatio = 1.0; tester.view.physicalSize = const Size(screenWidth, screenHeight); final outerKey = ValueNotifier(const ValueKey('outerKey')); @@ -2019,8 +1826,7 @@ void main() { listKey.value = const ValueKey('newListKey'); await tester.pumpAndSettle(); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pumpAndSettle(); expect(find.text('Item 100'), findsOneWidget); @@ -2093,15 +1899,13 @@ void main() { key.value = const ValueKey('newKey'); await tester.pumpAndSettle(); - unawaited( - itemScrollController.scrollTo(index: 70, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 70, duration: scrollDuration)); await tester.pumpAndSettle(); expect(find.text('Item 70'), findsOneWidget); }); - testWidgets('Scroll after rebuild when resusing state', - (WidgetTester tester) async { + testWidgets('Scroll after rebuild when resusing state', (WidgetTester tester) async { tester.view.devicePixelRatio = 1.0; tester.view.physicalSize = const Size(screenWidth, screenHeight); final containerKey = ValueNotifier(const ValueKey('key')); @@ -2137,15 +1941,13 @@ void main() { containerKey.value = const ValueKey('newKey'); await tester.pumpAndSettle(); - unawaited( - itemScrollController.scrollTo(index: 70, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 70, duration: scrollDuration)); await tester.pumpAndSettle(); expect(find.text('Item 70'), findsOneWidget); }); - testWidgets('Scroll after changing scroll controller', - (WidgetTester tester) async { + testWidgets('Scroll after changing scroll controller', (WidgetTester tester) async { tester.view.devicePixelRatio = 1.0; tester.view.physicalSize = const Size(screenWidth, screenHeight); @@ -2183,65 +1985,63 @@ void main() { expect(itemScrollController0.isAttached, false); expect(itemScrollController1.isAttached, true); - unawaited( - itemScrollController1.scrollTo(index: 70, duration: scrollDuration)); + unawaited(itemScrollController1.scrollTo(index: 70, duration: scrollDuration)); await tester.pumpAndSettle(); expect(find.text('Item 70'), findsOneWidget); }); - testWidgets('Scroll after swapping scroll controllers', - (WidgetTester tester) async { + testWidgets('Scroll after swapping scroll controllers', (WidgetTester tester) async { tester.view.devicePixelRatio = 1.0; tester.view.physicalSize = const Size(screenWidth, screenHeight); final itemScrollController0 = ItemScrollController(); final itemScrollController1 = ItemScrollController(); - final topItemScrollControllerListenable = - ValueNotifier(itemScrollController0); - final bottomItemScrollControllerListenable = - ValueNotifier(itemScrollController1); - - await tester.pumpWidget(MaterialApp( - home: Column( - children: [ - Expanded( - child: ValueListenableBuilder( - valueListenable: topItemScrollControllerListenable, - builder: (context, itemScrollController, child) { - return ScrollablePositionedList.builder( - itemCount: 100, - itemScrollController: itemScrollController, - itemBuilder: (context, index) { - return SizedBox( - height: itemHeight, - child: Text('Item $index'), - ); - }, - ); - }, + final topItemScrollControllerListenable = ValueNotifier(itemScrollController0); + final bottomItemScrollControllerListenable = ValueNotifier(itemScrollController1); + + await tester.pumpWidget( + MaterialApp( + home: Column( + children: [ + Expanded( + child: ValueListenableBuilder( + valueListenable: topItemScrollControllerListenable, + builder: (context, itemScrollController, child) { + return ScrollablePositionedList.builder( + itemCount: 100, + itemScrollController: itemScrollController, + itemBuilder: (context, index) { + return SizedBox( + height: itemHeight, + child: Text('Item $index'), + ); + }, + ); + }, + ), ), - ), - Expanded( - child: ValueListenableBuilder( - valueListenable: bottomItemScrollControllerListenable, - builder: (context, itemScrollController, child) { - return ScrollablePositionedList.builder( - itemCount: 100, - itemScrollController: itemScrollController, - itemBuilder: (context, index) { - return SizedBox( - height: itemHeight, - child: Text('Item $index'), - ); - }, - ); - }, + Expanded( + child: ValueListenableBuilder( + valueListenable: bottomItemScrollControllerListenable, + builder: (context, itemScrollController, child) { + return ScrollablePositionedList.builder( + itemCount: 100, + itemScrollController: itemScrollController, + itemBuilder: (context, index) { + return SizedBox( + height: itemHeight, + child: Text('Item $index'), + ); + }, + ); + }, + ), ), - ), - ], + ], + ), ), - )); + ); await tester.pumpAndSettle(); expect(itemScrollController0.isAttached, true); @@ -2254,10 +2054,8 @@ void main() { expect(itemScrollController0.isAttached, true); expect(itemScrollController1.isAttached, true); - unawaited( - itemScrollController1.scrollTo(index: 70, duration: scrollDuration)); - unawaited( - itemScrollController0.scrollTo(index: 50, duration: scrollDuration)); + unawaited(itemScrollController1.scrollTo(index: 70, duration: scrollDuration)); + unawaited(itemScrollController0.scrollTo(index: 50, duration: scrollDuration)); await tester.pumpAndSettle(); expect(find.text('Item 70'), findsOneWidget); diff --git a/packages/stream_chat_flutter/test/scrollable_positioned_list/separated_positioned_list_test.dart b/packages/stream_chat_flutter/test/scrollable_positioned_list/separated_positioned_list_test.dart index f6dc2b5cef..f947d38bf7 100644 --- a/packages/stream_chat_flutter/test/scrollable_positioned_list/separated_positioned_list_test.dart +++ b/packages/stream_chat_flutter/test/scrollable_positioned_list/separated_positioned_list_test.dart @@ -68,24 +68,17 @@ void main() { expect(find.text('Separator 2'), findsNothing); expect(find.text('Item 3'), findsNothing); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 0).itemLeadingEdge, 0); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 0) - .itemLeadingEdge, - 0); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 2) - .itemTrailingEdge, - _screenProportion(numberOfItems: 3, numberOfSeparators: 2)); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 2).itemTrailingEdge, + _screenProportion(numberOfItems: 3, numberOfSeparators: 2), + ); }); - testWidgets('Short list centered at 1 scrolled up', - (WidgetTester tester) async { + testWidgets('Short list centered at 1 scrolled up', (WidgetTester tester) async { await setUpWidgetTest(tester, itemCount: 3, topItem: 1); - await tester.drag( - find.byType(PositionedList), const Offset(0, itemHeight * 2)); + await tester.drag(find.byType(PositionedList), const Offset(0, itemHeight * 2)); await tester.pumpAndSettle(); expect(find.text('Item 0'), findsOneWidget); @@ -96,16 +89,11 @@ void main() { expect(find.text('Separator 2'), findsNothing); expect(find.text('Item 3'), findsNothing); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 0).itemLeadingEdge, 0); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 0) - .itemLeadingEdge, - 0); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 2) - .itemTrailingEdge, - _screenProportion(numberOfItems: 3, numberOfSeparators: 2)); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 2).itemTrailingEdge, + _screenProportion(numberOfItems: 3, numberOfSeparators: 2), + ); }); testWidgets('List positioned with 0 at top', (WidgetTester tester) async { @@ -118,22 +106,13 @@ void main() { expect(find.text('Separator 6'), findsNothing); expect(find.text('Item 7'), findsNothing); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 0).itemLeadingEdge, 0); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 0) - .itemLeadingEdge, - 0); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 5) - .itemTrailingEdge, - 1 - _screenProportion(numberOfItems: 1, numberOfSeparators: 1)); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 5).itemTrailingEdge, + 1 - _screenProportion(numberOfItems: 1, numberOfSeparators: 1), + ); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 6) - .itemTrailingEdge, - 1); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 6).itemTrailingEdge, 1); }); testWidgets('List positioned with 5 at top', (WidgetTester tester) async { @@ -149,21 +128,15 @@ void main() { expect(find.text('Item 11'), findsOneWidget); expect(find.text('Separator 11'), findsNothing); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 5).itemLeadingEdge, 0); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 5) - .itemLeadingEdge, - 0); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 6) - .itemLeadingEdge, - _screenProportion(numberOfItems: 1, numberOfSeparators: 1)); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 6).itemLeadingEdge, + _screenProportion(numberOfItems: 1, numberOfSeparators: 1), + ); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 11) - .itemTrailingEdge, - 1); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 11).itemTrailingEdge, + 1, + ); }); testWidgets('List positioned with 20 at bottom', (WidgetTester tester) async { @@ -179,82 +152,60 @@ void main() { expect(find.text('Separator 12'), findsNothing); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 19) - .itemTrailingEdge, - 1 - _screenProportion(numberOfItems: 0, numberOfSeparators: 1)); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 20) - .itemLeadingEdge, - 1); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 19).itemTrailingEdge, + 1 - _screenProportion(numberOfItems: 0, numberOfSeparators: 1), + ); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 20).itemLeadingEdge, 1); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 13) - .itemLeadingEdge, - _screenProportion(numberOfItems: -0.5, numberOfSeparators: 0)); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 13).itemLeadingEdge, + _screenProportion(numberOfItems: -0.5, numberOfSeparators: 0), + ); }); - testWidgets('List positioned with item 20 at halfway', - (WidgetTester tester) async { + testWidgets('List positioned with item 20 at halfway', (WidgetTester tester) async { await setUpWidgetTest(tester, topItem: 20, anchor: 0.5); await tester.pump(); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 20) - .itemLeadingEdge, - 0.5); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 20).itemLeadingEdge, + 0.5, + ); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 20) - .itemTrailingEdge, - 0.5 + itemHeight / screenHeight); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 20).itemTrailingEdge, + 0.5 + itemHeight / screenHeight, + ); }); - testWidgets('List positioned with item 20 half off top of screen', - (WidgetTester tester) async { - await setUpWidgetTest(tester, - topItem: 20, anchor: -(itemHeight / screenHeight) / 2); + testWidgets('List positioned with item 20 half off top of screen', (WidgetTester tester) async { + await setUpWidgetTest(tester, topItem: 20, anchor: -(itemHeight / screenHeight) / 2); await tester.pump(); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 20) - .itemLeadingEdge, - _screenProportion(numberOfItems: -0.5, numberOfSeparators: 0)); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 20).itemLeadingEdge, + _screenProportion(numberOfItems: -0.5, numberOfSeparators: 0), + ); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 20) - .itemTrailingEdge, - _screenProportion(numberOfItems: 0.5, numberOfSeparators: 0)); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 20).itemTrailingEdge, + _screenProportion(numberOfItems: 0.5, numberOfSeparators: 0), + ); }); - testWidgets('List positioned with 5 at top then scroll up 2 items', - (WidgetTester tester) async { + testWidgets('List positioned with 5 at top then scroll up 2 items', (WidgetTester tester) async { await setUpWidgetTest(tester, topItem: 5); - await tester.drag(find.byType(PositionedList), - const Offset(0, 2 * (itemHeight + separatorHeight))); + await tester.drag(find.byType(PositionedList), const Offset(0, 2 * (itemHeight + separatorHeight))); await tester.pump(); expect(find.text('Separator 2'), findsNothing); expect(find.text('Item 3'), findsOneWidget); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 2) - .itemLeadingEdge, - _screenProportion(numberOfItems: -1, numberOfSeparators: -1)); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 3) - .itemLeadingEdge, - 0); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 2).itemLeadingEdge, + _screenProportion(numberOfItems: -1, numberOfSeparators: -1), + ); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 3).itemLeadingEdge, 0); }); } -double _screenProportion( - {required double numberOfItems, required double numberOfSeparators}) => - (numberOfItems * itemHeight + numberOfSeparators * separatorHeight) / - screenHeight; +double _screenProportion({required double numberOfItems, required double numberOfSeparators}) => + (numberOfItems * itemHeight + numberOfSeparators * separatorHeight) / screenHeight; diff --git a/packages/stream_chat_flutter/test/scrollable_positioned_list/separated_scrollable_positioned_list_test.dart b/packages/stream_chat_flutter/test/scrollable_positioned_list/separated_scrollable_positioned_list_test.dart index d4d99595dd..d6ef7bc527 100644 --- a/packages/stream_chat_flutter/test/scrollable_positioned_list/separated_scrollable_positioned_list_test.dart +++ b/packages/stream_chat_flutter/test/scrollable_positioned_list/separated_scrollable_positioned_list_test.dart @@ -75,30 +75,17 @@ void main() { expect(find.text('Separator 6'), findsNothing); expect(find.text('Item 7'), findsNothing); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 0).itemLeadingEdge, 0); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 0) - .itemLeadingEdge, - 0); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 5) - .itemTrailingEdge, - 1 - _screenProportion(numberOfItems: 1, numberOfSeparators: 1)); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 5).itemTrailingEdge, + 1 - _screenProportion(numberOfItems: 1, numberOfSeparators: 1), + ); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 6) - .itemTrailingEdge, - 1); - expect( - itemPositionsListener.itemPositions.value - .where((position) => position.index == 7), - isEmpty); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 6).itemTrailingEdge, 1); + expect(itemPositionsListener.itemPositions.value.where((position) => position.index == 7), isEmpty); }); - testWidgets('List positioned with 0 at top - use default values', - (WidgetTester tester) async { + testWidgets('List positioned with 0 at top - use default values', (WidgetTester tester) async { final itemPositionsListener = ItemPositionsListener.create(); tester.view.devicePixelRatio = 1.0; tester.view.physicalSize = const Size(screenWidth, screenHeight); @@ -126,32 +113,19 @@ void main() { expect(find.text('Separator 6'), findsNothing); expect(find.text('Item 7'), findsNothing); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 0).itemLeadingEdge, 0); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 0) - .itemLeadingEdge, - 0); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 5) - .itemTrailingEdge, - 1 - _screenProportion(numberOfItems: 1, numberOfSeparators: 1)); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 5).itemTrailingEdge, + 1 - _screenProportion(numberOfItems: 1, numberOfSeparators: 1), + ); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 6) - .itemTrailingEdge, - 1); - expect( - itemPositionsListener.itemPositions.value - .where((position) => position.index == 7), - isEmpty); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 6).itemTrailingEdge, 1); + expect(itemPositionsListener.itemPositions.value.where((position) => position.index == 7), isEmpty); }); testWidgets('List positioned with 5 at top', (WidgetTester tester) async { final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemPositionsListener: itemPositionsListener, initialIndex: 5); + await setUpWidgetTest(tester, itemPositionsListener: itemPositionsListener, initialIndex: 5); expect(find.text('Item 4'), findsNothing); expect(find.text('Separator 4'), findsNothing); @@ -161,58 +135,44 @@ void main() { expect(find.text('Item 11'), findsOneWidget); expect(find.text('Separator 11'), findsNothing); - expect( - itemPositionsListener.itemPositions.value - .where((position) => position.index == 4), - isEmpty); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 5) - .itemLeadingEdge, - 0); + expect(itemPositionsListener.itemPositions.value.where((position) => position.index == 4), isEmpty); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 5).itemLeadingEdge, 0); }); testWidgets('List positioned with 9 at middle', (WidgetTester tester) async { final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemPositionsListener: itemPositionsListener, - initialIndex: 9, - initialAlignment: 0.5); + await setUpWidgetTest(tester, itemPositionsListener: itemPositionsListener, initialIndex: 9, initialAlignment: 0.5); expect(tester.getTopLeft(find.text('Item 9')).dy, screenHeight / 2); - expect(tester.getTopLeft(find.text('Item 8')).dy, - screenHeight / 2 - itemHeight - separatorHeight); - expect(tester.getTopLeft(find.text('Item 10')).dy, - screenHeight / 2 + itemHeight + separatorHeight); + expect(tester.getTopLeft(find.text('Item 8')).dy, screenHeight / 2 - itemHeight - separatorHeight); + expect(tester.getTopLeft(find.text('Item 10')).dy, screenHeight / 2 + itemHeight + separatorHeight); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 9) - .itemLeadingEdge, - 0.5); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 9).itemLeadingEdge, + 0.5, + ); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 8) - .itemLeadingEdge, - 0.5 - _screenProportion(numberOfItems: 1, numberOfSeparators: 1)); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 8).itemLeadingEdge, + 0.5 - _screenProportion(numberOfItems: 1, numberOfSeparators: 1), + ); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 10) - .itemLeadingEdge, - 0.5 + _screenProportion(numberOfItems: 1, numberOfSeparators: 1)); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 10).itemLeadingEdge, + 0.5 + _screenProportion(numberOfItems: 1, numberOfSeparators: 1), + ); }); testWidgets('Scroll to 9 half way off top', (WidgetTester tester) async { final itemPositionsListener = ItemPositionsListener.create(); final itemScrollController = ItemScrollController(); - await setUpWidgetTest(tester, - itemPositionsListener: itemPositionsListener, - itemScrollController: itemScrollController); - - unawaited(itemScrollController.scrollTo( - index: 9, - duration: scrollDuration, - alignment: -(itemHeight / screenHeight) / 2)); + await setUpWidgetTest( + tester, + itemPositionsListener: itemPositionsListener, + itemScrollController: itemScrollController, + ); + + unawaited( + itemScrollController.scrollTo(index: 9, duration: scrollDuration, alignment: -(itemHeight / screenHeight) / 2), + ); await tester.pump(); await tester.pump(); await tester.pump(scrollDuration + scrollDurationTolerance); @@ -220,76 +180,68 @@ void main() { expect(tester.getTopLeft(find.text('Item 9')).dy, -itemHeight / 2); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 9) - .itemLeadingEdge, - _screenProportion(numberOfItems: -0.5, numberOfSeparators: 0)); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 9).itemLeadingEdge, + _screenProportion(numberOfItems: -0.5, numberOfSeparators: 0), + ); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 9) - .itemTrailingEdge, - _screenProportion(numberOfItems: 0.5, numberOfSeparators: 0)); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 9).itemTrailingEdge, + _screenProportion(numberOfItems: 0.5, numberOfSeparators: 0), + ); }); testWidgets('Jump to 9 half way off top', (WidgetTester tester) async { final itemPositionsListener = ItemPositionsListener.create(); final itemScrollController = ItemScrollController(); - await setUpWidgetTest(tester, - itemPositionsListener: itemPositionsListener, - itemScrollController: itemScrollController); + await setUpWidgetTest( + tester, + itemPositionsListener: itemPositionsListener, + itemScrollController: itemScrollController, + ); - itemScrollController.jumpTo( - index: 9, alignment: -(itemHeight / screenHeight) / 2); + itemScrollController.jumpTo(index: 9, alignment: -(itemHeight / screenHeight) / 2); await tester.pump(); expect(tester.getTopLeft(find.text('Item 9')).dy, -itemHeight / 2); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 9) - .itemLeadingEdge, - _screenProportion(numberOfItems: -0.5, numberOfSeparators: 0)); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 9).itemLeadingEdge, + _screenProportion(numberOfItems: -0.5, numberOfSeparators: 0), + ); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 9) - .itemTrailingEdge, - _screenProportion(numberOfItems: 0.5, numberOfSeparators: 0)); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 9).itemTrailingEdge, + _screenProportion(numberOfItems: 0.5, numberOfSeparators: 0), + ); }); - testWidgets('List positioned with 9 at middle scroll to 16 at bottom', - (WidgetTester tester) async { + testWidgets('List positioned with 9 at middle scroll to 16 at bottom', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, - itemPositionsListener: itemPositionsListener, - initialIndex: 9, - initialAlignment: 0.5); - - unawaited(itemScrollController.scrollTo( - index: 16, duration: scrollDuration, alignment: 1)); + await setUpWidgetTest( + tester, + itemScrollController: itemScrollController, + itemPositionsListener: itemPositionsListener, + initialIndex: 9, + initialAlignment: 0.5, + ); + + unawaited(itemScrollController.scrollTo(index: 16, duration: scrollDuration, alignment: 1)); await tester.pump(); await tester.pump(); await tester.pump(scrollDuration + scrollDurationTolerance); - expect(tester.getBottomRight(find.text('Item 15')).dy, - screenHeight - separatorHeight); + expect(tester.getBottomRight(find.text('Item 15')).dy, screenHeight - separatorHeight); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 15) - .itemTrailingEdge, - 1 - _screenProportion(numberOfItems: 0, numberOfSeparators: 1)); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 15).itemTrailingEdge, + 1 - _screenProportion(numberOfItems: 0, numberOfSeparators: 1), + ); }); testWidgets('physics', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, - physics: const BouncingScrollPhysics()); + await setUpWidgetTest(tester, itemScrollController: itemScrollController, physics: const BouncingScrollPhysics()); - await tester.drag( - find.byType(ScrollablePositionedList), const Offset(0, 50)); + await tester.drag(find.byType(ScrollablePositionedList), const Offset(0, 50)); await tester.pump(const Duration(milliseconds: 200)); expect(tester.getTopLeft(find.text('Item 0')).dy, greaterThan(0)); @@ -297,14 +249,12 @@ void main() { await tester.pumpAndSettle(); expect(tester.getTopLeft(find.text('Item 0')).dy, 0); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pumpAndSettle(); itemScrollController.jumpTo(index: 0); await tester.pumpAndSettle(); - await tester.drag( - find.byType(ScrollablePositionedList), const Offset(0, 50)); + await tester.drag(find.byType(ScrollablePositionedList), const Offset(0, 50)); await tester.pump(const Duration(milliseconds: 200)); expect(tester.getTopLeft(find.text('Item 0')).dy, greaterThan(0)); @@ -316,15 +266,16 @@ void main() { testWidgets('correct index semantics', (WidgetTester tester) async { await setUpWidgetTest(tester, initialIndex: 5); - await tester.drag( - find.byType(ScrollablePositionedList), const Offset(0, itemHeight * 4)); + await tester.drag(find.byType(ScrollablePositionedList), const Offset(0, itemHeight * 4)); await tester.pumpAndSettle(); - final indexSemantics3 = tester.widget(find.ancestor( - of: find.text('Item 3'), matching: find.byType(IndexedSemantics))); + final indexSemantics3 = tester.widget( + find.ancestor(of: find.text('Item 3'), matching: find.byType(IndexedSemantics)), + ); expect(indexSemantics3.index, 3); - final indexSemantics4 = tester.widget(find.ancestor( - of: find.text('Item 4'), matching: find.byType(IndexedSemantics))); + final indexSemantics4 = tester.widget( + find.ancestor(of: find.text('Item 4'), matching: find.byType(IndexedSemantics)), + ); expect(indexSemantics4.index, 4); }); @@ -339,8 +290,7 @@ void main() { expect(find.byType(IndexedSemantics), findsNothing); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pumpAndSettle(); expect(find.byType(IndexedSemantics), findsNothing); @@ -355,16 +305,13 @@ void main() { itemScrollController: itemScrollController, ); - final customScrollView = - tester.widget(find.byType(UnboundedCustomScrollView)); + final customScrollView = tester.widget(find.byType(UnboundedCustomScrollView)); expect(customScrollView.semanticChildCount, 30); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pumpAndSettle(); - final customScrollView2 = - tester.widget(find.byType(UnboundedCustomScrollView)); + final customScrollView2 = tester.widget(find.byType(UnboundedCustomScrollView)); expect(customScrollView2.semanticChildCount, 30); }); @@ -375,16 +322,13 @@ void main() { itemScrollController: itemScrollController, ); - final customScrollView = - tester.widget(find.byType(UnboundedCustomScrollView)); + final customScrollView = tester.widget(find.byType(UnboundedCustomScrollView)); expect(customScrollView.semanticChildCount, defaultItemCount); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pumpAndSettle(); - final customScrollView2 = - tester.widget(find.byType(UnboundedCustomScrollView)); + final customScrollView2 = tester.widget(find.byType(UnboundedCustomScrollView)); expect(customScrollView2.semanticChildCount, defaultItemCount); }); @@ -397,25 +341,19 @@ void main() { ); expect(tester.getTopLeft(find.text('Item 0')), const Offset(10, 10)); - expect(tester.getTopLeft(find.text('Item 1')), - const Offset(10, itemHeight + 10 + separatorHeight)); - expect(tester.getTopRight(find.text('Item 1')), - const Offset(screenWidth - 10, itemHeight + 10 + separatorHeight)); + expect(tester.getTopLeft(find.text('Item 1')), const Offset(10, itemHeight + 10 + separatorHeight)); + expect(tester.getTopRight(find.text('Item 1')), const Offset(screenWidth - 10, itemHeight + 10 + separatorHeight)); - unawaited( - itemScrollController.scrollTo(index: 494, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 494, duration: scrollDuration)); await tester.pumpAndSettle(); - await tester.drag( - find.byType(ScrollablePositionedList), const Offset(0, -500)); + await tester.drag(find.byType(ScrollablePositionedList), const Offset(0, -500)); await tester.pumpAndSettle(); - expect(tester.getBottomRight(find.text('Item 499')), - const Offset(screenWidth - 10, screenHeight - 10)); + expect(tester.getBottomRight(find.text('Item 499')), const Offset(screenWidth - 10, screenHeight - 10)); }); - testWidgets('padding test - centered sliver not at top', - (WidgetTester tester) async { + testWidgets('padding test - centered sliver not at top', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); await setUpWidgetTest( tester, @@ -424,17 +362,15 @@ void main() { padding: const EdgeInsets.all(10), ); - await tester.drag( - find.byType(ScrollablePositionedList), const Offset(0, 200)); + await tester.drag(find.byType(ScrollablePositionedList), const Offset(0, 200)); await tester.pumpAndSettle(); expect(tester.getTopLeft(find.text('Item 0')), const Offset(10, 10)); - expect(tester.getTopLeft(find.text('Item 2')), - const Offset(10, 10 + 2 * (separatorHeight + itemHeight))); + expect(tester.getTopLeft(find.text('Item 2')), const Offset(10, 10 + 2 * (separatorHeight + itemHeight))); expect( - tester.getTopRight(find.text('Item 3')), - const Offset( - screenWidth - 10, 10 + 3 * (itemHeight + separatorHeight))); + tester.getTopRight(find.text('Item 3')), + const Offset(screenWidth - 10, 10 + 3 * (itemHeight + separatorHeight)), + ); }); testWidgets('no repaint bounderies', (WidgetTester tester) async { @@ -448,12 +384,13 @@ void main() { ); expect( - tester - .widgetList(find.descendant( - of: find.byType(ScrollablePositionedList), - matching: find.byType(RepaintBoundary))) - .length, - lessThan(5)); + tester + .widgetList( + find.descendant(of: find.byType(ScrollablePositionedList), matching: find.byType(RepaintBoundary)), + ) + .length, + lessThan(5), + ); }); testWidgets('no automatic keep alives', (WidgetTester tester) async { @@ -467,10 +404,9 @@ void main() { ); expect( - find.descendant( - of: find.byType(ScrollablePositionedList), - matching: find.byType(AutomaticKeepAlive)), - findsNothing); + find.descendant(of: find.byType(ScrollablePositionedList), matching: find.byType(AutomaticKeepAlive)), + findsNothing, + ); }); testWidgets('List can be keyed', (WidgetTester tester) async { @@ -481,8 +417,7 @@ void main() { expect(find.byKey(key), findsOneWidget); }); - testWidgets('Empty list then update to single item list', - (WidgetTester tester) async { + testWidgets('Empty list then update to single item list', (WidgetTester tester) async { tester.view.devicePixelRatio = 1.0; tester.view.physicalSize = const Size(screenWidth, screenHeight); @@ -522,8 +457,7 @@ void main() { expect(find.text('Separator 0'), findsNothing); }); - testWidgets('ItemPositions: Empty list then update to 10 items list', - (WidgetTester tester) async { + testWidgets('ItemPositions: Empty list then update to 10 items list', (WidgetTester tester) async { tester.view.devicePixelRatio = 1.0; tester.view.physicalSize = const Size(screenWidth, screenHeight); @@ -570,30 +504,16 @@ void main() { expect(find.text('Item 7'), findsNothing); expect(itemPositionsListener.itemPositions.value, isNotEmpty); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 0).itemLeadingEdge, 0); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 0) - .itemLeadingEdge, - 0); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 5) - .itemTrailingEdge, - 1 - _screenProportion(numberOfItems: 1, numberOfSeparators: 1)); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 5).itemTrailingEdge, + 1 - _screenProportion(numberOfItems: 1, numberOfSeparators: 1), + ); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 6) - .itemTrailingEdge, - 1); - expect( - itemPositionsListener.itemPositions.value - .where((position) => position.index == 7), - isEmpty); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 6).itemTrailingEdge, 1); + expect(itemPositionsListener.itemPositions.value.where((position) => position.index == 7), isEmpty); }); } -double _screenProportion( - {required double numberOfItems, required double numberOfSeparators}) => - (numberOfItems * itemHeight + numberOfSeparators * separatorHeight) / - screenHeight; +double _screenProportion({required double numberOfItems, required double numberOfSeparators}) => + (numberOfItems * itemHeight + numberOfSeparators * separatorHeight) / screenHeight; diff --git a/packages/stream_chat_flutter/test/scrollable_positioned_list/seperated_horizontal_scrollable_positioned_list_test.dart b/packages/stream_chat_flutter/test/scrollable_positioned_list/seperated_horizontal_scrollable_positioned_list_test.dart index 88b0fae0ac..b0c151666e 100644 --- a/packages/stream_chat_flutter/test/scrollable_positioned_list/seperated_horizontal_scrollable_positioned_list_test.dart +++ b/packages/stream_chat_flutter/test/scrollable_positioned_list/seperated_horizontal_scrollable_positioned_list_test.dart @@ -56,105 +56,90 @@ void main() { await setUpWidgetTest(tester, itemPositionsListener: itemPositionsListener); expect(tester.getTopLeft(find.text('Item 0')).dx, 0); - expect(tester.getBottomLeft(find.text('Item 1')).dx, - itemWidth + separatorWidth); + expect(tester.getBottomLeft(find.text('Item 1')).dx, itemWidth + separatorWidth); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 0).itemLeadingEdge, 0); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 0) - .itemLeadingEdge, - 0); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 1) - .itemLeadingEdge, - _screenProportion(numberOfItems: 1, numberOfSeparators: 1)); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 1).itemLeadingEdge, + _screenProportion(numberOfItems: 1, numberOfSeparators: 1), + ); }); testWidgets('Scroll to 2 (already on screen)', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, - itemPositionsListener: itemPositionsListener); + await setUpWidgetTest( + tester, + itemScrollController: itemScrollController, + itemPositionsListener: itemPositionsListener, + ); - unawaited( - itemScrollController.scrollTo(index: 2, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 2, duration: scrollDuration)); await tester.pump(); await tester.pump(scrollDuration); expect(find.text('Item 1'), findsNothing); expect(tester.getTopLeft(find.text('Item 2')).dx, 0); - expect( - tester.getTopLeft(find.text('Item 3')).dx, itemWidth + separatorWidth); + expect(tester.getTopLeft(find.text('Item 3')).dx, itemWidth + separatorWidth); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 2).itemLeadingEdge, 0); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 2) - .itemLeadingEdge, - 0); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 3) - .itemLeadingEdge, - _screenProportion(numberOfItems: 1, numberOfSeparators: 1)); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 3).itemLeadingEdge, + _screenProportion(numberOfItems: 1, numberOfSeparators: 1), + ); }); - testWidgets('Scroll to 100 (not already on screen)', - (WidgetTester tester) async { + testWidgets('Scroll to 100 (not already on screen)', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, - itemPositionsListener: itemPositionsListener); + await setUpWidgetTest( + tester, + itemScrollController: itemScrollController, + itemPositionsListener: itemPositionsListener, + ); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pumpAndSettle(); expect(find.text('Item 99'), findsNothing); expect(find.text('Item 100'), findsOneWidget); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 100) - .itemLeadingEdge, - 0); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 100).itemLeadingEdge, + 0, + ); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 101) - .itemLeadingEdge, - _screenProportion(numberOfItems: 1, numberOfSeparators: 1)); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 101).itemLeadingEdge, + _screenProportion(numberOfItems: 1, numberOfSeparators: 1), + ); }); testWidgets('Jump to 100', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, - itemPositionsListener: itemPositionsListener); + await setUpWidgetTest( + tester, + itemScrollController: itemScrollController, + itemPositionsListener: itemPositionsListener, + ); itemScrollController.jumpTo(index: 100); await tester.pumpAndSettle(); expect(tester.getTopLeft(find.text('Item 100')).dx, 0); - expect(tester.getTopLeft(find.text('Item 101')).dx, - itemWidth + separatorWidth); + expect(tester.getTopLeft(find.text('Item 101')).dx, itemWidth + separatorWidth); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 100) - .itemLeadingEdge, - 0); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 100).itemLeadingEdge, + 0, + ); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 101) - .itemLeadingEdge, - _screenProportion(numberOfItems: 1, numberOfSeparators: 1)); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 101).itemLeadingEdge, + _screenProportion(numberOfItems: 1, numberOfSeparators: 1), + ); }); - testWidgets('padding test - centered sliver at left', - (WidgetTester tester) async { + testWidgets('padding test - centered sliver at left', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); await setUpWidgetTest( tester, @@ -163,25 +148,22 @@ void main() { ); expect(tester.getTopLeft(find.text('Item 0')), const Offset(10, 10)); - expect(tester.getTopLeft(find.text('Item 1')), - const Offset(itemWidth + 10 + separatorWidth, 10)); - expect(tester.getBottomRight(find.text('Item 1')), - const Offset(10 + itemWidth * 2 + separatorWidth, screenHeight - 10)); + expect(tester.getTopLeft(find.text('Item 1')), const Offset(itemWidth + 10 + separatorWidth, 10)); + expect( + tester.getBottomRight(find.text('Item 1')), + const Offset(10 + itemWidth * 2 + separatorWidth, screenHeight - 10), + ); - unawaited( - itemScrollController.scrollTo(index: 494, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 494, duration: scrollDuration)); await tester.pumpAndSettle(); - await tester.drag( - find.byType(ScrollablePositionedList), const Offset(-500, 0)); + await tester.drag(find.byType(ScrollablePositionedList), const Offset(-500, 0)); await tester.pumpAndSettle(); - expect(tester.getBottomRight(find.text('Item 499')), - const Offset(screenWidth - 10, screenHeight - 10)); + expect(tester.getBottomRight(find.text('Item 499')), const Offset(screenWidth - 10, screenHeight - 10)); }); - testWidgets('padding test - centered sliver not at left', - (WidgetTester tester) async { + testWidgets('padding test - centered sliver not at left', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); final itemPositionsListener = ItemPositionsListener.create(); await setUpWidgetTest( @@ -192,27 +174,19 @@ void main() { padding: const EdgeInsets.all(10), ); - await tester.drag( - find.byType(ScrollablePositionedList), const Offset(300, 0)); + await tester.drag(find.byType(ScrollablePositionedList), const Offset(300, 0)); await tester.pumpAndSettle(); expect(tester.getTopLeft(find.text('Item 0')), const Offset(10, 10)); - expect(tester.getTopLeft(find.text('Item 2')), - const Offset(10 + 2 * (itemWidth + separatorWidth), 10)); - expect(tester.getTopLeft(find.text('Item 3')), - const Offset(10 + 3 * (itemWidth + separatorWidth), 10)); + expect(tester.getTopLeft(find.text('Item 2')), const Offset(10 + 2 * (itemWidth + separatorWidth), 10)); + expect(tester.getTopLeft(find.text('Item 3')), const Offset(10 + 3 * (itemWidth + separatorWidth), 10)); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 2) - .itemLeadingEdge, - closeTo( - 10 / screenWidth + 2 * ((itemWidth + separatorWidth) / screenWidth), - tolerance)); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 2).itemLeadingEdge, + closeTo(10 / screenWidth + 2 * ((itemWidth + separatorWidth) / screenWidth), tolerance), + ); }); } -double _screenProportion( - {required double numberOfItems, required double numberOfSeparators}) => - (numberOfItems * itemWidth + numberOfSeparators * separatorWidth) / - screenHeight; +double _screenProportion({required double numberOfItems, required double numberOfSeparators}) => + (numberOfItems * itemWidth + numberOfSeparators * separatorWidth) / screenHeight; diff --git a/packages/stream_chat_flutter/test/scrollable_positioned_list/shrink_wrap_position_list_test.dart b/packages/stream_chat_flutter/test/scrollable_positioned_list/shrink_wrap_position_list_test.dart index 8cf4303c4f..2c13c0d601 100644 --- a/packages/stream_chat_flutter/test/scrollable_positioned_list/shrink_wrap_position_list_test.dart +++ b/packages/stream_chat_flutter/test/scrollable_positioned_list/shrink_wrap_position_list_test.dart @@ -32,28 +32,28 @@ void main() { MaterialApp( // Use flex layout to ensure that the minimum height is not limited to // screenHeight. - home: Column(children: [ - // Use Constrained to make max height not more than screenHeight - ConstrainedBox( - constraints: const BoxConstraints( - maxHeight: screenHeight, maxWidth: screenWidth), - child: PositionedList( - key: key, - itemCount: itemCount, - positionedIndex: topItem, - alignment: anchor, - controller: scrollController, - itemBuilder: (context, index) => SizedBox( - height: itemHeight, - child: Text('Item $index'), + home: Column( + children: [ + // Use Constrained to make max height not more than screenHeight + ConstrainedBox( + constraints: const BoxConstraints(maxHeight: screenHeight, maxWidth: screenWidth), + child: PositionedList( + key: key, + itemCount: itemCount, + positionedIndex: topItem, + alignment: anchor, + controller: scrollController, + itemBuilder: (context, index) => SizedBox( + height: itemHeight, + child: Text('Item $index'), + ), + itemPositionsNotifier: itemPositionsNotifier as ItemPositionsNotifier, + shrinkWrap: true, + reverse: reverse, ), - itemPositionsNotifier: - itemPositionsNotifier as ItemPositionsNotifier, - shrinkWrap: true, - reverse: reverse, ), - ), - ]), + ], + ), ), ); } @@ -64,28 +64,21 @@ void main() { await setUpWidgetTest(tester, itemCount: itemCount, key: key); await tester.pump(); - expect( - tester.getBottomRight(find.text('Item 4')).dy, itemHeight * itemCount); + expect(tester.getBottomRight(find.text('Item 4')).dy, itemHeight * itemCount); expect(find.text('Item 4'), findsOneWidget); expect(find.text('Item 5'), findsNothing); final positionList = find.byKey(key); expect(tester.getBottomRight(positionList).dy, itemHeight * itemCount); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 0).itemLeadingEdge, 0); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 0) - .itemLeadingEdge, - 0); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 4) - .itemTrailingEdge, - 1.0); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 4).itemTrailingEdge, + 1.0, + ); }); - testWidgets('List positioned with 0 at top and shrink wrap', - (WidgetTester tester) async { + testWidgets('List positioned with 0 at top and shrink wrap', (WidgetTester tester) async { await setUpWidgetTest(tester); await tester.pump(); @@ -93,30 +86,16 @@ void main() { expect(find.text('Item 9'), findsOneWidget); expect(find.text('Item 10'), findsNothing); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 0).itemLeadingEdge, 0); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 9).itemTrailingEdge, 1); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 10).itemLeadingEdge, 1); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 0) - .itemLeadingEdge, - 0); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 9) - .itemTrailingEdge, - 1); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 10) - .itemLeadingEdge, - 1); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 10) - .itemTrailingEdge, - 11 / 10); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 10).itemTrailingEdge, + 11 / 10, + ); }); - testWidgets('List positioned with 5 at top and shrink wrap', - (WidgetTester tester) async { + testWidgets('List positioned with 5 at top and shrink wrap', (WidgetTester tester) async { await setUpWidgetTest(tester, topItem: 5); await tester.pump(); @@ -126,29 +105,18 @@ void main() { expect(find.text('Item 15'), findsNothing); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 4) - .itemLeadingEdge, - -1 / 10); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 4) - .itemTrailingEdge, - 0); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 5) - .itemLeadingEdge, - 0); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 4).itemLeadingEdge, + -1 / 10, + ); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 4).itemTrailingEdge, 0); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 5).itemLeadingEdge, 0); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 14) - .itemTrailingEdge, - 1); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 14).itemTrailingEdge, + 1, + ); }); - testWidgets('List positioned with 20 at bottom and shrink wrap', - (WidgetTester tester) async { + testWidgets('List positioned with 20 at bottom and shrink wrap', (WidgetTester tester) async { await setUpWidgetTest(tester, topItem: 20, anchor: 1); await tester.pump(); @@ -156,69 +124,50 @@ void main() { expect(find.text('Item 19'), findsOneWidget); expect(find.text('Item 10'), findsOneWidget); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 10).itemLeadingEdge, 0); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 10) - .itemLeadingEdge, - 0); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 19) - .itemLeadingEdge, - 9 / 10); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 19) - .itemTrailingEdge, - 1); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 19).itemLeadingEdge, + 9 / 10, + ); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 20) - .itemLeadingEdge, - 1); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 19).itemTrailingEdge, + 1, + ); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 20).itemLeadingEdge, 1); }); - testWidgets('List positioned with 20 at halfway and shrink wrap', - (WidgetTester tester) async { + testWidgets('List positioned with 20 at halfway and shrink wrap', (WidgetTester tester) async { await setUpWidgetTest(tester, topItem: 20, anchor: 0.5); await tester.pump(); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 20) - .itemLeadingEdge, - 0.5); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 20).itemLeadingEdge, + 0.5, + ); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 20) - .itemTrailingEdge, - 0.5 + itemHeight / screenHeight); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 20).itemTrailingEdge, + 0.5 + itemHeight / screenHeight, + ); }); - testWidgets('List positioned with 20 half off top of screen and shrink wrap', - (WidgetTester tester) async { - await setUpWidgetTest(tester, - topItem: 20, anchor: -(itemHeight / screenHeight) / 2); + testWidgets('List positioned with 20 half off top of screen and shrink wrap', (WidgetTester tester) async { + await setUpWidgetTest(tester, topItem: 20, anchor: -(itemHeight / screenHeight) / 2); await tester.pump(); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 20) - .itemLeadingEdge, - -(itemHeight / screenHeight) / 2); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 20).itemLeadingEdge, + -(itemHeight / screenHeight) / 2, + ); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 20) - .itemTrailingEdge, - (itemHeight / screenHeight) / 2); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 20).itemTrailingEdge, + (itemHeight / screenHeight) / 2, + ); }); - testWidgets('List positioned with 5 at top then scroll up 2 and shrink wrap', - (WidgetTester tester) async { + testWidgets('List positioned with 5 at top then scroll up 2 and shrink wrap', (WidgetTester tester) async { await setUpWidgetTest(tester, topItem: 5); - await tester.drag( - find.byType(PositionedList), const Offset(0, itemHeight * 2)); + await tester.drag(find.byType(PositionedList), const Offset(0, itemHeight * 2)); await tester.pump(); expect(find.text('Item 2'), findsNothing); @@ -227,45 +176,33 @@ void main() { expect(find.text('Item 13'), findsNothing); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 2) - .itemLeadingEdge, - -1 / 10); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 3) - .itemLeadingEdge, - 0); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 2).itemLeadingEdge, + -1 / 10, + ); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 3).itemLeadingEdge, 0); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 12) - .itemTrailingEdge, - 1); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 12).itemTrailingEdge, + 1, + ); }); - testWidgets( - 'List positioned with 5 at top then scroll down 1/2 and shrink wrap', - (WidgetTester tester) async { + testWidgets('List positioned with 5 at top then scroll down 1/2 and shrink wrap', (WidgetTester tester) async { await setUpWidgetTest(tester, topItem: 5); - await tester.drag( - find.byType(PositionedList), const Offset(0, -1 / 2 * itemHeight)); + await tester.drag(find.byType(PositionedList), const Offset(0, -1 / 2 * itemHeight)); await tester.pump(); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 5) - .itemTrailingEdge, - 1 / 20); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 5).itemTrailingEdge, + 1 / 20, + ); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 14) - .itemLeadingEdge, - 17 / 20); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 14).itemLeadingEdge, + 17 / 20, + ); }); - testWidgets('List positioned with 0 at top scroll up 5 and shrink wrap', - (WidgetTester tester) async { + testWidgets('List positioned with 0 at top scroll up 5 and shrink wrap', (WidgetTester tester) async { final scrollController = ScrollController(); await setUpWidgetTest(tester, scrollController: scrollController); await tester.pump(); @@ -279,24 +216,18 @@ void main() { expect(find.text('Item 14'), findsOneWidget); expect(find.text('Item 15'), findsNothing); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 5).itemLeadingEdge, 0); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 5) - .itemLeadingEdge, - 0); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 4) - .itemLeadingEdge, - -1 / 10); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 4).itemLeadingEdge, + -1 / 10, + ); }); testWidgets( '''List positioned with 5 at top then scroll up 2 programatically and shrink wrap''', (WidgetTester tester) async { final scrollController = ScrollController(); - await setUpWidgetTest(tester, - topItem: 5, scrollController: scrollController); + await setUpWidgetTest(tester, topItem: 5, scrollController: scrollController); scrollController.jumpTo(-2 * itemHeight); await tester.pump(); @@ -307,20 +238,17 @@ void main() { expect(find.text('Item 13'), findsNothing); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 2) - .itemLeadingEdge, - -1 / 10); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 2).itemLeadingEdge, + -1 / 10, + ); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 3) - .itemLeadingEdge, - 0); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 3).itemLeadingEdge, + 0, + ); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 12) - .itemTrailingEdge, - 1); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 12).itemTrailingEdge, + 1, + ); }, ); @@ -328,93 +256,70 @@ void main() { '''List positioned with 5 at top then scroll down 20 programatically and shrink wrap''', (WidgetTester tester) async { final scrollController = ScrollController(); - await setUpWidgetTest(tester, - topItem: 5, scrollController: scrollController); + await setUpWidgetTest(tester, topItem: 5, scrollController: scrollController); scrollController.jumpTo(itemHeight * 20); await tester.pump(); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 23) - .itemLeadingEdge, - -2 / 10); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 23).itemLeadingEdge, + -2 / 10, + ); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 24) - .itemLeadingEdge, - -1 / 10); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 24).itemLeadingEdge, + -1 / 10, + ); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 25) - .itemLeadingEdge, - 0); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 25).itemLeadingEdge, + 0, + ); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 4) - .itemLeadingEdge, - -21 / 10); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 4).itemLeadingEdge, + -21 / 10, + ); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 5) - .itemLeadingEdge, - -20 / 10); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 5).itemLeadingEdge, + -20 / 10, + ); }, ); - testWidgets( - 'List positioned with 5 at top and initial scroll offset and shrink wrap', - (WidgetTester tester) async { - final scrollController = - ScrollController(initialScrollOffset: -2 * itemHeight); - await setUpWidgetTest(tester, - topItem: 5, scrollController: scrollController); + testWidgets('List positioned with 5 at top and initial scroll offset and shrink wrap', (WidgetTester tester) async { + final scrollController = ScrollController(initialScrollOffset: -2 * itemHeight); + await setUpWidgetTest(tester, topItem: 5, scrollController: scrollController); expect(find.text('Item 2'), findsNothing); expect(find.text('Item 3'), findsOneWidget); expect(find.text('Item 12'), findsOneWidget); expect(find.text('Item 13'), findsNothing); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 3).itemLeadingEdge, 0); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 3) - .itemLeadingEdge, - 0); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 12) - .itemTrailingEdge, - 1); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 12).itemTrailingEdge, + 1, + ); }); - testWidgets('short List with reverse and shrink wrap', - (WidgetTester tester) async { + testWidgets('short List with reverse and shrink wrap', (WidgetTester tester) async { const itemCount = 5; const key = Key('short_list'); - await setUpWidgetTest(tester, - itemCount: itemCount, key: key, reverse: true); + await setUpWidgetTest(tester, itemCount: itemCount, key: key, reverse: true); await tester.pump(); expect(find.text('Item 4'), findsOneWidget); expect(find.text('Item 5'), findsNothing); - expect( - tester.getBottomRight(find.text('Item 0')).dy, itemHeight * itemCount); + expect(tester.getBottomRight(find.text('Item 0')).dy, itemHeight * itemCount); expect(tester.getTopLeft(find.text('Item 4')).dy, 0); final positionList = find.byKey(key); expect(tester.getBottomRight(positionList).dy, itemHeight * itemCount); expect(tester.getTopLeft(positionList).dy, 0); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 0).itemLeadingEdge, 0); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 0) - .itemLeadingEdge, - 0); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 4) - .itemTrailingEdge, - 1.0); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 4).itemTrailingEdge, + 1.0, + ); }); testWidgets('test nested positioned list', (WidgetTester tester) async { @@ -432,13 +337,14 @@ void main() { itemBuilder: (context, index) { if (index == 0) { return PositionedList( - key: key, - itemCount: itemCount, - shrinkWrap: true, - itemBuilder: (context, idx) => SizedBox( - height: itemHeight, - child: Text('Item $idx'), - )); + key: key, + itemCount: itemCount, + shrinkWrap: true, + itemBuilder: (context, idx) => SizedBox( + height: itemHeight, + child: Text('Item $idx'), + ), + ); } else { return SizedBox( height: itemHeight, @@ -461,15 +367,10 @@ void main() { expect(tester.getBottomRight(positionList).dy, itemHeight * itemCount); expect(tester.getTopLeft(positionList).dy, 0); + expect(itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 0).itemLeadingEdge, 0); expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 0) - .itemLeadingEdge, - 0); - expect( - itemPositionsNotifier.itemPositions.value - .firstWhere((position) => position.index == 0) - .itemTrailingEdge, - 5.0); + itemPositionsNotifier.itemPositions.value.firstWhere((position) => position.index == 0).itemTrailingEdge, + 5.0, + ); }); } diff --git a/packages/stream_chat_flutter/test/scrollable_positioned_list/shrink_wrap_scrollable_position_list_test.dart b/packages/stream_chat_flutter/test/scrollable_positioned_list/shrink_wrap_scrollable_position_list_test.dart index 37830e00c9..7b0655b150 100644 --- a/packages/stream_chat_flutter/test/scrollable_positioned_list/shrink_wrap_scrollable_position_list_test.dart +++ b/packages/stream_chat_flutter/test/scrollable_positioned_list/shrink_wrap_scrollable_position_list_test.dart @@ -29,31 +29,31 @@ void main() { MaterialApp( // Use flex layout to ensure that the minimum height is not limited to // screenHeight. - home: Column(children: [ - // Use Constrained to make max height not more than screenHeight - ConstrainedBox( - constraints: const BoxConstraints( - maxHeight: screenHeight, maxWidth: screenWidth), - child: ScrollablePositionedList.builder( - itemCount: itemCount, - initialScrollIndex: initialIndex, - itemScrollController: itemScrollController, - itemBuilder: (context, index) => SizedBox( - height: itemHeight, - child: Text('Item $index'), + home: Column( + children: [ + // Use Constrained to make max height not more than screenHeight + ConstrainedBox( + constraints: const BoxConstraints(maxHeight: screenHeight, maxWidth: screenWidth), + child: ScrollablePositionedList.builder( + itemCount: itemCount, + initialScrollIndex: initialIndex, + itemScrollController: itemScrollController, + itemBuilder: (context, index) => SizedBox( + height: itemHeight, + child: Text('Item $index'), + ), + itemPositionsListener: itemPositionsListener, + shrinkWrap: true, + padding: padding, ), - itemPositionsListener: itemPositionsListener, - shrinkWrap: true, - padding: padding, ), - ), - ]), + ], + ), ), ); } - testWidgets('List positioned with 0 at top and shrink wrap', - (WidgetTester tester) async { + testWidgets('List positioned with 0 at top and shrink wrap', (WidgetTester tester) async { final itemPositionsListener = ItemPositionsListener.create(); await setUpWidgetTest(tester, itemPositionsListener: itemPositionsListener); @@ -61,123 +61,95 @@ void main() { expect(tester.getBottomRight(find.text('Item 9')).dy, screenHeight); expect(find.text('Item 10'), findsNothing); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 0) - .itemLeadingEdge, - 0); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 9) - .itemTrailingEdge, - 1); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 0).itemLeadingEdge, 0); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 9).itemTrailingEdge, 1); }); - testWidgets('Scroll to 1 then 2 (both already on screen) with shrink wrap', - (WidgetTester tester) async { + testWidgets('Scroll to 1 then 2 (both already on screen) with shrink wrap', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, - itemPositionsListener: itemPositionsListener); + await setUpWidgetTest( + tester, + itemScrollController: itemScrollController, + itemPositionsListener: itemPositionsListener, + ); - unawaited( - itemScrollController.scrollTo(index: 1, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 1, duration: scrollDuration)); await tester.pump(); await tester.pump(scrollDuration); expect(find.text('Item 0'), findsNothing); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 1) - .itemLeadingEdge, - 0); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 1).itemLeadingEdge, 0); expect(tester.getTopLeft(find.text('Item 1')).dy, 0); - unawaited( - itemScrollController.scrollTo(index: 2, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 2, duration: scrollDuration)); await tester.pump(); await tester.pump(scrollDuration); expect(find.text('Item 1'), findsNothing); expect(tester.getTopLeft(find.text('Item 2')).dy, 0); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 2).itemLeadingEdge, 0); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 2) - .itemLeadingEdge, - 0); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 11) - .itemTrailingEdge, - 1); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 11).itemTrailingEdge, + 1, + ); }); - testWidgets( - 'Scroll to 5 (already on screen) and then back to 0 with shrink wrap', - (WidgetTester tester) async { + testWidgets('Scroll to 5 (already on screen) and then back to 0 with shrink wrap', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, - itemPositionsListener: itemPositionsListener); + await setUpWidgetTest( + tester, + itemScrollController: itemScrollController, + itemPositionsListener: itemPositionsListener, + ); - unawaited( - itemScrollController.scrollTo(index: 5, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 5, duration: scrollDuration)); await tester.pumpAndSettle(); - unawaited( - itemScrollController.scrollTo(index: 0, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 0, duration: scrollDuration)); await tester.pumpAndSettle(); expect(find.text('Item 0'), findsOneWidget); expect(find.text('Item 9'), findsOneWidget); expect(find.text('Item 10'), findsNothing); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 0) - .itemLeadingEdge, - 0); - expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 9) - .itemTrailingEdge, - 1); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 0).itemLeadingEdge, 0); + expect(itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 9).itemTrailingEdge, 1); }); - testWidgets('Scroll to 100 (not already on screen) with shrink wrap', - (WidgetTester tester) async { + testWidgets('Scroll to 100 (not already on screen) with shrink wrap', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, - itemPositionsListener: itemPositionsListener); + await setUpWidgetTest( + tester, + itemScrollController: itemScrollController, + itemPositionsListener: itemPositionsListener, + ); - unawaited( - itemScrollController.scrollTo(index: 100, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 100, duration: scrollDuration)); await tester.pumpAndSettle(); expect(find.text('Item 99'), findsNothing); expect(find.text('Item 100'), findsOneWidget); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 100) - .itemLeadingEdge, - 0); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 100).itemLeadingEdge, + 0, + ); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 109) - .itemTrailingEdge, - 1); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 109).itemTrailingEdge, + 1, + ); }); testWidgets('Jump to 100 with shrink wrap', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); final itemPositionsListener = ItemPositionsListener.create(); - await setUpWidgetTest(tester, - itemScrollController: itemScrollController, - itemPositionsListener: itemPositionsListener); + await setUpWidgetTest( + tester, + itemScrollController: itemScrollController, + itemPositionsListener: itemPositionsListener, + ); itemScrollController.jumpTo(index: 100); await tester.pumpAndSettle(); @@ -186,19 +158,16 @@ void main() { expect(tester.getBottomRight(find.text('Item 109')).dy, screenHeight); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 100) - .itemLeadingEdge, - 0); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 100).itemLeadingEdge, + 0, + ); expect( - itemPositionsListener.itemPositions.value - .firstWhere((position) => position.index == 109) - .itemTrailingEdge, - 1); + itemPositionsListener.itemPositions.value.firstWhere((position) => position.index == 109).itemTrailingEdge, + 1, + ); }); - testWidgets('padding test - centered sliver at bottom with shrink wrap', - (WidgetTester tester) async { + testWidgets('padding test - centered sliver at bottom with shrink wrap', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); await setUpWidgetTest( tester, @@ -207,25 +176,19 @@ void main() { ); expect(tester.getTopLeft(find.text('Item 0')), const Offset(10, 10)); - expect(tester.getTopLeft(find.text('Item 1')), - const Offset(10, itemHeight + 10)); - expect(tester.getBottomRight(find.text('Item 1')), - const Offset(screenWidth - 10, 10 + itemHeight * 2)); + expect(tester.getTopLeft(find.text('Item 1')), const Offset(10, itemHeight + 10)); + expect(tester.getBottomRight(find.text('Item 1')), const Offset(screenWidth - 10, 10 + itemHeight * 2)); - unawaited( - itemScrollController.scrollTo(index: 490, duration: scrollDuration)); + unawaited(itemScrollController.scrollTo(index: 490, duration: scrollDuration)); await tester.pumpAndSettle(); - await tester.drag( - find.byType(ScrollablePositionedList), const Offset(0, -100)); + await tester.drag(find.byType(ScrollablePositionedList), const Offset(0, -100)); await tester.pumpAndSettle(); - expect(tester.getTopLeft(find.text('Item 499')), - const Offset(10, screenHeight - itemHeight - 10)); + expect(tester.getTopLeft(find.text('Item 499')), const Offset(10, screenHeight - itemHeight - 10)); }); - testWidgets('padding test - centered sliver not at bottom', - (WidgetTester tester) async { + testWidgets('padding test - centered sliver not at bottom', (WidgetTester tester) async { final itemScrollController = ItemScrollController(); await setUpWidgetTest( tester, @@ -234,14 +197,11 @@ void main() { padding: const EdgeInsets.all(10), ); - await tester.drag( - find.byType(ScrollablePositionedList), const Offset(0, 200)); + await tester.drag(find.byType(ScrollablePositionedList), const Offset(0, 200)); await tester.pumpAndSettle(); expect(tester.getTopLeft(find.text('Item 0')), const Offset(10, 10)); - expect(tester.getTopLeft(find.text('Item 2')), - const Offset(10, 10 + itemHeight * 2)); - expect(tester.getTopLeft(find.text('Item 3')), - const Offset(10, 10 + itemHeight * 3)); + expect(tester.getTopLeft(find.text('Item 2')), const Offset(10, 10 + itemHeight * 2)); + expect(tester.getTopLeft(find.text('Item 3')), const Offset(10, 10 + itemHeight * 3)); }); } diff --git a/packages/stream_chat_flutter/test/src/attachment/attachment_handler_test.dart b/packages/stream_chat_flutter/test/src/attachment/attachment_handler_test.dart index 6765434452..b7f449e271 100644 --- a/packages/stream_chat_flutter/test/src/attachment/attachment_handler_test.dart +++ b/packages/stream_chat_flutter/test/src/attachment/attachment_handler_test.dart @@ -17,8 +17,7 @@ void main() { final attachmentHandler = MockAttachmentHandler(); - when(() => attachmentHandler.downloadAttachment(attachment)) - .thenAnswer((invocation) async => 'filePath'); + when(() => attachmentHandler.downloadAttachment(attachment)).thenAnswer((invocation) async => 'filePath'); expect( await attachmentHandler.downloadAttachment(attachment), @@ -31,15 +30,13 @@ void main() { title: 'test giphy attachment', type: 'giphy', extraData: const { - 'original': - 'https://giphy.com/gifs/nrkp3-dance-happy-3o7TKnCdBx5cMg0qti', + 'original': 'https://giphy.com/gifs/nrkp3-dance-happy-3o7TKnCdBx5cMg0qti', }, ); final attachmentHandler = MockAttachmentHandler(); - when(() => attachmentHandler.downloadAttachment(attachment)) - .thenAnswer((invocation) async => 'filePath'); + when(() => attachmentHandler.downloadAttachment(attachment)).thenAnswer((invocation) async => 'filePath'); expect( await attachmentHandler.downloadAttachment(attachment), @@ -56,8 +53,7 @@ void main() { final attachmentHandler = MockAttachmentHandler(); - when(() => attachmentHandler.downloadAttachment(attachment)) - .thenAnswer((invocation) async => 'filePath'); + when(() => attachmentHandler.downloadAttachment(attachment)).thenAnswer((invocation) async => 'filePath'); expect( await attachmentHandler.downloadAttachment(attachment), diff --git a/packages/stream_chat_flutter/test/src/attachment/attachment_upload_state_builder_test.dart b/packages/stream_chat_flutter/test/src/attachment/attachment_upload_state_builder_test.dart index f87bcdedf5..104f254be7 100644 --- a/packages/stream_chat_flutter/test/src/attachment/attachment_upload_state_builder_test.dart +++ b/packages/stream_chat_flutter/test/src/attachment/attachment_upload_state_builder_test.dart @@ -5,9 +5,7 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import '../mocks.dart'; void main() { - testWidgets( - 'AttachmentUploadStateBuilder returns Offstage when message is sent', - (tester) async { + testWidgets('AttachmentUploadStateBuilder returns Offstage when message is sent', (tester) async { await tester.pumpWidget( MaterialApp( home: Scaffold( diff --git a/packages/stream_chat_flutter/test/src/attachment/builder/voice_recording_attachment_playlist_builder.dart b/packages/stream_chat_flutter/test/src/attachment/builder/voice_recording_attachment_playlist_builder.dart index eef7c8279e..5e55598a3c 100644 --- a/packages/stream_chat_flutter/test/src/attachment/builder/voice_recording_attachment_playlist_builder.dart +++ b/packages/stream_chat_flutter/test/src/attachment/builder/voice_recording_attachment_playlist_builder.dart @@ -74,13 +74,15 @@ Widget _wrapWithStreamChatApp( return MaterialApp( home: StreamChatTheme( data: StreamChatThemeData(brightness: brightness), - child: Builder(builder: (context) { - final theme = StreamChatTheme.of(context); - return Scaffold( - backgroundColor: theme.colorTheme.appBg, - body: Center(child: widget), - ); - }), + child: Builder( + builder: (context) { + final theme = StreamChatTheme.of(context); + return Scaffold( + backgroundColor: theme.colorTheme.appBg, + body: Center(child: widget), + ); + }, + ), ), ); } diff --git a/packages/stream_chat_flutter/test/src/attachment/file_attachment_test.dart b/packages/stream_chat_flutter/test/src/attachment/file_attachment_test.dart index 578eabe6fa..d3105d2457 100644 --- a/packages/stream_chat_flutter/test/src/attachment/file_attachment_test.dart +++ b/packages/stream_chat_flutter/test/src/attachment/file_attachment_test.dart @@ -25,10 +25,12 @@ void main() { channel: channel, child: SizedBox( child: StreamFileAttachment( - constraints: BoxConstraints.tight(const Size( - 300, - 300, - )), + constraints: BoxConstraints.tight( + const Size( + 300, + 300, + ), + ), message: Message(), file: Attachment( type: 'file', diff --git a/packages/stream_chat_flutter/test/src/attachment/gallery_attachment_test.dart b/packages/stream_chat_flutter/test/src/attachment/gallery_attachment_test.dart index 8b08044a00..d14591c69e 100644 --- a/packages/stream_chat_flutter/test/src/attachment/gallery_attachment_test.dart +++ b/packages/stream_chat_flutter/test/src/attachment/gallery_attachment_test.dart @@ -22,8 +22,7 @@ void main() { Attachment( type: 'image', title: 'example.png', - imageUrl: - 'https://logowik.com/content/uploads/images/flutter5786.jpg', + imageUrl: 'https://logowik.com/content/uploads/images/flutter5786.jpg', extraData: const { 'mime_type': 'png', }, @@ -31,8 +30,7 @@ void main() { Attachment( type: 'image', title: 'example.png', - imageUrl: - 'https://logowik.com/content/uploads/images/flutter5786.jpg', + imageUrl: 'https://logowik.com/content/uploads/images/flutter5786.jpg', extraData: const { 'mime_type': 'png', }, @@ -47,10 +45,12 @@ void main() { channel: channel, child: SizedBox( child: StreamGalleryAttachment( - constraints: BoxConstraints.tight(const Size( - 300, - 300, - )), + constraints: BoxConstraints.tight( + const Size( + 300, + 300, + ), + ), message: Message(), attachments: attachments, itemBuilder: (context, index) { diff --git a/packages/stream_chat_flutter/test/src/attachment/giphy_attachment_test.dart b/packages/stream_chat_flutter/test/src/attachment/giphy_attachment_test.dart index 42a604c791..a61b47e080 100644 --- a/packages/stream_chat_flutter/test/src/attachment/giphy_attachment_test.dart +++ b/packages/stream_chat_flutter/test/src/attachment/giphy_attachment_test.dart @@ -25,16 +25,17 @@ void main() { channel: channel, child: SizedBox( child: StreamGiphyAttachment( - constraints: BoxConstraints.tight(const Size( - 300, - 300, - )), + constraints: BoxConstraints.tight( + const Size( + 300, + 300, + ), + ), message: Message(), giphy: Attachment( type: 'giphy', title: 'example.gif', - imageUrl: - 'https://media.giphy.com/media/35H0pwQNaO2iLTnnBf/giphy.gif', + imageUrl: 'https://media.giphy.com/media/35H0pwQNaO2iLTnnBf/giphy.gif', extraData: const { 'mime_type': 'gif', }, diff --git a/packages/stream_chat_flutter/test/src/attachment/goldens/ci/stream_voice_recording_attachment_idle_dark.png b/packages/stream_chat_flutter/test/src/attachment/goldens/ci/stream_voice_recording_attachment_idle_dark.png index 5b8b181963..3a811f6109 100644 Binary files a/packages/stream_chat_flutter/test/src/attachment/goldens/ci/stream_voice_recording_attachment_idle_dark.png and b/packages/stream_chat_flutter/test/src/attachment/goldens/ci/stream_voice_recording_attachment_idle_dark.png differ diff --git a/packages/stream_chat_flutter/test/src/attachment/goldens/ci/stream_voice_recording_attachment_idle_light.png b/packages/stream_chat_flutter/test/src/attachment/goldens/ci/stream_voice_recording_attachment_idle_light.png index 3388c25411..e751233e68 100644 Binary files a/packages/stream_chat_flutter/test/src/attachment/goldens/ci/stream_voice_recording_attachment_idle_light.png and b/packages/stream_chat_flutter/test/src/attachment/goldens/ci/stream_voice_recording_attachment_idle_light.png differ diff --git a/packages/stream_chat_flutter/test/src/attachment/goldens/ci/stream_voice_recording_attachment_playing_dark.png b/packages/stream_chat_flutter/test/src/attachment/goldens/ci/stream_voice_recording_attachment_playing_dark.png index f1b39c3422..b49426e1aa 100644 Binary files a/packages/stream_chat_flutter/test/src/attachment/goldens/ci/stream_voice_recording_attachment_playing_dark.png and b/packages/stream_chat_flutter/test/src/attachment/goldens/ci/stream_voice_recording_attachment_playing_dark.png differ diff --git a/packages/stream_chat_flutter/test/src/attachment/goldens/ci/stream_voice_recording_attachment_playing_light.png b/packages/stream_chat_flutter/test/src/attachment/goldens/ci/stream_voice_recording_attachment_playing_light.png index 142b228787..635539b0f5 100644 Binary files a/packages/stream_chat_flutter/test/src/attachment/goldens/ci/stream_voice_recording_attachment_playing_light.png and b/packages/stream_chat_flutter/test/src/attachment/goldens/ci/stream_voice_recording_attachment_playing_light.png differ diff --git a/packages/stream_chat_flutter/test/src/attachment/goldens/ci/stream_voice_recording_attachment_playlist_dark.png b/packages/stream_chat_flutter/test/src/attachment/goldens/ci/stream_voice_recording_attachment_playlist_dark.png index 3825488b27..d06d94aab5 100644 Binary files a/packages/stream_chat_flutter/test/src/attachment/goldens/ci/stream_voice_recording_attachment_playlist_dark.png and b/packages/stream_chat_flutter/test/src/attachment/goldens/ci/stream_voice_recording_attachment_playlist_dark.png differ diff --git a/packages/stream_chat_flutter/test/src/attachment/goldens/ci/stream_voice_recording_attachment_playlist_light.png b/packages/stream_chat_flutter/test/src/attachment/goldens/ci/stream_voice_recording_attachment_playlist_light.png index c964b59887..e3a71314eb 100644 Binary files a/packages/stream_chat_flutter/test/src/attachment/goldens/ci/stream_voice_recording_attachment_playlist_light.png and b/packages/stream_chat_flutter/test/src/attachment/goldens/ci/stream_voice_recording_attachment_playlist_light.png differ diff --git a/packages/stream_chat_flutter/test/src/attachment/image_attachment_test.dart b/packages/stream_chat_flutter/test/src/attachment/image_attachment_test.dart index 1bab6234d1..d30cce0941 100644 --- a/packages/stream_chat_flutter/test/src/attachment/image_attachment_test.dart +++ b/packages/stream_chat_flutter/test/src/attachment/image_attachment_test.dart @@ -26,16 +26,17 @@ void main() { channel: channel, child: SizedBox( child: StreamImageAttachment( - constraints: BoxConstraints.tight(const Size( - 300, - 300, - )), + constraints: BoxConstraints.tight( + const Size( + 300, + 300, + ), + ), message: Message(), image: Attachment( type: 'image', title: 'example.png', - imageUrl: - 'https://logowik.com/content/uploads/images/flutter5786.jpg', + imageUrl: 'https://logowik.com/content/uploads/images/flutter5786.jpg', extraData: const { 'mime_type': 'png', }, diff --git a/packages/stream_chat_flutter/test/src/attachment/voice_recording_attachment_playlist_test.dart b/packages/stream_chat_flutter/test/src/attachment/voice_recording_attachment_playlist_test.dart index fc3ffd107a..748f383028 100644 --- a/packages/stream_chat_flutter/test/src/attachment/voice_recording_attachment_playlist_test.dart +++ b/packages/stream_chat_flutter/test/src/attachment/voice_recording_attachment_playlist_test.dart @@ -253,15 +253,26 @@ Widget _wrapWithStreamChatApp( Brightness? brightness, }) { return MaterialApp( + theme: ThemeData( + brightness: .light, + extensions: [StreamTheme.light()], + ), + darkTheme: ThemeData( + brightness: .dark, + extensions: [StreamTheme.dark()], + ), + themeMode: brightness == Brightness.light ? ThemeMode.light : ThemeMode.dark, home: StreamChatTheme( data: StreamChatThemeData(brightness: brightness), - child: Builder(builder: (context) { - final theme = StreamChatTheme.of(context); - return Scaffold( - backgroundColor: theme.colorTheme.appBg, - body: widget, - ); - }), + child: Builder( + builder: (context) { + final theme = StreamChatTheme.of(context); + return Scaffold( + backgroundColor: theme.colorTheme.appBg, + body: widget, + ); + }, + ), ), ); } diff --git a/packages/stream_chat_flutter/test/src/attachment/voice_recording_attachment_test.dart b/packages/stream_chat_flutter/test/src/attachment/voice_recording_attachment_test.dart index 529e14f133..e511c030e5 100644 --- a/packages/stream_chat_flutter/test/src/attachment/voice_recording_attachment_test.dart +++ b/packages/stream_chat_flutter/test/src/attachment/voice_recording_attachment_test.dart @@ -3,11 +3,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; import 'package:stream_chat_flutter/src/audio/audio_playlist_state.dart'; -import 'package:stream_chat_flutter/src/misc/audio_waveform.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:svg_icon_widget/svg_icon_widget.dart'; import '../mocks.dart'; -import '../utils/finders.dart'; void main() { group( @@ -35,8 +34,6 @@ void main() { // Verify key components are present expect(find.byType(AudioControlButton), findsOneWidget); expect(find.byType(StreamAudioWaveformSlider), findsOneWidget); - expect( - find.bySvgIcon(StreamSvgIcons.filetypeAudioM4a), findsOneWidget); }, ); @@ -90,7 +87,7 @@ void main() { ), ); - expect(find.text('x1.0'), findsOneWidget); + expect(find.text('x1'), findsOneWidget); expect(find.byType(SpeedControlButton), findsOneWidget); }, ); @@ -215,7 +212,7 @@ void main() { PlaybackSpeed speed, ValueChanged? onChangeSpeed, ) { - return const StreamSvgIcon(icon: StreamSvgIcons.closeSmall); + return const Icon(Icons.close); } await tester.pumpWidget( @@ -229,8 +226,13 @@ void main() { ); // Verify custom trailing widget is rendered - expect(find.bySvgIcon(StreamSvgIcons.closeSmall), findsOneWidget); - expect(find.bySvgIcon(StreamSvgIcons.filetypeAudioM4a), findsNothing); + expect(find.byIcon(Icons.close), findsOneWidget); + expect( + find.byWidgetPredicate( + (w) => w is SvgIcon && w.icon == StreamSvgIcons.filetypeAudioM4a, + ), + findsNothing, + ); }, ); @@ -282,15 +284,18 @@ Widget _wrapWithStreamChatApp( Brightness? brightness, }) { return MaterialApp( + theme: ThemeData(brightness: brightness), home: StreamChatTheme( data: StreamChatThemeData(brightness: brightness), - child: Builder(builder: (context) { - final theme = StreamChatTheme.of(context); - return Scaffold( - backgroundColor: theme.colorTheme.appBg, - body: Center(child: widget), - ); - }), + child: Builder( + builder: (context) { + final theme = StreamChatTheme.of(context); + return Scaffold( + backgroundColor: theme.colorTheme.appBg, + body: Center(child: widget), + ); + }, + ), ), ); } diff --git a/packages/stream_chat_flutter/test/src/attachment_actions_modal/attachment_actions_modal_test.dart b/packages/stream_chat_flutter/test/src/attachment_actions_modal/attachment_actions_modal_test.dart index 08213b0981..511089188c 100644 --- a/packages/stream_chat_flutter/test/src/attachment_actions_modal/attachment_actions_modal_test.dart +++ b/packages/stream_chat_flutter/test/src/attachment_actions_modal/attachment_actions_modal_test.dart @@ -26,8 +26,7 @@ class MockAttachmentDownloader extends Mock { void main() { setUpAll(() { - registerFallbackValue( - MaterialPageRoute(builder: (context) => const SizedBox())); + registerFallbackValue(MaterialPageRoute(builder: (context) => const SizedBox())); registerFallbackValue(Message()); }); @@ -265,8 +264,7 @@ void main() { final clientState = MockClientState(); final mockChannel = MockChannel(); - when(() => mockChannel.updateMessage(any())) - .thenAnswer((_) async => UpdateMessageResponse()); + when(() => mockChannel.updateMessage(any())).thenAnswer((_) async => UpdateMessageResponse()); when(() => client.state).thenReturn(clientState); when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id')); @@ -306,11 +304,15 @@ void main() { ), ); await tester.tap(find.text('Delete')); - verify(() => mockChannel.updateMessage(message.copyWith( + verify( + () => mockChannel.updateMessage( + message.copyWith( attachments: [ message.attachments[1], ], - ))).called(1); + ), + ), + ).called(1); }, ); @@ -321,8 +323,7 @@ void main() { final clientState = MockClientState(); final mockChannel = MockChannel(); - when(() => mockChannel.updateMessage(any())) - .thenAnswer((_) async => UpdateMessageResponse()); + when(() => mockChannel.updateMessage(any())).thenAnswer((_) async => UpdateMessageResponse()); when(() => client.state).thenReturn(clientState); when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id')); @@ -357,9 +358,13 @@ void main() { ), ); await tester.tap(find.text('Delete')); - verify(() => mockChannel.updateMessage(message.copyWith( + verify( + () => mockChannel.updateMessage( + message.copyWith( attachments: [], - ))).called(1); + ), + ), + ).called(1); }, ); @@ -371,8 +376,7 @@ void main() { final clientState = MockClientState(); final mockChannel = MockChannel(); - when(() => mockChannel.deleteMessage(any())) - .thenAnswer((_) async => EmptyResponse()); + when(() => mockChannel.deleteMessage(any())).thenAnswer((_) async => EmptyResponse()); when(() => client.state).thenReturn(clientState); when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id')); diff --git a/packages/stream_chat_flutter/test/src/audio/audio_playlist_controller_test.dart b/packages/stream_chat_flutter/test/src/audio/audio_playlist_controller_test.dart index 2a5cb09b5c..1f4df27c0a 100644 --- a/packages/stream_chat_flutter/test/src/audio/audio_playlist_controller_test.dart +++ b/packages/stream_chat_flutter/test/src/audio/audio_playlist_controller_test.dart @@ -52,12 +52,9 @@ void main() { speedController = PublishSubject(); // Default mock behaviors - when(() => mockPlayer.playerStateStream) - .thenAnswer((_) => stateController.stream); - when(() => mockPlayer.positionStream) - .thenAnswer((_) => positionController.stream); - when(() => mockPlayer.speedStream) - .thenAnswer((_) => speedController.stream); + when(() => mockPlayer.playerStateStream).thenAnswer((_) => stateController.stream); + when(() => mockPlayer.positionStream).thenAnswer((_) => positionController.stream); + when(() => mockPlayer.speedStream).thenAnswer((_) => speedController.stream); controller = StreamAudioPlaylistController.raw( player: mockPlayer, @@ -124,7 +121,7 @@ void main() { PlaylistTrack( title: 'new-track.mp3', uri: Uri.parse('https://example.com/new-track.mp3'), - ) + ), ]; await controller.updatePlaylist(newTracks); diff --git a/packages/stream_chat_flutter/test/src/audio/audio_sampling_test.dart b/packages/stream_chat_flutter/test/src/audio/audio_sampling_test.dart index a1f2918274..e48ab7a14c 100644 --- a/packages/stream_chat_flutter/test/src/audio/audio_sampling_test.dart +++ b/packages/stream_chat_flutter/test/src/audio/audio_sampling_test.dart @@ -76,8 +76,7 @@ void main() { countMap[value] = (countMap[value] ?? 0) + 1; } // Each value should appear either 2 or 3 times - expect( - countMap.values.every((count) => count == 2 || count == 3), isTrue); + expect(countMap.values.every((count) => count == 2 || count == 3), isTrue); }); test('returns original data when target size is smaller', () { diff --git a/packages/stream_chat_flutter/test/src/avatars/goldens/ci/gradient_avatar_0.png b/packages/stream_chat_flutter/test/src/avatars/goldens/ci/gradient_avatar_0.png index 7069fe981d..6e685f7848 100644 Binary files a/packages/stream_chat_flutter/test/src/avatars/goldens/ci/gradient_avatar_0.png and b/packages/stream_chat_flutter/test/src/avatars/goldens/ci/gradient_avatar_0.png differ diff --git a/packages/stream_chat_flutter/test/src/avatars/goldens/ci/gradient_avatar_1.png b/packages/stream_chat_flutter/test/src/avatars/goldens/ci/gradient_avatar_1.png index 5838bbf505..8779ad28ae 100644 Binary files a/packages/stream_chat_flutter/test/src/avatars/goldens/ci/gradient_avatar_1.png and b/packages/stream_chat_flutter/test/src/avatars/goldens/ci/gradient_avatar_1.png differ diff --git a/packages/stream_chat_flutter/test/src/avatars/goldens/ci/gradient_avatar_2.png b/packages/stream_chat_flutter/test/src/avatars/goldens/ci/gradient_avatar_2.png index 7069fe981d..6e685f7848 100644 Binary files a/packages/stream_chat_flutter/test/src/avatars/goldens/ci/gradient_avatar_2.png and b/packages/stream_chat_flutter/test/src/avatars/goldens/ci/gradient_avatar_2.png differ diff --git a/packages/stream_chat_flutter/test/src/avatars/goldens/ci/gradient_avatar_3.png b/packages/stream_chat_flutter/test/src/avatars/goldens/ci/gradient_avatar_3.png index c711450831..88abc34ded 100644 Binary files a/packages/stream_chat_flutter/test/src/avatars/goldens/ci/gradient_avatar_3.png and b/packages/stream_chat_flutter/test/src/avatars/goldens/ci/gradient_avatar_3.png differ diff --git a/packages/stream_chat_flutter/test/src/avatars/goldens/ci/gradient_avatar_issue_2369.png b/packages/stream_chat_flutter/test/src/avatars/goldens/ci/gradient_avatar_issue_2369.png index 42ce91def5..bff58396b2 100644 Binary files a/packages/stream_chat_flutter/test/src/avatars/goldens/ci/gradient_avatar_issue_2369.png and b/packages/stream_chat_flutter/test/src/avatars/goldens/ci/gradient_avatar_issue_2369.png differ diff --git a/packages/stream_chat_flutter/test/src/avatars/goldens/ci/group_avatar_0.png b/packages/stream_chat_flutter/test/src/avatars/goldens/ci/group_avatar_0.png index 7a3fe14616..367f47bfb5 100644 Binary files a/packages/stream_chat_flutter/test/src/avatars/goldens/ci/group_avatar_0.png and b/packages/stream_chat_flutter/test/src/avatars/goldens/ci/group_avatar_0.png differ diff --git a/packages/stream_chat_flutter/test/src/avatars/goldens/ci/user_avatar_0.png b/packages/stream_chat_flutter/test/src/avatars/goldens/ci/user_avatar_0.png index a511a95448..48e867a140 100644 Binary files a/packages/stream_chat_flutter/test/src/avatars/goldens/ci/user_avatar_0.png and b/packages/stream_chat_flutter/test/src/avatars/goldens/ci/user_avatar_0.png differ diff --git a/packages/stream_chat_flutter/test/src/avatars/goldens/ci/user_avatar_1.png b/packages/stream_chat_flutter/test/src/avatars/goldens/ci/user_avatar_1.png index 531f4ac3ae..87e44f277c 100644 Binary files a/packages/stream_chat_flutter/test/src/avatars/goldens/ci/user_avatar_1.png and b/packages/stream_chat_flutter/test/src/avatars/goldens/ci/user_avatar_1.png differ diff --git a/packages/stream_chat_flutter/test/src/avatars/gradient_avatar_test.dart b/packages/stream_chat_flutter/test/src/avatars/gradient_avatar_test.dart index e63fe05246..478f834361 100644 --- a/packages/stream_chat_flutter/test/src/avatars/gradient_avatar_test.dart +++ b/packages/stream_chat_flutter/test/src/avatars/gradient_avatar_test.dart @@ -28,8 +28,7 @@ void main() { child: SizedBox( width: 100, height: 100, - child: StreamGradientAvatar( - name: 'demo user', userId: 'demo123'), + child: StreamGradientAvatar(name: 'demo user', userId: 'demo123'), ), ), ), @@ -368,16 +367,18 @@ Widget _wrapWithMaterialApp( return MaterialApp( home: StreamChatTheme( data: StreamChatThemeData(brightness: brightness), - child: Builder(builder: (context) { - final theme = StreamChatTheme.of(context); - return Scaffold( - backgroundColor: theme.colorTheme.appBg, - body: Container( - padding: const EdgeInsets.all(16), - child: Center(child: widget), - ), - ); - }), + child: Builder( + builder: (context) { + final theme = StreamChatTheme.of(context); + return Scaffold( + backgroundColor: theme.colorTheme.appBg, + body: Container( + padding: const EdgeInsets.all(16), + child: Center(child: widget), + ), + ); + }, + ), ), ); } diff --git a/packages/stream_chat_flutter/test/src/avatars/group_avatar_test.dart b/packages/stream_chat_flutter/test/src/avatars/group_avatar_test.dart index 3cc769213d..9614b24e92 100644 --- a/packages/stream_chat_flutter/test/src/avatars/group_avatar_test.dart +++ b/packages/stream_chat_flutter/test/src/avatars/group_avatar_test.dart @@ -16,8 +16,8 @@ void main() { late MockChannel channel; late MockChannelState channelState; - late final member = Member(user: User(id: 'alice', name: 'Alice')); - late final member2 = Member(user: User(id: 'bob', name: 'Bob')); + late final user1 = User(id: 'alice', name: 'Alice'); + late final user2 = User(id: 'bob', name: 'Bob'); setUpAll(() { client = MockClient(); @@ -26,7 +26,10 @@ void main() { when(() => channel.state!).thenReturn(channelState); when(() => channelState.membersStream).thenAnswer( - (_) => Stream>.value([member, member2]), + (_) => Stream>.value([ + Member(user: user1), + Member(user: user2), + ]), ); }); @@ -46,11 +49,8 @@ void main() { channel: channel, child: Scaffold( body: Center( - child: StreamGroupAvatar( - members: [ - member, - member2, - ], + child: StreamUserAvatarGroup( + users: [user1, user2], ), ), ), @@ -82,11 +82,8 @@ void main() { child: SizedBox( width: 100, height: 100, - child: StreamGroupAvatar( - members: [ - member, - member2, - ], + child: StreamUserAvatarGroup( + users: [user1, user2], ), ), ), diff --git a/packages/stream_chat_flutter/test/src/avatars/user_avatar_test.dart b/packages/stream_chat_flutter/test/src/avatars/user_avatar_test.dart index 6a95d3637e..6ec007a992 100644 --- a/packages/stream_chat_flutter/test/src/avatars/user_avatar_test.dart +++ b/packages/stream_chat_flutter/test/src/avatars/user_avatar_test.dart @@ -28,15 +28,17 @@ void main() { home: StreamChat( client: client, streamChatThemeData: StreamChatThemeData.light(), - child: Builder(builder: (context) { - return Scaffold( - body: Center( - child: StreamUserAvatar( - user: user, + child: Builder( + builder: (context) { + return Scaffold( + body: Center( + child: StreamUserAvatar( + user: user, + ), ), - ), - ); - }), + ); + }, + ), ), ), ); diff --git a/packages/stream_chat_flutter/test/src/bottom_sheets/attachment_modal_sheet_test.dart b/packages/stream_chat_flutter/test/src/bottom_sheets/attachment_modal_sheet_test.dart index 0779c67e87..a1ba65dc58 100644 --- a/packages/stream_chat_flutter/test/src/bottom_sheets/attachment_modal_sheet_test.dart +++ b/packages/stream_chat_flutter/test/src/bottom_sheets/attachment_modal_sheet_test.dart @@ -11,21 +11,23 @@ void main() { await tester.pumpWidget( MaterialApp( home: Scaffold( - body: Builder(builder: (context) { - return Center( - child: ElevatedButton( - child: const Text('Show Modal'), - onPressed: () => showModalBottomSheet( - context: context, - builder: (_) => AttachmentModalSheet( - onFileTap: () {}, - onPhotoTap: () {}, - onVideoTap: () {}, + body: Builder( + builder: (context) { + return Center( + child: ElevatedButton( + child: const Text('Show Modal'), + onPressed: () => showModalBottomSheet( + context: context, + builder: (_) => AttachmentModalSheet( + onFileTap: () {}, + onPhotoTap: () {}, + onVideoTap: () {}, + ), ), ), - ), - ); - }), + ); + }, + ), ), ), ); @@ -42,15 +44,17 @@ void main() { await tester.pumpWidget( MaterialApp( home: Scaffold( - body: Builder(builder: (context) { - return Center( - child: AttachmentModalSheet( - onPhotoTap: () => called = 1, - onFileTap: () {}, - onVideoTap: () {}, - ), - ); - }), + body: Builder( + builder: (context) { + return Center( + child: AttachmentModalSheet( + onPhotoTap: () => called = 1, + onFileTap: () {}, + onVideoTap: () {}, + ), + ); + }, + ), ), ), ); @@ -68,15 +72,17 @@ void main() { await tester.pumpWidget( MaterialApp( home: Scaffold( - body: Builder(builder: (context) { - return Center( - child: AttachmentModalSheet( - onPhotoTap: () {}, - onVideoTap: () => called = 1, - onFileTap: () {}, - ), - ); - }), + body: Builder( + builder: (context) { + return Center( + child: AttachmentModalSheet( + onPhotoTap: () {}, + onVideoTap: () => called = 1, + onFileTap: () {}, + ), + ); + }, + ), ), ), ); @@ -94,15 +100,17 @@ void main() { await tester.pumpWidget( MaterialApp( home: Scaffold( - body: Builder(builder: (context) { - return Center( - child: AttachmentModalSheet( - onPhotoTap: () {}, - onVideoTap: () {}, - onFileTap: () => called = 1, - ), - ); - }), + body: Builder( + builder: (context) { + return Center( + child: AttachmentModalSheet( + onPhotoTap: () {}, + onVideoTap: () {}, + onFileTap: () => called = 1, + ), + ); + }, + ), ), ), ); @@ -121,15 +129,17 @@ void main() { constraints: const BoxConstraints.tightFor(width: 300, height: 300), builder: () => MaterialAppWrapper( home: Scaffold( - body: Builder(builder: (context) { - return Center( - child: AttachmentModalSheet( - onPhotoTap: () {}, - onVideoTap: () {}, - onFileTap: () {}, - ), - ); - }), + body: Builder( + builder: (context) { + return Center( + child: AttachmentModalSheet( + onPhotoTap: () {}, + onVideoTap: () {}, + onFileTap: () {}, + ), + ); + }, + ), ), ), ); diff --git a/packages/stream_chat_flutter/test/src/bottom_sheets/error_alert_sheet_test.dart b/packages/stream_chat_flutter/test/src/bottom_sheets/error_alert_sheet_test.dart index 7dc89683b8..4915e5f6bb 100644 --- a/packages/stream_chat_flutter/test/src/bottom_sheets/error_alert_sheet_test.dart +++ b/packages/stream_chat_flutter/test/src/bottom_sheets/error_alert_sheet_test.dart @@ -9,18 +9,15 @@ import '../mocks.dart'; void main() { group('ErrorAlertSheet tests', () { - const methodChannel = - MethodChannel('dev.fluttercommunity.plus/connectivity_status'); + const methodChannel = MethodChannel('dev.fluttercommunity.plus/connectivity_status'); setUp(() { - TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger - .setMockMethodCallHandler(methodChannel, - (MethodCall methodCall) async { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(methodChannel, ( + MethodCall methodCall, + ) async { if (methodCall.method == 'listen') { try { - await TestDefaultBinaryMessengerBinding - .instance.defaultBinaryMessenger - .handlePlatformMessage( + await TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.handlePlatformMessage( methodChannel.name, methodChannel.codec.encodeSuccessEnvelope(['wifi']), (_) {}, @@ -93,8 +90,7 @@ void main() { ); tearDown(() { - TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger - .setMockMethodCallHandler(methodChannel, null); + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(methodChannel, null); }); }); } diff --git a/packages/stream_chat_flutter/test/src/bottom_sheets/goldens/ci/attachment_modal_sheet_0.png b/packages/stream_chat_flutter/test/src/bottom_sheets/goldens/ci/attachment_modal_sheet_0.png index d37466f32f..d61f6d8fcc 100644 Binary files a/packages/stream_chat_flutter/test/src/bottom_sheets/goldens/ci/attachment_modal_sheet_0.png and b/packages/stream_chat_flutter/test/src/bottom_sheets/goldens/ci/attachment_modal_sheet_0.png differ diff --git a/packages/stream_chat_flutter/test/src/bottom_sheets/goldens/ci/edit_message_sheet_0.png b/packages/stream_chat_flutter/test/src/bottom_sheets/goldens/ci/edit_message_sheet_0.png index 4e07e17bec..b73cd7f166 100644 Binary files a/packages/stream_chat_flutter/test/src/bottom_sheets/goldens/ci/edit_message_sheet_0.png and b/packages/stream_chat_flutter/test/src/bottom_sheets/goldens/ci/edit_message_sheet_0.png differ diff --git a/packages/stream_chat_flutter/test/src/bottom_sheets/goldens/ci/error_alert_sheet_0.png b/packages/stream_chat_flutter/test/src/bottom_sheets/goldens/ci/error_alert_sheet_0.png index b821537482..caad68ac9c 100644 Binary files a/packages/stream_chat_flutter/test/src/bottom_sheets/goldens/ci/error_alert_sheet_0.png and b/packages/stream_chat_flutter/test/src/bottom_sheets/goldens/ci/error_alert_sheet_0.png differ diff --git a/packages/stream_chat_flutter/test/src/channel/channel_header_test.dart b/packages/stream_chat_flutter/test/src/channel/channel_header_test.dart index 0518fd84fd..fbdc6b4d88 100644 --- a/packages/stream_chat_flutter/test/src/channel/channel_header_test.dart +++ b/packages/stream_chat_flutter/test/src/channel/channel_header_test.dart @@ -28,8 +28,7 @@ void main() { when(() => client.state).thenReturn(clientState); when(() => clientState.currentUser).thenReturn(user); - when(() => clientState.currentUserStream) - .thenAnswer((_) => Stream.value(user)); + when(() => clientState.currentUserStream).thenAnswer((_) => Stream.value(user)); when(() => channel.lastMessageAt).thenReturn(lastMessageAt); when(() => channel.state).thenReturn(channelState); when(() => channel.client).thenReturn(client); @@ -37,23 +36,19 @@ void main() { when(() => channel.isMutedStream).thenAnswer((i) => Stream.value(false)); when(() => channel.nameStream).thenAnswer((_) => Stream.value('test')); when(() => channel.name).thenReturn('test'); - when(() => channel.imageStream) - .thenAnswer((i) => Stream.value('https://bit.ly/321RmWb')); + when(() => channel.imageStream).thenAnswer((i) => Stream.value('https://bit.ly/321RmWb')); when(() => channel.image).thenReturn('https://bit.ly/321RmWb'); when(() => channelState.unreadCount).thenReturn(1); - when(() => client.wsConnectionStatusStream) - .thenAnswer((_) => Stream.value(ConnectionStatus.connected)); - when(() => channelState.unreadCountStream) - .thenAnswer((i) => Stream.value(1)); + when(() => client.wsConnectionStatusStream).thenAnswer((_) => Stream.value(ConnectionStatus.connected)); + when(() => channelState.unreadCountStream).thenAnswer((i) => Stream.value(1)); when(() => clientState.totalUnreadCount).thenAnswer((i) => 1); - when(() => clientState.totalUnreadCountStream) - .thenAnswer((i) => Stream.value(1)); + when(() => clientState.totalUnreadCountStream).thenAnswer((i) => Stream.value(1)); when(() => channelState.membersStream).thenAnswer( (i) => Stream.value([ Member( userId: 'user-id', user: User(id: 'user-id'), - ) + ), ]), ); when(() => channelState.members).thenReturn([ @@ -99,8 +94,7 @@ void main() { when(() => client.state).thenReturn(clientState); when(() => clientState.currentUser).thenReturn(user); - when(() => clientState.currentUserStream) - .thenAnswer((_) => Stream.value(user)); + when(() => clientState.currentUserStream).thenAnswer((_) => Stream.value(user)); when(() => channel.lastMessageAt).thenReturn(lastMessageAt); when(() => channel.state).thenReturn(channelState); when(() => channel.client).thenReturn(client); @@ -108,18 +102,16 @@ void main() { when(() => channel.isMutedStream).thenAnswer((i) => Stream.value(false)); when(() => channel.nameStream).thenAnswer((_) => Stream.value('test')); when(() => channel.name).thenReturn('test'); - when(() => channel.imageStream) - .thenAnswer((i) => Stream.value('https://bit.ly/321RmWb')); + when(() => channel.imageStream).thenAnswer((i) => Stream.value('https://bit.ly/321RmWb')); when(() => channel.image).thenReturn('https://bit.ly/321RmWb'); when(() => channelState.unreadCount).thenReturn(1); - when(() => channelState.unreadCountStream) - .thenAnswer((i) => Stream.value(1)); + when(() => channelState.unreadCountStream).thenAnswer((i) => Stream.value(1)); when(() => channelState.membersStream).thenAnswer( (i) => Stream.value([ Member( userId: 'user-id', user: User(id: 'user-id'), - ) + ), ]), ); when(() => channelState.members).thenReturn([ @@ -128,13 +120,10 @@ void main() { user: User(id: 'user-id'), ), ]); - when(() => client.wsConnectionStatusStream) - .thenAnswer((_) => Stream.value(ConnectionStatus.disconnected)); - when(() => client.wsConnectionStatus) - .thenReturn(ConnectionStatus.disconnected); + when(() => client.wsConnectionStatusStream).thenAnswer((_) => Stream.value(ConnectionStatus.disconnected)); + when(() => client.wsConnectionStatus).thenReturn(ConnectionStatus.disconnected); when(() => clientState.totalUnreadCount).thenAnswer((i) => 1); - when(() => clientState.totalUnreadCountStream) - .thenAnswer((i) => Stream.value(1)); + when(() => clientState.totalUnreadCountStream).thenAnswer((i) => Stream.value(1)); await tester.pumpWidget( MaterialApp( @@ -155,13 +144,8 @@ void main() { // wait for the initial state to be rendered. await tester.pumpAndSettle(); - expect( - tester - .widget(find.byType(StreamInfoTile)) - .showMessage, - true); - expect(tester.widget(find.byType(StreamInfoTile)).message, - 'Disconnected'); + expect(tester.widget(find.byType(StreamInfoTile)).showMessage, true); + expect(tester.widget(find.byType(StreamInfoTile)).message, 'Disconnected'); }, ); @@ -177,8 +161,7 @@ void main() { when(() => client.state).thenReturn(clientState); when(() => clientState.currentUser).thenReturn(user); - when(() => clientState.currentUserStream) - .thenAnswer((_) => Stream.value(user)); + when(() => clientState.currentUserStream).thenAnswer((_) => Stream.value(user)); when(() => channel.lastMessageAt).thenReturn(lastMessageAt); when(() => channel.state).thenReturn(channelState); when(() => channel.client).thenReturn(client); @@ -186,18 +169,16 @@ void main() { when(() => channel.isMutedStream).thenAnswer((i) => Stream.value(false)); when(() => channel.nameStream).thenAnswer((_) => Stream.value('test')); when(() => channel.name).thenReturn('test'); - when(() => channel.imageStream) - .thenAnswer((i) => Stream.value('https://bit.ly/321RmWb')); + when(() => channel.imageStream).thenAnswer((i) => Stream.value('https://bit.ly/321RmWb')); when(() => channel.image).thenReturn('https://bit.ly/321RmWb'); when(() => channelState.unreadCount).thenReturn(1); - when(() => channelState.unreadCountStream) - .thenAnswer((i) => Stream.value(1)); + when(() => channelState.unreadCountStream).thenAnswer((i) => Stream.value(1)); when(() => channelState.membersStream).thenAnswer( (i) => Stream.value([ Member( userId: 'user-id', user: User(id: 'user-id'), - ) + ), ]), ); when(() => channelState.members).thenReturn([ @@ -206,11 +187,9 @@ void main() { user: User(id: 'user-id'), ), ]); - when(() => client.wsConnectionStatusStream) - .thenAnswer((_) => Stream.value(ConnectionStatus.connecting)); + when(() => client.wsConnectionStatusStream).thenAnswer((_) => Stream.value(ConnectionStatus.connecting)); when(() => clientState.totalUnreadCount).thenAnswer((i) => 1); - when(() => clientState.totalUnreadCountStream) - .thenAnswer((i) => Stream.value(1)); + when(() => clientState.totalUnreadCountStream).thenAnswer((i) => Stream.value(1)); await tester.pumpWidget( MaterialApp( @@ -231,13 +210,8 @@ void main() { await tester.pump(); - expect( - tester - .widget(find.byType(StreamInfoTile)) - .showMessage, - true); - expect(tester.widget(find.byType(StreamInfoTile)).message, - 'Reconnecting...'); + expect(tester.widget(find.byType(StreamInfoTile)).showMessage, true); + expect(tester.widget(find.byType(StreamInfoTile)).message, 'Reconnecting...'); }, ); @@ -253,28 +227,28 @@ void main() { when(() => client.state).thenReturn(clientState); when(() => clientState.currentUser).thenReturn(user); - when(() => clientState.currentUserStream) - .thenAnswer((_) => Stream.value(user)); + when(() => clientState.currentUserStream).thenAnswer((_) => Stream.value(user)); when(() => channel.lastMessageAt).thenReturn(lastMessageAt); when(() => channel.state).thenReturn(channelState); when(() => channel.client).thenReturn(client); when(() => channel.isMuted).thenReturn(false); when(() => channel.isMutedStream).thenAnswer((i) => Stream.value(false)); - when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({ - 'name': 'test', - })); + when(() => channel.extraDataStream).thenAnswer( + (i) => Stream.value({ + 'name': 'test', + }), + ); when(() => channel.extraData).thenReturn({ 'name': 'test', }); when(() => channelState.unreadCount).thenReturn(1); - when(() => channelState.unreadCountStream) - .thenAnswer((i) => Stream.value(1)); + when(() => channelState.unreadCountStream).thenAnswer((i) => Stream.value(1)); when(() => channelState.membersStream).thenAnswer( (i) => Stream.value([ Member( userId: 'user-id', user: User(id: 'user-id'), - ) + ), ]), ); when(() => channelState.members).thenReturn([ @@ -283,10 +257,8 @@ void main() { user: User(id: 'user-id'), ), ]); - when(() => client.wsConnectionStatusStream) - .thenAnswer((_) => Stream.value(ConnectionStatus.connecting)); - when(() => clientState.totalUnreadCountStream) - .thenAnswer((i) => Stream.value(1)); + when(() => client.wsConnectionStatusStream).thenAnswer((_) => Stream.value(ConnectionStatus.connecting)); + when(() => clientState.totalUnreadCountStream).thenAnswer((i) => Stream.value(1)); await tester.pumpWidget( MaterialAppWrapper( @@ -337,8 +309,7 @@ void main() { when(() => client.state).thenReturn(clientState); when(() => clientState.currentUser).thenReturn(user); - when(() => clientState.currentUserStream) - .thenAnswer((_) => Stream.value(user)); + when(() => clientState.currentUserStream).thenAnswer((_) => Stream.value(user)); when(() => channel.lastMessageAt).thenReturn(lastMessageAt); when(() => channel.state).thenReturn(channelState); when(() => channel.client).thenReturn(client); @@ -346,18 +317,16 @@ void main() { when(() => channel.isMutedStream).thenAnswer((i) => Stream.value(false)); when(() => channel.nameStream).thenAnswer((_) => Stream.value('test')); when(() => channel.name).thenReturn('test'); - when(() => channel.imageStream) - .thenAnswer((i) => Stream.value('https://bit.ly/321RmWb')); + when(() => channel.imageStream).thenAnswer((i) => Stream.value('https://bit.ly/321RmWb')); when(() => channel.image).thenReturn('https://bit.ly/321RmWb'); when(() => channelState.unreadCount).thenReturn(1); - when(() => channelState.unreadCountStream) - .thenAnswer((i) => Stream.value(1)); + when(() => channelState.unreadCountStream).thenAnswer((i) => Stream.value(1)); when(() => channelState.membersStream).thenAnswer( (i) => Stream.value([ Member( userId: 'user-id', user: User(id: 'user-id'), - ) + ), ]), ); when(() => channelState.members).thenReturn([ @@ -366,8 +335,7 @@ void main() { user: User(id: 'user-id'), ), ]); - when(() => client.wsConnectionStatusStream) - .thenAnswer((_) => Stream.value(ConnectionStatus.disconnected)); + when(() => client.wsConnectionStatusStream).thenAnswer((_) => Stream.value(ConnectionStatus.disconnected)); await tester.pumpWidget( MaterialApp( @@ -391,16 +359,10 @@ void main() { expect(find.byType(StreamBackButton), findsNothing); expect( - tester - .widget(find.byType(StreamChannelInfo)) - .showTypingIndicator, + tester.widget(find.byType(StreamChannelInfo)).showTypingIndicator, false, ); - expect( - tester - .widget(find.byType(StreamInfoTile)) - .showMessage, - false); + expect(tester.widget(find.byType(StreamInfoTile)).showMessage, false); }, ); @@ -416,8 +378,7 @@ void main() { when(() => client.state).thenReturn(clientState); when(() => clientState.currentUser).thenReturn(user); - when(() => clientState.currentUserStream) - .thenAnswer((_) => Stream.value(user)); + when(() => clientState.currentUserStream).thenAnswer((_) => Stream.value(user)); when(() => channel.lastMessageAt).thenReturn(lastMessageAt); when(() => channel.state).thenReturn(channelState); when(() => channel.client).thenReturn(client); @@ -425,18 +386,16 @@ void main() { when(() => channel.isMutedStream).thenAnswer((i) => Stream.value(false)); when(() => channel.nameStream).thenAnswer((_) => Stream.value('test')); when(() => channel.name).thenReturn('test'); - when(() => channel.imageStream) - .thenAnswer((i) => Stream.value('https://bit.ly/321RmWb')); + when(() => channel.imageStream).thenAnswer((i) => Stream.value('https://bit.ly/321RmWb')); when(() => channel.image).thenReturn('https://bit.ly/321RmWb'); when(() => channelState.unreadCount).thenReturn(1); - when(() => channelState.unreadCountStream) - .thenAnswer((i) => Stream.value(1)); + when(() => channelState.unreadCountStream).thenAnswer((i) => Stream.value(1)); when(() => channelState.membersStream).thenAnswer( (i) => Stream.value([ Member( userId: 'user-id', user: User(id: 'user-id'), - ) + ), ]), ); when(() => channelState.members).thenReturn([ @@ -445,11 +404,9 @@ void main() { user: User(id: 'user-id'), ), ]); - when(() => client.wsConnectionStatusStream) - .thenAnswer((_) => Stream.value(ConnectionStatus.connecting)); + when(() => client.wsConnectionStatusStream).thenAnswer((_) => Stream.value(ConnectionStatus.connecting)); when(() => clientState.totalUnreadCount).thenAnswer((i) => 1); - when(() => clientState.totalUnreadCountStream) - .thenAnswer((i) => Stream.value(1)); + when(() => clientState.totalUnreadCountStream).thenAnswer((i) => Stream.value(1)); var backPressed = false; var imageTapped = false; @@ -500,8 +457,7 @@ void main() { when(() => client.state).thenReturn(clientState); when(() => clientState.currentUser).thenReturn(user); - when(() => clientState.currentUserStream) - .thenAnswer((_) => Stream.value(user)); + when(() => clientState.currentUserStream).thenAnswer((_) => Stream.value(user)); when(() => channel.lastMessageAt).thenReturn(lastMessageAt); when(() => channel.state).thenReturn(channelState); when(() => channel.client).thenReturn(client); @@ -509,23 +465,19 @@ void main() { when(() => channel.isMutedStream).thenAnswer((i) => Stream.value(false)); when(() => channel.nameStream).thenAnswer((_) => Stream.value('test')); when(() => channel.name).thenReturn('test'); - when(() => channel.imageStream) - .thenAnswer((i) => Stream.value('https://bit.ly/321RmWb')); + when(() => channel.imageStream).thenAnswer((i) => Stream.value('https://bit.ly/321RmWb')); when(() => channel.image).thenReturn('https://bit.ly/321RmWb'); when(() => channelState.unreadCount).thenReturn(1); - when(() => client.wsConnectionStatusStream) - .thenAnswer((_) => Stream.value(ConnectionStatus.connected)); - when(() => channelState.unreadCountStream) - .thenAnswer((i) => Stream.value(1)); + when(() => client.wsConnectionStatusStream).thenAnswer((_) => Stream.value(ConnectionStatus.connected)); + when(() => channelState.unreadCountStream).thenAnswer((i) => Stream.value(1)); when(() => clientState.totalUnreadCount).thenAnswer((i) => 1); - when(() => clientState.totalUnreadCountStream) - .thenAnswer((i) => Stream.value(1)); + when(() => clientState.totalUnreadCountStream).thenAnswer((i) => Stream.value(1)); when(() => channelState.membersStream).thenAnswer( (i) => Stream.value([ Member( userId: 'user-id', user: User(id: 'user-id'), - ) + ), ]), ); when(() => channelState.members).thenReturn([ diff --git a/packages/stream_chat_flutter/test/src/channel/channel_image_test.dart b/packages/stream_chat_flutter/test/src/channel/channel_image_test.dart index 6bcde77973..a1e4d22ff2 100644 --- a/packages/stream_chat_flutter/test/src/channel/channel_image_test.dart +++ b/packages/stream_chat_flutter/test/src/channel/channel_image_test.dart @@ -21,8 +21,7 @@ void main() { when(() => channel.client).thenReturn(client); when(() => channel.nameStream).thenAnswer((_) => Stream.value('test')); when(() => channel.name).thenReturn('test'); - when(() => channel.imageStream) - .thenAnswer((i) => Stream.value('https://bit.ly/321RmWb')); + when(() => channel.imageStream).thenAnswer((i) => Stream.value('https://bit.ly/321RmWb')); when(() => channel.image).thenReturn('https://bit.ly/321RmWb'); await tester.pumpWidget( @@ -42,8 +41,7 @@ void main() { // wait for the initial state to be rendered. await tester.pumpAndSettle(); - final image = - tester.widget(find.byType(CachedNetworkImage)); + final image = tester.widget(find.byType(CachedNetworkImage)); expect(image.imageUrl, 'https://bit.ly/321RmWb'); }, ); @@ -62,6 +60,7 @@ void main() { when(() => channel.client).thenReturn(client); when(() => channel.nameStream).thenAnswer((_) => Stream.value('test')); when(() => channel.name).thenReturn('test'); + when(() => channel.isDistinct).thenReturn(true); when(() => channel.imageStream).thenAnswer((i) => Stream.value(null)); when(() => channel.image).thenReturn(null); when(() => channelState.membersStream).thenAnswer( @@ -76,7 +75,7 @@ void main() { id: 'user-id2', image: 'testimage', ), - ) + ), ]), ); when(() => channelState.members).thenReturn([ @@ -90,7 +89,7 @@ void main() { Member( userId: 'user-id', user: User(id: 'user-id'), - ) + ), ]); when(() => clientState.usersStream).thenAnswer( (i) => Stream.value({ @@ -121,8 +120,7 @@ void main() { // wait for the initial state to be rendered. await tester.pumpAndSettle(); - final image = - tester.widget(find.byType(CachedNetworkImage)); + final image = tester.widget(find.byType(CachedNetworkImage)); expect(image.imageUrl, 'testimage'); }, ); @@ -140,6 +138,7 @@ void main() { when(() => clientState.currentUser).thenReturn(currentUser); when(() => channel.state).thenReturn(channelState); when(() => channel.client).thenReturn(client); + when(() => channel.isDistinct).thenReturn(false); when(() => channel.nameStream).thenAnswer((_) => Stream.value('test')); when(() => channel.name).thenReturn('test'); when(() => channel.imageStream).thenAnswer((i) => Stream.value(null)); @@ -167,8 +166,7 @@ void main() { ), ]; when(() => channelState.members).thenReturn(members); - when(() => channelState.membersStream) - .thenAnswer((_) => Stream.value(members)); + when(() => channelState.membersStream).thenAnswer((_) => Stream.value(members)); await tester.pumpWidget( MaterialApp( @@ -187,55 +185,17 @@ void main() { // wait for the initial state to be rendered. await tester.pumpAndSettle(); - final image = - tester.widget(find.byType(StreamGroupAvatar)); - final otherMembers = members.where((it) => it.userId != currentUser.id); - expect( - image.members.map((it) => it.user?.id), - otherMembers.map((it) => it.user?.id), - ); - }, - ); + // The new StreamChannelAvatar uses StreamUserAvatarGroup internally + // for multi-member channels + final avatarGroup = find.byType(StreamUserAvatarGroup); + expect(avatarGroup, findsOneWidget); - testWidgets( - 'using select: true should show a selection border', - (tester) async { - final client = MockClient(); - final clientState = MockClientState(); - final channel = MockChannel(); - final channelState = MockChannelState(); - - when(() => client.state).thenReturn(clientState); - when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id')); - when(() => channel.state).thenReturn(channelState); - when(() => channel.client).thenReturn(client); - when(() => channel.nameStream).thenAnswer((_) => Stream.value('test')); - when(() => channel.name).thenReturn('test'); - when(() => channel.imageStream) - .thenAnswer((i) => Stream.value('https://bit.ly/321RmWb')); - when(() => channel.image).thenReturn('https://bit.ly/321RmWb'); - - await tester.pumpWidget( - MaterialApp( - home: StreamChat( - client: client, - child: StreamChannel( - channel: channel, - child: Scaffold( - body: StreamChannelAvatar( - channel: channel, - selected: true, - ), - ), - ), - ), - ), - ); - - // wait for the initial state to be rendered. - await tester.pumpAndSettle(); - - expect(find.byKey(const Key('selectedImage')), findsOneWidget); + // Verify user avatars are shown for all members + expect(find.byType(StreamUserAvatar), findsNWidgets(members.length)); }, ); + + // Note: The 'selected' parameter has been removed in the redesigned + // StreamChannelAvatar component. Selection states should now be handled + // at the parent widget level if needed. } diff --git a/packages/stream_chat_flutter/test/src/channel/channel_list_header_test.dart b/packages/stream_chat_flutter/test/src/channel/channel_list_header_test.dart index 6193d08d97..b39b0a4cbf 100644 --- a/packages/stream_chat_flutter/test/src/channel/channel_list_header_test.dart +++ b/packages/stream_chat_flutter/test/src/channel/channel_list_header_test.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; import '../mocks.dart'; @@ -14,8 +15,7 @@ void main() { when(() => client.state).thenReturn(clientState); when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id')); - when(() => client.wsConnectionStatusStream) - .thenAnswer((_) => Stream.value(ConnectionStatus.connected)); + when(() => client.wsConnectionStatusStream).thenAnswer((_) => Stream.value(ConnectionStatus.connected)); await tester.pumpWidget( MaterialApp( @@ -29,10 +29,9 @@ void main() { ); await tester.pumpAndSettle(); - final userAvatar = - tester.widget(find.byType(StreamUserAvatar)); + final userAvatar = tester.widget(find.byType(StreamUserAvatar)); expect(userAvatar.user, clientState.currentUser); - expect(find.byType(StreamNeumorphicButton), findsOneWidget); + expect(find.byType(StreamButton), findsOneWidget); expect(find.text('Stream Chat'), findsOneWidget); }, ); @@ -45,8 +44,7 @@ void main() { when(() => client.state).thenReturn(clientState); when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id')); - when(() => client.wsConnectionStatusStream) - .thenAnswer((_) => Stream.value(ConnectionStatus.disconnected)); + when(() => client.wsConnectionStatusStream).thenAnswer((_) => Stream.value(ConnectionStatus.disconnected)); await tester.pumpWidget( MaterialApp( @@ -74,8 +72,7 @@ void main() { when(() => client.state).thenReturn(clientState); when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id')); - when(() => client.wsConnectionStatusStream) - .thenAnswer((_) => Stream.value(ConnectionStatus.connecting)); + when(() => client.wsConnectionStatusStream).thenAnswer((_) => Stream.value(ConnectionStatus.connecting)); await tester.pumpWidget( MaterialApp( @@ -103,8 +100,7 @@ void main() { when(() => client.state).thenReturn(clientState); when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id')); - when(() => client.wsConnectionStatusStream) - .thenAnswer((_) => Stream.value(ConnectionStatus.connecting)); + when(() => client.wsConnectionStatusStream).thenAnswer((_) => Stream.value(ConnectionStatus.connecting)); await tester.pumpWidget( MaterialApp( @@ -141,8 +137,7 @@ void main() { when(() => client.state).thenReturn(clientState); when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id')); - when(() => client.wsConnectionStatusStream) - .thenAnswer((_) => Stream.value(ConnectionStatus.connecting)); + when(() => client.wsConnectionStatusStream).thenAnswer((_) => Stream.value(ConnectionStatus.connecting)); var tapped = false; @@ -175,8 +170,7 @@ void main() { when(() => client.state).thenReturn(clientState); when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id')); - when(() => client.wsConnectionStatusStream) - .thenAnswer((_) => Stream.value(ConnectionStatus.connecting)); + when(() => client.wsConnectionStatusStream).thenAnswer((_) => Stream.value(ConnectionStatus.connected)); var tapped = 0; await tester.pumpWidget( @@ -199,7 +193,7 @@ void main() { await tester.pump(); await tester.tap(find.byType(StreamUserAvatar)); - await tester.tap(find.byType(StreamNeumorphicButton)); + await tester.tap(find.byIcon(StreamIconData.iconPlusLarge)); expect(tapped, 2); }, ); diff --git a/packages/stream_chat_flutter/test/src/channel/channel_name_test.dart b/packages/stream_chat_flutter/test/src/channel/channel_name_test.dart index 22cea8a99b..5bd913308c 100644 --- a/packages/stream_chat_flutter/test/src/channel/channel_name_test.dart +++ b/packages/stream_chat_flutter/test/src/channel/channel_name_test.dart @@ -25,14 +25,13 @@ void main() { when(() => channel.nameStream).thenAnswer((_) => Stream.value('test')); when(() => channel.name).thenReturn('test'); when(() => channelState.unreadCount).thenReturn(1); - when(() => channelState.unreadCountStream) - .thenAnswer((i) => Stream.value(1)); + when(() => channelState.unreadCountStream).thenAnswer((i) => Stream.value(1)); when(() => channelState.membersStream).thenAnswer( (_) => Stream.value([ Member( userId: 'user-id', user: User(id: 'user-id'), - ) + ), ]), ); when(() => channelState.members).thenReturn([ @@ -45,14 +44,14 @@ void main() { Message( text: 'hello', user: User(id: 'other-user'), - ) + ), ]); when(() => channelState.messagesStream).thenAnswer( (i) => Stream.value([ Message( text: 'hello', user: User(id: 'other-user'), - ) + ), ]), ); diff --git a/packages/stream_chat_flutter/test/src/channel/goldens/ci/channel_header_bottom_widget.png b/packages/stream_chat_flutter/test/src/channel/goldens/ci/channel_header_bottom_widget.png index a5be81e46f..0bd5c7fbd8 100644 Binary files a/packages/stream_chat_flutter/test/src/channel/goldens/ci/channel_header_bottom_widget.png and b/packages/stream_chat_flutter/test/src/channel/goldens/ci/channel_header_bottom_widget.png differ diff --git a/packages/stream_chat_flutter/test/src/channel/stream_message_preview_text_test.dart b/packages/stream_chat_flutter/test/src/channel/stream_message_preview_text_test.dart index a8e6f674d9..eb1ab51f47 100644 --- a/packages/stream_chat_flutter/test/src/channel/stream_message_preview_text_test.dart +++ b/packages/stream_chat_flutter/test/src/channel/stream_message_preview_text_test.dart @@ -1,3 +1,5 @@ +import 'dart:typed_data'; + import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; @@ -19,7 +21,6 @@ void main() { when(() => clientState.currentUser).thenReturn(currentUser); }); - // Helper to pump the message preview widget Future pumpMessagePreview( WidgetTester tester, Message message, { @@ -57,7 +58,7 @@ void main() { await tester.pump(); } - group('StreamMessagePreviewText', () { + group('Message types', () { testWidgets('renders regular text message', (tester) async { final message = Message( text: 'Hello, world!', @@ -67,9 +68,10 @@ void main() { await pumpMessagePreview(tester, message); expect(find.text('Hello, world!'), findsOneWidget); + expect(_findIcons(tester), isEmpty); }); - testWidgets('renders deleted message', (tester) async { + testWidgets('renders deleted message with ban icon', (tester) async { final message = Message( text: 'Original message', type: MessageType.deleted, @@ -79,10 +81,19 @@ void main() { await pumpMessagePreview(tester, message); - expect(find.text('Message deleted'), findsOneWidget); + final icons = _findIcons(tester); + expect(icons, hasLength(1)); + expect(icons.first.size, 16); + + expect(_extractText(tester), 'Message deleted'); + + final span = _getPreviewSpan(tester); + final styledSpans = _findTextSpans(span); + final deletedSpan = styledSpans.firstWhere((s) => s.text == 'Message deleted'); + expect(deletedSpan.style?.color, isNotNull); }); - testWidgets('renders system message', (tester) async { + testWidgets('renders system message with text', (tester) async { final message = Message( text: 'User joined the channel', type: MessageType.system, @@ -91,9 +102,10 @@ void main() { await pumpMessagePreview(tester, message); expect(find.text('User joined the channel'), findsOneWidget); + expect(_findIcons(tester), isEmpty); }); - testWidgets('renders empty system message', (tester) async { + testWidgets('renders system message without text as fallback label', (tester) async { final message = Message(type: MessageType.system); await pumpMessagePreview(tester, message); @@ -101,7 +113,7 @@ void main() { expect(find.text('System Message'), findsOneWidget); }); - testWidgets('renders empty message with no attachments', (tester) async { + testWidgets('renders empty message with no text or attachments', (tester) async { final message = Message( text: '', user: User(id: 'other-user-id', name: 'Other User'), @@ -109,10 +121,35 @@ void main() { await pumpMessagePreview(tester, message); - expect(find.text(''), findsOneWidget); + expect(find.byType(StreamMessagePreviewText), findsOneWidget); + expect(_findIcons(tester), isEmpty); + }); + + testWidgets('renders null text message as empty', (tester) async { + final message = Message( + user: User(id: 'other-user-id', name: 'Other User'), + ); + + await pumpMessagePreview(tester, message); + + expect(find.byType(StreamMessagePreviewText), findsOneWidget); }); - testWidgets('renders message with mentioned users in bold', (tester) async { + testWidgets('trims whitespace-only text as empty', (tester) async { + final message = Message( + text: ' ', + user: User(id: 'other-user-id', name: 'Other User'), + ); + + await pumpMessagePreview(tester, message); + + expect(find.byType(StreamMessagePreviewText), findsOneWidget); + expect(_findIcons(tester), isEmpty); + }); + }); + + group('Mentions', () { + testWidgets('renders mentioned users with bold styling', (tester) async { final mentionedUser = User(id: 'mentioned-id', name: 'Mentioned User'); final message = Message( text: 'Hello @Mentioned User, how are you?', @@ -124,305 +161,421 @@ void main() { expect(find.text('Hello @Mentioned User, how are you?'), findsOneWidget); - // Find the rich text and verify that it contains a valid TextSpan - final textWidget = tester.widget(find.byType(Text).last); - expect(textWidget.textSpan, isNotNull); + final span = _getPreviewSpan(tester); + final mentionSpan = _findTextSpans(span).firstWhere( + (s) => s.text == '@Mentioned User', + ); + expect(mentionSpan.style?.fontWeight, FontWeight.bold); }); - group('Attachments', () { - testWidgets('renders image attachment', (tester) async { - final message = Message( - text: '', - user: User(id: 'other-user-id', name: 'Other User'), - attachments: [ - Attachment( - type: AttachmentType.image, - ), - ], - ); + testWidgets('renders multiple mentions with bold styling', (tester) async { + final user1 = User(id: 'user-1', name: 'Alice'); + final user2 = User(id: 'user-2', name: 'Bob'); + final message = Message( + text: 'Hey @Alice and @Bob!', + user: User(id: 'other-user-id', name: 'Other User'), + mentionedUsers: [user1, user2], + ); - await pumpMessagePreview(tester, message); + await pumpMessagePreview(tester, message, textStyle: const TextStyle()); - expect(find.text('📷 Image'), findsOneWidget); - }); + expect(find.text('Hey @Alice and @Bob!'), findsOneWidget); - testWidgets('renders image attachment with text', (tester) async { - final message = Message( - text: 'Check this out', - user: User(id: 'other-user-id', name: 'Other User'), - attachments: [ - Attachment( - type: AttachmentType.image, - ), - ], - ); + final span = _getPreviewSpan(tester); + final textSpans = _findTextSpans(span); + final aliceSpan = textSpans.firstWhere((s) => s.text == '@Alice'); + final bobSpan = textSpans.firstWhere((s) => s.text == '@Bob'); + expect(aliceSpan.style?.fontWeight, FontWeight.bold); + expect(bobSpan.style?.fontWeight, FontWeight.bold); + }); - await pumpMessagePreview(tester, message); + testWidgets('renders message without matching mention as plain text', (tester) async { + final mentionedUser = User(id: 'mentioned-id', name: 'NoMatch'); + final message = Message( + text: 'Hello @SomeoneElse', + user: User(id: 'other-user-id', name: 'Other User'), + mentionedUsers: [mentionedUser], + ); - expect(find.text('📷 Check this out'), findsOneWidget); - }); + await pumpMessagePreview(tester, message); - testWidgets('renders video attachment', (tester) async { - final message = Message( - text: '', - user: User(id: 'other-user-id', name: 'Other User'), - attachments: [ - Attachment( - type: AttachmentType.video, - ), - ], - ); + expect(find.text('Hello @SomeoneElse'), findsOneWidget); + }); + }); - await pumpMessagePreview(tester, message); + group('Single attachments', () { + testWidgets('image attachment shows camera icon and "Photo" label', (tester) async { + final message = Message( + text: '', + user: User(id: 'other-user-id', name: 'Other User'), + attachments: [Attachment(type: AttachmentType.image)], + ); - expect(find.text('📹 Video'), findsOneWidget); - }); + await pumpMessagePreview(tester, message); - testWidgets('renders video attachment with text', (tester) async { - final message = Message( - text: 'Check this out', - user: User(id: 'other-user-id', name: 'Other User'), - attachments: [ - Attachment( - type: AttachmentType.video, - ), - ], - ); + final icons = _findIcons(tester); + expect(icons, hasLength(1)); + expect(icons.first.size, 16); + expect(_extractText(tester), 'Photo'); + }); - await pumpMessagePreview(tester, message); + testWidgets('image attachment with caption shows icon and caption', (tester) async { + final message = Message( + text: 'Check this out', + user: User(id: 'other-user-id', name: 'Other User'), + attachments: [Attachment(type: AttachmentType.image)], + ); - expect(find.text('📹 Check this out'), findsOneWidget); - }); + await pumpMessagePreview(tester, message); - testWidgets('renders file attachment', (tester) async { - final message = Message( - text: '', - user: User(id: 'other-user-id', name: 'Other User'), - attachments: [ - Attachment( - type: AttachmentType.file, - title: 'document.pdf', - ), - ], - ); + expect(_findIcons(tester), hasLength(1)); + expect(_extractText(tester), 'Check this out'); + }); - await pumpMessagePreview(tester, message); + testWidgets('video attachment shows video icon and "Video" label', (tester) async { + final message = Message( + text: '', + user: User(id: 'other-user-id', name: 'Other User'), + attachments: [Attachment(type: AttachmentType.video)], + ); - expect(find.text('📄 document.pdf'), findsOneWidget); - }); + await pumpMessagePreview(tester, message); - testWidgets('renders audio attachment', (tester) async { - final message = Message( - text: '', - user: User(id: 'other-user-id', name: 'Other User'), - attachments: [ - Attachment( - type: AttachmentType.audio, - ), - ], - ); + expect(_findIcons(tester), hasLength(1)); + expect(_extractText(tester), 'Video'); + }); + + testWidgets('video attachment with caption shows icon and caption', (tester) async { + final message = Message( + text: 'Watch this', + user: User(id: 'other-user-id', name: 'Other User'), + attachments: [Attachment(type: AttachmentType.video)], + ); - await pumpMessagePreview(tester, message); + await pumpMessagePreview(tester, message); - expect(find.text('🎧 Audio'), findsOneWidget); - }); + expect(_findIcons(tester), hasLength(1)); + expect(_extractText(tester), 'Watch this'); + }); - testWidgets('renders audio attachment with text', (tester) async { - final message = Message( - text: 'Check this out', - user: User(id: 'other-user-id', name: 'Other User'), - attachments: [ - Attachment( - type: AttachmentType.audio, - ), - ], - ); + testWidgets('file attachment shows file icon and "File" fallback label', (tester) async { + final message = Message( + text: '', + user: User(id: 'other-user-id', name: 'Other User'), + attachments: [Attachment(type: AttachmentType.file)], + ); - await pumpMessagePreview(tester, message); + await pumpMessagePreview(tester, message); - expect(find.text('🎧 Check this out'), findsOneWidget); - }); + expect(_findIcons(tester), hasLength(1)); + expect(_extractText(tester), 'File'); + }); - testWidgets('renders giphy attachment with text', (tester) async { - final message = Message( - text: 'Check this out', - user: User(id: 'other-user-id', name: 'Other User'), - attachments: [ - Attachment( - type: AttachmentType.giphy, - ), - ], - ); + testWidgets('file attachment with file name shows the file name', (tester) async { + final message = Message( + text: '', + user: User(id: 'other-user-id', name: 'Other User'), + attachments: [ + Attachment( + type: AttachmentType.file, + file: AttachmentFile(size: 100, bytes: Uint8List(100), name: 'report.pdf'), + ), + ], + ); - await pumpMessagePreview(tester, message); + await pumpMessagePreview(tester, message); - expect(find.text('/giphy Check this out'), findsOneWidget); - }); + expect(_findIcons(tester), hasLength(1)); + expect(_extractText(tester), 'report.pdf'); + }); - testWidgets('renders voice recording attachment', (tester) async { - final message = Message( - text: '', - user: User(id: 'other-user-id', name: 'Other User'), - attachments: [ - Attachment( - type: AttachmentType.voiceRecording, - ), - ], - ); + testWidgets('audio attachment shows microphone icon and "Audio" label', (tester) async { + final message = Message( + text: '', + user: User(id: 'other-user-id', name: 'Other User'), + attachments: [Attachment(type: AttachmentType.audio)], + ); - await pumpMessagePreview(tester, message); + await pumpMessagePreview(tester, message); - expect(find.text('🎤 Voice Recording'), findsOneWidget); - }); + expect(_findIcons(tester), hasLength(1)); + expect(_extractText(tester), 'Audio'); + }); - testWidgets('renders unknown attachment type with text', (tester) async { - final message = Message( - text: 'Some text', - user: User(id: 'other-user-id', name: 'Other User'), - attachments: [ - Attachment( - type: 'unknown', - ), - ], - ); + testWidgets('audio attachment with caption shows icon and caption', (tester) async { + final message = Message( + text: 'New podcast episode', + user: User(id: 'other-user-id', name: 'Other User'), + attachments: [Attachment(type: AttachmentType.audio)], + ); + + await pumpMessagePreview(tester, message); + + expect(_findIcons(tester), hasLength(1)); + expect(_extractText(tester), 'New podcast episode'); + }); + + testWidgets('voice recording shows microphone icon with duration', (tester) async { + final message = Message( + text: '', + user: User(id: 'other-user-id', name: 'Other User'), + attachments: [Attachment(type: AttachmentType.voiceRecording)], + ); + + await pumpMessagePreview(tester, message); + + expect(_findIcons(tester), hasLength(1)); + expect(_extractText(tester), contains('Voice Recording')); + expect(_extractText(tester), contains('00:00')); + }); + + testWidgets('voice recording with duration shows formatted time', (tester) async { + final message = Message( + text: '', + user: User(id: 'other-user-id', name: 'Other User'), + attachments: [ + Attachment( + type: AttachmentType.voiceRecording, + extraData: const {'duration': 125}, + ), + ], + ); + + await pumpMessagePreview(tester, message); + + expect(_findIcons(tester), hasLength(1)); + expect(_extractText(tester), contains('Voice Recording')); + expect(_extractText(tester), contains('02:05')); + }); + + testWidgets('giphy attachment shows /giphy text prefix (no icon)', (tester) async { + final message = Message( + text: 'funny cat', + user: User(id: 'other-user-id', name: 'Other User'), + attachments: [Attachment(type: AttachmentType.giphy)], + ); + + await pumpMessagePreview(tester, message); + + expect(_findIcons(tester), isEmpty); + expect(_extractText(tester), contains('/giphy')); + expect(_extractText(tester), contains('funny cat')); + }); + + testWidgets('unknown attachment type shows file icon with text', (tester) async { + final message = Message( + text: 'Some text', + user: User(id: 'other-user-id', name: 'Other User'), + attachments: [Attachment(type: 'custom_type')], + ); + + await pumpMessagePreview(tester, message); + + expect(_findIcons(tester), hasLength(1)); + expect(_extractText(tester), 'Some text'); + }); + + testWidgets('unknown attachment type without text shows file icon only', (tester) async { + final message = Message( + text: '', + user: User(id: 'other-user-id', name: 'Other User'), + attachments: [Attachment(type: 'custom_type')], + ); + + await pumpMessagePreview(tester, message); + + expect(_findIcons(tester), hasLength(1)); + expect(_extractText(tester), isEmpty); + }); + }); + + group('Multiple same-type attachments', () { + testWidgets('multiple images show camera icon and photo count', (tester) async { + final message = Message( + text: '', + user: User(id: 'other-user-id', name: 'Other User'), + attachments: [ + Attachment(type: AttachmentType.image), + Attachment(type: AttachmentType.image), + Attachment(type: AttachmentType.image), + ], + ); + + await pumpMessagePreview(tester, message); + + expect(_findIcons(tester), hasLength(1)); + expect(_extractText(tester), '3 photos'); + }); + + testWidgets('multiple videos show video icon and video count', (tester) async { + final message = Message( + text: '', + user: User(id: 'other-user-id', name: 'Other User'), + attachments: [ + Attachment(type: AttachmentType.video), + Attachment(type: AttachmentType.video), + ], + ); + + await pumpMessagePreview(tester, message); + + expect(_findIcons(tester), hasLength(1)); + expect(_extractText(tester), '2 videos'); + }); + + testWidgets('multiple files show file icon and file count', (tester) async { + final message = Message( + text: '', + user: User(id: 'other-user-id', name: 'Other User'), + attachments: [ + Attachment(type: AttachmentType.file), + Attachment(type: AttachmentType.file), + Attachment(type: AttachmentType.file), + Attachment(type: AttachmentType.file), + ], + ); + + await pumpMessagePreview(tester, message); + + expect(_findIcons(tester), hasLength(1)); + expect(_extractText(tester), '4 files'); + }); + + testWidgets('multiple images with caption show icon and caption text', (tester) async { + final message = Message( + text: 'Vacation photos', + user: User(id: 'other-user-id', name: 'Other User'), + attachments: [ + Attachment(type: AttachmentType.image), + Attachment(type: AttachmentType.image), + ], + ); + + await pumpMessagePreview(tester, message); + + expect(_findIcons(tester), hasLength(1)); + expect(_extractText(tester), 'Vacation photos'); + }); + }); + + group('Mixed-type attachments', () { + testWidgets('mixed types show generic file icon and total count', (tester) async { + final message = Message( + text: '', + user: User(id: 'other-user-id', name: 'Other User'), + attachments: [ + Attachment(type: AttachmentType.image), + Attachment(type: AttachmentType.video), + ], + ); + + await pumpMessagePreview(tester, message); + + expect(_findIcons(tester), hasLength(1)); + expect(_extractText(tester), '2 files'); + }); + + testWidgets('three mixed types show count of all', (tester) async { + final message = Message( + text: '', + user: User(id: 'other-user-id', name: 'Other User'), + attachments: [ + Attachment(type: AttachmentType.image), + Attachment(type: AttachmentType.video), + Attachment(type: AttachmentType.file), + ], + ); - await pumpMessagePreview(tester, message); + await pumpMessagePreview(tester, message); - expect(find.text('Some text'), findsOneWidget); - }); + expect(_findIcons(tester), hasLength(1)); + expect(_extractText(tester), '3 files'); }); - group('Poll Tests', () { - testWidgets('renders poll with latest voter (current user)', - (tester) async { - final voterPoll = Poll( + testWidgets('mixed types with caption show generic icon and caption', (tester) async { + final message = Message( + text: 'Mixed media', + user: User(id: 'other-user-id', name: 'Other User'), + attachments: [ + Attachment(type: AttachmentType.image), + Attachment(type: AttachmentType.file), + ], + ); + + await pumpMessagePreview(tester, message); + + expect(_findIcons(tester), hasLength(1)); + expect(_extractText(tester), 'Mixed media'); + }); + }); + + group('Polls', () { + testWidgets('poll shows chart icon and poll name', (tester) async { + final message = Message( + user: User(id: 'other-user-id', name: 'Poll Creator'), + poll: Poll( name: 'Favorite Color?', options: const [ PollOption(id: 'option-1', text: 'Red'), PollOption(id: 'option-2', text: 'Blue'), ], - latestVotesByOption: { - 'option-1': [ - PollVote( - user: currentUser, - optionId: 'option-1', - ), - ], - }, - ); - - final message = Message( - user: User(id: 'other-user-id', name: 'Poll Creator'), - poll: voterPoll, - ); + ), + ); - await pumpMessagePreview(tester, message); + await pumpMessagePreview(tester, message); - expect(find.text('📊 You voted: "Favorite Color?"'), findsOneWidget); - }); + expect(_findIcons(tester), hasLength(1)); + expect(_extractText(tester), 'Favorite Color?'); + }); - testWidgets('renders poll with latest voter (another user)', - (tester) async { - final voter = User(id: 'voter-id', name: 'Voter'); - final voterPoll = Poll( - name: 'Favorite Color?', + testWidgets('poll with empty name shows chart icon only', (tester) async { + final message = Message( + user: User(id: 'other-user-id', name: 'Message Sender'), + poll: Poll( + name: ' ', options: const [ PollOption(id: 'option-1', text: 'Red'), PollOption(id: 'option-2', text: 'Blue'), ], - latestVotesByOption: { - 'option-1': [ - PollVote( - user: voter, - optionId: 'option-1', - ), - ], - }, - ); - - final message = Message( - user: User(id: 'other-user-id', name: 'Poll Creator'), - poll: voterPoll, - ); - - await pumpMessagePreview(tester, message); - - expect(find.text('📊 Voter voted: "Favorite Color?"'), findsOneWidget); - }); - - testWidgets('renders poll with creator (current user)', (tester) async { - final message = Message( - user: User(id: 'other-user-id', name: 'Message Sender'), - poll: Poll( - name: 'Favorite Color?', - options: const [ - PollOption(id: 'option-1', text: 'Red'), - PollOption(id: 'option-2', text: 'Blue'), - ], - createdBy: currentUser, - ), - ); - - await pumpMessagePreview(tester, message); - - expect(find.text('📊 You created: "Favorite Color?"'), findsOneWidget); - }); - - testWidgets('renders poll with creator (another user)', (tester) async { - final creator = User(id: 'creator-id', name: 'Alex'); - final message = Message( - user: User(id: 'other-user-id', name: 'Message Sender'), - poll: Poll( - name: 'Favorite Color?', - options: const [ - PollOption(id: 'option-1', text: 'Red'), - PollOption(id: 'option-2', text: 'Blue'), - ], - createdBy: creator, - ), - ); - - await pumpMessagePreview(tester, message); - - expect(find.text('📊 Alex created: "Favorite Color?"'), findsOneWidget); - }); - - testWidgets('renders poll with only name', (tester) async { - final message = Message( - user: User(id: 'other-user-id', name: 'Message Sender'), - poll: Poll( - name: 'Favorite Color?', - options: const [ - PollOption(id: 'option-1', text: 'Red'), - PollOption(id: 'option-2', text: 'Blue'), - ], - ), - ); - - await pumpMessagePreview(tester, message); - - expect(find.text('📊 Favorite Color?'), findsOneWidget); - }); - - testWidgets('renders poll with empty name', (tester) async { - final message = Message( - user: User(id: 'other-user-id', name: 'Message Sender'), - poll: Poll( - name: ' ', - options: const [ - PollOption(id: 'option-1', text: 'Red'), - PollOption(id: 'option-2', text: 'Blue'), - ], - ), - ); + ), + ); - await pumpMessagePreview(tester, message); + await pumpMessagePreview(tester, message); - expect(find.text('📊'), findsOneWidget); - }); + expect(_findIcons(tester), hasLength(1)); + expect(_extractText(tester), isEmpty); }); - testWidgets('renders location attachment', (tester) async { + testWidgets('poll in group channel doesnt includes sender prefix', (tester) async { + final channel = ChannelModel( + id: 'test-channel', + type: 'messaging', + memberCount: 3, + ); + + final message = Message( + user: User(id: 'test-user-id', name: 'Test User'), + poll: Poll( + name: 'Lunch spot?', + options: const [ + PollOption(id: 'option-1', text: 'Pizza'), + PollOption(id: 'option-2', text: 'Sushi'), + ], + ), + ); + + await pumpMessagePreview(tester, message, channel: channel); + + expect(_findIcons(tester), hasLength(1)); + + final text = _extractText(tester); + expect(text, isNot(contains('You: '))); + expect(text, contains('Lunch spot?')); + }); + }); + + group('Locations', () { + testWidgets('static location shows map pin icon and location label', (tester) async { final message = Message( text: '', user: User(id: 'other-user-id', name: 'Other User'), @@ -434,10 +587,12 @@ void main() { await pumpMessagePreview(tester, message); - expect(find.text('📍 Location'), findsOneWidget); + expect(_findIcons(tester), hasLength(1)); + expect(_extractText(tester), contains('Location')); + expect(_extractText(tester), isNot(contains('Live'))); }); - testWidgets('renders live location attachment', (tester) async { + testWidgets('live location shows map pin icon and live location label', (tester) async { final message = Message( text: '', user: User(id: 'other-user-id', name: 'Other User'), @@ -450,133 +605,205 @@ void main() { await pumpMessagePreview(tester, message); - expect(find.text('📍 Live Location'), findsOneWidget); + expect(_findIcons(tester), hasLength(1)); + expect(_extractText(tester), contains('Live Location')); }); - testWidgets('supports different language for translation', (tester) async { + testWidgets('location with caption shows map pin icon and caption text', (tester) async { final message = Message( - text: 'Hello, world!', + text: 'Meet me here', user: User(id: 'other-user-id', name: 'Other User'), - i18n: const { - 'fr_text': 'Bonjour, monde!', - }, + sharedLocation: Location( + latitude: 37.7749, + longitude: -122.4194, + ), ); - await pumpMessagePreview(tester, message, language: 'fr'); + await pumpMessagePreview(tester, message); - expect(find.text('Bonjour, monde!'), findsOneWidget); + expect(_findIcons(tester), hasLength(1)); + expect(_extractText(tester), 'Meet me here'); }); + }); - group('Channel-specific behaviors', () { - testWidgets( - 'prepends "You:" for current user\'s messages in group channels', - (tester) async { - final channel = ChannelModel( - id: 'test-channel', - type: 'messaging', - memberCount: 3, - ); + group('Channel context', () { + testWidgets('group channel prepends bold "You:" for current user', (tester) async { + final channel = ChannelModel( + id: 'test-channel', + type: 'messaging', + memberCount: 3, + ); - final message = Message( - text: 'Hello everyone', - user: User(id: 'test-user-id', name: 'Test User'), // Current user - ); + final message = Message( + text: 'Hello everyone', + user: User(id: 'test-user-id', name: 'Test User'), + ); - await pumpMessagePreview(tester, message, channel: channel); + await pumpMessagePreview(tester, message, channel: channel); - expect(find.text('You: Hello everyone'), findsOneWidget); - }, + expect(find.text('You: Hello everyone'), findsOneWidget); + + final span = _getPreviewSpan(tester); + final youSpan = _findTextSpans(span).firstWhere((s) => s.text == 'You: '); + expect(youSpan.style?.fontWeight, FontWeight.bold); + }); + + testWidgets('group channel prepends bold first name for other users', (tester) async { + final channel = ChannelModel( + id: 'test-channel', + type: 'messaging', + memberCount: 3, + ); + + final message = Message( + text: 'Hello everyone', + user: User(id: 'other-user-id', name: 'Jane Doe'), + ); + + await pumpMessagePreview(tester, message, channel: channel); + + expect(find.text('Jane: Hello everyone'), findsOneWidget); + + final span = _getPreviewSpan(tester); + final nameSpan = _findTextSpans(span).firstWhere((s) => s.text == 'Jane: '); + expect(nameSpan.style?.fontWeight, FontWeight.bold); + }); + + testWidgets('group channel skips prefix when message has no user', (tester) async { + final channel = ChannelModel( + id: 'test-channel', + type: 'messaging', + memberCount: 3, + ); + + final message = Message(text: 'Hello'); + + await pumpMessagePreview(tester, message, channel: channel); + + expect(find.text('Hello'), findsOneWidget); + }); + + testWidgets('1:1 channel does not prepend author name', (tester) async { + final channel = ChannelModel( + id: 'test-channel', + type: 'messaging', + memberCount: 2, + ); + + final message = Message( + text: 'Hello there', + user: User(id: 'other-user-id', name: 'Jane Doe'), ); - testWidgets( - 'prepends author name for other messages in group channels', - (tester) async { - final channel = ChannelModel( - id: 'test-channel', - type: 'messaging', - memberCount: 3, - ); + await pumpMessagePreview(tester, message, channel: channel); - final message = Message( - text: 'Hello everyone', - user: User(id: 'other-user-id', name: 'Jane Doe'), - ); + expect(find.text('Hello there'), findsOneWidget); + }); + + testWidgets('no channel does not prepend author name', (tester) async { + final message = Message( + text: 'Hello there', + user: User(id: 'other-user-id', name: 'Jane Doe'), + ); + + await pumpMessagePreview(tester, message); + + expect(find.text('Hello there'), findsOneWidget); + }); + + testWidgets('group channel with attachment includes sender prefix', (tester) async { + final channel = ChannelModel( + id: 'test-channel', + type: 'messaging', + memberCount: 3, + ); + + final message = Message( + text: '', + user: User(id: 'other-user-id', name: 'Jane Doe'), + attachments: [Attachment(type: AttachmentType.image)], + ); - await pumpMessagePreview(tester, message, channel: channel); + await pumpMessagePreview(tester, message, channel: channel); - expect(find.text('Jane Doe: Hello everyone'), findsOneWidget); + final text = _extractText(tester); + expect(text, contains('Jane: ')); + expect(text, contains('Photo')); + }); + }); + + group('Translations', () { + testWidgets('uses explicit language parameter for translation', (tester) async { + final message = Message( + text: 'Hello, world!', + user: User(id: 'other-user-id', name: 'Other User'), + i18n: const { + 'fr_text': 'Bonjour, monde!', }, ); - testWidgets( - 'does not prepend author name in 1:1 channels', - (tester) async { - final channel = ChannelModel( - id: 'test-channel', - type: 'messaging', - memberCount: 2, - ); + await pumpMessagePreview(tester, message, language: 'fr'); + + expect(find.text('Bonjour, monde!'), findsOneWidget); + }); - final message = Message( - text: 'Hello there', - user: User(id: 'other-user-id', name: 'Jane Doe'), - ); + testWidgets('falls back to user language when no explicit language', (tester) async { + final client = MockClient(); + final clientState = MockClientState(); + final currentUser = OwnUser( + id: 'test-user-id', + name: 'Test User', + language: 'es', + ); - await pumpMessagePreview(tester, message, channel: channel); + when(() => client.state).thenReturn(clientState); + when(() => clientState.currentUser).thenReturn(currentUser); - expect(find.text('Hello there'), findsOneWidget); + final message = Message( + text: 'Hello, world!', + user: User(id: 'other-user-id', name: 'Other User'), + i18n: const { + 'es_text': 'Hola, mundo!', }, ); - testWidgets( - 'falls back to user language for translation when available', - (tester) async { - final client = MockClient(); - final clientState = MockClientState(); - final currentUser = OwnUser( - id: 'test-user-id', - name: 'Test User', - language: 'es', // Spanish language - ); - - when(() => client.state).thenReturn(clientState); - when(() => clientState.currentUser).thenReturn(currentUser); - - final message = Message( - text: 'Hello, world!', - user: User(id: 'other-user-id', name: 'Other User'), - i18n: const { - 'es_text': 'Hola, mundo!', - }, - ); - - await tester.pumpWidget( - MaterialApp( - home: Scaffold( - body: StreamChat( - client: client, - streamChatThemeData: StreamChatThemeData.light(), - child: Center( - child: StreamMessagePreviewText( - message: message, - ), - ), - ), + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: StreamChat( + client: client, + streamChatThemeData: StreamChatThemeData.light(), + child: Center( + child: StreamMessagePreviewText(message: message), ), ), - ); - await tester.pump(); + ), + ), + ); + await tester.pump(); - expect(find.text('Hola, mundo!'), findsOneWidget); + expect(find.text('Hola, mundo!'), findsOneWidget); + }); + + testWidgets('falls back to original text when translation missing', (tester) async { + final message = Message( + text: 'Hello, world!', + user: User(id: 'other-user-id', name: 'Other User'), + i18n: const { + 'fr_text': 'Bonjour, monde!', }, ); + + await pumpMessagePreview(tester, message, language: 'de'); + + expect(find.text('Hello, world!'), findsOneWidget); }); }); group('Custom MessagePreviewFormatter', () { const customFormatter = _CustomMessagePreviewFormatter(); - testWidgets('can override formatCurrentUserMessage', (tester) async { + testWidgets('can remove current user prefix via formatGroupMessage', (tester) async { final channel = ChannelModel( id: 'test-channel', type: 'messaging', @@ -585,7 +812,7 @@ void main() { final message = Message( text: 'Hello everyone', - user: User(id: 'test-user-id', name: 'Test User'), // Current user + user: User(id: 'test-user-id', name: 'Test User'), ); await pumpMessagePreview( @@ -597,12 +824,11 @@ void main() { ), ); - // Custom formatter removes "You:" prefix expect(find.text('Hello everyone'), findsOneWidget); expect(find.text('You: Hello everyone'), findsNothing); }); - testWidgets('can override formatGroupMessage', (tester) async { + testWidgets('can customize group message prefix via formatGroupMessage', (tester) async { final channel = ChannelModel( id: 'test-channel', type: 'messaging', @@ -623,11 +849,10 @@ void main() { ), ); - // Custom formatter uses "says:" instead of ":" expect(find.text('John Doe says: Hello'), findsOneWidget); }); - testWidgets('can override formatPollMessage', (tester) async { + testWidgets('can customize poll formatting via formatPollMessage', (tester) async { final message = Message( user: User(id: 'other-user-id', name: 'Message Sender'), poll: Poll( @@ -647,11 +872,11 @@ void main() { ), ); - // Custom formatter uses different format expect(find.text('📊 Poll: Favorite Color?'), findsOneWidget); + expect(_findIcons(tester), isEmpty); }); - testWidgets('can override formatLocationMessage', (tester) async { + testWidgets('can customize location formatting via formatLocationMessage', (tester) async { final message = Message( user: User(id: 'other-user-id', name: 'Message Sender'), sharedLocation: Location( @@ -668,11 +893,11 @@ void main() { ), ); - // Custom formatter uses different format expect(find.text('🗺️ -> Location Shared'), findsOneWidget); + expect(_findIcons(tester), isEmpty); }); - testWidgets('can override formatMessageAttachments', (tester) async { + testWidgets('can handle custom attachment types via formatMessageAttachments', (tester) async { final message = Message( text: '', user: User(id: 'user-id'), @@ -692,11 +917,29 @@ void main() { ), ); - // Custom formatter handles custom attachment type expect(find.text('🛍️ iPhone'), findsOneWidget); }); - testWidgets('can override formatDirectMessage', (tester) async { + testWidgets('custom formatter falls through to default for known types', (tester) async { + final message = Message( + text: '', + user: User(id: 'user-id'), + attachments: [Attachment(type: AttachmentType.image)], + ); + + await pumpMessagePreview( + tester, + message, + configData: StreamChatConfigurationData( + messagePreviewFormatter: customFormatter, + ), + ); + + expect(_findIcons(tester), hasLength(1)); + expect(_extractText(tester), 'Photo'); + }); + + testWidgets('can customize direct message via formatMessage override', (tester) async { final channel = ChannelModel( id: 'test-channel', type: 'messaging', @@ -717,72 +960,154 @@ void main() { ), ); - // Custom formatter adds emoji prefix expect(find.text('💬 Hey there'), findsOneWidget); }); }); } -// Custom formatter for testing overrides +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +/// Extracts concatenated text from all [TextSpan]s in the preview, ignoring +/// [WidgetSpan]s (icons, spacers). +String _extractText(WidgetTester tester) { + final span = _getPreviewSpan(tester); + return _spanText(span); +} + +String _spanText(InlineSpan span) { + if (span is TextSpan) { + final buffer = StringBuffer(span.text ?? ''); + for (final child in span.children ?? []) { + buffer.write(_spanText(child)); + } + return buffer.toString(); + } + return ''; +} + +/// Returns the root [TextSpan] rendered by the [StreamMessagePreviewText]. +TextSpan _getPreviewSpan(WidgetTester tester) { + final text = tester.widget( + find.descendant( + of: find.byType(StreamMessagePreviewText), + matching: find.byType(Text), + ), + ); + return text.textSpan! as TextSpan; +} + +/// Recursively collects all leaf [TextSpan]s that have non-null [text]. +List _findTextSpans(InlineSpan span) { + final result = []; + if (span is TextSpan) { + if (span.text != null) result.add(span); + for (final child in span.children ?? []) { + result.addAll(_findTextSpans(child)); + } + } + return result; +} + +/// Finds all [Icon] widgets rendered inside the [StreamMessagePreviewText]. +List _findIcons(WidgetTester tester) { + return tester + .widgetList( + find.descendant( + of: find.byType(StreamMessagePreviewText), + matching: find.byType(Icon), + ), + ) + .toList(); +} + +// --------------------------------------------------------------------------- +// Custom formatter for override tests +// --------------------------------------------------------------------------- + class _CustomMessagePreviewFormatter extends StreamMessagePreviewFormatter { const _CustomMessagePreviewFormatter(); @override - String formatCurrentUserMessage(BuildContext context, String messageText) { - // Remove "You:" prefix - return messageText; + TextSpan formatMessage( + BuildContext context, + Message message, { + bool showCaption = true, + ChannelModel? channel, + User? currentUser, + TextStyle? textStyle, + }) { + if (channel != null && channel.memberCount <= 2) { + final text = message.text ?? ''; + return TextSpan(text: '💬 $text', style: textStyle); + } + return super.formatMessage( + context, + message, + showCaption: showCaption, + channel: channel, + currentUser: currentUser, + textStyle: textStyle, + ); } @override - String formatGroupMessage( + TextSpan? formatGroupMessage( BuildContext context, User? messageAuthor, - String messageText, + User? currentUser, ) { + if (messageAuthor?.id == currentUser?.id) return null; + final authorName = messageAuthor?.name; - if (authorName == null || authorName.isEmpty) return messageText; + if (authorName == null || authorName.isEmpty) return null; - // Use "says:" instead of ":" - return '$authorName says: $messageText'; + return TextSpan(text: '$authorName says: '); } @override - String formatPollMessage( + TextSpan formatPollMessage( BuildContext context, Poll poll, User? currentUser, ) { - // Simple format with "Poll:" prefix - return poll.name.isEmpty ? '📊 Poll' : '📊 Poll: ${poll.name}'; - } - - @override - String formatLocationMessage(BuildContext context, Location location) { - // Simple format with custom emoji - return '🗺️ -> Location Shared'; + return TextSpan( + text: poll.name.trim().isEmpty ? '📊 Poll' : '📊 Poll: ${poll.name}', + ); } @override - String formatDirectMessage(BuildContext context, String messageText) { - // Add emoji prefix - return '💬 $messageText'; + TextSpan formatLocationMessage( + BuildContext context, + Message message, + Location location, { + bool showCaption = true, + }) { + return const TextSpan(text: '🗺️ -> Location Shared'); } @override - String? formatMessageAttachments( + TextSpan? formatMessageAttachments( BuildContext context, String? messageText, - Iterable attachments, - ) { + Iterable attachments, { + List mentionedUsers = const [], + bool showCaption = true, + }) { final attachment = attachments.firstOrNull; - // Handle custom product attachment type if (attachment?.type == 'product') { final title = attachment?.extraData['title'] as String?; - return '🛍️ ${title ?? "Product"}'; + return TextSpan(text: '🛍️ ${title ?? "Product"}'); } - // Fallback to default implementation - return super.formatMessageAttachments(context, messageText, attachments); + return super.formatMessageAttachments( + context, + messageText, + attachments, + mentionedUsers: mentionedUsers, + showCaption: showCaption, + ); } } diff --git a/packages/stream_chat_flutter/test/src/dialogs/confirmation_dialog_test.dart b/packages/stream_chat_flutter/test/src/dialogs/confirmation_dialog_test.dart index 27e74c5892..671837c4d7 100644 --- a/packages/stream_chat_flutter/test/src/dialogs/confirmation_dialog_test.dart +++ b/packages/stream_chat_flutter/test/src/dialogs/confirmation_dialog_test.dart @@ -18,12 +18,9 @@ void main() { child: StreamChatTheme( data: StreamChatThemeData.light(), child: ConfirmationDialog( - titleText: context.translations - .toggleMuteUnmuteUserText(isMuted: false), - promptText: context.translations - .toggleMuteUnmuteUserQuestion(isMuted: false), - affirmativeText: context.translations - .toggleMuteUnmuteAction(isMuted: false), + titleText: context.translations.toggleMuteUnmuteUserText(isMuted: false), + promptText: context.translations.toggleMuteUnmuteUserQuestion(isMuted: false), + affirmativeText: context.translations.toggleMuteUnmuteAction(isMuted: false), onConfirmation: () {}, ), ), @@ -36,8 +33,7 @@ void main() { expect(find.byType(AlertDialog), findsOneWidget); expect(find.text('Mute User'), findsOneWidget); - expect(find.text('Are you sure you want to mute this user?'), - findsOneWidget); + expect(find.text('Are you sure you want to mute this user?'), findsOneWidget); expect(find.text('MUTE'), findsOneWidget); }); @@ -53,12 +49,9 @@ void main() { child: StreamChatTheme( data: StreamChatThemeData.light(), child: ConfirmationDialog( - titleText: context.translations - .toggleMuteUnmuteUserText(isMuted: false), - promptText: context.translations - .toggleMuteUnmuteUserQuestion(isMuted: false), - affirmativeText: context.translations - .toggleMuteUnmuteAction(isMuted: false), + titleText: context.translations.toggleMuteUnmuteUserText(isMuted: false), + promptText: context.translations.toggleMuteUnmuteUserQuestion(isMuted: false), + affirmativeText: context.translations.toggleMuteUnmuteAction(isMuted: false), onConfirmation: () {}, ), ), diff --git a/packages/stream_chat_flutter/test/src/dialogs/goldens/ci/confirmation_dialog_0.png b/packages/stream_chat_flutter/test/src/dialogs/goldens/ci/confirmation_dialog_0.png index 4c69a1e814..c426684cc6 100644 Binary files a/packages/stream_chat_flutter/test/src/dialogs/goldens/ci/confirmation_dialog_0.png and b/packages/stream_chat_flutter/test/src/dialogs/goldens/ci/confirmation_dialog_0.png differ diff --git a/packages/stream_chat_flutter/test/src/dialogs/goldens/ci/delete_message_dialog_0.png b/packages/stream_chat_flutter/test/src/dialogs/goldens/ci/delete_message_dialog_0.png index 53706b4f8c..99f0dc354b 100644 Binary files a/packages/stream_chat_flutter/test/src/dialogs/goldens/ci/delete_message_dialog_0.png and b/packages/stream_chat_flutter/test/src/dialogs/goldens/ci/delete_message_dialog_0.png differ diff --git a/packages/stream_chat_flutter/test/src/dialogs/goldens/ci/message_dialog_0.png b/packages/stream_chat_flutter/test/src/dialogs/goldens/ci/message_dialog_0.png index 0e3074511d..ef6313474e 100644 Binary files a/packages/stream_chat_flutter/test/src/dialogs/goldens/ci/message_dialog_0.png and b/packages/stream_chat_flutter/test/src/dialogs/goldens/ci/message_dialog_0.png differ diff --git a/packages/stream_chat_flutter/test/src/dialogs/goldens/ci/message_dialog_1.png b/packages/stream_chat_flutter/test/src/dialogs/goldens/ci/message_dialog_1.png index 0e6d1770b8..2dd9ed245d 100644 Binary files a/packages/stream_chat_flutter/test/src/dialogs/goldens/ci/message_dialog_1.png and b/packages/stream_chat_flutter/test/src/dialogs/goldens/ci/message_dialog_1.png differ diff --git a/packages/stream_chat_flutter/test/src/dialogs/goldens/ci/message_dialog_2.png b/packages/stream_chat_flutter/test/src/dialogs/goldens/ci/message_dialog_2.png index 0e3074511d..ef6313474e 100644 Binary files a/packages/stream_chat_flutter/test/src/dialogs/goldens/ci/message_dialog_2.png and b/packages/stream_chat_flutter/test/src/dialogs/goldens/ci/message_dialog_2.png differ diff --git a/packages/stream_chat_flutter/test/src/fakes.dart b/packages/stream_chat_flutter/test/src/fakes.dart index 3c54a85aa6..d488a3f365 100644 --- a/packages/stream_chat_flutter/test/src/fakes.dart +++ b/packages/stream_chat_flutter/test/src/fakes.dart @@ -12,9 +12,7 @@ const String kApplicationDocumentsPath = 'applicationDocumentsPath'; const String kExternalCachePath = 'externalCachePath'; const String kExternalStoragePath = 'externalStoragePath'; -class FakePathProviderPlatform extends Fake - with MockPlatformInterfaceMixin - implements PathProviderPlatform { +class FakePathProviderPlatform extends Fake with MockPlatformInterfaceMixin implements PathProviderPlatform { @override Future getTemporaryPath() async { return kTemporaryPath; @@ -58,9 +56,7 @@ class FakePathProviderPlatform extends Fake } } -class AllNullFakePathProviderPlatform extends Fake - with MockPlatformInterfaceMixin - implements PathProviderPlatform { +class AllNullFakePathProviderPlatform extends Fake with MockPlatformInterfaceMixin implements PathProviderPlatform { @override Future getTemporaryPath() async { return null; @@ -104,9 +100,7 @@ class AllNullFakePathProviderPlatform extends Fake } } -class FakeRecordPlatform extends Fake - with MockPlatformInterfaceMixin - implements RecordPlatform { +class FakeRecordPlatform extends Fake with MockPlatformInterfaceMixin implements RecordPlatform { @override Future create(String recorderId) async {} @@ -143,9 +137,7 @@ class FakeRecordPlatform extends Fake Future dispose(String recorderId) async {} } -class FakeConnectivityPlatform extends Fake - with MockPlatformInterfaceMixin - implements ConnectivityPlatform { +class FakeConnectivityPlatform extends Fake with MockPlatformInterfaceMixin implements ConnectivityPlatform { @override Future> checkConnectivity() { return Future.value([ConnectivityResult.wifi]); diff --git a/packages/stream_chat_flutter/test/src/full_screen_media/full_screen_media_test.dart b/packages/stream_chat_flutter/test/src/full_screen_media/full_screen_media_test.dart index 43cb1bb35a..a9f0b6b5f0 100644 --- a/packages/stream_chat_flutter/test/src/full_screen_media/full_screen_media_test.dart +++ b/packages/stream_chat_flutter/test/src/full_screen_media/full_screen_media_test.dart @@ -36,7 +36,7 @@ void main() { Member( userId: 'user-id', user: User(id: 'user-id'), - ) + ), ]), ); when(() => channelState.members).thenReturn([ @@ -49,24 +49,24 @@ void main() { Message( text: 'hello', user: User(id: 'other-user'), - ) + ), ]); when(() => channelState.messagesStream).thenAnswer( (i) => Stream.value([ Message( text: 'hello', user: User(id: 'other-user'), - ) + ), ]), ); - when(() => channelState.typingEvents).thenAnswer((i) => { - User(id: 'other-user', extraData: const {'name': 'demo'}): - Event(type: EventType.typingStart), - }); + when(() => channelState.typingEvents).thenAnswer( + (i) => { + User(id: 'other-user', extraData: const {'name': 'demo'}): Event(type: EventType.typingStart), + }, + ); when(() => channelState.typingEventsStream).thenAnswer( (i) => Stream.value({ - User(id: 'other-user', extraData: const {'name': 'demo'}): - Event(type: EventType.typingStart), + User(id: 'other-user', extraData: const {'name': 'demo'}): Event(type: EventType.typingStart), }), ); @@ -81,28 +81,30 @@ void main() { attachment, ], ); - await tester.pumpWidget(MaterialApp( - home: StreamChat( - client: client, - child: StreamChannel( - channel: channel, - child: StreamFullScreenMedia( - mediaAttachmentPackages: [ - StreamAttachmentPackage( - attachment: attachment, - message: message, - ), - ], + await tester.pumpWidget( + MaterialApp( + home: StreamChat( + client: client, + child: StreamChannel( + channel: channel, + child: StreamFullScreenMedia( + mediaAttachmentPackages: [ + StreamAttachmentPackage( + attachment: attachment, + message: message, + ), + ], + ), ), ), ), - )); + ); // wait for the initial state to be rendered. await tester.pump(Duration.zero); expect(find.byType(PhotoView), findsOneWidget); - expect(find.byType(StreamSvgIcon), findsNWidgets(4)); + expect(find.byType(Icon), findsNWidgets(4)); }, ); } diff --git a/packages/stream_chat_flutter/test/src/gallery/gallery_footer_test.dart b/packages/stream_chat_flutter/test/src/gallery/gallery_footer_test.dart index 7480968063..cfb4cd9efa 100644 --- a/packages/stream_chat_flutter/test/src/gallery/gallery_footer_test.dart +++ b/packages/stream_chat_flutter/test/src/gallery/gallery_footer_test.dart @@ -13,8 +13,7 @@ void main() { late MockClientState clientState; late MockChannel channel; late MockChannelState channelState; - const methodChannel = - MethodChannel('dev.fluttercommunity.plus/connectivity_status'); + const methodChannel = MethodChannel('dev.fluttercommunity.plus/connectivity_status'); setUpAll(() { client = MockClient(); @@ -41,13 +40,12 @@ void main() { }); setUp(() { - TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger - .setMockMethodCallHandler(methodChannel, (MethodCall methodCall) async { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(methodChannel, ( + MethodCall methodCall, + ) async { if (methodCall.method == 'listen') { try { - await TestDefaultBinaryMessengerBinding - .instance.defaultBinaryMessenger - .handlePlatformMessage( + await TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.handlePlatformMessage( methodChannel.name, methodChannel.codec.encodeSuccessEnvelope(['wifi']), (_) {}, @@ -85,7 +83,7 @@ void main() { // wait for the initial state to be rendered. await tester.pumpAndSettle(); - expect(find.byType(StreamSvgIcon), findsNWidgets(2)); + expect(find.byType(Icon), findsNWidgets(2)); }, ); @@ -112,7 +110,6 @@ void main() { ); tearDown(() { - TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger - .setMockMethodCallHandler(methodChannel, null); + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(methodChannel, null); }); } diff --git a/packages/stream_chat_flutter/test/src/gallery/gallery_header_test.dart b/packages/stream_chat_flutter/test/src/gallery/gallery_header_test.dart index f29f9e596d..d467151397 100644 --- a/packages/stream_chat_flutter/test/src/gallery/gallery_header_test.dart +++ b/packages/stream_chat_flutter/test/src/gallery/gallery_header_test.dart @@ -13,8 +13,7 @@ void main() { late MockClientState clientState; late MockChannel channel; late MockChannelState channelState; - const methodChannel = - MethodChannel('dev.fluttercommunity.plus/connectivity_status'); + const methodChannel = MethodChannel('dev.fluttercommunity.plus/connectivity_status'); setUpAll(() { client = MockClient(); @@ -41,13 +40,12 @@ void main() { }); setUp(() { - TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger - .setMockMethodCallHandler(methodChannel, (MethodCall methodCall) async { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(methodChannel, ( + MethodCall methodCall, + ) async { if (methodCall.method == 'listen') { try { - await TestDefaultBinaryMessengerBinding - .instance.defaultBinaryMessenger - .handlePlatformMessage( + await TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.handlePlatformMessage( methodChannel.name, methodChannel.codec.encodeSuccessEnvelope(['wifi']), (_) {}, @@ -86,7 +84,7 @@ void main() { // wait for the initial state to be rendered. await tester.pumpAndSettle(); - expect(find.byType(StreamSvgIcon), findsNWidgets(2)); + expect(find.byType(Icon), findsNWidgets(2)); }, ); @@ -118,7 +116,6 @@ void main() { ); tearDown(() { - TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger - .setMockMethodCallHandler(methodChannel, null); + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(methodChannel, null); }); } diff --git a/packages/stream_chat_flutter/test/src/gallery/goldens/ci/gallery_footer_0.png b/packages/stream_chat_flutter/test/src/gallery/goldens/ci/gallery_footer_0.png index 7217acb047..1de647f17b 100644 Binary files a/packages/stream_chat_flutter/test/src/gallery/goldens/ci/gallery_footer_0.png and b/packages/stream_chat_flutter/test/src/gallery/goldens/ci/gallery_footer_0.png differ diff --git a/packages/stream_chat_flutter/test/src/gallery/goldens/ci/gallery_header_0.png b/packages/stream_chat_flutter/test/src/gallery/goldens/ci/gallery_header_0.png index 9a3f6097fe..f1be7db164 100644 Binary files a/packages/stream_chat_flutter/test/src/gallery/goldens/ci/gallery_header_0.png and b/packages/stream_chat_flutter/test/src/gallery/goldens/ci/gallery_header_0.png differ diff --git a/packages/stream_chat_flutter/test/src/icons/goldens/ci/stream_svg_icon_dark.png b/packages/stream_chat_flutter/test/src/icons/goldens/ci/stream_svg_icon_dark.png index e498b63bf8..a181028960 100644 Binary files a/packages/stream_chat_flutter/test/src/icons/goldens/ci/stream_svg_icon_dark.png and b/packages/stream_chat_flutter/test/src/icons/goldens/ci/stream_svg_icon_dark.png differ diff --git a/packages/stream_chat_flutter/test/src/icons/goldens/ci/stream_svg_icon_light.png b/packages/stream_chat_flutter/test/src/icons/goldens/ci/stream_svg_icon_light.png index 42eefb8b11..8098dacd46 100644 Binary files a/packages/stream_chat_flutter/test/src/icons/goldens/ci/stream_svg_icon_light.png and b/packages/stream_chat_flutter/test/src/icons/goldens/ci/stream_svg_icon_light.png differ diff --git a/packages/stream_chat_flutter/test/src/icons/stream_svg_icon_test.dart b/packages/stream_chat_flutter/test/src/icons/stream_svg_icon_test.dart index 5c4fc86d9c..47f28610a5 100644 --- a/packages/stream_chat_flutter/test/src/icons/stream_svg_icon_test.dart +++ b/packages/stream_chat_flutter/test/src/icons/stream_svg_icon_test.dart @@ -181,13 +181,15 @@ Widget _wrapWithMaterialApp( data: ThemeData(brightness: brightness), child: StreamChatTheme( data: StreamChatThemeData(brightness: brightness), - child: Builder(builder: (context) { - final theme = StreamChatTheme.of(context); - return Scaffold( - backgroundColor: theme.colorTheme.appBg, - body: Center(child: widget), - ); - }), + child: Builder( + builder: (context) { + final theme = StreamChatTheme.of(context); + return Scaffold( + backgroundColor: theme.colorTheme.appBg, + body: Center(child: widget), + ); + }, + ), ), ), ); diff --git a/packages/stream_chat_flutter/test/src/indicators/goldens/ci/sending_indicator_0.png b/packages/stream_chat_flutter/test/src/indicators/goldens/ci/sending_indicator_0.png index 838561febd..be8989f375 100644 Binary files a/packages/stream_chat_flutter/test/src/indicators/goldens/ci/sending_indicator_0.png and b/packages/stream_chat_flutter/test/src/indicators/goldens/ci/sending_indicator_0.png differ diff --git a/packages/stream_chat_flutter/test/src/indicators/goldens/ci/sending_indicator_1.png b/packages/stream_chat_flutter/test/src/indicators/goldens/ci/sending_indicator_1.png index 9185f12656..311529e247 100644 Binary files a/packages/stream_chat_flutter/test/src/indicators/goldens/ci/sending_indicator_1.png and b/packages/stream_chat_flutter/test/src/indicators/goldens/ci/sending_indicator_1.png differ diff --git a/packages/stream_chat_flutter/test/src/indicators/goldens/ci/sending_indicator_2.png b/packages/stream_chat_flutter/test/src/indicators/goldens/ci/sending_indicator_2.png index 06e22f1b1a..311529e247 100644 Binary files a/packages/stream_chat_flutter/test/src/indicators/goldens/ci/sending_indicator_2.png and b/packages/stream_chat_flutter/test/src/indicators/goldens/ci/sending_indicator_2.png differ diff --git a/packages/stream_chat_flutter/test/src/indicators/goldens/ci/sending_indicator_3.png b/packages/stream_chat_flutter/test/src/indicators/goldens/ci/sending_indicator_3.png index 31704acd50..311529e247 100644 Binary files a/packages/stream_chat_flutter/test/src/indicators/goldens/ci/sending_indicator_3.png and b/packages/stream_chat_flutter/test/src/indicators/goldens/ci/sending_indicator_3.png differ diff --git a/packages/stream_chat_flutter/test/src/indicators/goldens/ci/upload_progress_indicator_0.png b/packages/stream_chat_flutter/test/src/indicators/goldens/ci/upload_progress_indicator_0.png index 4724897fe5..834961f86a 100644 Binary files a/packages/stream_chat_flutter/test/src/indicators/goldens/ci/upload_progress_indicator_0.png and b/packages/stream_chat_flutter/test/src/indicators/goldens/ci/upload_progress_indicator_0.png differ diff --git a/packages/stream_chat_flutter/test/src/indicators/goldens/ci/upload_progress_indicator_1.png b/packages/stream_chat_flutter/test/src/indicators/goldens/ci/upload_progress_indicator_1.png index 3ee1a56391..419582b6f3 100644 Binary files a/packages/stream_chat_flutter/test/src/indicators/goldens/ci/upload_progress_indicator_1.png and b/packages/stream_chat_flutter/test/src/indicators/goldens/ci/upload_progress_indicator_1.png differ diff --git a/packages/stream_chat_flutter/test/src/indicators/goldens/ci/upload_progress_indicator_2.png b/packages/stream_chat_flutter/test/src/indicators/goldens/ci/upload_progress_indicator_2.png index 8d3d9a3c78..e53612c8e7 100644 Binary files a/packages/stream_chat_flutter/test/src/indicators/goldens/ci/upload_progress_indicator_2.png and b/packages/stream_chat_flutter/test/src/indicators/goldens/ci/upload_progress_indicator_2.png differ diff --git a/packages/stream_chat_flutter/test/src/indicators/sending_indicator_test.dart b/packages/stream_chat_flutter/test/src/indicators/sending_indicator_test.dart index a686a966e3..aaa620a197 100644 --- a/packages/stream_chat_flutter/test/src/indicators/sending_indicator_test.dart +++ b/packages/stream_chat_flutter/test/src/indicators/sending_indicator_test.dart @@ -2,6 +2,7 @@ import 'package:alchemist/alchemist.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; import '../material_app_wrapper.dart'; void main() { @@ -28,7 +29,7 @@ void main() { ); goldenTest( - 'golden test for StreamSendingIndicator with StreamSvgIcon.checkAll', + 'golden test for StreamSendingIndicator with Icon checkAll', fileName: 'sending_indicator_0', constraints: const BoxConstraints.tightFor(width: 50, height: 50), builder: () => MaterialAppWrapper( @@ -47,7 +48,7 @@ void main() { ); goldenTest( - 'golden test for StreamSendingIndicator with StreamSvgIcon.checkAll ' + 'golden test for StreamSendingIndicator with Icon checkAll ' '(delivered)', fileName: 'sending_indicator_1', constraints: const BoxConstraints.tightFor(width: 50, height: 50), @@ -69,7 +70,7 @@ void main() { ); goldenTest( - 'golden test for StreamSendingIndicator with StreamSvgIcon.check', + 'golden test for StreamSendingIndicator with Icon check', fileName: 'sending_indicator_2', constraints: const BoxConstraints.tightFor(width: 50, height: 50), builder: () => MaterialAppWrapper( @@ -89,7 +90,7 @@ void main() { ); goldenTest( - 'golden test for StreamSendingIndicator with StreamSvgIcons.time', + 'golden test for StreamSendingIndicator with clock icon', fileName: 'sending_indicator_3', constraints: const BoxConstraints.tightFor(width: 50, height: 50), builder: () => MaterialAppWrapper( @@ -129,13 +130,13 @@ void main() { ), ); - final streamSvgIcon = tester.widget( - find.byType(StreamSvgIcon), + final icon = tester.widget( + find.byType(Icon), ); - expect(streamSvgIcon.icon, StreamSvgIcons.checkAll); + expect(icon.icon, StreamIconData.iconDoupleCheckmark1Small); expect( - streamSvgIcon.color, + icon.color, StreamChatThemeData.light().colorTheme.textLowEmphasis, ); }, @@ -162,13 +163,13 @@ void main() { ), ); - final streamSvgIcon = tester.widget( - find.byType(StreamSvgIcon), + final icon = tester.widget( + find.byType(Icon), ); - expect(streamSvgIcon.icon, StreamSvgIcons.checkAll); + expect(icon.icon, StreamIconData.iconDoupleCheckmark1Small); expect( - streamSvgIcon.color, + icon.color, StreamChatThemeData.light().colorTheme.accentPrimary, ); }, @@ -196,14 +197,14 @@ void main() { ), ); - final streamSvgIcon = tester.widget( - find.byType(StreamSvgIcon), + final icon = tester.widget( + find.byType(Icon), ); - expect(streamSvgIcon.icon, StreamSvgIcons.checkAll); + expect(icon.icon, StreamIconData.iconDoupleCheckmark1Small); // Should use accentPrimary (read) not textLowEmphasis (delivered) expect( - streamSvgIcon.color, + icon.color, StreamChatThemeData.light().colorTheme.accentPrimary, ); }, diff --git a/packages/stream_chat_flutter/test/src/indicators/typing_indicator_test.dart b/packages/stream_chat_flutter/test/src/indicators/typing_indicator_test.dart index bcc2f13701..0a55b81f2c 100644 --- a/packages/stream_chat_flutter/test/src/indicators/typing_indicator_test.dart +++ b/packages/stream_chat_flutter/test/src/indicators/typing_indicator_test.dart @@ -35,7 +35,7 @@ void main() { Member( userId: 'user-id', user: User(id: 'user-id'), - ) + ), ]), ); when(() => channelState.members).thenReturn([ @@ -48,43 +48,45 @@ void main() { Message( text: 'hello', user: User(id: 'other-user'), - ) + ), ]); when(() => channelState.messagesStream).thenAnswer( (i) => Stream.value([ Message( text: 'hello', user: User(id: 'other-user'), - ) + ), ]), ); - when(() => channelState.typingEvents).thenAnswer((i) => { - User(id: 'other-user', extraData: const {'name': 'demo'}): - Event(type: EventType.typingStart), - }); + when(() => channelState.typingEvents).thenAnswer( + (i) => { + User(id: 'other-user', extraData: const {'name': 'demo'}): Event(type: EventType.typingStart), + }, + ); when(() => channelState.typingEventsStream).thenAnswer( (i) => Stream.value({ - User(id: 'other-user', extraData: const {'name': 'demo'}): - Event(type: EventType.typingStart), + User(id: 'other-user', extraData: const {'name': 'demo'}): Event(type: EventType.typingStart), }), ); const typingKey = Key('typing'); - await tester.pumpWidget(MaterialApp( - home: StreamChat( - client: client, - child: StreamChannel( - channel: channel, - child: const Scaffold( - body: StreamTypingIndicator( - key: typingKey, + await tester.pumpWidget( + MaterialApp( + home: StreamChat( + client: client, + child: StreamChannel( + channel: channel, + child: const Scaffold( + body: StreamTypingIndicator( + key: typingKey, + ), ), ), ), ), - )); + ); // wait for the initial state to be rendered. await tester.pump(Duration.zero); diff --git a/packages/stream_chat_flutter/test/src/indicators/unread_indicator_test.dart b/packages/stream_chat_flutter/test/src/indicators/unread_indicator_test.dart index 71f91c0235..c6fe1a058a 100644 --- a/packages/stream_chat_flutter/test/src/indicators/unread_indicator_test.dart +++ b/packages/stream_chat_flutter/test/src/indicators/unread_indicator_test.dart @@ -30,20 +30,21 @@ void main() { }); when(() => clientState.totalUnreadCount).thenReturn(10); - when(() => clientState.totalUnreadCountStream) - .thenAnswer((i) => Stream.value(10)); - - await tester.pumpWidget(MaterialApp( - home: StreamChat( - client: client, - child: StreamChannel( - channel: channel, - child: const Scaffold( - body: StreamUnreadIndicator(), + when(() => clientState.totalUnreadCountStream).thenAnswer((i) => Stream.value(10)); + + await tester.pumpWidget( + MaterialApp( + home: StreamChat( + client: client, + child: StreamChannel( + channel: channel, + child: const Scaffold( + body: StreamUnreadIndicator(), + ), ), ), ), - )); + ); // wait for the initial state to be rendered. await tester.pumpAndSettle(); @@ -70,22 +71,23 @@ void main() { when(() => channel.state).thenReturn(channelState); when(() => channel.client).thenReturn(client); when(() => channelState.unreadCount).thenReturn(0); - when(() => channelState.unreadCountStream) - .thenAnswer((i) => Stream.value(0)); - - await tester.pumpWidget(MaterialApp( - home: StreamChat( - client: client, - child: StreamChannel( - channel: channel, - child: Scaffold( - body: StreamUnreadIndicator.channels( - cid: channel.cid, + when(() => channelState.unreadCountStream).thenAnswer((i) => Stream.value(0)); + + await tester.pumpWidget( + MaterialApp( + home: StreamChat( + client: client, + child: StreamChannel( + channel: channel, + child: Scaffold( + body: StreamUnreadIndicator.channels( + cid: channel.cid, + ), ), ), ), ), - )); + ); // wait for the initial state to be rendered. await tester.pumpAndSettle(); @@ -112,22 +114,23 @@ void main() { when(() => channel.state).thenReturn(channelState); when(() => channel.client).thenReturn(client); when(() => channelState.unreadCount).thenReturn(100); - when(() => channelState.unreadCountStream) - .thenAnswer((i) => Stream.value(100)); - - await tester.pumpWidget(MaterialApp( - home: StreamChat( - client: client, - child: StreamChannel( - channel: channel, - child: Scaffold( - body: StreamUnreadIndicator.channels( - cid: channel.cid, + when(() => channelState.unreadCountStream).thenAnswer((i) => Stream.value(100)); + + await tester.pumpWidget( + MaterialApp( + home: StreamChat( + client: client, + child: StreamChannel( + channel: channel, + child: Scaffold( + body: StreamUnreadIndicator.channels( + cid: channel.cid, + ), ), ), ), ), - )); + ); // wait for the initial state to be rendered. await tester.pumpAndSettle(); diff --git a/packages/stream_chat_flutter/test/src/indicators/upload_progress_indicator_test.dart b/packages/stream_chat_flutter/test/src/indicators/upload_progress_indicator_test.dart index 09a9237d81..d7250346cd 100644 --- a/packages/stream_chat_flutter/test/src/indicators/upload_progress_indicator_test.dart +++ b/packages/stream_chat_flutter/test/src/indicators/upload_progress_indicator_test.dart @@ -6,8 +6,7 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import '../material_app_wrapper.dart'; void main() { - testWidgets('StreamUploadProgressIndicator at 0% with no background', - (tester) async { + testWidgets('StreamUploadProgressIndicator at 0% with no background', (tester) async { await tester.pumpWidget( MaterialApp( home: StreamChatTheme( @@ -28,8 +27,7 @@ void main() { expect(find.text('0%'), findsOneWidget); }); - testWidgets('StreamUploadProgressIndicator at 50% with no background', - (tester) async { + testWidgets('StreamUploadProgressIndicator at 50% with no background', (tester) async { await tester.pumpWidget( MaterialApp( home: StreamChatTheme( @@ -50,8 +48,7 @@ void main() { expect(find.text('50%'), findsOneWidget); }); - testWidgets('StreamUploadProgressIndicator at 100% with no background', - (tester) async { + testWidgets('StreamUploadProgressIndicator at 100% with no background', (tester) async { await tester.pumpWidget( MaterialApp( home: StreamChatTheme( @@ -72,8 +69,7 @@ void main() { expect(find.text('100%'), findsOneWidget); }); - testWidgets('StreamUploadProgressIndicator at 50% with background', - (tester) async { + testWidgets('StreamUploadProgressIndicator at 50% with background', (tester) async { await tester.pumpWidget( MaterialApp( home: StreamChatTheme( @@ -91,9 +87,7 @@ void main() { ); final backgroundColor = - ((find.byType(DecoratedBox).evaluate().first.widget as DecoratedBox) - .decoration as BoxDecoration) - .color; + ((find.byType(DecoratedBox).evaluate().first.widget as DecoratedBox).decoration as BoxDecoration).color; expect(const Color(0x99000000), backgroundColor); }); diff --git a/packages/stream_chat_flutter/test/src/material_app_wrapper.dart b/packages/stream_chat_flutter/test/src/material_app_wrapper.dart index 904b9a080d..b0e354997e 100644 --- a/packages/stream_chat_flutter/test/src/material_app_wrapper.dart +++ b/packages/stream_chat_flutter/test/src/material_app_wrapper.dart @@ -13,16 +13,15 @@ class MaterialAppWrapper extends MaterialApp { TransitionBuilder? builder, Widget? home, }) : super( - key: key, - builder: builder, - localizationsDelegates: localizations, - supportedLocales: localeOverrides ?? const [Locale('en')], - theme: theme?.copyWith(platform: platform) ?? - ThemeData(platform: platform, useMaterial3: false), - debugShowCheckedModeBanner: false, - home: home, - navigatorObservers: [ - if (navigatorObserver != null) navigatorObserver, - ], - ); + key: key, + builder: builder, + localizationsDelegates: localizations, + supportedLocales: localeOverrides ?? const [Locale('en')], + theme: theme?.copyWith(platform: platform) ?? ThemeData(platform: platform, useMaterial3: false), + debugShowCheckedModeBanner: false, + home: home, + navigatorObservers: [ + if (navigatorObserver != null) navigatorObserver, + ], + ); } diff --git a/packages/stream_chat_flutter/test/src/message_action/goldens/ci/stream_message_action_item_custom_styling_dark.png b/packages/stream_chat_flutter/test/src/message_action/goldens/ci/stream_message_action_item_custom_styling_dark.png index f212165ce5..2305901cff 100644 Binary files a/packages/stream_chat_flutter/test/src/message_action/goldens/ci/stream_message_action_item_custom_styling_dark.png and b/packages/stream_chat_flutter/test/src/message_action/goldens/ci/stream_message_action_item_custom_styling_dark.png differ diff --git a/packages/stream_chat_flutter/test/src/message_action/goldens/ci/stream_message_action_item_custom_styling_light.png b/packages/stream_chat_flutter/test/src/message_action/goldens/ci/stream_message_action_item_custom_styling_light.png index 2f341ee9e8..3f2d369e17 100644 Binary files a/packages/stream_chat_flutter/test/src/message_action/goldens/ci/stream_message_action_item_custom_styling_light.png and b/packages/stream_chat_flutter/test/src/message_action/goldens/ci/stream_message_action_item_custom_styling_light.png differ diff --git a/packages/stream_chat_flutter/test/src/message_action/goldens/ci/stream_message_action_item_delete_dark.png b/packages/stream_chat_flutter/test/src/message_action/goldens/ci/stream_message_action_item_delete_dark.png index ceb15cacdd..5ff006f9db 100644 Binary files a/packages/stream_chat_flutter/test/src/message_action/goldens/ci/stream_message_action_item_delete_dark.png and b/packages/stream_chat_flutter/test/src/message_action/goldens/ci/stream_message_action_item_delete_dark.png differ diff --git a/packages/stream_chat_flutter/test/src/message_action/goldens/ci/stream_message_action_item_delete_light.png b/packages/stream_chat_flutter/test/src/message_action/goldens/ci/stream_message_action_item_delete_light.png index 8911ab5aa2..b681b7092b 100644 Binary files a/packages/stream_chat_flutter/test/src/message_action/goldens/ci/stream_message_action_item_delete_light.png and b/packages/stream_chat_flutter/test/src/message_action/goldens/ci/stream_message_action_item_delete_light.png differ diff --git a/packages/stream_chat_flutter/test/src/message_action/goldens/ci/stream_message_action_item_reply_dark.png b/packages/stream_chat_flutter/test/src/message_action/goldens/ci/stream_message_action_item_reply_dark.png index eb2407025e..be0eb556f4 100644 Binary files a/packages/stream_chat_flutter/test/src/message_action/goldens/ci/stream_message_action_item_reply_dark.png and b/packages/stream_chat_flutter/test/src/message_action/goldens/ci/stream_message_action_item_reply_dark.png differ diff --git a/packages/stream_chat_flutter/test/src/message_action/goldens/ci/stream_message_action_item_reply_light.png b/packages/stream_chat_flutter/test/src/message_action/goldens/ci/stream_message_action_item_reply_light.png index ec6c889e52..22133451fa 100644 Binary files a/packages/stream_chat_flutter/test/src/message_action/goldens/ci/stream_message_action_item_reply_light.png and b/packages/stream_chat_flutter/test/src/message_action/goldens/ci/stream_message_action_item_reply_light.png differ diff --git a/packages/stream_chat_flutter/test/src/message_action/message_action_item_test.dart b/packages/stream_chat_flutter/test/src/message_action/message_action_item_test.dart deleted file mode 100644 index 1edf748ec1..0000000000 --- a/packages/stream_chat_flutter/test/src/message_action/message_action_item_test.dart +++ /dev/null @@ -1,167 +0,0 @@ -import 'package:alchemist/alchemist.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; - -void main() { - final message = Message( - id: 'test-message', - text: 'Hello, world!', - user: User(id: 'test-user'), - ); - - testWidgets('renders with title and icon', (tester) async { - await tester.pumpWidget( - _wrapWithMaterialApp( - StreamMessageActionItem( - action: StreamMessageAction( - title: const Text('Reply'), - leading: const Icon(Icons.reply), - action: QuotedReply(message: message), - ), - ), - ), - ); - - expect(find.text('Reply'), findsOneWidget); - expect(find.byIcon(Icons.reply), findsOneWidget); - }); - - testWidgets('calls onTap when tapped', (tester) async { - Message? tappedMessage; - - await tester.pumpWidget( - _wrapWithMaterialApp( - StreamMessageActionItem( - onTap: (action) => tappedMessage = action.message, - action: StreamMessageAction( - title: const Text('Reply'), - leading: const Icon(Icons.reply), - action: QuotedReply(message: message), - ), - ), - ), - ); - - await tester.tap(find.byType(InkWell)); - await tester.pump(); - - expect(tappedMessage, message); - }); - - testWidgets( - 'applies destructive styling when isDestructive is true', - (tester) async { - await tester.pumpWidget( - _wrapWithMaterialApp( - StreamMessageActionItem( - action: StreamMessageAction( - isDestructive: true, - title: const Text('Delete'), - leading: const Icon(Icons.delete), - action: DeleteMessage(message: message), - ), - ), - ), - ); - - expect(find.text('Delete'), findsOneWidget); - expect(find.byIcon(Icons.delete), findsOneWidget); - - // The icon and text should have the error color - final theme = StreamChatTheme.of(tester.element(find.text('Delete'))); - final iconTheme = IconTheme.of(tester.element(find.byIcon(Icons.delete))); - - expect(iconTheme.color, theme.colorTheme.accentError); - }, - ); - - group('StreamMessageActionItem Golden Tests', () { - for (final brightness in Brightness.values) { - final theme = brightness.name; - - // Test standard action - goldenTest( - 'StreamMessageActionItem (Reply) in $theme theme', - fileName: 'stream_message_action_item_reply_$theme', - constraints: const BoxConstraints(maxWidth: 300, maxHeight: 60), - builder: () => _wrapWithMaterialApp( - brightness: brightness, - StreamMessageActionItem( - action: StreamMessageAction( - title: const Text('Reply'), - leading: const Icon(Icons.reply), - action: QuotedReply(message: message), - ), - ), - ), - ); - - // Test destructive action (like delete) - goldenTest( - 'StreamMessageActionItem (Delete) in $theme theme', - fileName: 'stream_message_action_item_delete_$theme', - constraints: const BoxConstraints(maxWidth: 300, maxHeight: 60), - builder: () => _wrapWithMaterialApp( - brightness: brightness, - StreamMessageActionItem( - action: StreamMessageAction( - isDestructive: true, - title: const Text('Delete Message'), - leading: const Icon(Icons.delete), - action: DeleteMessage(message: message), - ), - ), - ), - ); - - // Test with custom styling - goldenTest( - 'StreamMessageActionItem with custom styling in $theme theme', - fileName: 'stream_message_action_item_custom_styling_$theme', - constraints: const BoxConstraints(maxWidth: 300, maxHeight: 60), - builder: () => _wrapWithMaterialApp( - brightness: brightness, - StreamMessageActionItem( - action: StreamMessageAction( - title: const Text('Styled Action'), - titleTextStyle: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 16, - color: Colors.purple[700], - ), - leading: const Icon(Icons.favorite), - iconColor: Colors.pink[400], - backgroundColor: Colors.amber[100], - action: CustomMessageAction(message: message), - ), - ), - ), - ); - } - }); -} - -Widget _wrapWithMaterialApp( - Widget child, { - Brightness? brightness, -}) { - return MaterialApp( - debugShowCheckedModeBanner: false, - home: StreamChatTheme( - data: StreamChatThemeData(brightness: brightness), - child: Builder(builder: (context) { - final theme = StreamChatTheme.of(context); - return Scaffold( - backgroundColor: theme.colorTheme.appBg, - body: Center( - child: Padding( - padding: const EdgeInsets.all(8), - child: child, - ), - ), - ); - }), - ), - ); -} diff --git a/packages/stream_chat_flutter/test/src/message_action/message_actions_builder_test.dart b/packages/stream_chat_flutter/test/src/message_action/message_actions_builder_test.dart index e6960c0712..427dd53215 100644 --- a/packages/stream_chat_flutter/test/src/message_action/message_actions_builder_test.dart +++ b/packages/stream_chat_flutter/test/src/message_action/message_actions_builder_test.dart @@ -30,9 +30,9 @@ Message createTestMessage({ replyCount: replyCount, moderation: switch (type) { MessageType.error => const Moderation( - action: ModerationAction.bounce, - originalText: 'Original message text that violated policy', - ), + action: ModerationAction.bounce, + originalText: 'Original message text that violated policy', + ), _ => null, }, ); @@ -83,10 +83,12 @@ void main() { await tester.pumpWidget( StreamChatTheme( data: StreamChatThemeData.light(), - child: Builder(builder: (ctx) { - context = ctx; - return const SizedBox.shrink(); - }), + child: Builder( + builder: (ctx) { + context = ctx; + return const SizedBox.shrink(); + }, + ), ), ); return context; @@ -130,31 +132,6 @@ void main() { expect(actions.isEmpty, isTrue); }); - testWidgets('includes custom actions', (tester) async { - final context = await _getContext(tester); - final customAction = StreamMessageAction( - title: const Text('Custom'), - leading: const Icon(Icons.star), - action: CustomMessageAction( - message: message, - extraData: {'customKey': 'customValue'}, - ), - ); - - final channel = _getChannelWithCapabilities(allChannelCapabilities); - final actions = StreamMessageActionsBuilder.buildActions( - context: context, - message: message, - channel: channel, - currentUser: currentUser, - customActions: [customAction], - ); - - actions.expects( - reason: 'Custom action should be included', - ); - }); - group('permission-based actions', () { testWidgets( 'includes/excludes edit action based on authorship', @@ -437,8 +414,7 @@ void main() { // Thread message final channel = _getChannelWithCapabilities(allChannelCapabilities); final threadMessage = createTestMessage(parentId: 'parent-message-id'); - final actionsForThreadMessage = - StreamMessageActionsBuilder.buildActions( + final actionsForThreadMessage = StreamMessageActionsBuilder.buildActions( context: context, message: threadMessage, channel: channel, @@ -556,15 +532,15 @@ void main() { }); } -/// Extension on Set to simplify action type checks. -extension StreamMessageActionSetExtension on List { +/// Extension on action lists to simplify message action type checks. +extension StreamMessageActionSetExtension on List { void expects({String? reason}) { - final containsActionType = this.any((it) => it.action is T); + final containsActionType = this.any((it) => it.props.value is T); return expect(containsActionType, isTrue, reason: reason); } void notExpects({String? reason}) { - final containsActionType = this.any((it) => it.action is T); + final containsActionType = this.any((it) => it.props.value is T); return expect(containsActionType, isFalse, reason: reason); } } diff --git a/packages/stream_chat_flutter/test/src/message_input/attachment_button_test.dart b/packages/stream_chat_flutter/test/src/message_input/attachment_button_test.dart index 7ac928d201..e5c54d2ed7 100644 --- a/packages/stream_chat_flutter/test/src/message_input/attachment_button_test.dart +++ b/packages/stream_chat_flutter/test/src/message_input/attachment_button_test.dart @@ -26,7 +26,7 @@ void main() { final button = find.byType(IconButton); expect(button, findsOneWidget); - expect(find.byType(StreamSvgIcon), findsOneWidget); + expect(find.byType(Icon), findsOneWidget); await tester.tap(button); expect(count, 1); }); @@ -78,9 +78,7 @@ void main() { home: Scaffold( body: Center( child: AttachmentButton( - color: StreamChatThemeData.light() - .messageInputTheme - .actionButtonIdleColor, + color: StreamChatThemeData.light().messageInputTheme.actionButtonIdleColor, onPressed: () {}, ), ), diff --git a/packages/stream_chat_flutter/test/src/message_input/attachment_picker/stream_attachment_picker_test.dart b/packages/stream_chat_flutter/test/src/message_input/attachment_picker/stream_attachment_picker_test.dart index 8675d05452..ca2ff3baf9 100644 --- a/packages/stream_chat_flutter/test/src/message_input/attachment_picker/stream_attachment_picker_test.dart +++ b/packages/stream_chat_flutter/test/src/message_input/attachment_picker/stream_attachment_picker_test.dart @@ -3,37 +3,38 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; void main() { - group('showStreamAttachmentPickerModalBottomSheet', () { - group('attachmentPickerOptionsBuilder', () { + group('tabbedAttachmentPickerBuilder', () { + group('optionsBuilder', () { testWidgets( 'should call optionsBuilder with default options', (tester) async { var builderCalled = false; int? defaultOptionsCount; + final controller = StreamAttachmentPickerController(); + addTearDown(controller.dispose); + await tester.pumpWidget( _wrapWithStreamChatApp( Builder( builder: (context) { - return ElevatedButton( - onPressed: () { - showStreamAttachmentPickerModalBottomSheet( - context: context, - optionsBuilder: (context, defaultOptions) { - builderCalled = true; - defaultOptionsCount = defaultOptions.length; - return defaultOptions; - }, - ); - }, - child: const Text('Show Picker'), + return SizedBox( + height: 400, + child: tabbedAttachmentPickerBuilder( + context: context, + controller: controller, + optionsBuilder: (context, defaultOptions) { + builderCalled = true; + defaultOptionsCount = defaultOptions.length; + return defaultOptions; + }, + ), ); }, ), ), ); - await tester.tap(find.text('Show Picker')); await tester.pumpAndSettle(); expect(builderCalled, isTrue); @@ -47,334 +48,263 @@ void main() { (tester) async { int? defaultOptionsCount; + final controller = StreamAttachmentPickerController(); + addTearDown(controller.dispose); + await tester.pumpWidget( _wrapWithStreamChatApp( Builder( builder: (context) { - return ElevatedButton( - onPressed: () { - showStreamAttachmentPickerModalBottomSheet( - context: context, - optionsBuilder: (context, defaultOptions) { - defaultOptionsCount = defaultOptions.length; - // Return only first option - return [defaultOptions.first]; - }, - ); - }, - child: const Text('Show Picker'), + return SizedBox( + height: 400, + child: tabbedAttachmentPickerBuilder( + context: context, + controller: controller, + optionsBuilder: (context, defaultOptions) { + defaultOptionsCount = defaultOptions.length; + return [defaultOptions.first]; + }, + ), ); }, ), ), ); - await tester.tap(find.text('Show Picker')); await tester.pumpAndSettle(); - final bottomSheet = - tester.widget( - find.byType(StreamSystemAttachmentPickerBottomSheet), + final picker = tester.widget( + find.byType(StreamTabbedAttachmentPicker), ); - expect(bottomSheet.options.length, equals(1)); - expect(bottomSheet.options.length, lessThan(defaultOptionsCount!)); + expect(picker.options.length, equals(1)); + expect(picker.options.length, lessThan(defaultOptionsCount!)); }, ); testWidgets( - 'should allow adding custom options', + 'should throw ArgumentError when wrong option types are provided', (tester) async { - int? defaultOptionsCount; - const customOptionKey = 'custom-location'; + final controller = StreamAttachmentPickerController(); + addTearDown(controller.dispose); await tester.pumpWidget( _wrapWithStreamChatApp( Builder( builder: (context) { - return ElevatedButton( - onPressed: () { - showStreamAttachmentPickerModalBottomSheet( - context: context, - optionsBuilder: (context, defaultOptions) { - defaultOptionsCount = defaultOptions.length; - return [ - ...defaultOptions, - SystemAttachmentPickerOption( - key: customOptionKey, - icon: const Icon(Icons.location_on), - supportedTypes: [AttachmentPickerType.images], - title: 'Send Location', - onTap: (context, controller) async {}, - ), - ]; - }, - ); - }, - child: const Text('Show Picker'), + return SizedBox( + height: 400, + child: tabbedAttachmentPickerBuilder( + context: context, + controller: controller, + optionsBuilder: (context, defaultOptions) { + return [ + SystemAttachmentPickerOption( + key: 'wrong', + icon: Icons.error, + title: 'Wrong', + supportedTypes: [AttachmentPickerType.images], + onTap: (context, controller) async {}, + ), + ]; + }, + ), ); }, ), ), ); - await tester.tap(find.text('Show Picker')); - await tester.pumpAndSettle(); - - final bottomSheet = - tester.widget( - find.byType(StreamSystemAttachmentPickerBottomSheet), - ); - - // Should have one more option than default - expect(bottomSheet.options.length, equals(defaultOptionsCount! + 1)); - - // Verify our custom option exists - expect( - bottomSheet.options.any((option) => option.key == customOptionKey), - isTrue, - ); + expect(tester.takeException(), isA()); }, ); + }); + group('allowedTypes', () { testWidgets( - 'should allow reordering options', + 'should filter options based on allowedTypes', (tester) async { - String? firstDefaultKey; - String? firstReversedKey; + final controller = StreamAttachmentPickerController(); + addTearDown(controller.dispose); await tester.pumpWidget( _wrapWithStreamChatApp( Builder( builder: (context) { - return ElevatedButton( - onPressed: () { - showStreamAttachmentPickerModalBottomSheet( - context: context, - optionsBuilder: (context, defaultOptions) { - firstDefaultKey = defaultOptions.first.key; - final reversed = defaultOptions.reversed.toList(); - firstReversedKey = reversed.first.key; - return reversed; - }, - ); - }, - child: const Text('Show Picker'), + return SizedBox( + height: 400, + child: tabbedAttachmentPickerBuilder( + context: context, + controller: controller, + allowedTypes: [AttachmentPickerType.images], + ), ); }, ), ), ); - await tester.tap(find.text('Show Picker')); await tester.pumpAndSettle(); - // Verify first option changed after reversing - expect(firstDefaultKey, isNotNull); - expect(firstReversedKey, isNotNull); - expect(firstDefaultKey, isNot(equals(firstReversedKey))); - }, - ); + final picker = tester.widget( + find.byType(StreamTabbedAttachmentPicker), + ); - testWidgets( - 'should throw ArgumentError when wrong option types are provided', - (tester) async { - await tester.pumpWidget( - _wrapWithStreamChatApp( - Builder( - builder: (context) { - return ElevatedButton( - onPressed: () { - showStreamAttachmentPickerModalBottomSheet( - context: context, - optionsBuilder: (context, defaultOptions) { - // Return tabbed option for system picker (wrong type) - return [ - TabbedAttachmentPickerOption( - key: 'wrong', - icon: const Icon(Icons.error), - title: 'Wrong', - supportedTypes: [AttachmentPickerType.images], - optionViewBuilder: (context, controller) { - return const Text('Wrong'); - }, - ), - ]; - }, - ); - }, - child: const Text('Show Picker'), - ); - }, - ), + expect( + picker.options.every( + (option) => option.supportedTypes.contains(AttachmentPickerType.images), ), + isTrue, ); - - await tester.tap(find.text('Show Picker')); - - // Should throw ArgumentError - await tester.pumpAndSettle(); - - expect(tester.takeException(), isA()); }, ); }); + }); - group('allowedTypes', () { + group('systemAttachmentPickerBuilder', () { + group('optionsBuilder', () { testWidgets( - 'should filter options based on allowedTypes', + 'should call optionsBuilder with default options', (tester) async { + var builderCalled = false; + + final controller = StreamAttachmentPickerController(); + addTearDown(controller.dispose); + await tester.pumpWidget( _wrapWithStreamChatApp( Builder( builder: (context) { - return ElevatedButton( - onPressed: () { - showStreamAttachmentPickerModalBottomSheet( - context: context, - allowedTypes: [AttachmentPickerType.images], - ); + return systemAttachmentPickerBuilder( + context: context, + controller: controller, + optionsBuilder: (context, defaultOptions) { + builderCalled = true; + return defaultOptions; }, - child: const Text('Show Picker'), ); }, ), ), ); - await tester.tap(find.text('Show Picker')); await tester.pumpAndSettle(); - final bottomSheet = - tester.widget( - find.byType(StreamSystemAttachmentPickerBottomSheet), - ); - - // All options should support images + expect(builderCalled, isTrue); expect( - bottomSheet.options.every( - (option) => - option.supportedTypes.contains(AttachmentPickerType.images), - ), - isTrue, + find.byType(StreamSystemAttachmentPicker), + findsOneWidget, ); }, ); testWidgets( - 'should work with optionsBuilder and allowedTypes together', + 'should allow adding custom system picker options', (tester) async { + final controller = StreamAttachmentPickerController(); + addTearDown(controller.dispose); + await tester.pumpWidget( _wrapWithStreamChatApp( Builder( builder: (context) { - return ElevatedButton( - onPressed: () { - showStreamAttachmentPickerModalBottomSheet( - context: context, - allowedTypes: [ - AttachmentPickerType.images, - AttachmentPickerType.videos, - ], - optionsBuilder: (context, defaultOptions) { - return defaultOptions; - }, - ); + return systemAttachmentPickerBuilder( + context: context, + controller: controller, + optionsBuilder: (context, defaultOptions) { + return [ + ...defaultOptions, + SystemAttachmentPickerOption( + key: 'custom-upload', + icon: Icons.cloud_upload, + title: 'Custom Upload', + supportedTypes: [AttachmentPickerType.files], + onTap: (context, controller) async {}, + ), + ]; }, - child: const Text('Show Picker'), ); }, ), ), ); - await tester.tap(find.text('Show Picker')); await tester.pumpAndSettle(); - expect( - find.byType(StreamSystemAttachmentPickerBottomSheet), - findsOneWidget, - ); + expect(find.text('Custom Upload'), findsOneWidget); }, ); - }); - group('System picker with optionsBuilder', () { testWidgets( - 'should use optionsBuilder with system picker', + 'should throw ArgumentError when wrong option types are provided', (tester) async { - var builderCalled = false; + final controller = StreamAttachmentPickerController(); + addTearDown(controller.dispose); await tester.pumpWidget( _wrapWithStreamChatApp( Builder( builder: (context) { - return ElevatedButton( - onPressed: () { - showStreamAttachmentPickerModalBottomSheet( - context: context, - useSystemAttachmentPicker: true, - optionsBuilder: (context, defaultOptions) { - builderCalled = true; - return defaultOptions; - }, - ); + return systemAttachmentPickerBuilder( + context: context, + controller: controller, + optionsBuilder: (context, defaultOptions) { + return [ + TabbedAttachmentPickerOption( + key: 'wrong', + icon: Icons.error, + title: 'Wrong', + supportedTypes: [AttachmentPickerType.images], + optionViewBuilder: (context, controller) { + return const Text('Wrong'); + }, + ), + ]; }, - child: const Text('Show Picker'), ); }, ), ), ); - await tester.tap(find.text('Show Picker')); - await tester.pumpAndSettle(); - - expect(builderCalled, isTrue); - expect( - find.byType(StreamSystemAttachmentPickerBottomSheet), - findsOneWidget, - ); + expect(tester.takeException(), isA()); }, ); + }); + group('allowedTypes', () { testWidgets( - 'should allow adding custom system picker options', + 'should filter options based on allowedTypes', (tester) async { + final controller = StreamAttachmentPickerController(); + addTearDown(controller.dispose); + await tester.pumpWidget( _wrapWithStreamChatApp( Builder( builder: (context) { - return ElevatedButton( - onPressed: () { - showStreamAttachmentPickerModalBottomSheet( - context: context, - useSystemAttachmentPicker: true, - optionsBuilder: (context, defaultOptions) { - return [ - ...defaultOptions, - SystemAttachmentPickerOption( - key: 'custom-upload', - icon: const Icon(Icons.cloud_upload), - title: 'Custom Upload', - supportedTypes: [AttachmentPickerType.files], - onTap: (context, controller) async {}, - ), - ]; - }, - ); - }, - child: const Text('Show Picker'), + return systemAttachmentPickerBuilder( + context: context, + controller: controller, + allowedTypes: [AttachmentPickerType.images], ); }, ), ), ); - await tester.tap(find.text('Show Picker')); await tester.pumpAndSettle(); - expect(find.text('Custom Upload'), findsOneWidget); + final picker = tester.widget( + find.byType(StreamSystemAttachmentPicker), + ); + + expect( + picker.options.every( + (option) => option.supportedTypes.contains(AttachmentPickerType.images), + ), + isTrue, + ); }, ); }); diff --git a/packages/stream_chat_flutter/test/src/message_input/audio_recorder/audio_recorder_controller_test.dart b/packages/stream_chat_flutter/test/src/message_input/audio_recorder/audio_recorder_controller_test.dart index 86ad93a548..7fb19aba7b 100644 --- a/packages/stream_chat_flutter/test/src/message_input/audio_recorder/audio_recorder_controller_test.dart +++ b/packages/stream_chat_flutter/test/src/message_input/audio_recorder/audio_recorder_controller_test.dart @@ -33,8 +33,7 @@ void main() { when(() => mockRecorder.dispose()).thenAnswer((_) async {}); amplitudeController = PublishSubject(); - when(() => mockRecorder.onAmplitudeChanged(any())) - .thenAnswer((_) => amplitudeController.stream); + when(() => mockRecorder.onAmplitudeChanged(any())).thenAnswer((_) => amplitudeController.stream); controller = StreamAudioRecorderController.raw( config: config, @@ -53,8 +52,7 @@ void main() { group('startRecord', () { setUp(() { - when(() => mockRecorder.start(config, path: any(named: 'path'))) - .thenAnswer((_) async {}); + when(() => mockRecorder.start(config, path: any(named: 'path'))).thenAnswer((_) async {}); }); test( @@ -86,8 +84,7 @@ void main() { setUp(() async { when(() => mockRecorder.hasPermission()).thenAnswer((_) async => true); when(() => mockRecorder.stop()).thenAnswer((_) async => testPath); - when(() => mockRecorder.start(config, path: any(named: 'path'))) - .thenAnswer((_) async {}); + when(() => mockRecorder.start(config, path: any(named: 'path'))).thenAnswer((_) async {}); }); test('stops recording and updates state to stopped', () async { @@ -122,8 +119,7 @@ void main() { setUp(() { when(() => mockRecorder.hasPermission()).thenAnswer((_) async => true); when(() => mockRecorder.cancel()).thenAnswer((_) async {}); - when(() => mockRecorder.start(config, path: any(named: 'path'))) - .thenAnswer((_) async {}); + when(() => mockRecorder.start(config, path: any(named: 'path'))).thenAnswer((_) async {}); }); test('cancels recording and returns to idle state', () async { @@ -208,8 +204,7 @@ void main() { group('amplitude changes', () { setUp(() { when(() => mockRecorder.hasPermission()).thenAnswer((_) async => true); - when(() => mockRecorder.start(config, path: any(named: 'path'))) - .thenAnswer((_) async {}); + when(() => mockRecorder.start(config, path: any(named: 'path'))).thenAnswer((_) async {}); }); test('updates waveform data when amplitude changes', () async { diff --git a/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_idle_dark.png b/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_idle_dark.png index 7f8b19262c..1d6da52c8b 100644 Binary files a/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_idle_dark.png and b/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_idle_dark.png differ diff --git a/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_idle_light.png b/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_idle_light.png index 619207c4bf..c4251c8932 100644 Binary files a/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_idle_light.png and b/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_idle_light.png differ diff --git a/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_recording_hold_dark.png b/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_recording_hold_dark.png index 9fe9ac4d43..c244bcc2ea 100644 Binary files a/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_recording_hold_dark.png and b/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_recording_hold_dark.png differ diff --git a/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_recording_hold_light.png b/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_recording_hold_light.png index 707603a471..3a76c145e4 100644 Binary files a/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_recording_hold_light.png and b/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_recording_hold_light.png differ diff --git a/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_recording_locked_dark.png b/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_recording_locked_dark.png index 994c918cd7..c3f7476ee6 100644 Binary files a/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_recording_locked_dark.png and b/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_recording_locked_dark.png differ diff --git a/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_recording_locked_light.png b/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_recording_locked_light.png index 48f4d73ae3..4b02161757 100644 Binary files a/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_recording_locked_light.png and b/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_recording_locked_light.png differ diff --git a/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_recording_stopped_dark.png b/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_recording_stopped_dark.png index fe3753a23b..069db0019e 100644 Binary files a/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_recording_stopped_dark.png and b/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_recording_stopped_dark.png differ diff --git a/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_recording_stopped_light.png b/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_recording_stopped_light.png index 4375352630..f1bad28687 100644 Binary files a/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_recording_stopped_light.png and b/packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_recording_stopped_light.png differ diff --git a/packages/stream_chat_flutter/test/src/message_input/audio_recorder/stream_audio_recorder_test.dart b/packages/stream_chat_flutter/test/src/message_input/audio_recorder/stream_audio_recorder_test.dart index 736649f03a..4a37c491cf 100644 --- a/packages/stream_chat_flutter/test/src/message_input/audio_recorder/stream_audio_recorder_test.dart +++ b/packages/stream_chat_flutter/test/src/message_input/audio_recorder/stream_audio_recorder_test.dart @@ -3,9 +3,8 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_portal/flutter_portal.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:stream_chat_flutter/src/misc/audio_waveform.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -import '../../utils/finders.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; void main() { group('StreamAudioRecorderButton', () { @@ -32,7 +31,7 @@ void main() { ), ); - expect(find.bySvgIcon(StreamSvgIcons.mic), findsOneWidget); + expect(find.byIcon(StreamIconData.iconMicrophone), findsOneWidget); }, ); @@ -176,9 +175,9 @@ void main() { ); expect(find.byType(StreamAudioWaveform), findsOneWidget); - expect(find.bySvgIcon(StreamSvgIcons.delete), findsOneWidget); - expect(find.bySvgIcon(StreamSvgIcons.stop), findsOneWidget); - expect(find.bySvgIcon(StreamSvgIcons.checkSend), findsOneWidget); + expect(find.byIcon(StreamIconData.iconTrashBin), findsOneWidget); + expect(find.byIcon(StreamIconData.iconStop), findsOneWidget); + expect(find.byIcon(StreamIconData.iconCircleCheck), findsOneWidget); }, ); @@ -200,7 +199,7 @@ void main() { ), ); - await tester.tap(find.bySvgIcon(StreamSvgIcons.delete)); + await tester.tap(find.byIcon(StreamIconData.iconTrashBin)); expect(onRecordCancelCalled, true); }, @@ -224,7 +223,7 @@ void main() { ), ); - await tester.tap(find.bySvgIcon(StreamSvgIcons.stop)); + await tester.tap(find.byIcon(StreamIconData.iconStop)); expect(onRecordStopCalled, true); }, @@ -248,7 +247,7 @@ void main() { ), ); - await tester.tap(find.bySvgIcon(StreamSvgIcons.checkSend)); + await tester.tap(find.byIcon(StreamIconData.iconCircleCheck)); expect(onRecordFinishCalled, true); }, @@ -270,8 +269,8 @@ void main() { expect(find.byType(PlaybackControlButton), findsOneWidget); expect(find.byType(PlaybackTimerText), findsOneWidget); expect(find.byType(StreamAudioWaveformSlider), findsOneWidget); - expect(find.bySvgIcon(StreamSvgIcons.delete), findsOneWidget); - expect(find.bySvgIcon(StreamSvgIcons.checkSend), findsOneWidget); + expect(find.byIcon(StreamIconData.iconTrashBin), findsOneWidget); + expect(find.byIcon(StreamIconData.iconCircleCheck), findsOneWidget); }, ); @@ -292,7 +291,7 @@ void main() { ), ); - await tester.tap(find.bySvgIcon(StreamSvgIcons.checkSend)); + await tester.tap(find.byIcon(StreamIconData.iconCircleCheck)); expect(onRecordFinishCalled, true); }, @@ -315,7 +314,7 @@ void main() { ), ); - await tester.tap(find.bySvgIcon(StreamSvgIcons.delete)); + await tester.tap(find.byIcon(StreamIconData.iconTrashBin)); expect(onRecordCancelCalled, true); }, @@ -455,7 +454,7 @@ void main() { ), ); - await tester.tap(find.bySvgIcon(StreamSvgIcons.stop)); + await tester.tap(find.byIcon(StreamIconData.iconStop)); expect(feedbackCalled, isTrue); }, @@ -482,7 +481,7 @@ void main() { ), ); - await tester.tap(find.bySvgIcon(StreamSvgIcons.delete)); + await tester.tap(find.byIcon(StreamIconData.iconTrashBin)); expect(feedbackCalled, isTrue); }, @@ -509,7 +508,7 @@ void main() { ), ); - await tester.tap(find.bySvgIcon(StreamSvgIcons.checkSend)); + await tester.tap(find.byIcon(StreamIconData.iconCircleCheck)); expect(feedbackCalled, isTrue); }, @@ -535,7 +534,7 @@ void main() { ), ); - await tester.tap(find.bySvgIcon(StreamSvgIcons.delete)); + await tester.tap(find.byIcon(StreamIconData.iconTrashBin)); expect(feedbackCalled, isTrue); }, @@ -561,7 +560,7 @@ void main() { ), ); - await tester.tap(find.bySvgIcon(StreamSvgIcons.checkSend)); + await tester.tap(find.byIcon(StreamIconData.iconCircleCheck)); expect(feedbackCalled, isTrue); }, @@ -637,8 +636,7 @@ void main() { goldenTest( '[${brightness.name}] -> should look fine in recording hold state', - fileName: - 'stream_audio_recorder_button_recording_hold_${brightness.name}', + fileName: 'stream_audio_recorder_button_recording_hold_${brightness.name}', constraints: const BoxConstraints.tightFor(width: 400, height: 160), builder: () => _wrapWithStreamChatApp( brightness: brightness, @@ -653,8 +651,7 @@ void main() { goldenTest( '[${brightness.name}] -> should look fine in recording locked state', - fileName: - 'stream_audio_recorder_button_recording_locked_${brightness.name}', + fileName: 'stream_audio_recorder_button_recording_locked_${brightness.name}', constraints: const BoxConstraints.tightFor(width: 400, height: 160), builder: () => _wrapWithStreamChatApp( brightness: brightness, @@ -672,8 +669,7 @@ void main() { goldenTest( '[${brightness.name}] -> should look fine in recording stopped state', - fileName: - 'stream_audio_recorder_button_recording_stopped_${brightness.name}', + fileName: 'stream_audio_recorder_button_recording_stopped_${brightness.name}', constraints: const BoxConstraints.tightFor(width: 400, height: 160), builder: () => _wrapWithStreamChatApp( brightness: brightness, @@ -695,24 +691,27 @@ Widget _wrapWithStreamChatApp( Brightness? brightness, }) { return MaterialApp( + theme: ThemeData(brightness: brightness), debugShowCheckedModeBanner: false, home: Portal( child: StreamChatTheme( data: StreamChatThemeData(brightness: brightness), - child: Builder(builder: (context) { - final theme = StreamChatTheme.of(context); - return Scaffold( - backgroundColor: theme.colorTheme.appBg, - bottomNavigationBar: Material( - elevation: 10, - color: theme.colorTheme.barsBg, - child: Padding( - padding: const EdgeInsets.all(8), - child: widget, + child: Builder( + builder: (context) { + final theme = StreamChatTheme.of(context); + return Scaffold( + backgroundColor: theme.colorTheme.appBg, + bottomNavigationBar: Material( + elevation: 10, + color: theme.colorTheme.barsBg, + child: Padding( + padding: const EdgeInsets.all(8), + child: widget, + ), ), - ), - ); - }), + ); + }, + ), ), ), ); diff --git a/packages/stream_chat_flutter/test/src/message_input/clear_input_item_test.dart b/packages/stream_chat_flutter/test/src/message_input/clear_input_item_test.dart index 68d1f430fd..8f896a164d 100644 --- a/packages/stream_chat_flutter/test/src/message_input/clear_input_item_test.dart +++ b/packages/stream_chat_flutter/test/src/message_input/clear_input_item_test.dart @@ -28,7 +28,7 @@ void main() { final button = find.byType(RawMaterialButton); expect(button, findsOneWidget); - expect(find.byType(StreamSvgIcon), findsOneWidget); + expect(find.byType(Icon), findsOneWidget); await tester.tap(button); expect(count, 1); }); diff --git a/packages/stream_chat_flutter/test/src/message_input/goldens/ci/attachment_button_0.png b/packages/stream_chat_flutter/test/src/message_input/goldens/ci/attachment_button_0.png index b7f33c7539..ca37dc0d4a 100644 Binary files a/packages/stream_chat_flutter/test/src/message_input/goldens/ci/attachment_button_0.png and b/packages/stream_chat_flutter/test/src/message_input/goldens/ci/attachment_button_0.png differ diff --git a/packages/stream_chat_flutter/test/src/message_input/goldens/ci/clear_input_item_0.png b/packages/stream_chat_flutter/test/src/message_input/goldens/ci/clear_input_item_0.png index 989e925b4e..8122de5b8f 100644 Binary files a/packages/stream_chat_flutter/test/src/message_input/goldens/ci/clear_input_item_0.png and b/packages/stream_chat_flutter/test/src/message_input/goldens/ci/clear_input_item_0.png differ diff --git a/packages/stream_chat_flutter/test/src/message_input/goldens/ci/command_button_0.png b/packages/stream_chat_flutter/test/src/message_input/goldens/ci/command_button_0.png index 588d580602..0b2e32388a 100644 Binary files a/packages/stream_chat_flutter/test/src/message_input/goldens/ci/command_button_0.png and b/packages/stream_chat_flutter/test/src/message_input/goldens/ci/command_button_0.png differ diff --git a/packages/stream_chat_flutter/test/src/message_input/goldens/ci/countdown_button_0.png b/packages/stream_chat_flutter/test/src/message_input/goldens/ci/countdown_button_0.png index 06b386f836..8f5524d281 100644 Binary files a/packages/stream_chat_flutter/test/src/message_input/goldens/ci/countdown_button_0.png and b/packages/stream_chat_flutter/test/src/message_input/goldens/ci/countdown_button_0.png differ diff --git a/packages/stream_chat_flutter/test/src/message_input/message_input_attachment_list_test.dart b/packages/stream_chat_flutter/test/src/message_input/message_input_attachment_list_test.dart index 15a655d9c1..a5ba233209 100644 --- a/packages/stream_chat_flutter/test/src/message_input/message_input_attachment_list_test.dart +++ b/packages/stream_chat_flutter/test/src/message_input/message_input_attachment_list_test.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; import '../mocks.dart'; @@ -36,8 +37,7 @@ void main() { ); // Expect 2 file attachments and 1 media attachment - expect(find.byType(MessageInputFileAttachments), findsOneWidget); - expect(find.byType(StreamFileAttachment), findsNWidgets(2)); + expect(find.byType(MessageComposerFileAttachment), findsNWidgets(2)); expect(find.byType(MessageInputMediaAttachments), findsOneWidget); expect(find.byType(StreamMediaAttachmentThumbnail), findsOneWidget); }, @@ -64,7 +64,7 @@ void main() { ), ); - final removeButtons = find.byType(RemoveAttachmentButton); + final removeButtons = find.byType(StreamRemoveControl); // Tap the first remove button await tester.tap(removeButtons.first); @@ -72,7 +72,7 @@ void main() { // Expect the onRemovePressed callback to be called with the second // attachment as they are reversed in the UI. - expect(removedAttachment, attachments[1]); + expect(removedAttachment, attachments[0]); }, ); @@ -113,7 +113,7 @@ void main() { ); // Expect 2 file attachments - expect(find.byType(StreamFileAttachment), findsNWidgets(2)); + expect(find.byType(MessageComposerFileAttachment), findsNWidgets(2)); }, ); @@ -137,7 +137,7 @@ void main() { ), ); - final removeButton = find.byType(RemoveAttachmentButton); + final removeButton = find.byType(StreamRemoveControl); // Tap the remove button await tester.tap(removeButton); @@ -228,7 +228,7 @@ void main() { ), ); - final removeButton = find.byType(RemoveAttachmentButton); + final removeButton = find.byType(StreamRemoveControl); // Tap the remove button await tester.tap(removeButton); diff --git a/packages/stream_chat_flutter/test/src/message_input/message_input_test.dart b/packages/stream_chat_flutter/test/src/message_input/message_input_test.dart index cfb48916e2..e24351704e 100644 --- a/packages/stream_chat_flutter/test/src/message_input/message_input_test.dart +++ b/packages/stream_chat_flutter/test/src/message_input/message_input_test.dart @@ -12,6 +12,7 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import '../fakes.dart'; import '../mocks.dart'; +/// TODO: remove skip once we have a proper message input test. void main() { final originalRecordPlatform = RecordPlatform.instance; setUp(() => RecordPlatform.instance = FakeRecordPlatform()); @@ -19,10 +20,13 @@ void main() { testWidgets( 'checks message input features', + skip: true, (WidgetTester tester) async { - await tester.pumpWidget(buildWidget( - const StreamMessageInput(), - )); + await tester.pumpWidget( + buildWidget( + const StreamMessageInput(), + ), + ); // wait for the initial state to be rendered. await tester.pumpAndSettle(); @@ -34,6 +38,7 @@ void main() { testWidgets( 'checks message input slow mode', + skip: true, (WidgetTester tester) async { final client = MockClient(); final clientState = MockClientState(); @@ -62,7 +67,7 @@ void main() { Member( userId: 'user-id', user: User(id: 'user-id'), - ) + ), ]), ); when(() => channelState.members).thenReturn([ @@ -75,14 +80,14 @@ void main() { Message( text: 'hello', user: User(id: 'other-user'), - ) + ), ]); when(() => channelState.messagesStream).thenAnswer( (i) => Stream.value([ Message( text: 'hello', user: User(id: 'other-user'), - ) + ), ]), ); @@ -109,6 +114,7 @@ void main() { testWidgets( 'allows setting padding on message input', + skip: true, (WidgetTester tester) async { await tester.pumpWidget( buildWidget( @@ -122,17 +128,18 @@ void main() { await tester.pumpAndSettle(); expect( - find.descendant( - of: find.byType(StreamMessageValueListenableBuilder), - matching: find.byWidgetPredicate((w) => - w is Padding && - w.padding == const EdgeInsets.only(left: 50))), - findsOneWidget); + find.descendant( + of: find.byType(StreamMessageValueListenableBuilder), + matching: find.byWidgetPredicate((w) => w is Padding && w.padding == const EdgeInsets.only(left: 50)), + ), + findsOneWidget, + ); }, ); testWidgets( 'allows setting explicit margin on text field', + skip: true, (WidgetTester tester) async { await tester.pumpWidget( buildWidget( @@ -145,12 +152,12 @@ void main() { await tester.pumpAndSettle(); expect( - find.descendant( - of: find.byType(DropTarget), - matching: find.byWidgetPredicate((w) => - w is Container && - w.margin == const EdgeInsets.only(left: 50))), - findsOneWidget); + find.descendant( + of: find.byType(DropTarget), + matching: find.byWidgetPredicate((w) => w is Container && w.margin == const EdgeInsets.only(left: 50)), + ), + findsOneWidget, + ); }, ); @@ -177,6 +184,7 @@ void main() { testWidgets( 'should send message when Enter key is pressed on desktop', + skip: true, (tester) async { when(() => channel.sendMessage(any())).thenAnswer( (i) async => SendMessageResponse() @@ -218,6 +226,7 @@ void main() { testWidgets( 'should not send message when Shift+Enter key is pressed on desktop', + skip: true, (tester) async { when(() => channel.sendMessage(any())).thenAnswer( (_) async => SendMessageResponse() @@ -263,6 +272,7 @@ void main() { testWidgets( 'should clear quoted message when Esc key is pressed on desktop', + skip: true, (tester) async { final quotedMessage = Message(text: 'I am a quoted message'); final initialMessage = Message(quotedMessage: quotedMessage); @@ -311,6 +321,7 @@ void main() { testWidgets( 'should not clear quoted message contains text and Esc key is pressed on desktop', + skip: true, (tester) async { final quotedMessage = Message(text: 'I am a quoted message'); final initialMessage = Message(quotedMessage: quotedMessage); @@ -358,6 +369,131 @@ void main() { ); }); + group('Edit message routing', () { + final client = MockClient(); + final clientState = MockClientState(); + final channel = MockChannel(); + final channelState = MockChannelState(); + + setUp(() { + registerFallbackValue(Message()); + + when(() => client.state).thenReturn(clientState); + when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id')); + when(() => clientState.currentUserStream).thenAnswer( + (_) => Stream.value(OwnUser(id: 'user-id')), + ); + + when(() => channel.state).thenReturn(channelState); + when(() => channel.client).thenReturn(client); + when(channel.getRemainingCooldown).thenReturn(0); + when(() => channel.isMuted).thenReturn(false); + when(() => channel.isMutedStream).thenAnswer((_) => Stream.value(false)); + when(() => channel.extraData).thenReturn({'name': 'test'}); + when(() => channel.extraDataStream).thenAnswer((_) => Stream.value({'name': 'test'})); + when(() => channelState.isUpToDate).thenReturn(true); + when(() => channelState.members).thenReturn([ + Member( + userId: 'user-id', + user: User(id: 'user-id'), + ), + ]); + when(() => channelState.membersStream).thenAnswer( + (_) => Stream.value([ + Member( + userId: 'user-id', + user: User(id: 'user-id'), + ), + ]), + ); + when(() => channelState.messages).thenReturn([]); + when(() => channelState.messagesStream).thenAnswer((_) => Stream.value([])); + }); + + testWidgets( + 'calls updateMessage when controller is in edit state', + (tester) async { + when(() => channel.updateMessage(any())).thenAnswer( + (_) async => UpdateMessageResponse()..message = Message(id: 'msg-1', text: 'Edited text'), + ); + + final existingMessage = Message(id: 'msg-1', text: 'Original text'); + final messageInputController = StreamMessageInputController()..editMessage(existingMessage); + addTearDown(messageInputController.dispose); + + final key = GlobalKey(); + + await tester.pumpWidget( + MaterialApp( + home: StreamChat( + client: client, + child: StreamChannel( + channel: channel, + child: Scaffold( + bottomNavigationBar: StreamMessageInput( + key: key, + messageInputController: messageInputController, + ), + ), + ), + ), + ), + ); + + await tester.pumpAndSettle(); + + await key.currentState!.sendMessage(); + // Pump past the debounce/throttle timers (350ms) + await tester.pump(const Duration(seconds: 1)); + + verify(() => channel.updateMessage(any())).called(1); + verifyNever(() => channel.sendMessage(any())); + }, + ); + + testWidgets( + 'calls sendMessage when controller is in normal (non-edit) state', + (tester) async { + when(() => channel.sendMessage(any())).thenAnswer( + (_) async => SendMessageResponse()..message = Message(text: 'Hello'), + ); + + final messageInputController = StreamMessageInputController( + message: Message(text: 'Hello'), + ); + addTearDown(messageInputController.dispose); + + final key = GlobalKey(); + + await tester.pumpWidget( + MaterialApp( + home: StreamChat( + client: client, + child: StreamChannel( + channel: channel, + child: Scaffold( + bottomNavigationBar: StreamMessageInput( + key: key, + messageInputController: messageInputController, + ), + ), + ), + ), + ), + ); + + await tester.pumpAndSettle(); + + await key.currentState!.sendMessage(); + // Pump past the debounce/throttle timers (350ms) + await tester.pump(const Duration(seconds: 1)); + + verify(() => channel.sendMessage(any())).called(1); + verifyNever(() => channel.updateMessage(any())); + }, + ); + }); + group('DmCheckboxListTile integration in MessageInput', () { final client = MockClient(); final clientState = MockClientState(); @@ -381,20 +517,21 @@ void main() { Message( text: 'hello', user: User(id: 'other-user'), - ) + ), ]); when(() => channelState.messagesStream).thenAnswer( (i) => Stream.value([ Message( text: 'hello', user: User(id: 'other-user'), - ) + ), ]), ); }); testWidgets( 'should not show DmCheckboxListTile when hideSendAsDm is true', + skip: true, (tester) async { await tester.pumpWidget( MaterialApp( @@ -404,7 +541,7 @@ void main() { channel: channel, child: const Scaffold( bottomNavigationBar: StreamMessageInput( - hideSendAsDm: true, + canAlsoSendToChannelFromThread: false, ), ), ), @@ -420,6 +557,7 @@ void main() { testWidgets( 'should not show DmCheckboxListTile when not in a thread', + skip: true, (tester) async { await tester.pumpWidget( MaterialApp( @@ -443,6 +581,7 @@ void main() { testWidgets( 'should show DmCheckboxListTile when in a thread and hideSendAsDm is false', + skip: true, (tester) async { // Set up a message controller with a parent message ID (thread) final messageInputController = StreamMessageInputController( @@ -473,6 +612,7 @@ void main() { testWidgets( 'should toggle showInChannel value when DmCheckboxListTile is tapped', + skip: true, (tester) async { // Set up a message controller with a parent message ID (thread) final messageInputController = StreamMessageInputController( @@ -548,7 +688,7 @@ MaterialApp buildWidget(StreamMessageInput input) { Member( userId: 'user-id', user: User(id: 'user-id'), - ) + ), ]), ); when(() => channelState.members).thenReturn([ @@ -561,14 +701,14 @@ MaterialApp buildWidget(StreamMessageInput input) { Message( text: 'hello', user: User(id: 'other-user'), - ) + ), ]); when(() => channelState.messagesStream).thenAnswer( (i) => Stream.value([ Message( text: 'hello', user: User(id: 'other-user'), - ) + ), ]), ); diff --git a/packages/stream_chat_flutter/test/src/message_input/stream_message_send_button_test.dart b/packages/stream_chat_flutter/test/src/message_input/stream_message_send_button_test.dart index a9e4d95052..888c5ad636 100644 --- a/packages/stream_chat_flutter/test/src/message_input/stream_message_send_button_test.dart +++ b/packages/stream_chat_flutter/test/src/message_input/stream_message_send_button_test.dart @@ -2,13 +2,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:stream_chat_flutter/src/icons/stream_svg_icon.dart'; import 'package:stream_chat_flutter/src/message_input/stream_message_input_icon_button.dart'; import 'package:stream_chat_flutter/src/message_input/stream_message_send_button.dart'; import 'package:stream_chat_flutter/src/theme/message_input_theme.dart'; import 'package:stream_chat_flutter/src/theme/stream_chat_theme.dart'; - -import '../utils/finders.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; void main() { group('StreamMessageSendButton', () { @@ -49,7 +47,7 @@ void main() { expect(iconButton.onPressed, isNull); // Verify default idle icon is shown - expect(find.bySvgIcon(StreamSvgIcons.sendMessage), findsOneWidget); + expect(find.byIcon(StreamIconData.iconPaperPlaneTopRight), findsOneWidget); }, ); @@ -73,7 +71,7 @@ void main() { expect(iconButton.onPressed, isNotNull); // Verify default active icon is shown - expect(find.bySvgIcon(StreamSvgIcons.circleUp), findsOneWidget); + expect(find.byIcon(StreamIconData.iconArrowUp), findsOneWidget); }, ); @@ -93,7 +91,7 @@ void main() { ); expect(find.byKey(const Key('custom_idle')), findsOneWidget); - expect(find.byType(StreamSvgIcon), findsNothing); + expect(find.byType(Icon), findsNothing); }, ); @@ -113,7 +111,7 @@ void main() { ); expect(find.byKey(const Key('custom_active')), findsOneWidget); - expect(find.byType(StreamSvgIcon), findsNothing); + expect(find.byType(Icon), findsNothing); }, ); @@ -176,20 +174,22 @@ Widget _wrapWithStreamChatApp( debugShowCheckedModeBanner: false, home: StreamChatTheme( data: StreamChatThemeData(brightness: brightness), - child: Builder(builder: (context) { - final theme = StreamChatTheme.of(context); - return Scaffold( - backgroundColor: theme.colorTheme.appBg, - bottomNavigationBar: Material( - elevation: 10, - color: theme.colorTheme.barsBg, - child: Padding( - padding: const EdgeInsets.all(8), - child: widget, + child: Builder( + builder: (context) { + final theme = StreamChatTheme.of(context); + return Scaffold( + backgroundColor: theme.colorTheme.appBg, + bottomNavigationBar: Material( + elevation: 10, + color: theme.colorTheme.barsBg, + child: Padding( + padding: const EdgeInsets.all(8), + child: widget, + ), ), - ), - ); - }), + ); + }, + ), ), ); } diff --git a/packages/stream_chat_flutter/test/src/message_list_view/bottom_row_test.dart b/packages/stream_chat_flutter/test/src/message_list_view/bottom_row_test.dart deleted file mode 100644 index 4f3059b99c..0000000000 --- a/packages/stream_chat_flutter/test/src/message_list_view/bottom_row_test.dart +++ /dev/null @@ -1,74 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; - -import '../mocks.dart'; - -void main() { - late Channel channel; - late ChannelClientState channelClientState; - - setUp(() { - channel = MockChannel(); - when(() => channel.on(any(), any(), any(), any())) - .thenAnswer((_) => const Stream.empty()); - channelClientState = MockChannelState(); - when(() => channel.state).thenReturn(channelClientState); - when(() => channelClientState.messages).thenReturn([ - Message( - id: 'parentId', - ) - ]); - }); - - setUpAll(() { - registerFallbackValue(Message()); - }); - - testWidgets('BottomRow', (tester) async { - final theme = StreamChatThemeData.light(); - final onThreadTap = MockValueChanged(); - - await tester.pumpWidget( - MaterialApp( - home: Scaffold( - body: Center( - child: StreamChannel( - channel: channel, - child: BottomRow( - message: Message( - parentId: 'parentId', - ), - isDeleted: false, - showThreadReplyIndicator: false, - showUsername: false, - showInChannel: true, - showTimeStamp: false, - showEditedLabel: false, - reverse: false, - showSendingIndicator: false, - hasUrlAttachments: false, - isGiphy: false, - isOnlyEmoji: false, - messageTheme: theme.otherMessageTheme, - streamChatTheme: theme, - hasNonUrlAttachments: false, - streamChat: StreamChatState(), - onThreadTap: onThreadTap, - ), - ), - ), - ), - ), - ); - - // wait for the initial state to be rendered. - await tester.pump(Duration.zero); - - await tester.tap(find.byType(GestureDetector)); - await tester.pumpAndSettle(); - - verify(() => onThreadTap.call(any())); - }); -} diff --git a/packages/stream_chat_flutter/test/src/message_list_view/message_list_view_test.dart b/packages/stream_chat_flutter/test/src/message_list_view/message_list_view_test.dart index 0fd7ae3eeb..96db684e56 100644 --- a/packages/stream_chat_flutter/test/src/message_list_view/message_list_view_test.dart +++ b/packages/stream_chat_flutter/test/src/message_list_view/message_list_view_test.dart @@ -18,34 +18,25 @@ void main() { clientState = MockClientState(); when(() => client.state).thenAnswer((_) => clientState); when(() => clientState.currentUser).thenReturn(OwnUser(id: 'testid')); - when(() => clientState.currentUserStream) - .thenAnswer((_) => Stream.value(OwnUser(id: 'testid'))); + when(() => clientState.currentUserStream).thenAnswer((_) => Stream.value(OwnUser(id: 'testid'))); channel = MockChannel(); - when(() => channel.on(any(), any(), any(), any())) - .thenAnswer((_) => const Stream.empty()); + when(() => channel.on(any(), any(), any(), any())).thenAnswer((_) => const Stream.empty()); channelClientState = MockChannelState(); when(() => channel.client).thenReturn(client); when(() => channel.state).thenReturn(channelClientState); - when(() => channelClientState.threadsStream) - .thenAnswer((_) => const Stream.empty()); - when(() => channelClientState.messagesStream) - .thenAnswer((_) => const Stream.empty()); + when(() => channelClientState.threadsStream).thenAnswer((_) => const Stream.empty()); + when(() => channelClientState.messagesStream).thenAnswer((_) => const Stream.empty()); when(() => channelClientState.messages).thenReturn([]); when(() => channelClientState.isUpToDate).thenReturn(true); - when(() => channelClientState.isUpToDateStream) - .thenAnswer((_) => Stream.value(true)); - when(() => channelClientState.unreadCountStream) - .thenAnswer((_) => Stream.value(0)); + when(() => channelClientState.isUpToDateStream).thenAnswer((_) => Stream.value(true)); + when(() => channelClientState.unreadCountStream).thenAnswer((_) => Stream.value(0)); when(() => channelClientState.unreadCount).thenReturn(0); - when(() => channelClientState.readStream) - .thenAnswer((_) => const Stream.empty()); + when(() => channelClientState.readStream).thenAnswer((_) => const Stream.empty()); when(() => channelClientState.read).thenReturn([]); - when(() => channelClientState.membersStream) - .thenAnswer((_) => const Stream.empty()); + when(() => channelClientState.membersStream).thenAnswer((_) => const Stream.empty()); when(() => channelClientState.members).thenReturn([]); when(() => channelClientState.currentUserRead).thenReturn(null); - when(() => channelClientState.currentUserReadStream) - .thenAnswer((_) => const Stream.empty()); + when(() => channelClientState.currentUserReadStream).thenAnswer((_) => const Stream.empty()); }); // https://github.com/GetStream/stream-chat-flutter/issues/674 @@ -71,8 +62,7 @@ void main() { expect(find.byKey(emptyWidgetKey), findsOneWidget); }); - testWidgets('renders a non empty message list view with custom background', - (tester) async { + testWidgets('renders a non empty message list view with custom background', (tester) async { final message = Message( id: 'message1', text: 'Hello world!', @@ -136,8 +126,7 @@ void main() { ); }); - testWidgets('renders a non empty message list view with unread messages', - (tester) async { + testWidgets('renders a non empty message list view with unread messages', (tester) async { final user = OwnUser(id: 'testid'); final message = Message( id: 'message1', @@ -148,8 +137,7 @@ void main() { ), ); - when(() => channelClientState.read) - .thenReturn([Read(lastRead: DateTime.now(), user: user)]); + when(() => channelClientState.read).thenReturn([Read(lastRead: DateTime.now(), user: user)]); when(() => channelClientState.messagesStream).thenAnswer( (_) => Stream.value([message]), diff --git a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/moderated_message_actions_modal_dark.png b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/moderated_message_actions_modal_dark.png index 0c67a41ec5..bb83a67f06 100644 Binary files a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/moderated_message_actions_modal_dark.png and b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/moderated_message_actions_modal_dark.png differ diff --git a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/moderated_message_actions_modal_light.png b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/moderated_message_actions_modal_light.png index 97ecba385d..cfc8c685b0 100644 Binary files a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/moderated_message_actions_modal_light.png and b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/moderated_message_actions_modal_light.png differ diff --git a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_dark.png b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_dark.png index fa77355965..c84f986c76 100644 Binary files a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_dark.png and b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_dark.png differ diff --git a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_light.png b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_light.png index 84c8a06ad9..b72aca0f2b 100644 Binary files a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_light.png and b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_light.png differ diff --git a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_reversed_dark.png b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_reversed_dark.png index 9c7b02944f..8ac58d0272 100644 Binary files a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_reversed_dark.png and b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_reversed_dark.png differ diff --git a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_reversed_light.png b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_reversed_light.png index 0e7a90ddbd..5d57a8a11a 100644 Binary files a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_reversed_light.png and b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_reversed_light.png differ diff --git a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_reversed_with_reactions_dark.png b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_reversed_with_reactions_dark.png index 70f016acae..becc690ec7 100644 Binary files a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_reversed_with_reactions_dark.png and b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_reversed_with_reactions_dark.png differ diff --git a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_reversed_with_reactions_light.png b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_reversed_with_reactions_light.png index 092fc3b0df..bbe907f69d 100644 Binary files a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_reversed_with_reactions_light.png and b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_reversed_with_reactions_light.png differ diff --git a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_with_reactions_dark.png b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_with_reactions_dark.png index 68182b20f4..415fcf54fd 100644 Binary files a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_with_reactions_dark.png and b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_with_reactions_dark.png differ diff --git a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_with_reactions_light.png b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_with_reactions_light.png index 6392c936be..94808ba067 100644 Binary files a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_with_reactions_light.png and b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_actions_modal_with_reactions_light.png differ diff --git a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_dark.png b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_dark.png deleted file mode 100644 index 9556a6d7c5..0000000000 Binary files a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_dark.png and /dev/null differ diff --git a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_light.png b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_light.png deleted file mode 100644 index 30ee9c682c..0000000000 Binary files a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_light.png and /dev/null differ diff --git a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_reversed_dark.png b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_reversed_dark.png deleted file mode 100644 index 0699b723af..0000000000 Binary files a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_reversed_dark.png and /dev/null differ diff --git a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_reversed_light.png b/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_reversed_light.png deleted file mode 100644 index 54cedd7d29..0000000000 Binary files a/packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_reversed_light.png and /dev/null differ diff --git a/packages/stream_chat_flutter/test/src/message_modal/message_actions_modal_test.dart b/packages/stream_chat_flutter/test/src/message_modal/message_actions_modal_test.dart index c035a9cb4a..a454847740 100644 --- a/packages/stream_chat_flutter/test/src/message_modal/message_actions_modal_test.dart +++ b/packages/stream_chat_flutter/test/src/message_modal/message_actions_modal_test.dart @@ -5,6 +5,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_portal/flutter_portal.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart' show StreamIconData; void main() { final message = Message( @@ -14,27 +15,26 @@ void main() { user: User(id: 'test-user', name: 'Test User'), ); - final messageActions = [ - StreamMessageAction( - title: const Text('Reply'), - leading: const StreamSvgIcon(icon: StreamSvgIcons.reply), - action: QuotedReply(message: message), + final messageActions = >[ + StreamContextMenuAction( + label: const Text('Reply'), + leading: const Icon(StreamIconData.iconArrowShareLeft), + value: QuotedReply(message: message), ), - StreamMessageAction( - title: const Text('Thread Reply'), - leading: const StreamSvgIcon(icon: StreamSvgIcons.threadReply), - action: ThreadReply(message: message), + StreamContextMenuAction( + label: const Text('Thread Reply'), + leading: const Icon(StreamIconData.iconBubbleAnnotation2ChatMessage), + value: ThreadReply(message: message), ), - StreamMessageAction( - title: const Text('Copy Message'), - leading: const StreamSvgIcon(icon: StreamSvgIcons.copy), - action: CopyMessage(message: message), + StreamContextMenuAction( + label: const Text('Copy Message'), + leading: const Icon(StreamIconData.iconSquareBehindSquare2Copy), + value: CopyMessage(message: message), ), - StreamMessageAction( - isDestructive: true, - title: const Text('Delete Message'), - leading: const StreamSvgIcon(icon: StreamSvgIcons.delete), - action: DeleteMessage(message: message), + StreamContextMenuAction.destructive( + label: const Text('Delete Message'), + leading: const Icon(StreamIconData.iconTrashBin), + value: DeleteMessage(message: message), ), ]; @@ -46,6 +46,7 @@ void main() { message: message, messageActions: messageActions, messageWidget: const Text('Message Widget'), + alignment: AlignmentDirectional.centerStart, ), ), ); @@ -66,6 +67,7 @@ void main() { message: message, messageActions: messageActions, messageWidget: const Text('Message Widget'), + alignment: AlignmentDirectional.centerStart, showReactionPicker: true, ), ), @@ -73,44 +75,52 @@ void main() { // Use a longer timeout to ensure everything is rendered await tester.pumpAndSettle(const Duration(seconds: 1)); - expect(find.byType(StreamReactionPicker), findsOneWidget); + expect(find.byType(StreamMessageReactionPicker), findsOneWidget); }); testWidgets( - 'calls onActionTap with SelectReaction when reaction is selected', + 'pops with SelectReaction when reaction is selected', (tester) async { MessageAction? messageAction; - // Define custom reaction icons for testing - final testReactionIcons = [ - StreamReactionIcon( - type: 'like', - builder: (context, isActive, size) => const Icon(Icons.thumb_up), - ), - StreamReactionIcon( - type: 'love', - builder: (context, isActive, size) => const Icon(Icons.favorite), - ), - ]; + // Define custom reaction icons via resolver for testing. + const testReactionResolver = _TestReactionIconResolver( + defaultReactionTypes: {'like', 'love'}, + iconByType: { + 'like': Icons.thumb_up, + 'love': Icons.favorite, + }, + ); await tester.pumpWidget( _wrapWithMaterialApp( - StreamMessageActionsModal( - message: message, - messageActions: messageActions, - messageWidget: const Text('Message Widget'), - showReactionPicker: true, - onActionTap: (action) => messageAction = action, + reactionIconResolver: testReactionResolver, + Builder( + builder: (context) => TextButton( + onPressed: () async { + messageAction = await showStreamDialog( + context: context, + builder: (_) => StreamMessageActionsModal( + message: message, + messageActions: messageActions, + messageWidget: const Text('Message Widget'), + alignment: AlignmentDirectional.centerStart, + showReactionPicker: true, + ), + ); + }, + child: const Text('Open Dialog'), + ), ), - reactionIcons: testReactionIcons, ), ); + await tester.tap(find.text('Open Dialog')); // Use a longer timeout to ensure everything is rendered await tester.pumpAndSettle(const Duration(seconds: 1)); // Verify reaction picker is shown - expect(find.byType(StreamReactionPicker), findsOneWidget); + expect(find.byType(StreamMessageReactionPicker), findsOneWidget); // Find and tap the first reaction (like) final reactionIconFinder = find.byIcon(Icons.thumb_up); @@ -119,17 +129,20 @@ void main() { await tester.pumpAndSettle(); expect(messageAction, isA()); - // Verify callback was called with correct reaction type + // Verify the popped value has correct reaction type expect((messageAction! as SelectReaction).reaction.type, 'like'); - // Find and tap the second reaction (love) + // Open dialog again and tap the second reaction (love) + await tester.tap(find.text('Open Dialog')); + await tester.pumpAndSettle(const Duration(seconds: 1)); + final loveIconFinder = find.byIcon(Icons.favorite); expect(loveIconFinder, findsOneWidget); await tester.tap(loveIconFinder); await tester.pumpAndSettle(); expect(messageAction, isA()); - // Verify callback was called with correct reaction type + // Verify the popped value has correct reaction type expect((messageAction! as SelectReaction).reaction.type, 'love'); }, ); @@ -173,6 +186,7 @@ void main() { message: message, messageActions: messageActions, messageWidget: buildMessageWidget(), + alignment: AlignmentDirectional.centerStart, ), ), ); @@ -187,6 +201,7 @@ void main() { message: message, messageActions: messageActions, messageWidget: buildMessageWidget(), + alignment: AlignmentDirectional.centerStart, showReactionPicker: true, ), ), @@ -202,7 +217,7 @@ void main() { message: message, messageActions: messageActions, messageWidget: buildMessageWidget(reverse: true), - reverse: true, + alignment: AlignmentDirectional.centerEnd, ), ), ); @@ -217,8 +232,8 @@ void main() { message: message, messageActions: messageActions, messageWidget: buildMessageWidget(reverse: true), + alignment: AlignmentDirectional.centerEnd, showReactionPicker: true, - reverse: true, ), ), ); @@ -229,30 +244,71 @@ void main() { Widget _wrapWithMaterialApp( Widget child, { Brightness? brightness, - List? reactionIcons, + ReactionIconResolver? reactionIconResolver, }) { return Portal( child: MaterialApp( debugShowCheckedModeBanner: false, - home: StreamChatConfiguration( - data: StreamChatConfigurationData(reactionIcons: reactionIcons), + theme: ThemeData(brightness: brightness), + builder: (context, child) => StreamChatConfiguration( + data: StreamChatConfigurationData( + reactionIconResolver: reactionIconResolver ?? const _TestReactionIconResolver(), + ), child: StreamChatTheme( data: StreamChatThemeData(brightness: brightness), - child: Builder(builder: (context) { - final theme = StreamChatTheme.of(context); - return Scaffold( - backgroundColor: theme.colorTheme.appBg, - body: ColoredBox( - color: theme.colorTheme.overlay, - child: Padding( - padding: const EdgeInsets.all(8), - child: child, - ), - ), - ); - }), + child: child ?? const SizedBox.shrink(), ), ), + home: Builder( + builder: (context) { + final theme = StreamChatTheme.of(context); + return Scaffold( + backgroundColor: theme.colorTheme.appBg, + body: ColoredBox( + color: theme.colorTheme.overlay, + child: Padding( + padding: const EdgeInsets.all(8), + child: child, + ), + ), + ); + }, + ), ), ); } + +class _TestReactionIconResolver extends ReactionIconResolver { + const _TestReactionIconResolver({ + this.defaultReactionTypes = const {'like', 'love', 'haha', 'wow', 'sad'}, + this.iconByType = const {}, + }); + + final Set defaultReactionTypes; + final Map iconByType; + + @override + Set get defaultReactions => defaultReactionTypes; + + @override + Set get supportedReactions => { + ...defaultReactionTypes, + ...iconByType.keys, + }; + + @override + String? emojiCode(String type) => streamSupportedEmojis[type]?.emoji; + + @override + Widget resolve(BuildContext context, String type) { + if (iconByType[type] case final icon?) { + return Icon(icon); + } + + if (emojiCode(type) case final emoji?) { + return Text(emoji); + } + + return const Text('❓'); + } +} diff --git a/packages/stream_chat_flutter/test/src/message_modal/message_reactions_modal_test.dart b/packages/stream_chat_flutter/test/src/message_modal/message_reactions_modal_test.dart deleted file mode 100644 index 2b942ad0de..0000000000 --- a/packages/stream_chat_flutter/test/src/message_modal/message_reactions_modal_test.dart +++ /dev/null @@ -1,276 +0,0 @@ -import 'package:alchemist/alchemist.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_portal/flutter_portal.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; - -import '../mocks.dart'; - -void main() { - final message = Message( - id: 'test-message', - text: 'This is a test message', - createdAt: DateTime.now(), - user: User(id: 'test-user', name: 'Test User'), - latestReactions: [ - Reaction( - type: 'love', - messageId: 'test-message', - user: User(id: 'user-1', name: 'User 1'), - createdAt: DateTime.now(), - ), - Reaction( - type: 'like', - messageId: 'test-message', - user: User(id: 'user-2', name: 'User 2'), - createdAt: DateTime.now(), - ), - ], - reactionGroups: { - 'love': ReactionGroup(count: 1, sumScores: 1), - 'like': ReactionGroup(count: 1, sumScores: 1), - }, - ); - - late MockClient mockClient; - - setUp(() { - mockClient = MockClient(); - - final mockClientState = MockClientState(); - when(() => mockClient.state).thenReturn(mockClientState); - - // Mock the current user for the message reactions test - final currentUser = OwnUser(id: 'current-user', name: 'Current User'); - when(() => mockClientState.currentUser).thenReturn(currentUser); - }); - - tearDown(() => reset(mockClient)); - - group('StreamMessageReactionsModal', () { - testWidgets( - 'renders message widget and reactions correctly', - (tester) async { - await tester.pumpWidget( - _wrapWithMaterialApp( - client: mockClient, - StreamMessageReactionsModal( - message: message, - messageWidget: const Text('Message Widget'), - ), - ), - ); - - // Use a longer timeout to ensure everything is rendered - await tester.pumpAndSettle(const Duration(seconds: 2)); - expect(find.text('Message Widget'), findsOneWidget); - // Check for reaction picker - expect(find.byType(StreamReactionPicker), findsOneWidget); - // Check for reaction details - expect(find.byType(StreamUserReactions), findsOneWidget); - }, - ); - - testWidgets( - 'calls onUserAvatarTap when user avatar is tapped', - (tester) async { - User? tappedUser; - - // Create just the StreamUserAvatar directly - await tester.pumpWidget( - _wrapWithMaterialApp( - client: mockClient, - StreamMessageReactionsModal( - message: message, - messageWidget: const Text('Message Widget'), - onUserAvatarTap: (user) { - tappedUser = user; - }, - ), - ), - ); - - await tester.pumpAndSettle(); - - final avatar = find.descendant( - of: find.byType(StreamUserReactions), - matching: find.byType(StreamUserAvatar), - ); - - // Verify the avatar is rendered - expect(avatar, findsNWidgets(2)); - - // Tap on the first avatar directly - await tester.tap(avatar.first); - await tester.pumpAndSettle(); - - // Verify the callback was called - expect(tappedUser, isNotNull); - }, - ); - - testWidgets( - 'calls onReactionPicked with SelectReaction when reaction is selected', - (tester) async { - MessageAction? messageAction; - - // Define custom reaction icons for testing - final testReactionIcons = [ - StreamReactionIcon( - type: 'like', - builder: (context, isActive, size) => const Icon(Icons.thumb_up), - ), - StreamReactionIcon( - type: 'love', - builder: (context, isActive, size) => const Icon(Icons.favorite), - ), - StreamReactionIcon( - type: 'camera', - builder: (context, isActive, size) => const Icon(Icons.camera), - ), - StreamReactionIcon( - type: 'call', - builder: (context, isActive, size) => const Icon(Icons.call), - ), - ]; - - await tester.pumpWidget( - _wrapWithMaterialApp( - client: mockClient, - reactionIcons: testReactionIcons, - StreamMessageReactionsModal( - message: message, - messageWidget: const Text('Message Widget'), - onReactionPicked: (action) => messageAction = action, - ), - ), - ); - - // Use a longer timeout to ensure everything is rendered - await tester.pumpAndSettle(const Duration(seconds: 1)); - - // Verify reaction picker is shown - expect(find.byType(StreamReactionPicker), findsOneWidget); - - // Find and tap the camera reaction (camera) - final reactionIconFinder = find.byIcon(Icons.camera); - expect(reactionIconFinder, findsOneWidget); - await tester.tap(reactionIconFinder); - await tester.pumpAndSettle(); - - expect(messageAction, isA()); - // Verify callback was called with correct reaction type - expect((messageAction! as SelectReaction).reaction.type, 'camera'); - - // Find and tap the call reaction (call) - final loveIconFinder = find.byIcon(Icons.call); - expect(loveIconFinder, findsOneWidget); - await tester.tap(loveIconFinder); - await tester.pumpAndSettle(); - - expect(messageAction, isA()); - // Verify callback was called with correct reaction type - expect((messageAction! as SelectReaction).reaction.type, 'call'); - }, - ); - }); - - group('StreamMessageReactionsModal Golden Tests', () { - Widget buildMessageWidget({bool reverse = false}) { - return Builder( - builder: (context) { - final theme = StreamChatTheme.of(context); - final messageTheme = theme.getMessageTheme(reverse: reverse); - - return Container( - padding: const EdgeInsets.symmetric( - vertical: 8, - horizontal: 16, - ), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(14), - color: messageTheme.messageBackgroundColor, - ), - child: Text( - message.text ?? '', - style: messageTheme.messageTextStyle, - ), - ); - }, - ); - } - - for (final brightness in Brightness.values) { - final theme = brightness.name; - - goldenTest( - 'StreamMessageReactionsModal in $theme theme', - fileName: 'stream_message_reactions_modal_$theme', - constraints: const BoxConstraints(maxWidth: 400, maxHeight: 600), - builder: () => _wrapWithMaterialApp( - client: mockClient, - brightness: brightness, - StreamMessageReactionsModal( - message: message, - messageWidget: buildMessageWidget(), - onReactionPicked: (_) {}, - ), - ), - ); - - goldenTest( - 'StreamMessageReactionsModal reversed in $theme theme', - fileName: 'stream_message_reactions_modal_reversed_$theme', - constraints: const BoxConstraints(maxWidth: 400, maxHeight: 600), - builder: () => _wrapWithMaterialApp( - client: mockClient, - brightness: brightness, - StreamMessageReactionsModal( - message: message, - messageWidget: buildMessageWidget(reverse: true), - reverse: true, - onReactionPicked: (_) {}, - ), - ), - ); - } - }); -} - -Widget _wrapWithMaterialApp( - Widget child, { - required StreamChatClient client, - Brightness? brightness, - List? reactionIcons, -}) { - return Portal( - child: MaterialApp( - debugShowCheckedModeBanner: false, - home: StreamChat( - client: client, - // Mock the connectivity stream to always return wifi. - connectivityStream: Stream.value([ConnectivityResult.wifi]), - child: StreamChatConfiguration( - data: StreamChatConfigurationData(reactionIcons: reactionIcons), - child: StreamChatTheme( - data: StreamChatThemeData(brightness: brightness), - child: Builder(builder: (context) { - final theme = StreamChatTheme.of(context); - return Scaffold( - backgroundColor: theme.colorTheme.appBg, - body: ColoredBox( - color: theme.colorTheme.overlay, - child: Padding( - padding: const EdgeInsets.all(8), - child: child, - ), - ), - ); - }), - ), - ), - ), - ), - ); -} diff --git a/packages/stream_chat_flutter/test/src/message_modal/moderated_message_actions_modal_test.dart b/packages/stream_chat_flutter/test/src/message_modal/moderated_message_actions_modal_test.dart index 59e76cf347..01f92d525e 100644 --- a/packages/stream_chat_flutter/test/src/message_modal/moderated_message_actions_modal_test.dart +++ b/packages/stream_chat_flutter/test/src/message_modal/moderated_message_actions_modal_test.dart @@ -4,6 +4,7 @@ import 'package:alchemist/alchemist.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; void main() { final message = Message( @@ -18,22 +19,21 @@ void main() { ), ); - final messageActions = [ - StreamMessageAction( - title: const Text('Send Anyway'), - leading: const StreamSvgIcon(icon: StreamSvgIcons.send), - action: ResendMessage(message: message), + final messageActions = >[ + StreamContextMenuAction( + label: const Text('Send Anyway'), + leading: const Icon(StreamIconData.iconPaperPlane), + value: ResendMessage(message: message), ), - StreamMessageAction( - title: const Text('Edit Message'), - leading: const StreamSvgIcon(icon: StreamSvgIcons.edit), - action: EditMessage(message: message), + StreamContextMenuAction( + label: const Text('Edit Message'), + leading: const Icon(StreamIconData.iconEditBig), + value: EditMessage(message: message), ), - StreamMessageAction( - isDestructive: true, - title: const Text('Delete Message'), - leading: const StreamSvgIcon(icon: StreamSvgIcons.delete), - action: HardDeleteMessage(message: message), + StreamContextMenuAction.destructive( + label: const Text('Delete Message'), + leading: const Icon(StreamIconData.iconTrashBin), + value: HardDeleteMessage(message: message), ), ]; @@ -52,7 +52,7 @@ void main() { await tester.pumpAndSettle(const Duration(seconds: 1)); // Check for icon, title and content - expect(find.byType(StreamSvgIcon), findsWidgets); + expect(find.byType(Icon), findsWidgets); expect(find.byType(Text), findsWidgets); // Check for actions @@ -61,19 +61,30 @@ void main() { expect(find.text('Delete Message'), findsOneWidget); }); - testWidgets('action buttons call the correct callbacks', (tester) async { + testWidgets('action buttons pop with the correct value', (tester) async { MessageAction? messageAction; await tester.pumpWidget( _wrapWithMaterialApp( - ModeratedMessageActionsModal( - message: message, - messageActions: messageActions, - onActionTap: (action) => messageAction = action, + Builder( + builder: (context) => TextButton( + onPressed: () async { + messageAction = await showStreamDialog( + context: context, + builder: (_) => ModeratedMessageActionsModal( + message: message, + messageActions: messageActions, + ), + ); + }, + child: const Text('Open Dialog'), + ), ), ), ); + // Open dialog and tap Send Anyway + await tester.tap(find.text('Open Dialog')); await tester.pumpAndSettle(); // Tap on Send Anyway button @@ -81,12 +92,16 @@ void main() { await tester.pumpAndSettle(); expect(messageAction, isA()); - // Tap on Edit Message button + // Open dialog and tap Edit Message + await tester.tap(find.text('Open Dialog')); + await tester.pumpAndSettle(); await tester.tap(find.text('Edit Message')); await tester.pumpAndSettle(); expect(messageAction, isA()); - // Tap on Delete Message button + // Open dialog and tap Delete Message + await tester.tap(find.text('Open Dialog')); + await tester.pumpAndSettle(); await tester.tap(find.text('Delete Message')); await tester.pumpAndSettle(); expect(messageAction, isA()); @@ -119,9 +134,16 @@ Widget _wrapWithMaterialApp( }) { return MaterialApp( debugShowCheckedModeBanner: false, - home: StreamChatTheme( - data: StreamChatThemeData(brightness: brightness), - child: Builder(builder: (context) { + theme: ThemeData(brightness: brightness), + builder: (context, child) => StreamChatConfiguration( + data: StreamChatConfigurationData(), + child: StreamChatTheme( + data: StreamChatThemeData(brightness: brightness), + child: child ?? const SizedBox.shrink(), + ), + ), + home: Builder( + builder: (context) { final theme = StreamChatTheme.of(context); return Scaffold( backgroundColor: theme.colorTheme.appBg, @@ -133,7 +155,7 @@ Widget _wrapWithMaterialApp( ), ), ); - }), + }, ), ); } diff --git a/packages/stream_chat_flutter/test/src/message_widget/deleted_message_test.dart b/packages/stream_chat_flutter/test/src/message_widget/deleted_message_test.dart deleted file mode 100644 index 6fc7bfe6eb..0000000000 --- a/packages/stream_chat_flutter/test/src/message_widget/deleted_message_test.dart +++ /dev/null @@ -1,217 +0,0 @@ -import 'package:alchemist/alchemist.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; - -import '../material_app_wrapper.dart'; -import '../mocks.dart'; - -void main() { - testWidgets('control test', (tester) async { - final client = MockClient(); - final clientState = MockClientState(); - - when(() => client.state).thenReturn(clientState); - when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id')); - - await tester.pumpWidget( - MaterialApp( - home: StreamChat( - client: client, - child: const Scaffold( - body: StreamDeletedMessage( - messageTheme: StreamMessageThemeData( - createdAtStyle: TextStyle( - color: Colors.black, - ), - messageTextStyle: TextStyle(), - ), - ), - ), - ), - ), - ); - - expect(find.text('Message deleted'), findsOneWidget); - }); - - goldenTest( - 'control golden light', - fileName: 'deleted_message_light', - constraints: const BoxConstraints.tightFor(width: 200, height: 200), - builder: () { - final client = MockClient(); - final clientState = MockClientState(); - final channel = MockChannel(); - final channelState = MockChannelState(); - final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); - - when(() => client.state).thenReturn(clientState); - when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id')); - when(() => channel.lastMessageAt).thenReturn(lastMessageAt); - when(() => channel.state).thenReturn(channelState); - when(() => channel.client).thenReturn(client); - when(() => channel.extraDataStream).thenAnswer( - (i) => Stream.value({ - 'name': 'test', - }), - ); - when(() => channel.extraData).thenReturn({ - 'name': 'test', - }); - - when(() => clientState.totalUnreadCount).thenReturn(10); - when(() => clientState.totalUnreadCountStream) - .thenAnswer((i) => Stream.value(10)); - - final materialTheme = ThemeData.light( - useMaterial3: false, - ); - final theme = StreamChatThemeData.fromTheme(materialTheme); - return MaterialAppWrapper( - theme: materialTheme, - home: StreamChat( - streamChatThemeData: theme, - client: client, - connectivityStream: Stream.value([ConnectivityResult.mobile]), - child: StreamChannel( - showLoading: false, - channel: channel, - child: Scaffold( - body: Center( - child: StreamDeletedMessage( - messageTheme: theme.ownMessageTheme, - ), - ), - ), - ), - ), - ); - }, - ); - - goldenTest( - 'control golden dark', - fileName: 'deleted_message_dark', - constraints: const BoxConstraints.tightFor(width: 200, height: 200), - builder: () { - final client = MockClient(); - final clientState = MockClientState(); - final channel = MockChannel(); - final channelState = MockChannelState(); - final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); - - when(() => client.state).thenReturn(clientState); - when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id')); - when(() => channel.lastMessageAt).thenReturn(lastMessageAt); - when(() => channel.state).thenReturn(channelState); - when(() => channel.client).thenReturn(client); - when(() => channel.extraDataStream).thenAnswer( - (i) => Stream.value({ - 'name': 'test', - }), - ); - when(() => channel.extraData).thenReturn({ - 'name': 'test', - }); - - when(() => clientState.totalUnreadCount).thenReturn(10); - when(() => clientState.totalUnreadCountStream) - .thenAnswer((i) => Stream.value(10)); - - final materialTheme = ThemeData.dark( - useMaterial3: false, - ); - final theme = StreamChatThemeData.fromTheme(materialTheme); - return MaterialAppWrapper( - theme: materialTheme, - home: StreamChat( - streamChatThemeData: theme, - client: client, - connectivityStream: Stream.value([ConnectivityResult.mobile]), - child: StreamChannel( - showLoading: false, - channel: channel, - child: Scaffold( - body: Center( - child: StreamDeletedMessage( - messageTheme: theme.ownMessageTheme, - ), - ), - ), - ), - ), - ); - }, - ); - - goldenTest( - 'golden customization test', - fileName: 'deleted_message_custom', - constraints: const BoxConstraints.tightFor(width: 200, height: 200), - builder: () { - final client = MockClient(); - final clientState = MockClientState(); - final channel = MockChannel(); - final channelState = MockChannelState(); - final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); - - when(() => client.state).thenReturn(clientState); - when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id')); - when(() => channel.lastMessageAt).thenReturn(lastMessageAt); - when(() => channel.state).thenReturn(channelState); - when(() => channel.client).thenReturn(client); - when(() => channel.extraDataStream).thenAnswer( - (i) => Stream.value({ - 'name': 'test', - }), - ); - when(() => channel.extraData).thenReturn({ - 'name': 'test', - }); - - when(() => clientState.totalUnreadCount).thenReturn(10); - when(() => clientState.totalUnreadCountStream) - .thenAnswer((i) => Stream.value(10)); - - final materialTheme = ThemeData.light( - useMaterial3: false, - ); - - var theme = StreamChatThemeData.fromTheme(materialTheme); - theme = theme.copyWith( - ownMessageTheme: theme.ownMessageTheme.copyWith( - messageDeletedStyle: theme.ownMessageTheme.messageTextStyle!.copyWith( - fontWeight: FontWeight.bold, - color: Colors.red, - ), - ), - ); - - return MaterialAppWrapper( - theme: materialTheme, - home: StreamChat( - streamChatThemeData: theme, - client: client, - connectivityStream: Stream.value([ConnectivityResult.mobile]), - child: StreamChannel( - showLoading: false, - channel: channel, - child: Scaffold( - body: Center( - child: StreamDeletedMessage( - messageTheme: theme.ownMessageTheme, - reverse: true, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), - ), - ), - ), - ), - ), - ); - }, - ); -} diff --git a/packages/stream_chat_flutter/test/src/message_widget/goldens/ci/deleted_message_custom.png b/packages/stream_chat_flutter/test/src/message_widget/goldens/ci/deleted_message_custom.png deleted file mode 100644 index 17676e62ba..0000000000 Binary files a/packages/stream_chat_flutter/test/src/message_widget/goldens/ci/deleted_message_custom.png and /dev/null differ diff --git a/packages/stream_chat_flutter/test/src/message_widget/goldens/ci/deleted_message_dark.png b/packages/stream_chat_flutter/test/src/message_widget/goldens/ci/deleted_message_dark.png deleted file mode 100644 index 8c45821b84..0000000000 Binary files a/packages/stream_chat_flutter/test/src/message_widget/goldens/ci/deleted_message_dark.png and /dev/null differ diff --git a/packages/stream_chat_flutter/test/src/message_widget/goldens/ci/deleted_message_light.png b/packages/stream_chat_flutter/test/src/message_widget/goldens/ci/deleted_message_light.png deleted file mode 100644 index d61bcdba89..0000000000 Binary files a/packages/stream_chat_flutter/test/src/message_widget/goldens/ci/deleted_message_light.png and /dev/null differ diff --git a/packages/stream_chat_flutter/test/src/message_widget/goldens/ci/message_text.png b/packages/stream_chat_flutter/test/src/message_widget/goldens/ci/message_text.png deleted file mode 100644 index 656da4e1cc..0000000000 Binary files a/packages/stream_chat_flutter/test/src/message_widget/goldens/ci/message_text.png and /dev/null differ diff --git a/packages/stream_chat_flutter/test/src/message_widget/message_text_test.dart b/packages/stream_chat_flutter/test/src/message_widget/message_text_test.dart deleted file mode 100644 index 1b294f9c21..0000000000 --- a/packages/stream_chat_flutter/test/src/message_widget/message_text_test.dart +++ /dev/null @@ -1,251 +0,0 @@ -import 'package:alchemist/alchemist.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_markdown/flutter_markdown.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; - -import '../material_app_wrapper.dart'; -import '../mocks.dart'; -import '../simple_frame.dart'; - -void expectTextStrings(Iterable widgets, List strings) { - var currentString = 0; - for (final widget in widgets) { - if (widget is RichText) { - final span = widget.text as TextSpan; - final text = _extractTextFromTextSpan(span); - expect(text, equals(strings[currentString])); - currentString += 1; - } - } -} - -String _extractTextFromTextSpan(TextSpan span) { - var text = span.text ?? ''; - if (span.children != null) { - for (final child in span.children! as Iterable) { - text += _extractTextFromTextSpan(child); - } - } - return text; -} - -void main() { - testWidgets( - 'it should show correct message text', - (WidgetTester tester) async { - final currentUser = OwnUser(id: 'user-id'); - final client = MockClient(); - final clientState = MockClientState(); - final channel = MockChannel(); - final channelState = MockChannelState(); - final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); - final themeData = ThemeData(); - final streamTheme = StreamChatThemeData.fromTheme(themeData); - - when(() => client.state).thenReturn(clientState); - when(() => clientState.currentUser).thenReturn(currentUser); - when(() => clientState.currentUserStream) - .thenAnswer((_) => Stream.value(currentUser)); - when(() => channel.lastMessageAt).thenReturn(lastMessageAt); - when(() => channel.state).thenReturn(channelState); - when(() => channel.client).thenReturn(client); - when(() => channel.isMuted).thenReturn(false); - when(() => channel.isMutedStream).thenAnswer((i) => Stream.value(false)); - when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({ - 'name': 'test', - })); - when(() => channel.extraData).thenReturn({ - 'name': 'test', - }); - - await tester.pumpWidget(MaterialApp( - home: StreamChat( - client: client, - child: StreamChannel( - channel: channel, - child: Scaffold( - body: StreamMessageText( - message: Message( - text: 'demo', - ), - messageTheme: streamTheme.otherMessageTheme), - ), - ), - ), - )); - - // wait for the initial state to be rendered. - await tester.pumpAndSettle(); - - expect(find.byType(MarkdownBody), findsOneWidget); - }, - ); - - group('Message with i18n field', () { - final client = MockClient(); - final clientState = MockClientState(); - final channel = MockChannel(); - final channelState = MockChannelState(); - const messageTheme = StreamMessageThemeData(); - - final currentUser = OwnUser( - id: 'sahil', - language: 'hi', - ); - - setUp(() { - when(() => client.state).thenReturn(clientState); - when(() => clientState.currentUser).thenReturn(currentUser); - when(() => clientState.currentUserStream) - .thenAnswer((_) => Stream.value(currentUser)); - - when(() => channel.state).thenReturn(channelState); - when(() => channel.client).thenReturn(client); - when(() => channel.isMuted).thenReturn(false); - when(() => channel.isMutedStream).thenAnswer((_) => Stream.value(false)); - }); - - testWidgets( - 'should show correct translated message text as per user language', - (WidgetTester tester) async { - final message = Message( - text: 'Hello', - i18n: const { - 'en_text': 'Hello', - 'hi_text': 'नमस्ते', - 'language': 'en', - }, - ); - - await tester.pumpWidget( - MaterialApp( - home: StreamChat( - client: client, - child: StreamChannel( - channel: channel, - child: Scaffold( - body: StreamMessageText( - message: message, - messageTheme: messageTheme, - ), - ), - ), - ), - ), - ); - - // wait for the initial state to be rendered. - await tester.pump(Duration.zero); - - expect(find.byType(MarkdownBody), findsOneWidget); - - final widgets = tester.allWidgets; - expectTextStrings(widgets, ['नमस्ते']); - }, - ); - - testWidgets( - '''should show default text if i18n does not contain translations as per user language''', - (WidgetTester tester) async { - final message = Message( - text: 'Hello', - i18n: const { - 'en_text': 'Hello', - 'fr_text': 'Bonjour', - 'language': 'en', - }, - ); - - await tester.pumpWidget( - MaterialApp( - home: StreamChat( - client: client, - child: StreamChannel( - channel: channel, - child: Scaffold( - body: StreamMessageText( - message: message, - messageTheme: messageTheme, - ), - ), - ), - ), - ), - ); - - // wait for the initial state to be rendered. - await tester.pump(Duration.zero); - - expect(find.byType(MarkdownBody), findsOneWidget); - - final widgets = tester.allWidgets; - expectTextStrings(widgets, ['Hello']); - }, - ); - }); - - goldenTest( - 'control test', - fileName: 'message_text', - constraints: const BoxConstraints.tightFor(width: 300, height: 200), - builder: () { - final currentUser = OwnUser(id: 'user-id'); - final client = MockClient(); - final clientState = MockClientState(); - final channel = MockChannel(); - final channelState = MockChannelState(); - final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); - final themeData = ThemeData(); - final streamTheme = StreamChatThemeData.fromTheme(themeData); - - when(() => client.state).thenReturn(clientState); - when(() => clientState.currentUser).thenReturn(currentUser); - when(() => clientState.currentUserStream) - .thenAnswer((_) => Stream.value(currentUser)); - when(() => channel.lastMessageAt).thenReturn(lastMessageAt); - when(() => channel.state).thenReturn(channelState); - when(() => channel.client).thenReturn(client); - when(() => channel.isMuted).thenReturn(false); - when(() => channel.isMutedStream).thenAnswer((i) => Stream.value(false)); - when(() => channel.extraDataStream).thenAnswer( - (i) => Stream.value({ - 'name': 'test', - }), - ); - when(() => channel.extraData).thenReturn({ - 'name': 'test', - }); - - const messageText = ''' -a message. -with multiple lines -and a list: -- a. okasd -- b lllll - -cool.'''; - - return MaterialAppWrapper( - home: SimpleFrame( - child: StreamChat( - client: client, - connectivityStream: Stream.value([ConnectivityResult.wifi]), - child: StreamChannel( - channel: channel, - child: Scaffold( - body: StreamMessageText( - message: Message( - text: messageText, - ), - messageTheme: streamTheme.otherMessageTheme, - ), - ), - ), - ), - ), - ); - }, - ); -} diff --git a/packages/stream_chat_flutter/test/src/message_widget/username_test.dart b/packages/stream_chat_flutter/test/src/message_widget/username_test.dart deleted file mode 100644 index bde411cb38..0000000000 --- a/packages/stream_chat_flutter/test/src/message_widget/username_test.dart +++ /dev/null @@ -1,23 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:stream_chat_flutter/src/message_widget/username.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; - -void main() { - testWidgets('Username', (tester) async { - await tester.pumpWidget( - MaterialApp( - home: Scaffold( - body: Center( - child: Username( - message: Message(), - messageTheme: StreamChatThemeData.light().ownMessageTheme, - ), - ), - ), - ), - ); - - expect(find.byType(Text), findsOneWidget); - }); -} diff --git a/packages/stream_chat_flutter/test/src/misc/audio_waveform_test.dart b/packages/stream_chat_flutter/test/src/misc/audio_waveform_test.dart index e9a8b75902..f9663694f9 100644 --- a/packages/stream_chat_flutter/test/src/misc/audio_waveform_test.dart +++ b/packages/stream_chat_flutter/test/src/misc/audio_waveform_test.dart @@ -2,7 +2,6 @@ import 'package:alchemist/alchemist.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; -import 'package:stream_chat_flutter/src/misc/audio_waveform.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import '../mocks.dart'; @@ -215,16 +214,19 @@ Widget _wrapWithMaterialApp( Brightness? brightness, }) { return MaterialApp( + theme: ThemeData(brightness: brightness), debugShowCheckedModeBanner: false, home: StreamChatTheme( data: StreamChatThemeData(brightness: brightness), - child: Builder(builder: (context) { - final theme = StreamChatTheme.of(context); - return Scaffold( - backgroundColor: theme.colorTheme.appBg, - body: Center(child: widget), - ); - }), + child: Builder( + builder: (context) { + final theme = StreamChatTheme.of(context); + return Scaffold( + backgroundColor: theme.colorTheme.appBg, + body: Center(child: widget), + ); + }, + ), ), ); } diff --git a/packages/stream_chat_flutter/test/src/misc/back_button_test.dart b/packages/stream_chat_flutter/test/src/misc/back_button_test.dart index c4ae6efc2a..5025c69c86 100644 --- a/packages/stream_chat_flutter/test/src/misc/back_button_test.dart +++ b/packages/stream_chat_flutter/test/src/misc/back_button_test.dart @@ -122,8 +122,7 @@ void main() { when(() => client.state).thenReturn(clientState); when(() => clientState.totalUnreadCount).thenAnswer((_) => 0); - when(() => clientState.totalUnreadCountStream) - .thenAnswer((_) => Stream.value(0)); + when(() => clientState.totalUnreadCountStream).thenAnswer((_) => Stream.value(0)); await tester.pumpWidget( MaterialApp( diff --git a/packages/stream_chat_flutter/test/src/misc/date_divider_test.dart b/packages/stream_chat_flutter/test/src/misc/date_divider_test.dart index af500a2039..a9eb6fb83a 100644 --- a/packages/stream_chat_flutter/test/src/misc/date_divider_test.dart +++ b/packages/stream_chat_flutter/test/src/misc/date_divider_test.dart @@ -49,8 +49,7 @@ void main() { child: Scaffold( body: StreamDateDivider( dateTime: testDate, - formatter: (context, date) => - 'Custom: ${date.day}/${date.month}', + formatter: (context, date) => 'Custom: ${date.day}/${date.month}', ), ), ), diff --git a/packages/stream_chat_flutter/test/src/misc/goldens/ci/reaction_bubble_2.png b/packages/stream_chat_flutter/test/src/misc/goldens/ci/reaction_bubble_2.png index e78fe9bfe4..b982f08bc4 100644 Binary files a/packages/stream_chat_flutter/test/src/misc/goldens/ci/reaction_bubble_2.png and b/packages/stream_chat_flutter/test/src/misc/goldens/ci/reaction_bubble_2.png differ diff --git a/packages/stream_chat_flutter/test/src/misc/goldens/ci/reaction_bubble_3_dark.png b/packages/stream_chat_flutter/test/src/misc/goldens/ci/reaction_bubble_3_dark.png index dee1aa31be..8cf9709e62 100644 Binary files a/packages/stream_chat_flutter/test/src/misc/goldens/ci/reaction_bubble_3_dark.png and b/packages/stream_chat_flutter/test/src/misc/goldens/ci/reaction_bubble_3_dark.png differ diff --git a/packages/stream_chat_flutter/test/src/misc/goldens/ci/reaction_bubble_3_light.png b/packages/stream_chat_flutter/test/src/misc/goldens/ci/reaction_bubble_3_light.png index d4de763486..4bef4095f2 100644 Binary files a/packages/stream_chat_flutter/test/src/misc/goldens/ci/reaction_bubble_3_light.png and b/packages/stream_chat_flutter/test/src/misc/goldens/ci/reaction_bubble_3_light.png differ diff --git a/packages/stream_chat_flutter/test/src/misc/goldens/ci/reaction_bubble_like_dark.png b/packages/stream_chat_flutter/test/src/misc/goldens/ci/reaction_bubble_like_dark.png index 08681c327f..f3cfab3aa4 100644 Binary files a/packages/stream_chat_flutter/test/src/misc/goldens/ci/reaction_bubble_like_dark.png and b/packages/stream_chat_flutter/test/src/misc/goldens/ci/reaction_bubble_like_dark.png differ diff --git a/packages/stream_chat_flutter/test/src/misc/goldens/ci/reaction_bubble_like_light.png b/packages/stream_chat_flutter/test/src/misc/goldens/ci/reaction_bubble_like_light.png index 8097b57606..eef3745e53 100644 Binary files a/packages/stream_chat_flutter/test/src/misc/goldens/ci/reaction_bubble_like_light.png and b/packages/stream_chat_flutter/test/src/misc/goldens/ci/reaction_bubble_like_light.png differ diff --git a/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_custom_dark.png b/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_custom_dark.png index 8fc9b61c50..3eade7a90f 100644 Binary files a/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_custom_dark.png and b/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_custom_dark.png differ diff --git a/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_custom_light.png b/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_custom_light.png index 6cade430e4..6468cbef90 100644 Binary files a/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_custom_light.png and b/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_custom_light.png differ diff --git a/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_dark.png b/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_dark.png index a5cfa0663a..5f37f0ac06 100644 Binary files a/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_dark.png and b/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_dark.png differ diff --git a/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_empty_dark.png b/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_empty_dark.png index b1cfc6c871..051dc6a03a 100644 Binary files a/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_empty_dark.png and b/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_empty_dark.png differ diff --git a/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_empty_light.png b/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_empty_light.png index c13687d05c..eb81386f80 100644 Binary files a/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_empty_light.png and b/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_empty_light.png differ diff --git a/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_inverted_dark.png b/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_inverted_dark.png index 5ed2a156ac..2a41c505bb 100644 Binary files a/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_inverted_dark.png and b/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_inverted_dark.png differ diff --git a/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_inverted_light.png b/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_inverted_light.png index 685f779862..0de2ead45e 100644 Binary files a/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_inverted_light.png and b/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_inverted_light.png differ diff --git a/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_less_data_dark.png b/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_less_data_dark.png index da0889c86b..d56033ae9c 100644 Binary files a/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_less_data_dark.png and b/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_less_data_dark.png differ diff --git a/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_less_data_light.png b/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_less_data_light.png index efb656ce3b..c6d2fa54ba 100644 Binary files a/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_less_data_light.png and b/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_less_data_light.png differ diff --git a/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_light.png b/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_light.png index 38ee4cfbd0..cc0cb28d0c 100644 Binary files a/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_light.png and b/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_light.png differ diff --git a/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_progress_dark.png b/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_progress_dark.png index b8db680f61..c7ebcf46d0 100644 Binary files a/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_progress_dark.png and b/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_progress_dark.png differ diff --git a/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_progress_light.png b/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_progress_light.png index 750abddc9d..c37ccc3e8a 100644 Binary files a/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_progress_light.png and b/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_progress_light.png differ diff --git a/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_timestamp_dark.png b/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_timestamp_dark.png index 90adba2dc0..4581216051 100644 Binary files a/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_timestamp_dark.png and b/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_timestamp_dark.png differ diff --git a/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_timestamp_light.png b/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_timestamp_light.png index 208b7b9270..0fe2ab210e 100644 Binary files a/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_timestamp_light.png and b/packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_timestamp_light.png differ diff --git a/packages/stream_chat_flutter/test/src/misc/goldens/ci/system_message_dark.png b/packages/stream_chat_flutter/test/src/misc/goldens/ci/system_message_dark.png index 5f48f32b09..3c10fdf7e3 100644 Binary files a/packages/stream_chat_flutter/test/src/misc/goldens/ci/system_message_dark.png and b/packages/stream_chat_flutter/test/src/misc/goldens/ci/system_message_dark.png differ diff --git a/packages/stream_chat_flutter/test/src/misc/goldens/ci/system_message_light.png b/packages/stream_chat_flutter/test/src/misc/goldens/ci/system_message_light.png index 70f6cda9c6..636b22dfa2 100644 Binary files a/packages/stream_chat_flutter/test/src/misc/goldens/ci/system_message_light.png and b/packages/stream_chat_flutter/test/src/misc/goldens/ci/system_message_light.png differ diff --git a/packages/stream_chat_flutter/test/src/misc/reaction_bubble_test.dart b/packages/stream_chat_flutter/test/src/misc/reaction_bubble_test.dart deleted file mode 100644 index 2dd7ef0b19..0000000000 --- a/packages/stream_chat_flutter/test/src/misc/reaction_bubble_test.dart +++ /dev/null @@ -1,238 +0,0 @@ -import 'package:alchemist/alchemist.dart'; -import 'package:flutter/material.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; - -import '../material_app_wrapper.dart'; -import '../mocks.dart'; - -void main() { - goldenTest( - 'it should show a like - light theme', - fileName: 'reaction_bubble_like_light', - constraints: const BoxConstraints.tightFor(width: 100, height: 100), - builder: () { - final client = MockClient(); - final clientState = MockClientState(); - final themeData = ThemeData.light( - useMaterial3: false, - ); - - when(() => client.state).thenReturn(clientState); - when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id')); - - final theme = StreamChatThemeData.fromTheme(themeData); - return MaterialAppWrapper( - theme: themeData, - home: StreamChat( - client: client, - streamChatThemeData: theme, - connectivityStream: Stream.value([ConnectivityResult.mobile]), - child: Scaffold( - body: Center( - child: StreamReactionBubble( - reactions: [ - Reaction( - type: 'like', - user: User(id: 'test'), - ), - ], - borderColor: theme.ownMessageTheme.reactionsBorderColor!, - backgroundColor: - theme.ownMessageTheme.reactionsBackgroundColor!, - maskColor: theme.ownMessageTheme.reactionsMaskColor!, - ), - ), - ), - ), - ); - }, - ); - - goldenTest( - 'it should show a like - dark theme', - fileName: 'reaction_bubble_like_dark', - constraints: const BoxConstraints.tightFor(width: 100, height: 100), - builder: () { - final client = MockClient(); - final clientState = MockClientState(); - final themeData = ThemeData.dark(); - final theme = StreamChatThemeData.fromTheme(themeData); - - when(() => client.state).thenReturn(clientState); - when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id')); - - return MaterialAppWrapper( - theme: themeData, - home: StreamChat( - client: client, - streamChatThemeData: StreamChatThemeData.fromTheme(themeData), - connectivityStream: Stream.value([ConnectivityResult.mobile]), - child: Scaffold( - body: Center( - child: StreamReactionBubble( - reactions: [ - Reaction( - type: 'like', - user: User(id: 'test'), - ), - ], - borderColor: theme.ownMessageTheme.reactionsBorderColor!, - backgroundColor: - theme.ownMessageTheme.reactionsBackgroundColor!, - maskColor: theme.ownMessageTheme.reactionsMaskColor!, - ), - ), - ), - ), - ); - }, - ); - - goldenTest( - 'it should show three reactions - light theme', - fileName: 'reaction_bubble_3_light', - constraints: const BoxConstraints.tightFor(width: 140, height: 140), - builder: () { - final client = MockClient(); - final clientState = MockClientState(); - final themeData = ThemeData.light(); - final theme = StreamChatThemeData.fromTheme(themeData); - - when(() => client.state).thenReturn(clientState); - when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id')); - - return MaterialAppWrapper( - theme: themeData, - home: StreamChat( - client: client, - streamChatThemeData: StreamChatThemeData.fromTheme(themeData), - connectivityStream: Stream.value([ConnectivityResult.mobile]), - child: Scaffold( - body: Center( - child: StreamReactionBubble( - reactions: [ - Reaction( - type: 'like', - user: User(id: 'test'), - ), - Reaction( - type: 'like', - user: User(id: 'user-id'), - ), - Reaction( - type: 'like', - user: User(id: 'test'), - ), - ], - borderColor: theme.ownMessageTheme.reactionsBorderColor!, - backgroundColor: - theme.ownMessageTheme.reactionsBackgroundColor!, - maskColor: theme.ownMessageTheme.reactionsMaskColor!, - ), - ), - ), - ), - ); - }, - ); - - goldenTest( - 'it should show three reactions - dark theme', - fileName: 'reaction_bubble_3_dark', - constraints: const BoxConstraints.tightFor(width: 140, height: 140), - builder: () { - final client = MockClient(); - final clientState = MockClientState(); - final themeData = ThemeData.dark(); - final theme = StreamChatThemeData.fromTheme(themeData); - - when(() => client.state).thenReturn(clientState); - when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id')); - - return MaterialAppWrapper( - theme: themeData, - home: StreamChat( - client: client, - streamChatThemeData: StreamChatThemeData.fromTheme(themeData), - connectivityStream: Stream.value([ConnectivityResult.mobile]), - child: Scaffold( - body: Center( - child: StreamReactionBubble( - reactions: [ - Reaction( - type: 'like', - user: User(id: 'test'), - ), - Reaction( - type: 'like', - user: User(id: 'user-id'), - ), - Reaction( - type: 'like', - user: User(id: 'test'), - ), - ], - borderColor: theme.ownMessageTheme.reactionsBorderColor!, - backgroundColor: - theme.ownMessageTheme.reactionsBackgroundColor!, - maskColor: theme.ownMessageTheme.reactionsMaskColor!, - ), - ), - ), - ), - ); - }, - ); - - goldenTest( - 'it should show two reactions with customized ui', - fileName: 'reaction_bubble_2', - constraints: const BoxConstraints.tightFor(width: 200, height: 200), - builder: () { - final client = MockClient(); - final clientState = MockClientState(); - final themeData = ThemeData( - useMaterial3: false, - ); - - when(() => client.state).thenReturn(clientState); - when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id')); - - return MaterialAppWrapper( - theme: themeData, - home: StreamChat( - client: client, - connectivityStream: Stream.value([ConnectivityResult.mobile]), - streamChatThemeData: StreamChatThemeData.fromTheme(themeData), - child: Scaffold( - body: Center( - child: StreamReactionBubble( - reactions: [ - Reaction( - type: 'like', - user: User(id: 'test'), - ), - Reaction( - type: 'love', - user: User(id: 'user-id'), - ), - Reaction( - type: 'unknown', - user: User(id: 'test'), - ), - ], - borderColor: Colors.red, - backgroundColor: Colors.blue, - maskColor: Colors.green, - reverse: true, - flipTail: true, - tailCirclesSpacing: 4, - ), - ), - ), - ), - ); - }, - ); -} diff --git a/packages/stream_chat_flutter/test/src/misc/system_message_test.dart b/packages/stream_chat_flutter/test/src/misc/system_message_test.dart index bbbb8dd896..03e4234d09 100644 --- a/packages/stream_chat_flutter/test/src/misc/system_message_test.dart +++ b/packages/stream_chat_flutter/test/src/misc/system_message_test.dart @@ -32,27 +32,28 @@ void main() { }); when(() => clientState.totalUnreadCount).thenReturn(10); - when(() => clientState.totalUnreadCountStream) - .thenAnswer((i) => Stream.value(10)); + when(() => clientState.totalUnreadCountStream).thenAnswer((i) => Stream.value(10)); var tapped = false; - await tester.pumpWidget(MaterialApp( - home: StreamChat( - client: client, - child: StreamChannel( - channel: channel, - child: Scaffold( - body: StreamSystemMessage( - onMessageTap: (m) => tapped = true, - message: Message( - text: 'demo message', + await tester.pumpWidget( + MaterialApp( + home: StreamChat( + client: client, + child: StreamChannel( + channel: channel, + child: Scaffold( + body: StreamSystemMessage( + onMessageTap: (m) => tapped = true, + message: Message( + text: 'demo message', + ), ), ), ), ), ), - )); + ); // wait for the initial state to be rendered. await tester.pumpAndSettle(); @@ -90,8 +91,7 @@ void main() { }); when(() => clientState.totalUnreadCount).thenReturn(10); - when(() => clientState.totalUnreadCountStream) - .thenAnswer((i) => Stream.value(10)); + when(() => clientState.totalUnreadCountStream).thenAnswer((i) => Stream.value(10)); return MaterialAppWrapper( theme: ThemeData.light(), @@ -142,8 +142,7 @@ void main() { }); when(() => clientState.totalUnreadCount).thenReturn(10); - when(() => clientState.totalUnreadCountStream) - .thenAnswer((i) => Stream.value(10)); + when(() => clientState.totalUnreadCountStream).thenAnswer((i) => Stream.value(10)); return MaterialAppWrapper( theme: ThemeData.dark(), diff --git a/packages/stream_chat_flutter/test/src/misc/thread_header_test.dart b/packages/stream_chat_flutter/test/src/misc/thread_header_test.dart index b7cbe4e96c..c818e9c2f8 100644 --- a/packages/stream_chat_flutter/test/src/misc/thread_header_test.dart +++ b/packages/stream_chat_flutter/test/src/misc/thread_header_test.dart @@ -27,14 +27,13 @@ void main() { when(() => channel.name).thenReturn('test'); when(() => channel.nameStream).thenAnswer((i) => Stream.value('test')); when(() => channelState.unreadCount).thenReturn(1); - when(() => channelState.unreadCountStream) - .thenAnswer((i) => Stream.value(1)); + when(() => channelState.unreadCountStream).thenAnswer((i) => Stream.value(1)); when(() => channelState.membersStream).thenAnswer( (i) => Stream.value([ Member( userId: 'user-id', user: User(id: 'user-id'), - ) + ), ]), ); when(() => channelState.members).thenReturn([ @@ -43,34 +42,33 @@ void main() { user: User(id: 'user-id'), ), ]); - when(() => client.wsConnectionStatusStream) - .thenAnswer((_) => Stream.value(ConnectionStatus.connecting)); + when(() => client.wsConnectionStatusStream).thenAnswer((_) => Stream.value(ConnectionStatus.connecting)); when(() => clientState.totalUnreadCount).thenAnswer((i) => 1); - when(() => clientState.totalUnreadCountStream) - .thenAnswer((i) => Stream.value(1)); + when(() => clientState.totalUnreadCountStream).thenAnswer((i) => Stream.value(1)); - await tester.pumpWidget(MaterialApp( - home: StreamChat( - client: client, - child: StreamChannel( - channel: channel, - child: Scaffold( - body: StreamThreadHeader( - parent: Message(), + await tester.pumpWidget( + MaterialApp( + home: StreamChat( + client: client, + child: StreamChannel( + channel: channel, + child: Scaffold( + body: StreamThreadHeader( + parent: Message(replyCount: 1), + showTypingIndicator: false, + ), ), ), ), ), - )); + ); // wait for the initial state to be rendered. await tester.pumpAndSettle(); - expect(find.text('with '), findsOneWidget); - expect(find.byType(StreamChannelName), findsOneWidget); expect(find.byType(StreamBackButton), findsOneWidget); - expect(find.text('1'), findsOneWidget); - expect(find.text('Thread Reply'), findsOneWidget); + expect(find.text('1 reply'), findsOneWidget); + expect(find.text('Thread'), findsOneWidget); }, ); @@ -99,14 +97,13 @@ void main() { 'name': 'test', }); when(() => channelState.unreadCount).thenReturn(1); - when(() => channelState.unreadCountStream) - .thenAnswer((i) => Stream.value(1)); + when(() => channelState.unreadCountStream).thenAnswer((i) => Stream.value(1)); when(() => channelState.membersStream).thenAnswer( (i) => Stream.value([ Member( userId: 'user-id', user: User(id: 'user-id'), - ) + ), ]), ); when(() => channelState.members).thenReturn([ @@ -117,28 +114,30 @@ void main() { ]); var tapped = false; - await tester.pumpWidget(MaterialAppWrapper( - home: StreamChat( - client: client, - child: StreamChannel( - channel: channel, - child: Scaffold( - body: StreamThreadHeader( - parent: Message(), - subtitle: const Text('subtitle'), - leading: const Text('leading'), - title: const Text('title'), - onTitleTap: () { - tapped = true; - }, - actions: const [ - Text('action'), - ], + await tester.pumpWidget( + MaterialAppWrapper( + home: StreamChat( + client: client, + child: StreamChannel( + channel: channel, + child: Scaffold( + body: StreamThreadHeader( + parent: Message(), + subtitle: const Text('subtitle'), + leading: const Text('leading'), + title: const Text('title'), + onTitleTap: () { + tapped = true; + }, + actions: const [ + Text('action'), + ], + ), ), ), ), ), - )); + ); // wait for the initial state to be rendered. await tester.pumpAndSettle(); diff --git a/packages/stream_chat_flutter/test/src/misc/timestamp_test.dart b/packages/stream_chat_flutter/test/src/misc/timestamp_test.dart index f08263ae77..0be181e0b6 100644 --- a/packages/stream_chat_flutter/test/src/misc/timestamp_test.dart +++ b/packages/stream_chat_flutter/test/src/misc/timestamp_test.dart @@ -34,13 +34,15 @@ Widget _wrapWithMaterialApp( return MaterialApp( home: StreamChatTheme( data: StreamChatThemeData(brightness: brightness), - child: Builder(builder: (context) { - final theme = StreamChatTheme.of(context); - return Scaffold( - backgroundColor: theme.colorTheme.appBg, - body: Center(child: widget), - ); - }), + child: Builder( + builder: (context) { + final theme = StreamChatTheme.of(context); + return Scaffold( + backgroundColor: theme.colorTheme.appBg, + body: Center(child: widget), + ); + }, + ), ), ); } diff --git a/packages/stream_chat_flutter/test/src/mocks.dart b/packages/stream_chat_flutter/test/src/mocks.dart index 5418d29303..fc6c5e4cce 100644 --- a/packages/stream_chat_flutter/test/src/mocks.dart +++ b/packages/stream_chat_flutter/test/src/mocks.dart @@ -6,8 +6,8 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; class MockClient extends Mock implements StreamChatClient { MockClient() { when(() => wsConnectionStatus).thenReturn(ConnectionStatus.connected); - when(() => wsConnectionStatusStream) - .thenAnswer((_) => Stream.value(ConnectionStatus.connected)); + when(() => wsConnectionStatusStream).thenAnswer((_) => Stream.value(ConnectionStatus.connected)); + when(() => state).thenReturn(MockClientState()); } } @@ -95,8 +95,7 @@ class MockAttachment extends Mock implements Attachment {} class MockVlcManagerDesktop extends Mock implements VlcManagerDesktop {} -class MockStreamMemberListController extends Mock - implements StreamMemberListController { +class MockStreamMemberListController extends Mock implements StreamMemberListController { @override PagedValue value = const PagedValue.loading(); } diff --git a/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/poll_delete_option_dialog_dark.png b/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/poll_delete_option_dialog_dark.png index 365f508f16..3147e1e01d 100644 Binary files a/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/poll_delete_option_dialog_dark.png and b/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/poll_delete_option_dialog_dark.png differ diff --git a/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/poll_delete_option_dialog_light.png b/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/poll_delete_option_dialog_light.png index 78fbfc5a0f..72d412692d 100644 Binary files a/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/poll_delete_option_dialog_light.png and b/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/poll_delete_option_dialog_light.png differ diff --git a/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/poll_option_reorderable_list_view_dark.png b/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/poll_option_reorderable_list_view_dark.png index e6376fec02..7d50ee1a0b 100644 Binary files a/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/poll_option_reorderable_list_view_dark.png and b/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/poll_option_reorderable_list_view_dark.png differ diff --git a/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/poll_option_reorderable_list_view_error_dark.png b/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/poll_option_reorderable_list_view_error_dark.png index f10d8de291..21e5bd213f 100644 Binary files a/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/poll_option_reorderable_list_view_error_dark.png and b/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/poll_option_reorderable_list_view_error_dark.png differ diff --git a/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/poll_option_reorderable_list_view_error_light.png b/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/poll_option_reorderable_list_view_error_light.png index 69bcfc0c41..629bb9ce23 100644 Binary files a/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/poll_option_reorderable_list_view_error_light.png and b/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/poll_option_reorderable_list_view_error_light.png differ diff --git a/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/poll_option_reorderable_list_view_light.png b/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/poll_option_reorderable_list_view_light.png index c19384d84a..24c03f908d 100644 Binary files a/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/poll_option_reorderable_list_view_light.png and b/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/poll_option_reorderable_list_view_light.png differ diff --git a/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/poll_question_text_field_dark.png b/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/poll_question_text_field_dark.png index 57ff5d7532..fdffc5c05a 100644 Binary files a/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/poll_question_text_field_dark.png and b/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/poll_question_text_field_dark.png differ diff --git a/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/poll_question_text_field_error_dark.png b/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/poll_question_text_field_error_dark.png index 2099c1e4e3..325781cf82 100644 Binary files a/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/poll_question_text_field_error_dark.png and b/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/poll_question_text_field_error_dark.png differ diff --git a/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/poll_question_text_field_error_light.png b/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/poll_question_text_field_error_light.png index 64a2f93e58..64ac751f02 100644 Binary files a/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/poll_question_text_field_error_light.png and b/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/poll_question_text_field_error_light.png differ diff --git a/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/poll_question_text_field_light.png b/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/poll_question_text_field_light.png index ba6de420da..ae1e3618ed 100644 Binary files a/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/poll_question_text_field_light.png and b/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/poll_question_text_field_light.png differ diff --git a/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/stream_poll_creator_dialog_dark.png b/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/stream_poll_creator_dialog_dark.png index 8daed0c300..52ca2ca4c9 100644 Binary files a/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/stream_poll_creator_dialog_dark.png and b/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/stream_poll_creator_dialog_dark.png differ diff --git a/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/stream_poll_creator_dialog_light.png b/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/stream_poll_creator_dialog_light.png index ae58aabff3..180d152692 100644 Binary files a/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/stream_poll_creator_dialog_light.png and b/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/stream_poll_creator_dialog_light.png differ diff --git a/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/stream_poll_creator_full_screen_dialog_dark.png b/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/stream_poll_creator_full_screen_dialog_dark.png index 36dbf7fbf1..73462eb438 100644 Binary files a/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/stream_poll_creator_full_screen_dialog_dark.png and b/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/stream_poll_creator_full_screen_dialog_dark.png differ diff --git a/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/stream_poll_creator_full_screen_dialog_light.png b/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/stream_poll_creator_full_screen_dialog_light.png index aa089895f4..97ca70c7e8 100644 Binary files a/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/stream_poll_creator_full_screen_dialog_light.png and b/packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/stream_poll_creator_full_screen_dialog_light.png differ diff --git a/packages/stream_chat_flutter/test/src/poll/creator/poll_option_reorderable_list_view_test.dart b/packages/stream_chat_flutter/test/src/poll/creator/poll_option_reorderable_list_view_test.dart index d0d6ab7e66..f1a65f47f0 100644 --- a/packages/stream_chat_flutter/test/src/poll/creator/poll_option_reorderable_list_view_test.dart +++ b/packages/stream_chat_flutter/test/src/poll/creator/poll_option_reorderable_list_view_test.dart @@ -5,8 +5,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:stream_chat_flutter/src/poll/creator/poll_option_reorderable_list_view.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; - -import '../../utils/finders.dart'; +import 'package:stream_core_flutter/stream_core_flutter.dart'; void main() { for (final brightness in Brightness.values) { @@ -53,15 +52,17 @@ void main() { testWidgets('should enforce minimum options requirement', (tester) async { var optionsChanged = []; - await tester.pumpWidget(_wrapWithMaterialApp( - PollOptionReorderableListView( - optionsRange: (min: 3, max: null), - initialOptions: [ - PollOptionItem(text: 'Option 1'), - ], - onOptionsChanged: (options) => optionsChanged = options, + await tester.pumpWidget( + _wrapWithMaterialApp( + PollOptionReorderableListView( + optionsRange: (min: 3, max: null), + initialOptions: [ + PollOptionItem(text: 'Option 1'), + ], + onOptionsChanged: (options) => optionsChanged = options, + ), ), - )); + ); // Should automatically add options to meet minimum requirement final textFields = find.byType(TextField); @@ -73,16 +74,18 @@ void main() { }); testWidgets('should respect maximum options limit', (tester) async { - await tester.pumpWidget(_wrapWithMaterialApp( - PollOptionReorderableListView( - optionsRange: (min: null, max: 3), - initialOptions: [ - PollOptionItem(text: 'Option 1'), - PollOptionItem(text: 'Option 2'), - PollOptionItem(text: 'Option 3'), - ], + await tester.pumpWidget( + _wrapWithMaterialApp( + PollOptionReorderableListView( + optionsRange: (min: null, max: 3), + initialOptions: [ + PollOptionItem(text: 'Option 1'), + PollOptionItem(text: 'Option 2'), + PollOptionItem(text: 'Option 3'), + ], + ), ), - )); + ); // Find the add button final addButton = find.byType(FilledButton); @@ -94,15 +97,17 @@ void main() { }); testWidgets('should respect both min and max options', (tester) async { - await tester.pumpWidget(_wrapWithMaterialApp( - PollOptionReorderableListView( - optionsRange: (min: 2, max: 4), - initialOptions: [ - PollOptionItem(text: 'Option 1'), - PollOptionItem(text: 'Option 2'), - ], + await tester.pumpWidget( + _wrapWithMaterialApp( + PollOptionReorderableListView( + optionsRange: (min: 2, max: 4), + initialOptions: [ + PollOptionItem(text: 'Option 1'), + PollOptionItem(text: 'Option 2'), + ], + ), ), - )); + ); // Should have 2 options initially (meeting minimum) final textFields = find.byType(TextField); @@ -133,15 +138,17 @@ void main() { testWidgets( 'should work with unlimited options when max is null', (tester) async { - await tester.pumpWidget(_wrapWithMaterialApp( - PollOptionReorderableListView( - optionsRange: (min: 2, max: null), - initialOptions: [ - PollOptionItem(text: 'Option 1'), - PollOptionItem(text: 'Option 2'), - ], + await tester.pumpWidget( + _wrapWithMaterialApp( + PollOptionReorderableListView( + optionsRange: (min: 2, max: null), + initialOptions: [ + PollOptionItem(text: 'Option 1'), + PollOptionItem(text: 'Option 2'), + ], + ), ), - )); + ); // Add button should be enabled for unlimited options final addButton = find.byType(FilledButton); @@ -153,14 +160,16 @@ void main() { group('Auto-Focus Functionality', () { testWidgets('should auto-focus on newly added option', (tester) async { - await tester.pumpWidget(_wrapWithMaterialApp( - PollOptionReorderableListView( - optionsRange: (min: 1, max: null), - initialOptions: [ - PollOptionItem(text: 'Option 1'), - ], + await tester.pumpWidget( + _wrapWithMaterialApp( + PollOptionReorderableListView( + optionsRange: (min: 1, max: null), + initialOptions: [ + PollOptionItem(text: 'Option 1'), + ], + ), ), - )); + ); // Find the add button and tap it final addButton = find.byType(FilledButton); @@ -182,16 +191,18 @@ void main() { testWidgets( 'should disable add button when empty option exists', (tester) async { - await tester.pumpWidget(_wrapWithMaterialApp( - PollOptionReorderableListView( - optionsRange: (min: null, max: 5), - initialOptions: [ - PollOptionItem(text: 'Option 1'), - PollOptionItem(text: 'Option 2'), - PollOptionItem(text: ''), // Empty option - ], + await tester.pumpWidget( + _wrapWithMaterialApp( + PollOptionReorderableListView( + optionsRange: (min: null, max: 5), + initialOptions: [ + PollOptionItem(text: 'Option 1'), + PollOptionItem(text: 'Option 2'), + PollOptionItem(text: ''), // Empty option + ], + ), ), - )); + ); // Find the add button final addButton = find.byType(FilledButton); @@ -206,15 +217,17 @@ void main() { testWidgets( 'should enable add button when no empty options exist', (tester) async { - await tester.pumpWidget(_wrapWithMaterialApp( - PollOptionReorderableListView( - optionsRange: (min: null, max: 5), - initialOptions: [ - PollOptionItem(text: 'Option 1'), - PollOptionItem(text: 'Option 2'), - ], + await tester.pumpWidget( + _wrapWithMaterialApp( + PollOptionReorderableListView( + optionsRange: (min: null, max: 5), + initialOptions: [ + PollOptionItem(text: 'Option 1'), + PollOptionItem(text: 'Option 2'), + ], + ), ), - )); + ); // Find the add button final addButton = find.byType(FilledButton); @@ -229,15 +242,17 @@ void main() { testWidgets( 'should re-enable add button after filling empty option', (tester) async { - await tester.pumpWidget(_wrapWithMaterialApp( - PollOptionReorderableListView( - optionsRange: (min: null, max: 5), - initialOptions: [ - PollOptionItem(text: 'Option 1'), - PollOptionItem(text: ''), // Empty option - ], + await tester.pumpWidget( + _wrapWithMaterialApp( + PollOptionReorderableListView( + optionsRange: (min: null, max: 5), + initialOptions: [ + PollOptionItem(text: 'Option 1'), + PollOptionItem(text: ''), // Empty option + ], + ), ), - )); + ); // Initially, add button should be disabled var addButton = find.byType(FilledButton); @@ -263,16 +278,18 @@ void main() { (tester) async { var optionsChanged = []; - await tester.pumpWidget(_wrapWithMaterialApp( - PollOptionReorderableListView( - optionsRange: (min: 2, max: 5), - initialOptions: [ - PollOptionItem(text: 'Option 1'), - PollOptionItem(text: 'Option 2'), - ], - onOptionsChanged: (options) => optionsChanged = options, + await tester.pumpWidget( + _wrapWithMaterialApp( + PollOptionReorderableListView( + optionsRange: (min: 2, max: 5), + initialOptions: [ + PollOptionItem(text: 'Option 1'), + PollOptionItem(text: 'Option 2'), + ], + onOptionsChanged: (options) => optionsChanged = options, + ), ), - )); + ); // Find the add button and tap it final addButton = find.byType(FilledButton); @@ -291,13 +308,15 @@ void main() { (tester) async { var optionsChanged = []; - await tester.pumpWidget(_wrapWithMaterialApp( - PollOptionReorderableListView( - optionsRange: (min: 2, max: null), - initialOptions: const [], // No initial options - onOptionsChanged: (options) => optionsChanged = options, + await tester.pumpWidget( + _wrapWithMaterialApp( + PollOptionReorderableListView( + optionsRange: (min: 2, max: null), + initialOptions: const [], // No initial options + onOptionsChanged: (options) => optionsChanged = options, + ), ), - )); + ); // Should auto-add options to meet minimum requirement final textFields = find.byType(TextField); @@ -307,27 +326,31 @@ void main() { ); testWidgets('should handle updating initial options', (tester) async { - await tester.pumpWidget(_wrapWithMaterialApp( - PollOptionReorderableListView( - initialOptions: [ - PollOptionItem(text: 'Option 1'), - ], + await tester.pumpWidget( + _wrapWithMaterialApp( + PollOptionReorderableListView( + initialOptions: [ + PollOptionItem(text: 'Option 1'), + ], + ), ), - )); + ); // Initially should have 1 option expect(find.byType(TextField), findsNWidgets(1)); // Update with new options - await tester.pumpWidget(_wrapWithMaterialApp( - PollOptionReorderableListView( - initialOptions: [ - PollOptionItem(text: 'Option 1'), - PollOptionItem(text: 'Option 2'), - PollOptionItem(text: 'Option 3'), - ], + await tester.pumpWidget( + _wrapWithMaterialApp( + PollOptionReorderableListView( + initialOptions: [ + PollOptionItem(text: 'Option 1'), + PollOptionItem(text: 'Option 2'), + PollOptionItem(text: 'Option 3'), + ], + ), ), - )); + ); // Should now have 3 options expect(find.byType(TextField), findsNWidgets(3)); @@ -336,19 +359,21 @@ void main() { group('Delete Option Functionality', () { testWidgets('should show delete confirmation dialog', (tester) async { - await tester.pumpWidget(_wrapWithMaterialApp( - PollOptionReorderableListView( - optionsRange: (min: 2, max: null), - initialOptions: [ - PollOptionItem(text: 'Option 1'), - PollOptionItem(text: 'Option 2'), - PollOptionItem(text: 'Option 3'), - ], + await tester.pumpWidget( + _wrapWithMaterialApp( + PollOptionReorderableListView( + optionsRange: (min: 2, max: null), + initialOptions: [ + PollOptionItem(text: 'Option 1'), + PollOptionItem(text: 'Option 2'), + PollOptionItem(text: 'Option 3'), + ], + ), ), - )); + ); // Find the delete buttons - final deleteButtons = find.bySvgIcon(StreamSvgIcons.delete); + final deleteButtons = find.byIcon(StreamIconData.iconTrashBin); expect(deleteButtons, findsNWidgets(3)); // Tap the first delete button @@ -368,23 +393,25 @@ void main() { testWidgets('should delete option when confirmed', (tester) async { var optionsChanged = []; - await tester.pumpWidget(_wrapWithMaterialApp( - PollOptionReorderableListView( - optionsRange: (min: 2, max: null), - initialOptions: [ - PollOptionItem(text: 'Option 1'), - PollOptionItem(text: 'Option 2'), - PollOptionItem(text: 'Option 3'), - ], - onOptionsChanged: (options) => optionsChanged = options, + await tester.pumpWidget( + _wrapWithMaterialApp( + PollOptionReorderableListView( + optionsRange: (min: 2, max: null), + initialOptions: [ + PollOptionItem(text: 'Option 1'), + PollOptionItem(text: 'Option 2'), + PollOptionItem(text: 'Option 3'), + ], + onOptionsChanged: (options) => optionsChanged = options, + ), ), - )); + ); // Initially should have 3 options expect(find.byType(TextField), findsNWidgets(3)); // Find and tap the delete button for the first option - final deleteButtons = find.bySvgIcon(StreamSvgIcons.delete); + final deleteButtons = find.byIcon(StreamIconData.iconTrashBin); await tester.tap(deleteButtons.first); await tester.pumpAndSettle(); @@ -400,23 +427,25 @@ void main() { testWidgets('should not delete option when cancelled', (tester) async { var optionsChanged = []; - await tester.pumpWidget(_wrapWithMaterialApp( - PollOptionReorderableListView( - optionsRange: (min: 2, max: null), - initialOptions: [ - PollOptionItem(text: 'Option 1'), - PollOptionItem(text: 'Option 2'), - PollOptionItem(text: 'Option 3'), - ], - onOptionsChanged: (options) => optionsChanged = options, + await tester.pumpWidget( + _wrapWithMaterialApp( + PollOptionReorderableListView( + optionsRange: (min: 2, max: null), + initialOptions: [ + PollOptionItem(text: 'Option 1'), + PollOptionItem(text: 'Option 2'), + PollOptionItem(text: 'Option 3'), + ], + onOptionsChanged: (options) => optionsChanged = options, + ), ), - )); + ); // Initially should have 3 options expect(find.byType(TextField), findsNWidgets(3)); // Find and tap the delete button for the first option - final deleteButtons = find.bySvgIcon(StreamSvgIcons.delete); + final deleteButtons = find.byIcon(StreamIconData.iconTrashBin); await tester.tap(deleteButtons.first); await tester.pumpAndSettle(); @@ -436,19 +465,21 @@ void main() { final option1 = PollOptionItem(text: 'Option 1'); final option2 = PollOptionItem(text: 'Option 2'); - await tester.pumpWidget(_wrapWithMaterialApp( - PollOptionReorderableListView( - optionsRange: (min: 2, max: null), - initialOptions: [option1, option2], - onOptionsChanged: (options) => optionsChanged = options, + await tester.pumpWidget( + _wrapWithMaterialApp( + PollOptionReorderableListView( + optionsRange: (min: 2, max: null), + initialOptions: [option1, option2], + onOptionsChanged: (options) => optionsChanged = options, + ), ), - )); + ); // Should have 2 options (minimum) expect(find.byType(TextField), findsNWidgets(2)); // Try to delete the first option - final deleteButtons = find.bySvgIcon(StreamSvgIcons.delete); + final deleteButtons = find.byIcon(StreamIconData.iconTrashBin); await tester.tap(deleteButtons.first); await tester.pumpAndSettle(); diff --git a/packages/stream_chat_flutter/test/src/poll/creator/poll_question_text_field_test.dart b/packages/stream_chat_flutter/test/src/poll/creator/poll_question_text_field_test.dart index 8a07c4e7a9..8a77888e51 100644 --- a/packages/stream_chat_flutter/test/src/poll/creator/poll_question_text_field_test.dart +++ b/packages/stream_chat_flutter/test/src/poll/creator/poll_question_text_field_test.dart @@ -47,18 +47,20 @@ Widget _wrapWithMaterialApp( return MaterialApp( home: StreamChatTheme( data: StreamChatThemeData(brightness: brightness), - child: Builder(builder: (context) { - final theme = StreamChatTheme.of(context); - return Scaffold( - backgroundColor: theme.colorTheme.appBg, - body: Center( - child: Padding( - padding: const EdgeInsets.all(8), - child: widget, + child: Builder( + builder: (context) { + final theme = StreamChatTheme.of(context); + return Scaffold( + backgroundColor: theme.colorTheme.appBg, + body: Center( + child: Padding( + padding: const EdgeInsets.all(8), + child: widget, + ), ), - ), - ); - }), + ); + }, + ), ), ); } diff --git a/packages/stream_chat_flutter/test/src/poll/creator/stream_poll_creator_widget_test.dart b/packages/stream_chat_flutter/test/src/poll/creator/stream_poll_creator_widget_test.dart index ba45ab36cb..78eb1b99ae 100644 --- a/packages/stream_chat_flutter/test/src/poll/creator/stream_poll_creator_widget_test.dart +++ b/packages/stream_chat_flutter/test/src/poll/creator/stream_poll_creator_widget_test.dart @@ -28,8 +28,7 @@ void main() { expect(find.byType(PollSwitchListTile), findsNWidgets(4)); }); - testWidgets('StreamPollCreatorWidget updates poll state correctly', - (tester) async { + testWidgets('StreamPollCreatorWidget updates poll state correctly', (tester) async { final controller = StreamPollController( config: const PollConfig( nameRange: (min: 1, max: 150), diff --git a/packages/stream_chat_flutter/test/src/poll/goldens/ci/poll_option_reorderable_list_view_dark.png b/packages/stream_chat_flutter/test/src/poll/goldens/ci/poll_option_reorderable_list_view_dark.png index e6376fec02..7d50ee1a0b 100644 Binary files a/packages/stream_chat_flutter/test/src/poll/goldens/ci/poll_option_reorderable_list_view_dark.png and b/packages/stream_chat_flutter/test/src/poll/goldens/ci/poll_option_reorderable_list_view_dark.png differ diff --git a/packages/stream_chat_flutter/test/src/poll/goldens/ci/poll_option_reorderable_list_view_error.png b/packages/stream_chat_flutter/test/src/poll/goldens/ci/poll_option_reorderable_list_view_error.png index 69bcfc0c41..629bb9ce23 100644 Binary files a/packages/stream_chat_flutter/test/src/poll/goldens/ci/poll_option_reorderable_list_view_error.png and b/packages/stream_chat_flutter/test/src/poll/goldens/ci/poll_option_reorderable_list_view_error.png differ diff --git a/packages/stream_chat_flutter/test/src/poll/goldens/ci/poll_option_reorderable_list_view_light.png b/packages/stream_chat_flutter/test/src/poll/goldens/ci/poll_option_reorderable_list_view_light.png index c19384d84a..24c03f908d 100644 Binary files a/packages/stream_chat_flutter/test/src/poll/goldens/ci/poll_option_reorderable_list_view_light.png and b/packages/stream_chat_flutter/test/src/poll/goldens/ci/poll_option_reorderable_list_view_light.png differ diff --git a/packages/stream_chat_flutter/test/src/poll/goldens/ci/poll_question_text_field_dark.png b/packages/stream_chat_flutter/test/src/poll/goldens/ci/poll_question_text_field_dark.png index 57ff5d7532..fdffc5c05a 100644 Binary files a/packages/stream_chat_flutter/test/src/poll/goldens/ci/poll_question_text_field_dark.png and b/packages/stream_chat_flutter/test/src/poll/goldens/ci/poll_question_text_field_dark.png differ diff --git a/packages/stream_chat_flutter/test/src/poll/goldens/ci/poll_question_text_field_error.png b/packages/stream_chat_flutter/test/src/poll/goldens/ci/poll_question_text_field_error.png index 64a2f93e58..64ac751f02 100644 Binary files a/packages/stream_chat_flutter/test/src/poll/goldens/ci/poll_question_text_field_error.png and b/packages/stream_chat_flutter/test/src/poll/goldens/ci/poll_question_text_field_error.png differ diff --git a/packages/stream_chat_flutter/test/src/poll/goldens/ci/poll_question_text_field_light.png b/packages/stream_chat_flutter/test/src/poll/goldens/ci/poll_question_text_field_light.png index ba6de420da..ae1e3618ed 100644 Binary files a/packages/stream_chat_flutter/test/src/poll/goldens/ci/poll_question_text_field_light.png and b/packages/stream_chat_flutter/test/src/poll/goldens/ci/poll_question_text_field_light.png differ diff --git a/packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_creator_dialog_dark.png b/packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_creator_dialog_dark.png index 8daed0c300..52ca2ca4c9 100644 Binary files a/packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_creator_dialog_dark.png and b/packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_creator_dialog_dark.png differ diff --git a/packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_creator_dialog_light.png b/packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_creator_dialog_light.png index ae58aabff3..180d152692 100644 Binary files a/packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_creator_dialog_light.png and b/packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_creator_dialog_light.png differ diff --git a/packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_creator_full_screen_dialog_dark.png b/packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_creator_full_screen_dialog_dark.png index 36dbf7fbf1..73462eb438 100644 Binary files a/packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_creator_full_screen_dialog_dark.png and b/packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_creator_full_screen_dialog_dark.png differ diff --git a/packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_creator_full_screen_dialog_light.png b/packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_creator_full_screen_dialog_light.png index aa089895f4..97ca70c7e8 100644 Binary files a/packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_creator_full_screen_dialog_light.png and b/packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_creator_full_screen_dialog_light.png differ diff --git a/packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_options_dialog_dark.png b/packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_options_dialog_dark.png index 01841c4f52..a71e6327d6 100644 Binary files a/packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_options_dialog_dark.png and b/packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_options_dialog_dark.png differ diff --git a/packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_options_dialog_light.png b/packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_options_dialog_light.png index 53ac417a28..4f7758a938 100644 Binary files a/packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_options_dialog_light.png and b/packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_options_dialog_light.png differ diff --git a/packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_results_dialog_dark.png b/packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_results_dialog_dark.png index 8fb76762d3..5c812dcd31 100644 Binary files a/packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_results_dialog_dark.png and b/packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_results_dialog_dark.png differ diff --git a/packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_results_dialog_light.png b/packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_results_dialog_light.png index 54fefe2d99..ec960f5a14 100644 Binary files a/packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_results_dialog_light.png and b/packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_results_dialog_light.png differ diff --git a/packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_results_dialog_with_show_all_dark.png b/packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_results_dialog_with_show_all_dark.png index 3905f50778..2ca407113b 100644 Binary files a/packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_results_dialog_with_show_all_dark.png and b/packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_results_dialog_with_show_all_dark.png differ diff --git a/packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_results_dialog_with_show_all_light.png b/packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_results_dialog_with_show_all_light.png index bd010d54d5..352a6c0e31 100644 Binary files a/packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_results_dialog_with_show_all_light.png and b/packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_results_dialog_with_show_all_light.png differ diff --git a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_add_comment_dialog_dark.png b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_add_comment_dialog_dark.png index e48fa36669..176065eb6a 100644 Binary files a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_add_comment_dialog_dark.png and b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_add_comment_dialog_dark.png differ diff --git a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_add_comment_dialog_light.png b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_add_comment_dialog_light.png index e7512a60ca..5785a75e81 100644 Binary files a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_add_comment_dialog_light.png and b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_add_comment_dialog_light.png differ diff --git a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_add_comment_dialog_with_initial_value_dark.png b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_add_comment_dialog_with_initial_value_dark.png index 003933a7b2..177e3ab489 100644 Binary files a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_add_comment_dialog_with_initial_value_dark.png and b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_add_comment_dialog_with_initial_value_dark.png differ diff --git a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_add_comment_dialog_with_initial_value_light.png b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_add_comment_dialog_with_initial_value_light.png index 9d27c88d7f..30ae1d679d 100644 Binary files a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_add_comment_dialog_with_initial_value_light.png and b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_add_comment_dialog_with_initial_value_light.png differ diff --git a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_end_vote_dialog_dark.png b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_end_vote_dialog_dark.png index 33575c2c74..9268dd838b 100644 Binary files a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_end_vote_dialog_dark.png and b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_end_vote_dialog_dark.png differ diff --git a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_end_vote_dialog_light.png b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_end_vote_dialog_light.png index 890bc6c051..8cd7b7a789 100644 Binary files a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_end_vote_dialog_light.png and b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_end_vote_dialog_light.png differ diff --git a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_dark.png b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_dark.png index f3c13cc1da..9abd07ad6b 100644 Binary files a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_dark.png and b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_dark.png differ diff --git a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_light.png b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_light.png index dccb508da1..65d45efa34 100644 Binary files a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_light.png and b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_light.png differ diff --git a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_long_question_dark.png b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_long_question_dark.png index 2feca6a6e6..78901b4d61 100644 Binary files a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_long_question_dark.png and b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_long_question_dark.png differ diff --git a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_long_question_light.png b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_long_question_light.png index 9c384b4be4..33261d08bf 100644 Binary files a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_long_question_light.png and b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_long_question_light.png differ diff --git a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_subtitle_voting_mode_all_dark.png b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_subtitle_voting_mode_all_dark.png index 7c4097df91..4b608ee018 100644 Binary files a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_subtitle_voting_mode_all_dark.png and b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_subtitle_voting_mode_all_dark.png differ diff --git a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_subtitle_voting_mode_all_light.png b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_subtitle_voting_mode_all_light.png index c702d77bae..c89e40cc73 100644 Binary files a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_subtitle_voting_mode_all_light.png and b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_subtitle_voting_mode_all_light.png differ diff --git a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_subtitle_voting_mode_disabled_dark.png b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_subtitle_voting_mode_disabled_dark.png index a91b1aab73..b803e57eec 100644 Binary files a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_subtitle_voting_mode_disabled_dark.png and b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_subtitle_voting_mode_disabled_dark.png differ diff --git a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_subtitle_voting_mode_disabled_light.png b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_subtitle_voting_mode_disabled_light.png index be52cf300b..bf03493cf8 100644 Binary files a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_subtitle_voting_mode_disabled_light.png and b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_subtitle_voting_mode_disabled_light.png differ diff --git a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_subtitle_voting_mode_limited_dark.png b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_subtitle_voting_mode_limited_dark.png index 58c7540d6a..f0c4fb26fb 100644 Binary files a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_subtitle_voting_mode_limited_dark.png and b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_subtitle_voting_mode_limited_dark.png differ diff --git a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_subtitle_voting_mode_limited_light.png b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_subtitle_voting_mode_limited_light.png index 83920a60db..9f630e7904 100644 Binary files a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_subtitle_voting_mode_limited_light.png and b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_subtitle_voting_mode_limited_light.png differ diff --git a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_subtitle_voting_mode_unique_dark.png b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_subtitle_voting_mode_unique_dark.png index f3c13cc1da..9abd07ad6b 100644 Binary files a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_subtitle_voting_mode_unique_dark.png and b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_subtitle_voting_mode_unique_dark.png differ diff --git a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_subtitle_voting_mode_unique_light.png b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_subtitle_voting_mode_unique_light.png index dccb508da1..65d45efa34 100644 Binary files a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_subtitle_voting_mode_unique_light.png and b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_subtitle_voting_mode_unique_light.png differ diff --git a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_suggest_option_dialog_dark.png b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_suggest_option_dialog_dark.png index e88c31e00d..3839ac31d2 100644 Binary files a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_suggest_option_dialog_dark.png and b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_suggest_option_dialog_dark.png differ diff --git a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_suggest_option_dialog_light.png b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_suggest_option_dialog_light.png index 8da896a7fc..c120f338a9 100644 Binary files a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_suggest_option_dialog_light.png and b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_suggest_option_dialog_light.png differ diff --git a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_suggest_option_dialog_with_initial_option_dark.png b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_suggest_option_dialog_with_initial_option_dark.png index 89e22005a3..dc69423fe0 100644 Binary files a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_suggest_option_dialog_with_initial_option_dark.png and b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_suggest_option_dialog_with_initial_option_dark.png differ diff --git a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_suggest_option_dialog_with_initial_option_light.png b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_suggest_option_dialog_with_initial_option_light.png index 2689a949c4..0cd030dd55 100644 Binary files a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_suggest_option_dialog_with_initial_option_light.png and b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_suggest_option_dialog_with_initial_option_light.png differ diff --git a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/stream_poll_interactor_closed_dark.png b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/stream_poll_interactor_closed_dark.png index 6f2fedb239..5a7a51af88 100644 Binary files a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/stream_poll_interactor_closed_dark.png and b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/stream_poll_interactor_closed_dark.png differ diff --git a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/stream_poll_interactor_closed_light.png b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/stream_poll_interactor_closed_light.png index 4e03d32bb3..737b6002b8 100644 Binary files a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/stream_poll_interactor_closed_light.png and b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/stream_poll_interactor_closed_light.png differ diff --git a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/stream_poll_interactor_dark.png b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/stream_poll_interactor_dark.png index 9194488c72..de4daf4317 100644 Binary files a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/stream_poll_interactor_dark.png and b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/stream_poll_interactor_dark.png differ diff --git a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/stream_poll_interactor_light.png b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/stream_poll_interactor_light.png index 1aaa2d083d..7fb7d88921 100644 Binary files a/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/stream_poll_interactor_light.png and b/packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/stream_poll_interactor_light.png differ diff --git a/packages/stream_chat_flutter/test/src/poll/interactor/poll_footer_test.dart b/packages/stream_chat_flutter/test/src/poll/interactor/poll_footer_test.dart index 1a61932345..516f8259d5 100644 --- a/packages/stream_chat_flutter/test/src/poll/interactor/poll_footer_test.dart +++ b/packages/stream_chat_flutter/test/src/poll/interactor/poll_footer_test.dart @@ -20,13 +20,15 @@ void main() async { testWidgets( 'End Vote button is visible and enabled for the creator on open poll', (WidgetTester tester) async { - await tester.pumpWidget(_wrapWithMaterialApp( - PollFooter( - poll: poll.copyWith(createdBy: currentUser), - currentUser: currentUser, - onEndVote: () {}, + await tester.pumpWidget( + _wrapWithMaterialApp( + PollFooter( + poll: poll.copyWith(createdBy: currentUser), + currentUser: currentUser, + onEndVote: () {}, + ), ), - )); + ); final endVoteButton = find.ancestor( of: find.text('End Vote'), @@ -45,13 +47,15 @@ void main() async { testWidgets( 'End Vote button is not visible for non-creator', (WidgetTester tester) async { - await tester.pumpWidget(_wrapWithMaterialApp( - PollFooter( - poll: poll, - currentUser: currentUser, - onEndVote: () {}, + await tester.pumpWidget( + _wrapWithMaterialApp( + PollFooter( + poll: poll, + currentUser: currentUser, + onEndVote: () {}, + ), ), - )); + ); final endVoteButton = find.ancestor( of: find.text('End Vote'), @@ -65,16 +69,18 @@ void main() async { testWidgets( 'End Vote button is not visible for closed poll', (WidgetTester tester) async { - await tester.pumpWidget(_wrapWithMaterialApp( - PollFooter( - poll: poll.copyWith( - isClosed: true, - createdBy: currentUser, + await tester.pumpWidget( + _wrapWithMaterialApp( + PollFooter( + poll: poll.copyWith( + isClosed: true, + createdBy: currentUser, + ), + currentUser: currentUser, + onEndVote: () {}, ), - currentUser: currentUser, - onEndVote: () {}, ), - )); + ); final endVoteButton = find.ancestor( of: find.text('End Vote'), @@ -88,13 +94,15 @@ void main() async { testWidgets( 'Add Comment button is visible and enabled when poll allows answers', (WidgetTester tester) async { - await tester.pumpWidget(_wrapWithMaterialApp( - PollFooter( - poll: poll.copyWith(allowAnswers: true), - currentUser: currentUser, - onAddComment: () {}, + await tester.pumpWidget( + _wrapWithMaterialApp( + PollFooter( + poll: poll.copyWith(allowAnswers: true), + currentUser: currentUser, + onAddComment: () {}, + ), ), - )); + ); final addCommentButton = find.ancestor( of: find.text('Add a comment'), @@ -112,16 +120,18 @@ void main() async { testWidgets( 'Add Comment button is not visible when poll is closed', (WidgetTester tester) async { - await tester.pumpWidget(_wrapWithMaterialApp( - PollFooter( - poll: poll.copyWith( - isClosed: true, - allowAnswers: true, + await tester.pumpWidget( + _wrapWithMaterialApp( + PollFooter( + poll: poll.copyWith( + isClosed: true, + allowAnswers: true, + ), + currentUser: currentUser, + onAddComment: () {}, ), - currentUser: currentUser, - onAddComment: () {}, ), - )); + ); final addCommentButton = find.ancestor( of: find.text('Add a comment'), @@ -135,13 +145,15 @@ void main() async { testWidgets( 'View Comments button is visible and enabled if there are answers', (WidgetTester tester) async { - await tester.pumpWidget(_wrapWithMaterialApp( - PollFooter( - poll: poll.copyWith(answersCount: 1), - currentUser: currentUser, - onViewComments: () {}, + await tester.pumpWidget( + _wrapWithMaterialApp( + PollFooter( + poll: poll.copyWith(answersCount: 1), + currentUser: currentUser, + onViewComments: () {}, + ), ), - )); + ); final viewCommentsButton = find.ancestor( of: find.text('View Comments'), @@ -159,15 +171,17 @@ void main() async { testWidgets( 'View Comments button is not visible when there are no answers', (WidgetTester tester) async { - await tester.pumpWidget(_wrapWithMaterialApp( - PollFooter( - poll: poll.copyWith( - answersCount: 0, + await tester.pumpWidget( + _wrapWithMaterialApp( + PollFooter( + poll: poll.copyWith( + answersCount: 0, + ), + currentUser: currentUser, + onViewComments: () {}, ), - currentUser: currentUser, - onViewComments: () {}, ), - )); + ); final viewCommentsButton = find.ancestor( of: find.text('View Comments'), @@ -181,15 +195,17 @@ void main() async { testWidgets( 'Suggest Option button is visible and enabled when allowed', (WidgetTester tester) async { - await tester.pumpWidget(_wrapWithMaterialApp( - PollFooter( - poll: poll.copyWith( - allowUserSuggestedOptions: true, + await tester.pumpWidget( + _wrapWithMaterialApp( + PollFooter( + poll: poll.copyWith( + allowUserSuggestedOptions: true, + ), + currentUser: currentUser, + onSuggestOption: () {}, ), - currentUser: currentUser, - onSuggestOption: () {}, ), - )); + ); final suggestOptionButton = find.ancestor( of: find.text('Suggest an option'), @@ -207,16 +223,18 @@ void main() async { testWidgets( 'Suggest Option button is not visible when poll is closed', (WidgetTester tester) async { - await tester.pumpWidget(_wrapWithMaterialApp( - PollFooter( - poll: poll.copyWith( - isClosed: true, - allowUserSuggestedOptions: true, + await tester.pumpWidget( + _wrapWithMaterialApp( + PollFooter( + poll: poll.copyWith( + isClosed: true, + allowUserSuggestedOptions: true, + ), + currentUser: currentUser, + onSuggestOption: () {}, ), - currentUser: currentUser, - onSuggestOption: () {}, ), - )); + ); final suggestOptionButton = find.ancestor( of: find.text('Suggest an option'), diff --git a/packages/stream_chat_flutter/test/src/poll/interactor/poll_header_test.dart b/packages/stream_chat_flutter/test/src/poll/interactor/poll_header_test.dart index 301dd5d5f5..f09dcede2f 100644 --- a/packages/stream_chat_flutter/test/src/poll/interactor/poll_header_test.dart +++ b/packages/stream_chat_flutter/test/src/poll/interactor/poll_header_test.dart @@ -99,17 +99,19 @@ Widget _wrapWithMaterialApp( return MaterialApp( home: StreamChatTheme( data: StreamChatThemeData(brightness: brightness), - child: Builder(builder: (context) { - final theme = StreamChatTheme.of(context); - return Scaffold( - backgroundColor: theme.colorTheme.appBg, - body: Container( - color: theme.colorTheme.disabled, - padding: const EdgeInsets.all(16), - child: Center(child: widget), - ), - ); - }), + child: Builder( + builder: (context) { + final theme = StreamChatTheme.of(context); + return Scaffold( + backgroundColor: theme.colorTheme.appBg, + body: Container( + color: theme.colorTheme.disabled, + padding: const EdgeInsets.all(16), + child: Center(child: widget), + ), + ); + }, + ), ), ); } diff --git a/packages/stream_chat_flutter/test/src/poll/interactor/poll_suggest_option_dialog_test.dart b/packages/stream_chat_flutter/test/src/poll/interactor/poll_suggest_option_dialog_test.dart index fb66e438df..3cb4b0bd11 100644 --- a/packages/stream_chat_flutter/test/src/poll/interactor/poll_suggest_option_dialog_test.dart +++ b/packages/stream_chat_flutter/test/src/poll/interactor/poll_suggest_option_dialog_test.dart @@ -19,8 +19,7 @@ void main() { goldenTest( '[${brightness.name}] -> PollSuggestOptionDialog with initialOption looks fine', - fileName: - 'poll_suggest_option_dialog_with_initial_option_${brightness.name}', + fileName: 'poll_suggest_option_dialog_with_initial_option_${brightness.name}', constraints: const BoxConstraints.tightFor(width: 600, height: 300), builder: () => _wrapWithMaterialApp( brightness: brightness, @@ -39,17 +38,19 @@ Widget _wrapWithMaterialApp( return MaterialApp( home: StreamChatTheme( data: StreamChatThemeData(brightness: brightness), - child: Builder(builder: (context) { - final theme = StreamChatTheme.of(context); - return Scaffold( - backgroundColor: theme.colorTheme.appBg, - body: Container( - color: theme.colorTheme.disabled, - padding: const EdgeInsets.all(16), - child: Center(child: widget), - ), - ); - }), + child: Builder( + builder: (context) { + final theme = StreamChatTheme.of(context); + return Scaffold( + backgroundColor: theme.colorTheme.appBg, + body: Container( + color: theme.colorTheme.disabled, + padding: const EdgeInsets.all(16), + child: Center(child: widget), + ), + ); + }, + ), ), ); } diff --git a/packages/stream_chat_flutter/test/src/poll/interactor/stream_poll_interactor_test.dart b/packages/stream_chat_flutter/test/src/poll/interactor/stream_poll_interactor_test.dart index acc34896a3..edbdb33d95 100644 --- a/packages/stream_chat_flutter/test/src/poll/interactor/stream_poll_interactor_test.dart +++ b/packages/stream_chat_flutter/test/src/poll/interactor/stream_poll_interactor_test.dart @@ -114,13 +114,15 @@ Widget _wrapWithMaterialApp( data: StreamChatConfigurationData(), child: StreamChatTheme( data: StreamChatThemeData(brightness: brightness), - child: Builder(builder: (context) { - final theme = StreamChatTheme.of(context); - return Scaffold( - backgroundColor: theme.colorTheme.appBg, - body: Center(child: widget), - ); - }), + child: Builder( + builder: (context) { + final theme = StreamChatTheme.of(context); + return Scaffold( + backgroundColor: theme.colorTheme.appBg, + body: Center(child: widget), + ); + }, + ), ), ), ); diff --git a/packages/stream_chat_flutter/test/src/poll/poll_option_reorderable_list_view_test.dart b/packages/stream_chat_flutter/test/src/poll/poll_option_reorderable_list_view_test.dart index 2609417970..6919559d5c 100644 --- a/packages/stream_chat_flutter/test/src/poll/poll_option_reorderable_list_view_test.dart +++ b/packages/stream_chat_flutter/test/src/poll/poll_option_reorderable_list_view_test.dart @@ -68,18 +68,20 @@ Widget _wrapWithMaterialApp( return MaterialApp( home: StreamChatTheme( data: StreamChatThemeData(brightness: brightness), - child: Builder(builder: (context) { - final theme = StreamChatTheme.of(context); - return Scaffold( - backgroundColor: theme.colorTheme.appBg, - body: Center( - child: Padding( - padding: const EdgeInsets.all(8), - child: widget, + child: Builder( + builder: (context) { + final theme = StreamChatTheme.of(context); + return Scaffold( + backgroundColor: theme.colorTheme.appBg, + body: Center( + child: Padding( + padding: const EdgeInsets.all(8), + child: widget, + ), ), - ), - ); - }), + ); + }, + ), ), ); } diff --git a/packages/stream_chat_flutter/test/src/poll/poll_question_text_field_test.dart b/packages/stream_chat_flutter/test/src/poll/poll_question_text_field_test.dart index 24928bbc67..2c203c10ae 100644 --- a/packages/stream_chat_flutter/test/src/poll/poll_question_text_field_test.dart +++ b/packages/stream_chat_flutter/test/src/poll/poll_question_text_field_test.dart @@ -55,18 +55,20 @@ Widget _wrapWithMaterialApp( return MaterialApp( home: StreamChatTheme( data: StreamChatThemeData(brightness: brightness), - child: Builder(builder: (context) { - final theme = StreamChatTheme.of(context); - return Scaffold( - backgroundColor: theme.colorTheme.appBg, - body: Center( - child: Padding( - padding: const EdgeInsets.all(8), - child: widget, + child: Builder( + builder: (context) { + final theme = StreamChatTheme.of(context); + return Scaffold( + backgroundColor: theme.colorTheme.appBg, + body: Center( + child: Padding( + padding: const EdgeInsets.all(8), + child: widget, + ), ), - ), - ); - }), + ); + }, + ), ), ); } diff --git a/packages/stream_chat_flutter/test/src/poll/stream_poll_options_dialog_test.dart b/packages/stream_chat_flutter/test/src/poll/stream_poll_options_dialog_test.dart index 81ebd027ac..0a2de54fec 100644 --- a/packages/stream_chat_flutter/test/src/poll/stream_poll_options_dialog_test.dart +++ b/packages/stream_chat_flutter/test/src/poll/stream_poll_options_dialog_test.dart @@ -94,13 +94,15 @@ Widget _wrapWithMaterialApp( data: StreamChatConfigurationData(), child: StreamChatTheme( data: StreamChatThemeData(brightness: brightness), - child: Builder(builder: (context) { - final theme = StreamChatTheme.of(context); - return Scaffold( - backgroundColor: theme.colorTheme.appBg, - body: Center(child: widget), - ); - }), + child: Builder( + builder: (context) { + final theme = StreamChatTheme.of(context); + return Scaffold( + backgroundColor: theme.colorTheme.appBg, + body: Center(child: widget), + ); + }, + ), ), ), ); diff --git a/packages/stream_chat_flutter/test/src/poll/stream_poll_results_dialog_test.dart b/packages/stream_chat_flutter/test/src/poll/stream_poll_results_dialog_test.dart index e5b8df69d0..5488228bbe 100644 --- a/packages/stream_chat_flutter/test/src/poll/stream_poll_results_dialog_test.dart +++ b/packages/stream_chat_flutter/test/src/poll/stream_poll_results_dialog_test.dart @@ -114,13 +114,15 @@ Widget _wrapWithMaterialApp( data: StreamChatConfigurationData(), child: StreamChatTheme( data: StreamChatThemeData(brightness: brightness), - child: Builder(builder: (context) { - final theme = StreamChatTheme.of(context); - return Scaffold( - backgroundColor: theme.colorTheme.appBg, - body: Center(child: widget), - ); - }), + child: Builder( + builder: (context) { + final theme = StreamChatTheme.of(context); + return Scaffold( + backgroundColor: theme.colorTheme.appBg, + body: Center(child: widget), + ); + }, + ), ), ), ); diff --git a/packages/stream_chat_flutter/test/src/reactions/detail/goldens/ci/reaction_detail_sheet_dark.png b/packages/stream_chat_flutter/test/src/reactions/detail/goldens/ci/reaction_detail_sheet_dark.png new file mode 100644 index 0000000000..19cb391e79 Binary files /dev/null and b/packages/stream_chat_flutter/test/src/reactions/detail/goldens/ci/reaction_detail_sheet_dark.png differ diff --git a/packages/stream_chat_flutter/test/src/reactions/detail/goldens/ci/reaction_detail_sheet_filtered_dark.png b/packages/stream_chat_flutter/test/src/reactions/detail/goldens/ci/reaction_detail_sheet_filtered_dark.png new file mode 100644 index 0000000000..a6f0c43965 Binary files /dev/null and b/packages/stream_chat_flutter/test/src/reactions/detail/goldens/ci/reaction_detail_sheet_filtered_dark.png differ diff --git a/packages/stream_chat_flutter/test/src/reactions/detail/goldens/ci/reaction_detail_sheet_filtered_light.png b/packages/stream_chat_flutter/test/src/reactions/detail/goldens/ci/reaction_detail_sheet_filtered_light.png new file mode 100644 index 0000000000..50c2490276 Binary files /dev/null and b/packages/stream_chat_flutter/test/src/reactions/detail/goldens/ci/reaction_detail_sheet_filtered_light.png differ diff --git a/packages/stream_chat_flutter/test/src/reactions/detail/goldens/ci/reaction_detail_sheet_light.png b/packages/stream_chat_flutter/test/src/reactions/detail/goldens/ci/reaction_detail_sheet_light.png new file mode 100644 index 0000000000..1d29a156fb Binary files /dev/null and b/packages/stream_chat_flutter/test/src/reactions/detail/goldens/ci/reaction_detail_sheet_light.png differ diff --git a/packages/stream_chat_flutter/test/src/reactions/detail/reaction_detail_sheet_test.dart b/packages/stream_chat_flutter/test/src/reactions/detail/reaction_detail_sheet_test.dart new file mode 100644 index 0000000000..dde8d56a9f --- /dev/null +++ b/packages/stream_chat_flutter/test/src/reactions/detail/reaction_detail_sheet_test.dart @@ -0,0 +1,448 @@ +import 'package:alchemist/alchemist.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +import '../../mocks.dart'; + +void main() { + late MockClient mockClient; + + setUpAll(() { + registerFallbackValue(const PaginationParams()); + registerFallbackValue(Filter.equal('type', 'like')); + }); + + setUp(() { + mockClient = MockClient(); + + final mockClientState = MockClientState(); + when(() => mockClient.state).thenReturn(mockClientState); + + final currentUser = OwnUser(id: 'current-user', name: 'Current User'); + when(() => mockClientState.currentUser).thenReturn(currentUser); + }); + + tearDown(() => reset(mockClient)); + + testWidgets('shows total reaction count and all reactions by default', (tester) async { + final reactions = [ + Reaction( + type: 'love', + messageId: 'test-message', + userId: 'user-1', + user: User(id: 'user-1', name: 'User 1'), + createdAt: DateTime.now(), + ), + Reaction( + type: 'like', + messageId: 'test-message', + userId: 'user-2', + user: User(id: 'user-2', name: 'User 2'), + createdAt: DateTime.now(), + ), + ]; + + when( + () => mockClient.queryReactions( + 'test-message', + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenAnswer( + (_) async => QueryReactionsResponse() + ..reactions = reactions + ..next = null, + ); + + final message = _buildMessage( + reactionGroups: { + 'love': ReactionGroup(count: 1, sumScores: 1), + 'like': ReactionGroup(count: 1, sumScores: 1), + }, + ); + + await tester.pumpWidget( + _wrapWithMaterialApp( + client: mockClient, + _ReactionDetailSheetLauncher(message: message), + ), + ); + + await tester.tap(find.text('Open Sheet')); + await tester.pumpAndSettle(); + + expect(find.byType(ReactionDetailSheet), findsOneWidget); + expect(find.text('2 Reactions'), findsOneWidget); + expect(find.byType(StreamUserAvatar), findsNWidgets(2)); + expect(find.text('User 1'), findsOneWidget); + expect(find.text('User 2'), findsOneWidget); + }); + + testWidgets('applies initial reaction filter when provided', (tester) async { + final loveReaction = Reaction( + type: 'love', + messageId: 'test-message', + userId: 'user-1', + user: User(id: 'user-1', name: 'User 1'), + createdAt: DateTime.now(), + ); + + // The controller is initialised with filter: type == 'love', so + // queryReactions will return only the love reaction. + when( + () => mockClient.queryReactions( + 'test-message', + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenAnswer( + (_) async => QueryReactionsResponse() + ..reactions = [loveReaction] + ..next = null, + ); + + final message = _buildMessage( + reactionGroups: { + 'love': ReactionGroup(count: 1, sumScores: 1), + 'like': ReactionGroup(count: 1, sumScores: 1), + }, + ); + + await tester.pumpWidget( + _wrapWithMaterialApp( + client: mockClient, + _ReactionDetailSheetLauncher( + message: message, + initialReactionType: 'love', + ), + ), + ); + + await tester.tap(find.text('Open Sheet')); + await tester.pumpAndSettle(); + + expect(find.text('1 Reaction'), findsOneWidget); + expect(find.byType(StreamUserAvatar), findsOneWidget); + expect(find.text('User 1'), findsOneWidget); + expect(find.text('User 2'), findsNothing); + }); + + testWidgets('pops with SelectReaction when own reaction row is tapped', (tester) async { + MessageAction? action; + + final reaction = Reaction( + type: 'love', + messageId: 'test-message', + userId: 'current-user', + user: User(id: 'current-user', name: 'Current User'), + createdAt: DateTime.now(), + ); + + when( + () => mockClient.queryReactions( + 'test-message', + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenAnswer( + (_) async => QueryReactionsResponse() + ..reactions = [reaction] + ..next = null, + ); + + final message = _buildMessage( + ownReactions: [reaction], + reactionGroups: { + 'love': ReactionGroup(count: 1, sumScores: 1), + }, + ); + + await tester.pumpWidget( + _wrapWithMaterialApp( + client: mockClient, + _ReactionDetailSheetLauncher( + message: message, + onAction: (value) => action = value, + ), + ), + ); + + await tester.tap(find.text('Open Sheet')); + await tester.pumpAndSettle(); + + expect(find.text('Tap to remove'), findsOneWidget); + + await tester.tap(find.text('Current User')); + await tester.pumpAndSettle(); + + expect(action, isA()); + expect((action! as SelectReaction).reaction.type, 'love'); + }); + + testWidgets('does not pop when non-own reaction row is tapped', (tester) async { + MessageAction? action; + + final otherReaction = Reaction( + type: 'love', + messageId: 'test-message', + userId: 'user-1', + user: User(id: 'user-1', name: 'User 1'), + createdAt: DateTime.now(), + ); + + when( + () => mockClient.queryReactions( + 'test-message', + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenAnswer( + (_) async => QueryReactionsResponse() + ..reactions = [otherReaction] + ..next = null, + ); + + final message = _buildMessage( + ownReactions: [ + Reaction( + type: 'love', + messageId: 'test-message', + userId: 'current-user', + user: User(id: 'current-user', name: 'Current User'), + createdAt: DateTime.now(), + ), + ], + reactionGroups: { + 'love': ReactionGroup(count: 1, sumScores: 1), + }, + ); + + await tester.pumpWidget( + _wrapWithMaterialApp( + client: mockClient, + _ReactionDetailSheetLauncher( + message: message, + onAction: (value) => action = value, + ), + ), + ); + + await tester.tap(find.text('Open Sheet')); + await tester.pumpAndSettle(); + + expect(find.text('Tap to remove'), findsNothing); + + await tester.tap(find.text('User 1')); + await tester.pumpAndSettle(); + + expect(action, isNull); + expect(find.byType(ReactionDetailSheet), findsOneWidget); + }); + + group('ReactionDetailSheet Golden Tests', () { + final reactions = [ + Reaction( + type: 'love', + messageId: 'test-message', + userId: 'user-1', + user: User(id: 'user-1', name: 'User 1'), + createdAt: DateTime(2026, 1, 1, 10, 0), + ), + Reaction( + type: 'like', + messageId: 'test-message', + userId: 'user-2', + user: User(id: 'user-2', name: 'User 2'), + createdAt: DateTime(2026, 1, 1, 10, 1), + ), + ]; + + final message = _buildMessage( + reactionGroups: { + 'love': ReactionGroup(count: 1, sumScores: 1), + 'like': ReactionGroup(count: 1, sumScores: 1), + }, + ); + + for (final brightness in Brightness.values) { + final theme = brightness.name; + + goldenTest( + 'ReactionDetailSheet in $theme theme', + fileName: 'reaction_detail_sheet_$theme', + constraints: const BoxConstraints(maxWidth: 400, maxHeight: 700), + pumpBeforeTest: (tester) async { + when( + () => mockClient.queryReactions( + 'test-message', + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenAnswer( + (_) async => QueryReactionsResponse() + ..reactions = reactions + ..next = null, + ); + // Pump once to trigger post-frame modal opening, then settle animation. + await tester.pump(); + await tester.pumpAndSettle(const Duration(seconds: 1)); + }, + builder: () => _wrapWithMaterialApp( + client: mockClient, + brightness: brightness, + _ReactionDetailSheetGoldenHost(message: message), + ), + ); + + goldenTest( + 'ReactionDetailSheet filtered in $theme theme', + fileName: 'reaction_detail_sheet_filtered_$theme', + constraints: const BoxConstraints(maxWidth: 400, maxHeight: 700), + pumpBeforeTest: (tester) async { + when( + () => mockClient.queryReactions( + 'test-message', + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenAnswer( + (_) async => QueryReactionsResponse() + ..reactions = reactions.where((r) => r.type == 'love').toList() + ..next = null, + ); + // Pump once to trigger post-frame modal opening, then settle animation. + await tester.pump(); + await tester.pumpAndSettle(const Duration(seconds: 1)); + }, + builder: () => _wrapWithMaterialApp( + client: mockClient, + brightness: brightness, + _ReactionDetailSheetGoldenHost( + message: message, + initialReactionType: 'love', + ), + ), + ); + } + }); +} + +class _ReactionDetailSheetLauncher extends StatelessWidget { + const _ReactionDetailSheetLauncher({ + required this.message, + this.initialReactionType, + this.onAction, + }); + + final Message message; + final String? initialReactionType; + final ValueChanged? onAction; + + @override + Widget build(BuildContext context) { + return Center( + child: TextButton( + onPressed: () async { + final action = await ReactionDetailSheet.show( + context: context, + message: message, + initialReactionType: initialReactionType, + ); + + onAction?.call(action); + }, + child: const Text('Open Sheet'), + ), + ); + } +} + +class _ReactionDetailSheetGoldenHost extends StatefulWidget { + const _ReactionDetailSheetGoldenHost({ + required this.message, + this.initialReactionType, + }); + + final Message message; + final String? initialReactionType; + + @override + State<_ReactionDetailSheetGoldenHost> createState() => _ReactionDetailSheetGoldenHostState(); +} + +class _ReactionDetailSheetGoldenHostState extends State<_ReactionDetailSheetGoldenHost> { + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) { + if (!mounted) return; + ReactionDetailSheet.show( + context: context, + message: widget.message, + initialReactionType: widget.initialReactionType, + ); + }); + } + + @override + Widget build(BuildContext context) { + return const SizedBox.expand(); + } +} + +Message _buildMessage({ + List? latestReactions, + List? ownReactions, + Map? reactionGroups, +}) { + return Message( + id: 'test-message', + text: 'This is a test message', + createdAt: DateTime.now(), + user: User(id: 'test-user', name: 'Test User'), + latestReactions: latestReactions, + ownReactions: ownReactions, + reactionGroups: reactionGroups, + ); +} + +Widget _wrapWithMaterialApp( + Widget child, { + required StreamChatClient client, + Brightness? brightness, +}) { + return MaterialApp( + debugShowCheckedModeBanner: false, + theme: ThemeData(brightness: brightness), + builder: (context, child) => StreamChat( + client: client, + // Mock the connectivity stream to always return wifi. + connectivityStream: Stream.value([ConnectivityResult.wifi]), + streamChatThemeData: StreamChatThemeData(brightness: brightness), + child: child ?? const SizedBox.shrink(), + ), + home: Builder( + builder: (context) { + final theme = StreamChatTheme.of(context); + return Scaffold( + backgroundColor: theme.colorTheme.appBg, + body: ColoredBox( + color: theme.colorTheme.overlay, + child: Padding( + padding: const EdgeInsets.all(8), + child: child, + ), + ), + ); + }, + ), + ); +} diff --git a/packages/stream_chat_flutter/test/src/reactions/indicator/goldens/ci/reaction_indicator_icon_list_dark.png b/packages/stream_chat_flutter/test/src/reactions/indicator/goldens/ci/reaction_indicator_icon_list_dark.png deleted file mode 100644 index 8e904035c0..0000000000 Binary files a/packages/stream_chat_flutter/test/src/reactions/indicator/goldens/ci/reaction_indicator_icon_list_dark.png and /dev/null differ diff --git a/packages/stream_chat_flutter/test/src/reactions/indicator/goldens/ci/reaction_indicator_icon_list_light.png b/packages/stream_chat_flutter/test/src/reactions/indicator/goldens/ci/reaction_indicator_icon_list_light.png deleted file mode 100644 index 2f80e3268b..0000000000 Binary files a/packages/stream_chat_flutter/test/src/reactions/indicator/goldens/ci/reaction_indicator_icon_list_light.png and /dev/null differ diff --git a/packages/stream_chat_flutter/test/src/reactions/indicator/goldens/ci/reaction_indicator_icon_list_selected_dark.png b/packages/stream_chat_flutter/test/src/reactions/indicator/goldens/ci/reaction_indicator_icon_list_selected_dark.png deleted file mode 100644 index 227c531012..0000000000 Binary files a/packages/stream_chat_flutter/test/src/reactions/indicator/goldens/ci/reaction_indicator_icon_list_selected_dark.png and /dev/null differ diff --git a/packages/stream_chat_flutter/test/src/reactions/indicator/goldens/ci/reaction_indicator_icon_list_selected_light.png b/packages/stream_chat_flutter/test/src/reactions/indicator/goldens/ci/reaction_indicator_icon_list_selected_light.png deleted file mode 100644 index c1651edf65..0000000000 Binary files a/packages/stream_chat_flutter/test/src/reactions/indicator/goldens/ci/reaction_indicator_icon_list_selected_light.png and /dev/null differ diff --git a/packages/stream_chat_flutter/test/src/reactions/indicator/goldens/ci/stream_reaction_indicator_dark.png b/packages/stream_chat_flutter/test/src/reactions/indicator/goldens/ci/stream_reaction_indicator_dark.png deleted file mode 100644 index 0c0fab9fb0..0000000000 Binary files a/packages/stream_chat_flutter/test/src/reactions/indicator/goldens/ci/stream_reaction_indicator_dark.png and /dev/null differ diff --git a/packages/stream_chat_flutter/test/src/reactions/indicator/goldens/ci/stream_reaction_indicator_light.png b/packages/stream_chat_flutter/test/src/reactions/indicator/goldens/ci/stream_reaction_indicator_light.png deleted file mode 100644 index c9d9896647..0000000000 Binary files a/packages/stream_chat_flutter/test/src/reactions/indicator/goldens/ci/stream_reaction_indicator_light.png and /dev/null differ diff --git a/packages/stream_chat_flutter/test/src/reactions/indicator/goldens/ci/stream_reaction_indicator_own_dark.png b/packages/stream_chat_flutter/test/src/reactions/indicator/goldens/ci/stream_reaction_indicator_own_dark.png deleted file mode 100644 index b20b3d4b88..0000000000 Binary files a/packages/stream_chat_flutter/test/src/reactions/indicator/goldens/ci/stream_reaction_indicator_own_dark.png and /dev/null differ diff --git a/packages/stream_chat_flutter/test/src/reactions/indicator/goldens/ci/stream_reaction_indicator_own_light.png b/packages/stream_chat_flutter/test/src/reactions/indicator/goldens/ci/stream_reaction_indicator_own_light.png deleted file mode 100644 index 13e063d6a1..0000000000 Binary files a/packages/stream_chat_flutter/test/src/reactions/indicator/goldens/ci/stream_reaction_indicator_own_light.png and /dev/null differ diff --git a/packages/stream_chat_flutter/test/src/reactions/indicator/reaction_indicator_icon_list_test.dart b/packages/stream_chat_flutter/test/src/reactions/indicator/reaction_indicator_icon_list_test.dart deleted file mode 100644 index f1abc515ba..0000000000 --- a/packages/stream_chat_flutter/test/src/reactions/indicator/reaction_indicator_icon_list_test.dart +++ /dev/null @@ -1,250 +0,0 @@ -import 'package:alchemist/alchemist.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; - -void main() { - final reactionIcons = [ - StreamReactionIcon( - type: 'love', - builder: (context, isSelected, iconSize) => StreamSvgIcon( - icon: StreamSvgIcons.loveReaction, - size: iconSize, - color: isSelected ? Colors.red : Colors.grey.shade700, - ), - ), - StreamReactionIcon( - type: 'thumbsUp', - builder: (context, isSelected, iconSize) => StreamSvgIcon( - icon: StreamSvgIcons.thumbsUpReaction, - size: iconSize, - color: isSelected ? Colors.blue : Colors.grey.shade700, - ), - ), - StreamReactionIcon( - type: 'thumbsDown', - builder: (context, isSelected, iconSize) => StreamSvgIcon( - icon: StreamSvgIcons.thumbsDownReaction, - size: iconSize, - color: isSelected ? Colors.orange : Colors.grey.shade700, - ), - ), - StreamReactionIcon( - type: 'lol', - builder: (context, isSelected, iconSize) => StreamSvgIcon( - icon: StreamSvgIcons.lolReaction, - size: iconSize, - color: isSelected ? Colors.amber : Colors.grey.shade700, - ), - ), - StreamReactionIcon( - type: 'wut', - builder: (context, isSelected, iconSize) => StreamSvgIcon( - icon: StreamSvgIcons.wutReaction, - size: iconSize, - color: isSelected ? Colors.purple : Colors.grey.shade700, - ), - ), - ]; - - group('ReactionIndicatorIconList', () { - testWidgets( - 'renders all reaction icons', - (WidgetTester tester) async { - final indicatorIcons = reactionIcons.map((icon) { - return ReactionIndicatorIcon( - type: icon.type, - builder: icon.builder, - ); - }); - - await tester.pumpWidget( - _wrapWithMaterialApp( - ReactionIndicatorIconList( - indicatorIcons: [...indicatorIcons], - ), - ), - ); - - await tester.pumpAndSettle(); - - // Should find the ReactionIndicatorIconList widget - expect(find.byType(ReactionIndicatorIconList), findsOneWidget); - }, - ); - - testWidgets( - 'uses custom iconBuilder when provided', - (WidgetTester tester) async { - final indicatorIcons = reactionIcons.map((icon) { - return ReactionIndicatorIcon( - type: icon.type, - builder: icon.builder, - ); - }); - - await tester.pumpWidget( - _wrapWithMaterialApp( - ReactionIndicatorIconList( - indicatorIcons: [...indicatorIcons], - iconBuilder: (context, icon) { - return Container( - key: Key('custom-icon-${icon.type}'), - child: icon.build(context), - ); - }, - ), - ), - ); - - await tester.pumpAndSettle(); - - // Verify custom builder was used - expect(find.byKey(const Key('custom-icon-love')), findsOneWidget); - }, - ); - - testWidgets( - 'properly handles icons with selected state', - (WidgetTester tester) async { - final indicatorIcons = reactionIcons.map((icon) { - return ReactionIndicatorIcon( - type: icon.type, - builder: icon.builder, - isSelected: icon.type == 'love', // First icon is selected - ); - }); - - await tester.pumpWidget( - _wrapWithMaterialApp( - ReactionIndicatorIconList( - indicatorIcons: [...indicatorIcons], - ), - ), - ); - - await tester.pumpAndSettle(); - - // Should render all icons - expect(find.byType(ReactionIndicatorIconList), findsOneWidget); - }, - ); - - testWidgets( - 'updates when indicatorIcons change', - (WidgetTester tester) async { - // Build with initial set of reaction icons - final initialIcons = reactionIcons.sublist(0, 2).map((icon) { - return ReactionIndicatorIcon( - type: icon.type, - builder: icon.builder, - ); - }); - - await tester.pumpWidget( - _wrapWithMaterialApp( - ReactionIndicatorIconList( - indicatorIcons: [...initialIcons], - ), - ), - ); - - await tester.pumpAndSettle(); - - // Rebuild with all reaction icons - final allIcons = reactionIcons.map((icon) { - return ReactionIndicatorIcon( - type: icon.type, - builder: icon.builder, - ); - }); - - await tester.pumpWidget( - _wrapWithMaterialApp( - ReactionIndicatorIconList( - indicatorIcons: [...allIcons], - ), - ), - ); - - await tester.pumpAndSettle(); - - // Should have all icons rendered - expect(find.byType(ReactionIndicatorIconList), findsOneWidget); - }, - ); - - group('Golden tests', () { - for (final brightness in [Brightness.light, Brightness.dark]) { - final theme = brightness.name; - - goldenTest( - 'ReactionIndicatorIconList in $theme theme', - fileName: 'reaction_indicator_icon_list_$theme', - constraints: const BoxConstraints.tightFor(width: 400, height: 100), - builder: () { - final indicatorIcons = reactionIcons.map((icon) { - return ReactionIndicatorIcon( - type: icon.type, - builder: icon.builder, - ); - }); - - return _wrapWithMaterialApp( - brightness: brightness, - ReactionIndicatorIconList( - indicatorIcons: [...indicatorIcons], - ), - ); - }, - ); - - goldenTest( - 'ReactionIndicatorIconList with selected icon in $theme theme', - fileName: 'reaction_indicator_icon_list_selected_$theme', - constraints: const BoxConstraints.tightFor(width: 400, height: 100), - builder: () { - final indicatorIcons = reactionIcons.map((icon) { - return ReactionIndicatorIcon( - type: icon.type, - builder: icon.builder, - isSelected: icon.type == 'love', - ); - }); - - return _wrapWithMaterialApp( - brightness: brightness, - ReactionIndicatorIconList( - indicatorIcons: [...indicatorIcons], - ), - ); - }, - ); - } - }); - }); -} - -Widget _wrapWithMaterialApp( - Widget child, { - Brightness? brightness, -}) { - return MaterialApp( - debugShowCheckedModeBanner: false, - home: StreamChatTheme( - data: StreamChatThemeData(brightness: brightness), - child: Builder(builder: (context) { - final theme = StreamChatTheme.of(context); - return Scaffold( - backgroundColor: theme.colorTheme.overlay, - body: Center( - child: Padding( - padding: const EdgeInsets.all(8), - child: child, - ), - ), - ); - }), - ), - ); -} diff --git a/packages/stream_chat_flutter/test/src/reactions/indicator/reaction_indicator_test.dart b/packages/stream_chat_flutter/test/src/reactions/indicator/reaction_indicator_test.dart deleted file mode 100644 index 72fc26913b..0000000000 --- a/packages/stream_chat_flutter/test/src/reactions/indicator/reaction_indicator_test.dart +++ /dev/null @@ -1,276 +0,0 @@ -import 'package:alchemist/alchemist.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; - -void main() { - final reactionIcons = [ - StreamReactionIcon( - type: 'love', - builder: (context, isSelected, iconSize) => StreamSvgIcon( - icon: StreamSvgIcons.loveReaction, - size: iconSize, - color: isSelected ? Colors.red : Colors.grey.shade700, - ), - ), - StreamReactionIcon( - type: 'thumbsUp', - builder: (context, isSelected, iconSize) => StreamSvgIcon( - icon: StreamSvgIcons.thumbsUpReaction, - size: iconSize, - color: isSelected ? Colors.blue : Colors.grey.shade700, - ), - ), - StreamReactionIcon( - type: 'thumbsDown', - builder: (context, isSelected, iconSize) => StreamSvgIcon( - icon: StreamSvgIcons.thumbsDownReaction, - size: iconSize, - color: isSelected ? Colors.orange : Colors.grey.shade700, - ), - ), - StreamReactionIcon( - type: 'lol', - builder: (context, isSelected, iconSize) => StreamSvgIcon( - icon: StreamSvgIcons.lolReaction, - size: iconSize, - color: isSelected ? Colors.amber : Colors.grey.shade700, - ), - ), - StreamReactionIcon( - type: 'wut', - builder: (context, isSelected, iconSize) => StreamSvgIcon( - icon: StreamSvgIcons.wutReaction, - size: iconSize, - color: isSelected ? Colors.purple : Colors.grey.shade700, - ), - ), - ]; - - testWidgets( - 'renders with correct message and reaction icons', - (WidgetTester tester) async { - final message = Message( - id: 'test-message', - text: 'Hello world', - user: User(id: 'test-user'), - reactionGroups: { - 'love': ReactionGroup( - count: 3, - sumScores: 3, - firstReactionAt: DateTime.now(), - lastReactionAt: DateTime.now(), - ), - 'thumbsUp': ReactionGroup( - count: 2, - sumScores: 2, - firstReactionAt: DateTime.now(), - lastReactionAt: DateTime.now(), - ), - }, - ); - - await tester.pumpWidget( - _wrapWithMaterialApp( - StreamReactionIndicator( - message: message, - reactionIcons: reactionIcons, - ), - ), - ); - - await tester.pumpAndSettle(); - - // Verify the widget renders with correct structure - expect(find.byType(StreamReactionIndicator), findsOneWidget); - expect(find.byType(ReactionIndicatorIconList), findsOneWidget); - }, - ); - - testWidgets( - 'triggers onTap callback when tapped', - (WidgetTester tester) async { - final message = Message( - id: 'test-message', - text: 'Hello world', - user: User(id: 'test-user'), - reactionGroups: { - 'love': ReactionGroup( - count: 1, - sumScores: 1, - firstReactionAt: DateTime.now(), - lastReactionAt: DateTime.now(), - ), - }, - ); - - var tapped = false; - - await tester.pumpWidget( - _wrapWithMaterialApp( - StreamReactionIndicator( - message: message, - reactionIcons: reactionIcons, - onTap: () { - tapped = true; - }, - ), - ), - ); - - await tester.pumpAndSettle(); - - // Tap the indicator - await tester.tap(find.byType(InkWell)); - await tester.pump(); - - // Verify the callback was called - expect(tapped, isTrue); - }, - ); - - testWidgets( - 'uses custom reactionIconBuilder when provided', - (WidgetTester tester) async { - final message = Message( - id: 'test-message', - text: 'Hello world', - user: User(id: 'test-user'), - reactionGroups: { - 'love': ReactionGroup( - count: 5, - sumScores: 5, - firstReactionAt: DateTime.now(), - lastReactionAt: DateTime.now(), - ), - }, - ); - - await tester.pumpWidget( - _wrapWithMaterialApp( - StreamReactionIndicator( - message: message, - reactionIcons: reactionIcons, - reactionIconBuilder: (context, icon) { - final count = message.reactionGroups?[icon.type]?.count ?? 0; - return Row( - children: [ - icon.build(context), - const SizedBox(width: 4), - Text('$count', key: const Key('reaction-count')), - ], - ); - }, - ), - ), - ); - - await tester.pumpAndSettle(); - - // Verify the custom count text is displayed - expect(find.byKey(const Key('reaction-count')), findsOneWidget); - expect(find.text('5'), findsOneWidget); - }, - ); - - group('Golden tests', () { - for (final brightness in [Brightness.light, Brightness.dark]) { - final theme = brightness.name; - - goldenTest( - 'StreamReactionIndicator in $theme theme', - fileName: 'stream_reaction_indicator_$theme', - constraints: const BoxConstraints.tightFor(width: 200, height: 60), - builder: () { - final message = Message( - id: 'test-message', - text: 'Hello world', - user: User(id: 'test-user'), - reactionGroups: { - 'love': ReactionGroup( - count: 3, - sumScores: 3, - firstReactionAt: DateTime.now(), - lastReactionAt: DateTime.now(), - ), - 'thumbsUp': ReactionGroup( - count: 2, - sumScores: 2, - firstReactionAt: DateTime.now(), - lastReactionAt: DateTime.now(), - ), - }, - ); - - return _wrapWithMaterialApp( - brightness: brightness, - StreamReactionIndicator( - message: message, - reactionIcons: reactionIcons, - ), - ); - }, - ); - - goldenTest( - 'StreamReactionIndicator with own reaction in $theme theme', - fileName: 'stream_reaction_indicator_own_$theme', - constraints: const BoxConstraints.tightFor(width: 200, height: 60), - builder: () { - final message = Message( - id: 'test-message', - text: 'Hello world', - user: User(id: 'test-user'), - reactionGroups: { - 'love': ReactionGroup( - count: 1, - sumScores: 1, - firstReactionAt: DateTime.now(), - lastReactionAt: DateTime.now(), - ), - }, - ownReactions: [ - Reaction( - type: 'love', - messageId: 'test-message', - userId: 'test-user', - ), - ], - ); - - return _wrapWithMaterialApp( - brightness: brightness, - StreamReactionIndicator( - message: message, - reactionIcons: reactionIcons, - ), - ); - }, - ); - } - }); -} - -Widget _wrapWithMaterialApp( - Widget child, { - Brightness? brightness, -}) { - return MaterialApp( - debugShowCheckedModeBanner: false, - home: StreamChatTheme( - data: StreamChatThemeData(brightness: brightness), - child: Builder(builder: (context) { - final theme = StreamChatTheme.of(context); - return Scaffold( - backgroundColor: theme.colorTheme.overlay, - body: Center( - child: Padding( - padding: const EdgeInsets.all(8), - child: child, - ), - ), - ); - }), - ), - ); -} diff --git a/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/reaction_icon_button_selected_dark.png b/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/reaction_icon_button_selected_dark.png index 25d222bed1..b23c628a3b 100644 Binary files a/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/reaction_icon_button_selected_dark.png and b/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/reaction_icon_button_selected_dark.png differ diff --git a/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/reaction_icon_button_selected_light.png b/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/reaction_icon_button_selected_light.png index 670bc5de2f..c47907a1c0 100644 Binary files a/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/reaction_icon_button_selected_light.png and b/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/reaction_icon_button_selected_light.png differ diff --git a/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/reaction_icon_button_unselected_dark.png b/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/reaction_icon_button_unselected_dark.png index 657eee5c62..e7cac1e1a0 100644 Binary files a/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/reaction_icon_button_unselected_dark.png and b/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/reaction_icon_button_unselected_dark.png differ diff --git a/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/reaction_icon_button_unselected_light.png b/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/reaction_icon_button_unselected_light.png index c089b20c3b..a8d37e94bb 100644 Binary files a/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/reaction_icon_button_unselected_light.png and b/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/reaction_icon_button_unselected_light.png differ diff --git a/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/reaction_picker_icon_list_dark.png b/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/reaction_picker_icon_list_dark.png index f0b4c634f8..3ea44c3993 100644 Binary files a/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/reaction_picker_icon_list_dark.png and b/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/reaction_picker_icon_list_dark.png differ diff --git a/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/reaction_picker_icon_list_light.png b/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/reaction_picker_icon_list_light.png index d14ce83155..dea0b18230 100644 Binary files a/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/reaction_picker_icon_list_light.png and b/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/reaction_picker_icon_list_light.png differ diff --git a/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/reaction_picker_icon_list_selected_dark.png b/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/reaction_picker_icon_list_selected_dark.png index 068b97997f..3de2c605ae 100644 Binary files a/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/reaction_picker_icon_list_selected_dark.png and b/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/reaction_picker_icon_list_selected_dark.png differ diff --git a/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/reaction_picker_icon_list_selected_light.png b/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/reaction_picker_icon_list_selected_light.png index 0a3feaed26..6a1b705330 100644 Binary files a/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/reaction_picker_icon_list_selected_light.png and b/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/reaction_picker_icon_list_selected_light.png differ diff --git a/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/stream_reaction_picker_dark.png b/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/stream_reaction_picker_dark.png index 04a299b283..ef499f77f0 100644 Binary files a/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/stream_reaction_picker_dark.png and b/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/stream_reaction_picker_dark.png differ diff --git a/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/stream_reaction_picker_light.png b/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/stream_reaction_picker_light.png index 3aaa202678..9c0016cb50 100644 Binary files a/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/stream_reaction_picker_light.png and b/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/stream_reaction_picker_light.png differ diff --git a/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/stream_reaction_picker_selected_dark.png b/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/stream_reaction_picker_selected_dark.png index 845ea2d8d9..0e37db5c46 100644 Binary files a/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/stream_reaction_picker_selected_dark.png and b/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/stream_reaction_picker_selected_dark.png differ diff --git a/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/stream_reaction_picker_selected_light.png b/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/stream_reaction_picker_selected_light.png index 1a134c74f9..622e046625 100644 Binary files a/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/stream_reaction_picker_selected_light.png and b/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/stream_reaction_picker_selected_light.png differ diff --git a/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/stream_reaction_picker_subset_dark.png b/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/stream_reaction_picker_subset_dark.png new file mode 100644 index 0000000000..c2716a1873 Binary files /dev/null and b/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/stream_reaction_picker_subset_dark.png differ diff --git a/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/stream_reaction_picker_subset_light.png b/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/stream_reaction_picker_subset_light.png new file mode 100644 index 0000000000..7d72f1d1f0 Binary files /dev/null and b/packages/stream_chat_flutter/test/src/reactions/picker/goldens/ci/stream_reaction_picker_subset_light.png differ diff --git a/packages/stream_chat_flutter/test/src/reactions/picker/reaction_picker_icon_list_test.dart b/packages/stream_chat_flutter/test/src/reactions/picker/reaction_picker_icon_list_test.dart deleted file mode 100644 index c72c145f15..0000000000 --- a/packages/stream_chat_flutter/test/src/reactions/picker/reaction_picker_icon_list_test.dart +++ /dev/null @@ -1,387 +0,0 @@ -import 'package:alchemist/alchemist.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; - -void main() { - final reactionIcons = [ - StreamReactionIcon( - type: 'love', - builder: (context, isSelected, iconSize) => StreamSvgIcon( - icon: StreamSvgIcons.loveReaction, - size: iconSize, - color: isSelected ? Colors.red : Colors.grey.shade700, - ), - ), - StreamReactionIcon( - type: 'thumbsUp', - builder: (context, isSelected, iconSize) => StreamSvgIcon( - icon: StreamSvgIcons.thumbsUpReaction, - size: iconSize, - color: isSelected ? Colors.blue : Colors.grey.shade700, - ), - ), - StreamReactionIcon( - type: 'thumbsDown', - builder: (context, isSelected, iconSize) => StreamSvgIcon( - icon: StreamSvgIcons.thumbsDownReaction, - size: iconSize, - color: isSelected ? Colors.orange : Colors.grey.shade700, - ), - ), - StreamReactionIcon( - type: 'lol', - builder: (context, isSelected, iconSize) => StreamSvgIcon( - icon: StreamSvgIcons.lolReaction, - size: iconSize, - color: isSelected ? Colors.amber : Colors.grey.shade700, - ), - ), - StreamReactionIcon( - type: 'wut', - builder: (context, isSelected, iconSize) => StreamSvgIcon( - icon: StreamSvgIcons.wutReaction, - size: iconSize, - color: isSelected ? Colors.purple : Colors.grey.shade700, - ), - ), - ]; - - group('ReactionIconButton', () { - testWidgets( - 'renders correctly with selected state', - (WidgetTester tester) async { - await tester.pumpWidget( - _wrapWithMaterialApp( - ReactionIconButton( - icon: ReactionPickerIcon( - isSelected: true, - type: reactionIcons.first.type, - builder: reactionIcons.first.builder, - ), - onPressed: () {}, - ), - ), - ); - - expect(find.byType(ReactionIconButton), findsOneWidget); - expect(find.byType(IconButton), findsOneWidget); - }, - ); - - testWidgets( - 'triggers callback when pressed', - (WidgetTester tester) async { - var callbackTriggered = false; - - await tester.pumpWidget( - _wrapWithMaterialApp( - ReactionIconButton( - icon: ReactionPickerIcon( - type: reactionIcons.first.type, - builder: reactionIcons.first.builder, - ), - onPressed: () { - callbackTriggered = true; - }, - ), - ), - ); - - await tester.tap(find.byType(IconButton)); - await tester.pump(); - - expect(callbackTriggered, isTrue); - }, - ); - - group('Golden tests', () { - for (final brightness in [Brightness.light, Brightness.dark]) { - final theme = brightness.name; - - goldenTest( - 'ReactionIconButton unselected in $theme theme', - fileName: 'reaction_icon_button_unselected_$theme', - constraints: const BoxConstraints.tightFor(width: 60, height: 60), - builder: () => _wrapWithMaterialApp( - brightness: brightness, - ReactionIconButton( - icon: ReactionPickerIcon( - type: reactionIcons.first.type, - builder: reactionIcons.first.builder, - ), - onPressed: () {}, - ), - ), - ); - - goldenTest( - 'ReactionIconButton selected in $theme theme', - fileName: 'reaction_icon_button_selected_$theme', - constraints: const BoxConstraints.tightFor(width: 60, height: 60), - builder: () => _wrapWithMaterialApp( - brightness: brightness, - ReactionIconButton( - icon: ReactionPickerIcon( - isSelected: true, - type: reactionIcons.first.type, - builder: reactionIcons.first.builder, - ), - onPressed: () {}, - ), - ), - ); - } - }); - }); - - group('ReactionPickerIconList', () { - testWidgets( - 'renders all reaction icons', - (WidgetTester tester) async { - final pickerIcons = reactionIcons.map((icon) { - return ReactionPickerIcon( - type: icon.type, - builder: icon.builder, - ); - }); - - await tester.pumpWidget( - _wrapWithMaterialApp( - ReactionPickerIconList( - reactionIcons: [...pickerIcons], - onIconPicked: (_) {}, - ), - ), - ); - - // Wait for animations to complete - await tester.pumpAndSettle(); - - // Should find same number of IconButtons as reactionIcons - expect(find.byType(IconButton), findsNWidgets(reactionIcons.length)); - }, - ); - - testWidgets( - 'triggers onIconPicked when a reaction icon is tapped', - (WidgetTester tester) async { - final pickerIcons = reactionIcons.map((icon) { - return ReactionPickerIcon( - type: icon.type, - builder: icon.builder, - ); - }); - - ReactionPickerIcon? pickedIcon; - - await tester.pumpWidget( - _wrapWithMaterialApp( - ReactionPickerIconList( - reactionIcons: [...pickerIcons], - onIconPicked: (icon) => pickedIcon = icon, - ), - ), - ); - - // Wait for animations to complete - await tester.pumpAndSettle(); - - // Tap the first reaction icon - await tester.tap(find.byType(IconButton).first); - await tester.pump(); - - // Verify callback was triggered with correct reaction type - expect(pickedIcon, isNotNull); - expect(pickedIcon!.type, 'love'); - }, - ); - - testWidgets( - 'shows reaction icons with animation', - (WidgetTester tester) async { - final pickerIcons = reactionIcons.map((icon) { - return ReactionPickerIcon( - type: icon.type, - builder: icon.builder, - ); - }); - - await tester.pumpWidget( - _wrapWithMaterialApp( - ReactionPickerIconList( - reactionIcons: [...pickerIcons], - onIconPicked: (_) {}, - ), - ), - ); - - // Initially the animations should be starting - await tester.pump(); - - // After animation completes - await tester.pumpAndSettle(); - - // Should have all reactions visible - expect( - find.byType(ReactionIconButton), - findsNWidgets(reactionIcons.length), - ); - }, - ); - - testWidgets( - 'properly handles icons with selected state', - (WidgetTester tester) async { - // Create icons with one being selected - final pickerIcons = reactionIcons.map((icon) { - return ReactionPickerIcon( - type: icon.type, - builder: icon.builder, - isSelected: icon.type == 'love', // First icon is selected - ); - }); - - await tester.pumpWidget( - _wrapWithMaterialApp( - Material( - child: ReactionPickerIconList( - reactionIcons: [...pickerIcons], - onIconPicked: (_) {}, - ), - ), - ), - ); - - // Wait for animations - await tester.pumpAndSettle(); - - // All reaction buttons should be rendered - expect( - find.byType(ReactionIconButton), - findsNWidgets(reactionIcons.length), - ); - }, - ); - - testWidgets( - 'updates when reactionIcons change', - (WidgetTester tester) async { - // Build with initial set of reaction icons - final initialIcons = reactionIcons.sublist(0, 2).map((icon) { - return ReactionPickerIcon( - type: icon.type, - builder: icon.builder, - ); - }); - - await tester.pumpWidget( - _wrapWithMaterialApp( - ReactionPickerIconList( - reactionIcons: [...initialIcons], - onIconPicked: (_) {}, - ), - ), - ); - - await tester.pumpAndSettle(); - expect(find.byType(IconButton), findsNWidgets(2)); - - // Rebuild with all reaction icons - final allIcons = reactionIcons.map((icon) { - return ReactionPickerIcon( - type: icon.type, - builder: icon.builder, - ); - }); - - await tester.pumpWidget( - _wrapWithMaterialApp( - ReactionPickerIconList( - reactionIcons: [...allIcons], - onIconPicked: (_) {}, - ), - ), - ); - - await tester.pumpAndSettle(); - expect(find.byType(IconButton), findsNWidgets(5)); - }, - ); - - group('Golden tests', () { - for (final brightness in [Brightness.light, Brightness.dark]) { - final theme = brightness.name; - - goldenTest( - 'ReactionPickerIconList in $theme theme', - fileName: 'reaction_picker_icon_list_$theme', - constraints: const BoxConstraints.tightFor(width: 400, height: 100), - builder: () { - final pickerIcons = reactionIcons.map((icon) { - return ReactionPickerIcon( - type: icon.type, - builder: icon.builder, - ); - }); - - return _wrapWithMaterialApp( - brightness: brightness, - ReactionPickerIconList( - reactionIcons: [...pickerIcons], - onIconPicked: (_) {}, - ), - ); - }, - ); - - goldenTest( - 'ReactionPickerIconList with selected reaction in $theme theme', - fileName: 'reaction_picker_icon_list_selected_$theme', - constraints: const BoxConstraints.tightFor(width: 400, height: 100), - builder: () { - final pickerIcons = reactionIcons.map((icon) { - return ReactionPickerIcon( - type: icon.type, - builder: icon.builder, - isSelected: icon.type == 'love', // First icon is selected - ); - }); - - return _wrapWithMaterialApp( - brightness: brightness, - ReactionPickerIconList( - reactionIcons: [...pickerIcons], - onIconPicked: (_) {}, - ), - ); - }, - ); - } - }); - }); -} - -Widget _wrapWithMaterialApp( - Widget child, { - Brightness? brightness, -}) { - return MaterialApp( - debugShowCheckedModeBanner: false, - home: StreamChatTheme( - data: StreamChatThemeData(brightness: brightness), - child: Builder(builder: (context) { - final theme = StreamChatTheme.of(context); - return Scaffold( - backgroundColor: theme.colorTheme.overlay, - body: Center( - child: Padding( - padding: const EdgeInsets.all(8), - child: child, - ), - ), - ); - }), - ), - ); -} diff --git a/packages/stream_chat_flutter/test/src/reactions/picker/reaction_picker_test.dart b/packages/stream_chat_flutter/test/src/reactions/picker/reaction_picker_test.dart index 3b80f884e4..bd39c55e6a 100644 --- a/packages/stream_chat_flutter/test/src/reactions/picker/reaction_picker_test.dart +++ b/packages/stream_chat_flutter/test/src/reactions/picker/reaction_picker_test.dart @@ -1,54 +1,14 @@ import 'package:alchemist/alchemist.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:stream_chat_flutter/src/misc/staggered_scale_transition.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; void main() { - final reactionIcons = [ - StreamReactionIcon( - type: 'love', - builder: (context, isSelected, iconSize) => StreamSvgIcon( - icon: StreamSvgIcons.loveReaction, - size: iconSize, - color: isSelected ? Colors.red : Colors.grey.shade700, - ), - ), - StreamReactionIcon( - type: 'thumbsUp', - builder: (context, isSelected, iconSize) => StreamSvgIcon( - icon: StreamSvgIcons.thumbsUpReaction, - size: iconSize, - color: isSelected ? Colors.blue : Colors.grey.shade700, - ), - ), - StreamReactionIcon( - type: 'thumbsDown', - builder: (context, isSelected, iconSize) => StreamSvgIcon( - icon: StreamSvgIcons.thumbsDownReaction, - size: iconSize, - color: isSelected ? Colors.orange : Colors.grey.shade700, - ), - ), - StreamReactionIcon( - type: 'lol', - builder: (context, isSelected, iconSize) => StreamSvgIcon( - icon: StreamSvgIcons.lolReaction, - size: iconSize, - color: isSelected ? Colors.amber : Colors.grey.shade700, - ), - ), - StreamReactionIcon( - type: 'wut', - builder: (context, isSelected, iconSize) => StreamSvgIcon( - icon: StreamSvgIcons.wutReaction, - size: iconSize, - color: isSelected ? Colors.purple : Colors.grey.shade700, - ), - ), - ]; + const resolver = _TestReactionIconResolver(); testWidgets( - 'renders with correct message and reaction icons', + 'renders with correct message and reaction buttons', (WidgetTester tester) async { final message = Message( id: 'test-message', @@ -58,22 +18,24 @@ void main() { await tester.pumpWidget( _wrapWithMaterialApp( - StreamReactionPicker( + StreamMessageReactionPicker( message: message, - reactionIcons: reactionIcons, onReactionPicked: (_) {}, ), + reactionIconResolver: resolver, ), ); await tester.pumpAndSettle(const Duration(seconds: 1)); - // Verify the widget renders with correct structure - expect(find.byType(StreamReactionPicker), findsOneWidget); - expect(find.byType(ReactionPickerIconList), findsOneWidget); - - // Verify the correct number of reaction icons - expect(find.byType(IconButton), findsNWidgets(reactionIcons.length)); + // Verify the widget renders with correct structure. + expect(find.byType(StreamMessageReactionPicker), findsOneWidget); + // Verify the correct number of reaction buttons. + expect( + find.byType(StreamEmojiButton), + findsNWidgets(resolver.defaultReactions.length), + ); + expect(find.byKey(const Key('add_reaction')), findsOneWidget); }, ); @@ -90,27 +52,235 @@ void main() { await tester.pumpWidget( _wrapWithMaterialApp( - StreamReactionPicker( + StreamMessageReactionPicker( message: message, - reactionIcons: reactionIcons, onReactionPicked: (reaction) { pickedReaction = reaction; }, ), + reactionIconResolver: resolver, ), ); - // Wait for animations to complete + // Wait for animations to complete. await tester.pumpAndSettle(const Duration(seconds: 1)); - // Tap the first reaction icon - await tester.tap(find.byType(IconButton).first); + // Tap the first reaction button. + await tester.tap(find.byKey(const Key('love'))); await tester.pump(); - // Verify the callback was called with the correct reaction + // Verify the callback was called with the correct reaction. expect(pickedReaction, isNotNull); - // Updated to match first reaction type in the list expect(pickedReaction!.type, 'love'); + expect(pickedReaction!.emojiCode, resolver.emojiCode('love')); + }, + ); + + testWidgets( + 'reuses own reaction when selected', + (WidgetTester tester) async { + final existingReaction = Reaction( + type: 'love', + messageId: 'test-message', + userId: 'test-user', + ); + + final message = Message( + id: 'test-message', + text: 'Hello world', + user: User(id: 'test-user'), + ownReactions: [existingReaction], + ); + + Reaction? pickedReaction; + + await tester.pumpWidget( + _wrapWithMaterialApp( + StreamMessageReactionPicker( + message: message, + onReactionPicked: (reaction) { + pickedReaction = reaction; + }, + ), + reactionIconResolver: resolver, + ), + ); + + // Wait for animations to complete. + await tester.pumpAndSettle(const Duration(seconds: 1)); + + await tester.tap(find.byKey(const Key('love'))); + await tester.pump(); + + expect(pickedReaction, same(existingReaction)); + }, + ); + + testWidgets( + 'marks own reactions as selected', + (WidgetTester tester) async { + final message = Message( + id: 'test-message', + text: 'Hello world', + user: User(id: 'test-user'), + ownReactions: [ + Reaction( + type: 'love', + messageId: 'test-message', + userId: 'test-user', + ), + ], + ); + + await tester.pumpWidget( + _wrapWithMaterialApp( + StreamMessageReactionPicker( + message: message, + onReactionPicked: (_) {}, + ), + reactionIconResolver: resolver, + ), + ); + + // Wait for animations to complete. + await tester.pumpAndSettle(const Duration(seconds: 1)); + + final selectedButton = tester.widget( + find.byKey(const Key('love')), + ); + final unselectedButton = tester.widget( + find.byKey(const Key('like')), + ); + + expect(selectedButton.props.isSelected, isTrue); + expect(unselectedButton.props.isSelected, isFalse); + }, + ); + + testWidgets( + 'updates reaction buttons when resolver default reactions change', + (WidgetTester tester) async { + final message = Message( + id: 'test-message', + text: 'Hello world', + user: User(id: 'test-user'), + ); + + const compactResolver = _CustomReactionIconResolver({'love', 'like'}); + + await tester.pumpWidget( + _wrapWithMaterialApp( + StreamMessageReactionPicker( + message: message, + onReactionPicked: (_) {}, + ), + reactionIconResolver: compactResolver, + ), + ); + + await tester.pumpAndSettle(const Duration(seconds: 1)); + // Initial resolver exposes two quick reactions. + expect(find.byType(StreamEmojiButton), findsNWidgets(2)); + + await tester.pumpWidget( + _wrapWithMaterialApp( + StreamMessageReactionPicker( + message: message, + onReactionPicked: (_) {}, + ), + reactionIconResolver: resolver, + ), + ); + + await tester.pumpAndSettle(const Duration(seconds: 1)); + // Updated resolver exposes the default quick-reaction set. + expect( + find.byType(StreamEmojiButton), + findsNWidgets(resolver.defaultReactions.length), + ); + }, + ); + + testWidgets( + 'uses only defaultReactions even when supportedReactions contains more types', + (WidgetTester tester) async { + final message = Message( + id: 'test-message', + text: 'Hello world', + user: User(id: 'test-user'), + ); + + const subsetResolver = _SubsetDefaultReactionIconResolver(); + + await tester.pumpWidget( + _wrapWithMaterialApp( + StreamMessageReactionPicker( + message: message, + onReactionPicked: (_) {}, + ), + reactionIconResolver: subsetResolver, + ), + ); + + // Wait for animations to complete. + await tester.pumpAndSettle(const Duration(seconds: 1)); + + // Picker should render only the resolver's quick-reaction defaults. + expect(find.byType(StreamEmojiButton), findsNWidgets(1)); + expect(find.byKey(const Key('love')), findsOneWidget); + expect(find.byKey(const Key('like')), findsNothing); + expect(find.byKey(const Key('wow')), findsNothing); + }, + ); + + testWidgets( + 'uses custom reaction resolver rendering', + (WidgetTester tester) async { + final message = Message( + id: 'test-message', + text: 'Hello world', + user: User(id: 'test-user'), + ); + + await tester.pumpWidget( + _wrapWithMaterialApp( + StreamMessageReactionPicker( + message: message, + onReactionPicked: (_) {}, + ), + reactionIconResolver: const _TypeBasedReactionIconResolver(), + ), + ); + + // Wait for animations to complete. + await tester.pumpAndSettle(const Duration(seconds: 1)); + + expect(find.byKey(const Key('custom-type-customParty')), findsOneWidget); + }, + ); + + testWidgets( + 'renders picker without staggered transition animation', + (WidgetTester tester) async { + final message = Message( + id: 'test-message', + text: 'Hello world', + user: User(id: 'test-user'), + ); + + await tester.pumpWidget( + _wrapWithMaterialApp( + StreamMessageReactionPicker( + message: message, + onReactionPicked: (_) {}, + ), + reactionIconResolver: resolver, + ), + ); + + await tester.pumpAndSettle(const Duration(seconds: 1)); + + expect(find.byType(StaggeredScaleTransition), findsNothing); }, ); @@ -119,7 +289,7 @@ void main() { final theme = brightness.name; goldenTest( - 'StreamReactionPicker in $theme theme', + 'StreamMessageReactionPicker in $theme theme', fileName: 'stream_reaction_picker_$theme', constraints: const BoxConstraints.tightFor(width: 400, height: 100), builder: () { @@ -131,17 +301,17 @@ void main() { return _wrapWithMaterialApp( brightness: brightness, - StreamReactionPicker( + StreamMessageReactionPicker( message: message, - reactionIcons: reactionIcons, onReactionPicked: (_) {}, ), + reactionIconResolver: resolver, ); }, ); goldenTest( - 'StreamReactionPicker with selected reaction in $theme theme', + 'StreamMessageReactionPicker with selected reaction in $theme theme', fileName: 'stream_reaction_picker_selected_$theme', constraints: const BoxConstraints.tightFor(width: 400, height: 100), builder: () { @@ -160,11 +330,33 @@ void main() { return _wrapWithMaterialApp( brightness: brightness, - StreamReactionPicker( + StreamMessageReactionPicker( + message: message, + onReactionPicked: (_) {}, + ), + reactionIconResolver: resolver, + ); + }, + ); + + goldenTest( + 'StreamMessageReactionPicker with subset defaults in $theme theme', + fileName: 'stream_reaction_picker_subset_$theme', + constraints: const BoxConstraints.tightFor(width: 400, height: 100), + builder: () { + final message = Message( + id: 'test-message', + text: 'Hello world', + user: User(id: 'test-user'), + ); + + return _wrapWithMaterialApp( + brightness: brightness, + StreamMessageReactionPicker( message: message, - reactionIcons: reactionIcons, onReactionPicked: (_) {}, ), + reactionIconResolver: const _SubsetDefaultReactionIconResolver(), ); }, ); @@ -175,12 +367,22 @@ void main() { Widget _wrapWithMaterialApp( Widget child, { Brightness? brightness, + ReactionIconResolver? reactionIconResolver, }) { return MaterialApp( debugShowCheckedModeBanner: false, - home: StreamChatTheme( - data: StreamChatThemeData(brightness: brightness), - child: Builder(builder: (context) { + theme: ThemeData(brightness: brightness), + builder: (context, child) => StreamChatConfiguration( + data: StreamChatConfigurationData( + reactionIconResolver: reactionIconResolver ?? const _TestReactionIconResolver(), + ), + child: StreamChatTheme( + data: StreamChatThemeData(brightness: brightness), + child: child ?? const SizedBox.shrink(), + ), + ), + home: Builder( + builder: (context) { final theme = StreamChatTheme.of(context); return Scaffold( backgroundColor: theme.colorTheme.overlay, @@ -191,7 +393,85 @@ Widget _wrapWithMaterialApp( ), ), ); - }), + }, ), ); } + +class _TestReactionIconResolver extends ReactionIconResolver { + const _TestReactionIconResolver(); + + static const _reactionTypes = {'like', 'haha', 'love', 'wow', 'sad'}; + + @override + Set get defaultReactions => _reactionTypes; + + @override + Set get supportedReactions => _reactionTypes; + + @override + String? emojiCode(String type) => streamSupportedEmojis[type]?.emoji; + + @override + Widget resolve(BuildContext context, String type) { + return Text(emojiCode(type) ?? type); + } +} + +class _CustomReactionIconResolver extends ReactionIconResolver { + const _CustomReactionIconResolver(this._types); + + final Set _types; + + @override + Set get defaultReactions => _types; + + @override + Set get supportedReactions => _types; + + @override + String? emojiCode(String type) => streamSupportedEmojis[type]?.emoji; + + @override + Widget resolve(BuildContext context, String type) => Text(emojiCode(type) ?? type); +} + +class _TypeBasedReactionIconResolver extends ReactionIconResolver { + const _TypeBasedReactionIconResolver(); + + @override + Set get defaultReactions => const {'customParty'}; + + @override + Set get supportedReactions => const {'customParty'}; + + @override + String? emojiCode(String type) => null; + + @override + Widget resolve(BuildContext context, String type) { + return SizedBox.square(key: Key('custom-type-$type')); + } +} + +class _SubsetDefaultReactionIconResolver extends ReactionIconResolver { + const _SubsetDefaultReactionIconResolver(); + + @override + Set get defaultReactions => const {'love'}; + + @override + Set get supportedReactions => const {'love', 'like', 'wow'}; + + @override + String? emojiCode(String type) => streamSupportedEmojis[type]?.emoji; + + @override + Widget resolve(BuildContext context, String type) { + if (emojiCode(type) case final emoji?) { + return Text(emoji); + } + + return const Text('❓'); + } +} diff --git a/packages/stream_chat_flutter/test/src/scroll_view/draft_scroll_view/goldens/ci/stream_draft_list_tile_dark.png b/packages/stream_chat_flutter/test/src/scroll_view/draft_scroll_view/goldens/ci/stream_draft_list_tile_dark.png index 84d0bbdf08..0be90f2024 100644 Binary files a/packages/stream_chat_flutter/test/src/scroll_view/draft_scroll_view/goldens/ci/stream_draft_list_tile_dark.png and b/packages/stream_chat_flutter/test/src/scroll_view/draft_scroll_view/goldens/ci/stream_draft_list_tile_dark.png differ diff --git a/packages/stream_chat_flutter/test/src/scroll_view/draft_scroll_view/goldens/ci/stream_draft_list_tile_light.png b/packages/stream_chat_flutter/test/src/scroll_view/draft_scroll_view/goldens/ci/stream_draft_list_tile_light.png index 5c2b31af7c..af3a1fd7f7 100644 Binary files a/packages/stream_chat_flutter/test/src/scroll_view/draft_scroll_view/goldens/ci/stream_draft_list_tile_light.png and b/packages/stream_chat_flutter/test/src/scroll_view/draft_scroll_view/goldens/ci/stream_draft_list_tile_light.png differ diff --git a/packages/stream_chat_flutter/test/src/scroll_view/member_scroll_view/stream_member_list_view_test.dart b/packages/stream_chat_flutter/test/src/scroll_view/member_scroll_view/stream_member_list_view_test.dart index b5275a9790..cbf70c5dad 100644 --- a/packages/stream_chat_flutter/test/src/scroll_view/member_scroll_view/stream_member_list_view_test.dart +++ b/packages/stream_chat_flutter/test/src/scroll_view/member_scroll_view/stream_member_list_view_test.dart @@ -16,17 +16,14 @@ void main() { clientState = MockClientState(); when(() => client.state).thenAnswer((_) => clientState); when(() => clientState.currentUser).thenReturn(OwnUser(id: 'testid')); - when(() => clientState.currentUserStream) - .thenAnswer((_) => Stream.value(OwnUser(id: 'testid'))); + when(() => clientState.currentUserStream).thenAnswer((_) => Stream.value(OwnUser(id: 'testid'))); channel = MockChannel(); - when(() => channel.on(any(), any(), any(), any())) - .thenAnswer((_) => const Stream.empty()); + when(() => channel.on(any(), any(), any(), any())).thenAnswer((_) => const Stream.empty()); channelClientState = MockChannelState(); when(() => channel.client).thenReturn(client); when(() => channel.state).thenReturn(channelClientState); - when(() => channelClientState.membersStream) - .thenAnswer((_) => const Stream.empty()); + when(() => channelClientState.membersStream).thenAnswer((_) => const Stream.empty()); when(() => channelClientState.members).thenReturn([]); }); diff --git a/packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/goldens/ci/stream_thread_list_tile_dark.png b/packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/goldens/ci/stream_thread_list_tile_dark.png index 4284f19d59..8de67dadfa 100644 Binary files a/packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/goldens/ci/stream_thread_list_tile_dark.png and b/packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/goldens/ci/stream_thread_list_tile_dark.png differ diff --git a/packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/goldens/ci/stream_thread_list_tile_light.png b/packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/goldens/ci/stream_thread_list_tile_light.png index cd20b7e2ff..09ff05963e 100644 Binary files a/packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/goldens/ci/stream_thread_list_tile_light.png and b/packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/goldens/ci/stream_thread_list_tile_light.png differ diff --git a/packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/goldens/ci/stream_unread_threads_banner_dark.png b/packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/goldens/ci/stream_unread_threads_banner_dark.png index bc3b26348a..cddfb16671 100644 Binary files a/packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/goldens/ci/stream_unread_threads_banner_dark.png and b/packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/goldens/ci/stream_unread_threads_banner_dark.png differ diff --git a/packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/goldens/ci/stream_unread_threads_banner_light.png b/packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/goldens/ci/stream_unread_threads_banner_light.png index 3346a781da..af50c7ee7d 100644 Binary files a/packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/goldens/ci/stream_unread_threads_banner_light.png and b/packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/goldens/ci/stream_unread_threads_banner_light.png differ diff --git a/packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/stream_unread_threads_banner_test.dart b/packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/stream_unread_threads_banner_test.dart index 897d140aed..4c1a8e7a83 100644 --- a/packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/stream_unread_threads_banner_test.dart +++ b/packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/stream_unread_threads_banner_test.dart @@ -23,13 +23,15 @@ Widget _wrapWithMaterialApp( return MaterialApp( home: StreamChatTheme( data: StreamChatThemeData(brightness: brightness), - child: Builder(builder: (context) { - final theme = StreamChatTheme.of(context); - return Scaffold( - backgroundColor: theme.colorTheme.appBg, - body: Center(child: widget), - ); - }), + child: Builder( + builder: (context) { + final theme = StreamChatTheme.of(context); + return Scaffold( + backgroundColor: theme.colorTheme.appBg, + body: Center(child: widget), + ); + }, + ), ), ); } diff --git a/packages/stream_chat_flutter/test/src/stream_chat_configuration_test.dart b/packages/stream_chat_flutter/test/src/stream_chat_configuration_test.dart index d9db650ab2..dcd94aba13 100644 --- a/packages/stream_chat_flutter/test/src/stream_chat_configuration_test.dart +++ b/packages/stream_chat_flutter/test/src/stream_chat_configuration_test.dart @@ -9,15 +9,17 @@ void main() { (t) async { final configuration = StreamChatConfigurationData(); late final StreamChatConfigurationData configurationFromProvider; - await t.pumpWidget(StreamChatConfiguration( - data: configuration, - child: Builder( - builder: (context) { - configurationFromProvider = StreamChatConfiguration.of(context); - return const SizedBox(); - }, + await t.pumpWidget( + StreamChatConfiguration( + data: configuration, + child: Builder( + builder: (context) { + configurationFromProvider = StreamChatConfiguration.of(context); + return const SizedBox(); + }, + ), ), - )); + ); expect(configuration, configurationFromProvider); }, @@ -30,15 +32,17 @@ void main() { enforceUniqueReactions: false, ); late final StreamChatConfigurationData configurationFromProvider; - await t.pumpWidget(StreamChatConfiguration( - data: configuration, - child: Builder( - builder: (context) { - configurationFromProvider = StreamChatConfiguration.of(context); - return const SizedBox(); - }, + await t.pumpWidget( + StreamChatConfiguration( + data: configuration, + child: Builder( + builder: (context) { + configurationFromProvider = StreamChatConfiguration.of(context); + return const SizedBox(); + }, + ), ), - )); + ); expect(configuration, configurationFromProvider); }, diff --git a/packages/stream_chat_flutter/test/src/theme/avatar_theme_test.dart b/packages/stream_chat_flutter/test/src/theme/avatar_theme_test.dart index 44f6f85113..bda9511ed9 100644 --- a/packages/stream_chat_flutter/test/src/theme/avatar_theme_test.dart +++ b/packages/stream_chat_flutter/test/src/theme/avatar_theme_test.dart @@ -4,18 +4,16 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; void main() { test('AvatarThemeData copyWith, ==, hashCode basics', () { - expect(const StreamAvatarThemeData(), - const StreamAvatarThemeData().copyWith()); - expect(const StreamAvatarThemeData().hashCode, - const StreamAvatarThemeData().copyWith().hashCode); + expect(const StreamAvatarThemeData(), const StreamAvatarThemeData().copyWith()); + expect(const StreamAvatarThemeData().hashCode, const StreamAvatarThemeData().copyWith().hashCode); }); group('AvatarThemeData lerps correctly', () { test('Lerp completely', () { expect( - const StreamAvatarThemeData() - .lerp(_avatarThemeDataControl1, _avatarThemeDataControl2, 1), - _avatarThemeDataControl2); + const StreamAvatarThemeData().lerp(_avatarThemeDataControl1, _avatarThemeDataControl2, 1), + _avatarThemeDataControl2, + ); }); test('Lerp halfway', () { @@ -34,8 +32,7 @@ void main() { }); test('Merging two AvatarThemeData results in the latter', () { - expect(_avatarThemeDataControl1.merge(_avatarThemeDataControl2), - _avatarThemeDataControl2); + expect(_avatarThemeDataControl1.merge(_avatarThemeDataControl2), _avatarThemeDataControl2); }); } diff --git a/packages/stream_chat_flutter/test/src/theme/channel_header_theme_test.dart b/packages/stream_chat_flutter/test/src/theme/channel_header_theme_test.dart index 1b9706154a..65cd2b5a0f 100644 --- a/packages/stream_chat_flutter/test/src/theme/channel_header_theme_test.dart +++ b/packages/stream_chat_flutter/test/src/theme/channel_header_theme_test.dart @@ -4,25 +4,19 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; void main() { test('ChannelHeaderThemeData copyWith, ==, hashCode basics', () { - expect(const StreamChannelHeaderThemeData(), - const StreamChannelHeaderThemeData().copyWith()); - expect(const StreamChannelHeaderThemeData().hashCode, - const StreamChannelHeaderThemeData().copyWith().hashCode); + expect(const StreamChannelHeaderThemeData(), const StreamChannelHeaderThemeData().copyWith()); + expect(const StreamChannelHeaderThemeData().hashCode, const StreamChannelHeaderThemeData().copyWith().hashCode); }); group('ChannelHeaderThemeData lerps', () { - test( - '''Light ChannelHeaderThemeData lerps completely to dark ChannelHeaderThemeData''', - () { + test('''Light ChannelHeaderThemeData lerps completely to dark ChannelHeaderThemeData''', () { expect( - const StreamChannelHeaderThemeData() - .lerp(_channelThemeControl, _channelThemeControlDark, 1), - _channelThemeControlDark); + const StreamChannelHeaderThemeData().lerp(_channelThemeControl, _channelThemeControlDark, 1), + _channelThemeControlDark, + ); }); - test( - '''Light ChannelHeaderThemeData lerps halfway to dark ChannelHeaderThemeData''', - () { + test('''Light ChannelHeaderThemeData lerps halfway to dark ChannelHeaderThemeData''', () { expect( const StreamChannelHeaderThemeData().lerp( _channelThemeControl, @@ -36,19 +30,16 @@ void main() { ); }); - test( - '''Dark ChannelHeaderThemeData lerps completely to light ChannelHeaderThemeData''', - () { + test('''Dark ChannelHeaderThemeData lerps completely to light ChannelHeaderThemeData''', () { expect( - const StreamChannelHeaderThemeData() - .lerp(_channelThemeControlDark, _channelThemeControl, 1), - _channelThemeControl); + const StreamChannelHeaderThemeData().lerp(_channelThemeControlDark, _channelThemeControl, 1), + _channelThemeControl, + ); }); }); test('Merging dark and light themes results in a dark theme', () { - expect(_channelThemeControl.merge(_channelThemeControlDark), - _channelThemeControlDark); + expect(_channelThemeControl.merge(_channelThemeControlDark), _channelThemeControlDark); }); } @@ -62,11 +53,11 @@ final _channelThemeControl = StreamChannelHeaderThemeData( ), color: const Color(0xff101418), titleStyle: const StreamTextTheme.light().headlineBold.copyWith( - color: const Color(0xffffffff), - ), + color: const Color(0xffffffff), + ), subtitleStyle: const StreamTextTheme.light().footnote.copyWith( - color: const Color(0xff7a7a7a), - ), + color: const Color(0xff7a7a7a), + ), ); final _channelThemeControlMidLerp = StreamChannelHeaderThemeData( @@ -84,8 +75,8 @@ final _channelThemeControlMidLerp = StreamChannelHeaderThemeData( fontSize: 16, ), subtitleStyle: const StreamTextTheme.light().footnote.copyWith( - color: const Color(0xff7a7a7a), - ), + color: const Color(0xff7a7a7a), + ), ); final _channelThemeControlDark = StreamChannelHeaderThemeData( @@ -99,6 +90,6 @@ final _channelThemeControlDark = StreamChannelHeaderThemeData( color: const StreamColorTheme.dark().barsBg, titleStyle: const StreamTextTheme.dark().headlineBold, subtitleStyle: const StreamTextTheme.dark().footnote.copyWith( - color: const Color(0xff7A7A7A), - ), + color: const Color(0xff7A7A7A), + ), ); diff --git a/packages/stream_chat_flutter/test/src/theme/channel_list_header_theme_test.dart b/packages/stream_chat_flutter/test/src/theme/channel_list_header_theme_test.dart index b7bfcbfebd..883fb441be 100644 --- a/packages/stream_chat_flutter/test/src/theme/channel_list_header_theme_test.dart +++ b/packages/stream_chat_flutter/test/src/theme/channel_list_header_theme_test.dart @@ -4,27 +4,26 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; void main() { test('ChannelListHeaderThemeData copyWith, ==, hashCode basics', () { - expect(const StreamChannelListHeaderThemeData(), - const StreamChannelListHeaderThemeData().copyWith()); - expect(const StreamChannelListHeaderThemeData().hashCode, - const StreamChannelListHeaderThemeData().copyWith().hashCode); + expect(const StreamChannelListHeaderThemeData(), const StreamChannelListHeaderThemeData().copyWith()); + expect( + const StreamChannelListHeaderThemeData().hashCode, + const StreamChannelListHeaderThemeData().copyWith().hashCode, + ); }); group('ChannelListHeaderThemeData lerps', () { - test( - '''Light ChannelListHeaderThemeData lerps completely to dark ChannelListHeaderThemeData''', - () { + test('''Light ChannelListHeaderThemeData lerps completely to dark ChannelListHeaderThemeData''', () { expect( - const StreamChannelListHeaderThemeData().lerp( - _channelListHeaderThemeControl, - _channelListHeaderThemeControlDark, - 1), - _channelListHeaderThemeControlDark); + const StreamChannelListHeaderThemeData().lerp( + _channelListHeaderThemeControl, + _channelListHeaderThemeControlDark, + 1, + ), + _channelListHeaderThemeControlDark, + ); }); - test( - '''Light ChannelListHeaderThemeData lerps halfway to dark ChannelListHeaderThemeData''', - () { + test('''Light ChannelListHeaderThemeData lerps halfway to dark ChannelListHeaderThemeData''', () { expect( const StreamChannelListHeaderThemeData().lerp( _channelListHeaderThemeControl, @@ -38,23 +37,23 @@ void main() { ); }); - test( - '''Dark ChannelListHeaderThemeData lerps completely to light ChannelListHeaderThemeData''', - () { + test('''Dark ChannelListHeaderThemeData lerps completely to light ChannelListHeaderThemeData''', () { expect( - const StreamChannelListHeaderThemeData().lerp( - _channelListHeaderThemeControlDark, - _channelListHeaderThemeControl, - 1), - _channelListHeaderThemeControl); + const StreamChannelListHeaderThemeData().lerp( + _channelListHeaderThemeControlDark, + _channelListHeaderThemeControl, + 1, + ), + _channelListHeaderThemeControl, + ); }); }); test('Merging dark and light themes results in a dark theme', () { expect( - _channelListHeaderThemeControl - .merge(_channelListHeaderThemeControlDark), - _channelListHeaderThemeControlDark); + _channelListHeaderThemeControl.merge(_channelListHeaderThemeControlDark), + _channelListHeaderThemeControlDark, + ); }); } diff --git a/packages/stream_chat_flutter/test/src/theme/channel_preview_theme_test.dart b/packages/stream_chat_flutter/test/src/theme/channel_preview_theme_test.dart index 51664dd9f7..b0fd386b2e 100644 --- a/packages/stream_chat_flutter/test/src/theme/channel_preview_theme_test.dart +++ b/packages/stream_chat_flutter/test/src/theme/channel_preview_theme_test.dart @@ -6,25 +6,19 @@ String _dummyFormatter(BuildContext context, DateTime date) => 'formatted'; void main() { test('ChannelPreviewThemeData copyWith, ==, hashCode basics', () { - expect(const StreamChannelPreviewThemeData(), - const StreamChannelPreviewThemeData().copyWith()); - expect(const StreamChannelPreviewThemeData().hashCode, - const StreamChannelPreviewThemeData().copyWith().hashCode); + expect(const StreamChannelPreviewThemeData(), const StreamChannelPreviewThemeData().copyWith()); + expect(const StreamChannelPreviewThemeData().hashCode, const StreamChannelPreviewThemeData().copyWith().hashCode); }); group('ChannelPreviewThemeData lerps', () { - test( - '''Light ChannelPreviewThemeData lerps completely to dark ChannelPreviewThemeData''', - () { + test('''Light ChannelPreviewThemeData lerps completely to dark ChannelPreviewThemeData''', () { expect( - const StreamChannelPreviewThemeData().lerp( - _channelPreviewThemeControl, _channelPreviewThemeControlDark, 1), - _channelPreviewThemeControlDark); + const StreamChannelPreviewThemeData().lerp(_channelPreviewThemeControl, _channelPreviewThemeControlDark, 1), + _channelPreviewThemeControlDark, + ); }); - test( - '''Light ChannelPreviewThemeData lerps halfway to dark ChannelPreviewThemeData''', - () { + test('''Light ChannelPreviewThemeData lerps halfway to dark ChannelPreviewThemeData''', () { expect( const StreamChannelPreviewThemeData().lerp( _channelPreviewThemeControl, @@ -38,19 +32,16 @@ void main() { ); }); - test( - '''Dark ChannelPreviewThemeData lerps completely to light ChannelPreviewThemeData''', - () { + test('''Dark ChannelPreviewThemeData lerps completely to light ChannelPreviewThemeData''', () { expect( - const StreamChannelPreviewThemeData().lerp( - _channelPreviewThemeControlDark, _channelPreviewThemeControl, 1), - _channelPreviewThemeControl); + const StreamChannelPreviewThemeData().lerp(_channelPreviewThemeControlDark, _channelPreviewThemeControl, 1), + _channelPreviewThemeControl, + ); }); }); test('Merging dark and light themes results in a dark theme', () { - expect(_channelPreviewThemeControl.merge(_channelPreviewThemeControlDark), - _channelPreviewThemeControlDark); + expect(_channelPreviewThemeControl.merge(_channelPreviewThemeControlDark), _channelPreviewThemeControlDark); }); } @@ -65,12 +56,12 @@ final _channelPreviewThemeControl = StreamChannelPreviewThemeData( ), titleStyle: const StreamTextTheme.light().bodyBold, subtitleStyle: const StreamTextTheme.light().footnote.copyWith( - color: const Color(0xff7A7A7A), - ), + color: const Color(0xff7A7A7A), + ), lastMessageAtStyle: const StreamTextTheme.light().footnote.copyWith( - // ignore: deprecated_member_use - color: const StreamColorTheme.light().textHighEmphasis.withOpacity(0.5), - ), + // ignore: deprecated_member_use + color: const StreamColorTheme.light().textHighEmphasis.withOpacity(0.5), + ), lastMessageAtFormatter: _dummyFormatter, indicatorIconSize: 16, ); @@ -95,9 +86,9 @@ final _channelPreviewThemeControlMidLerp = StreamChannelPreviewThemeData( fontWeight: FontWeight.w400, ), lastMessageAtStyle: const StreamTextTheme.light().footnote.copyWith( - // ignore: deprecated_member_use - color: const Color(0x807f7f7f).withOpacity(0.5), - ), + // ignore: deprecated_member_use + color: const Color(0x807f7f7f).withOpacity(0.5), + ), lastMessageAtFormatter: _dummyFormatter, indicatorIconSize: 16, ); @@ -113,12 +104,12 @@ final _channelPreviewThemeControlDark = StreamChannelPreviewThemeData( ), titleStyle: const StreamTextTheme.dark().bodyBold, subtitleStyle: const StreamTextTheme.dark().footnote.copyWith( - color: const Color(0xff7A7A7A), - ), + color: const Color(0xff7A7A7A), + ), lastMessageAtStyle: const StreamTextTheme.dark().footnote.copyWith( - // ignore: deprecated_member_use - color: const StreamColorTheme.dark().textHighEmphasis.withOpacity(0.5), - ), + // ignore: deprecated_member_use + color: const StreamColorTheme.dark().textHighEmphasis.withOpacity(0.5), + ), lastMessageAtFormatter: _dummyFormatter, indicatorIconSize: 16, ); diff --git a/packages/stream_chat_flutter/test/src/theme/draft_list_tile_theme_test.dart b/packages/stream_chat_flutter/test/src/theme/draft_list_tile_theme_test.dart index e7f91b0fba..2f9e4c939b 100644 --- a/packages/stream_chat_flutter/test/src/theme/draft_list_tile_theme_test.dart +++ b/packages/stream_chat_flutter/test/src/theme/draft_list_tile_theme_test.dart @@ -5,8 +5,7 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; String _dummyFormatter(BuildContext context, DateTime date) => 'formatted'; void main() { - testWidgets('StreamDraftListTileTheme merges with ancestor theme', - (tester) async { + testWidgets('StreamDraftListTileTheme merges with ancestor theme', (tester) async { const backgroundColor = Colors.blue; const childBackgroundColor = Colors.red; @@ -164,14 +163,14 @@ void main() { // t = 0.5 should return something in between final lerpedAt05 = data1.lerp(data1, data2, 0.5); - expect(lerpedAt05.backgroundColor, - Color.lerp(Colors.black, Colors.white, 0.5)); + expect(lerpedAt05.backgroundColor, Color.lerp(Colors.black, Colors.white, 0.5)); expect( - lerpedAt05.padding, - EdgeInsetsGeometry.lerp( - const EdgeInsets.all(8), - const EdgeInsets.all(16), - 0.5, - )); + lerpedAt05.padding, + EdgeInsetsGeometry.lerp( + const EdgeInsets.all(8), + const EdgeInsets.all(16), + 0.5, + ), + ); }); } diff --git a/packages/stream_chat_flutter/test/src/theme/gallery_footer_theme_test.dart b/packages/stream_chat_flutter/test/src/theme/gallery_footer_theme_test.dart index c02d77cccc..fb0c5699a0 100644 --- a/packages/stream_chat_flutter/test/src/theme/gallery_footer_theme_test.dart +++ b/packages/stream_chat_flutter/test/src/theme/gallery_footer_theme_test.dart @@ -7,26 +7,18 @@ class MockStreamChatClient extends Mock implements StreamChatClient {} void main() { test('GalleryFooterThemeData copyWith, ==, hashCode basics', () { - expect(const StreamGalleryFooterThemeData(), - const StreamGalleryFooterThemeData().copyWith()); - expect(const StreamGalleryFooterThemeData().hashCode, - const StreamGalleryFooterThemeData().copyWith().hashCode); + expect(const StreamGalleryFooterThemeData(), const StreamGalleryFooterThemeData().copyWith()); + expect(const StreamGalleryFooterThemeData().hashCode, const StreamGalleryFooterThemeData().copyWith().hashCode); }); - test( - '''Light GalleryFooterThemeData lerps completely to dark GalleryFooterThemeData''', - () { + test('''Light GalleryFooterThemeData lerps completely to dark GalleryFooterThemeData''', () { expect( - const StreamGalleryFooterThemeData().lerp( - _galleryFooterThemeDataControl, - _galleryFooterThemeDataControlDark, - 1), - _galleryFooterThemeDataControlDark); + const StreamGalleryFooterThemeData().lerp(_galleryFooterThemeDataControl, _galleryFooterThemeDataControlDark, 1), + _galleryFooterThemeDataControlDark, + ); }); - test( - '''Light GalleryFooterThemeData lerps halfway to dark GalleryFooterThemeData''', - () { + test('''Light GalleryFooterThemeData lerps halfway to dark GalleryFooterThemeData''', () { expect( const StreamGalleryFooterThemeData().lerp( _galleryFooterThemeDataControl, @@ -40,34 +32,25 @@ void main() { ); }); - test( - '''Dark GalleryFooterThemeData lerps completely to light GalleryFooterThemeData''', - () { + test('''Dark GalleryFooterThemeData lerps completely to light GalleryFooterThemeData''', () { expect( - const StreamGalleryFooterThemeData().lerp( - _galleryFooterThemeDataControlDark, - _galleryFooterThemeDataControl, - 1), - _galleryFooterThemeDataControl); + const StreamGalleryFooterThemeData().lerp(_galleryFooterThemeDataControlDark, _galleryFooterThemeDataControl, 1), + _galleryFooterThemeDataControl, + ); }); test('Merging dark and light themes results in a dark theme', () { expect( - _galleryFooterThemeDataControl - .merge(_galleryFooterThemeDataControlDark), - _galleryFooterThemeDataControlDark); + _galleryFooterThemeDataControl.merge(_galleryFooterThemeDataControlDark), + _galleryFooterThemeDataControlDark, + ); }); test('Merging dark and light themes results in a dark theme', () { - expect( - _galleryFooterThemeDataControlDark - .merge(_galleryFooterThemeDataControl), - _galleryFooterThemeDataControl); + expect(_galleryFooterThemeDataControlDark.merge(_galleryFooterThemeDataControl), _galleryFooterThemeDataControl); }); - testWidgets( - 'Passing no GalleryFooterThemeData returns default light theme values', - (WidgetTester tester) async { + testWidgets('Passing no GalleryFooterThemeData returns default light theme values', (WidgetTester tester) async { late BuildContext _context; await tester.pumpWidget( MaterialApp( @@ -85,27 +68,17 @@ void main() { ); final imageFooterTheme = StreamGalleryFooterTheme.of(_context); - expect(imageFooterTheme.backgroundColor, - _galleryFooterThemeDataControl.backgroundColor); - expect(imageFooterTheme.shareIconColor, - _galleryFooterThemeDataControl.shareIconColor); - expect(imageFooterTheme.titleTextStyle, - _galleryFooterThemeDataControl.titleTextStyle); - expect(imageFooterTheme.gridIconButtonColor, - _galleryFooterThemeDataControl.gridIconButtonColor); - expect(imageFooterTheme.bottomSheetBarrierColor, - _galleryFooterThemeDataControl.bottomSheetBarrierColor); - expect(imageFooterTheme.bottomSheetBackgroundColor, - _galleryFooterThemeDataControl.bottomSheetBackgroundColor); - expect(imageFooterTheme.bottomSheetCloseIconColor, - _galleryFooterThemeDataControl.bottomSheetCloseIconColor); - expect(imageFooterTheme.bottomSheetPhotosTextStyle, - _galleryFooterThemeDataControl.bottomSheetPhotosTextStyle); + expect(imageFooterTheme.backgroundColor, _galleryFooterThemeDataControl.backgroundColor); + expect(imageFooterTheme.shareIconColor, _galleryFooterThemeDataControl.shareIconColor); + expect(imageFooterTheme.titleTextStyle, _galleryFooterThemeDataControl.titleTextStyle); + expect(imageFooterTheme.gridIconButtonColor, _galleryFooterThemeDataControl.gridIconButtonColor); + expect(imageFooterTheme.bottomSheetBarrierColor, _galleryFooterThemeDataControl.bottomSheetBarrierColor); + expect(imageFooterTheme.bottomSheetBackgroundColor, _galleryFooterThemeDataControl.bottomSheetBackgroundColor); + expect(imageFooterTheme.bottomSheetCloseIconColor, _galleryFooterThemeDataControl.bottomSheetCloseIconColor); + expect(imageFooterTheme.bottomSheetPhotosTextStyle, _galleryFooterThemeDataControl.bottomSheetPhotosTextStyle); }); - testWidgets( - 'Passing no GalleryFooterThemeData returns default dark theme values', - (WidgetTester tester) async { + testWidgets('Passing no GalleryFooterThemeData returns default dark theme values', (WidgetTester tester) async { late BuildContext _context; await tester.pumpWidget( MaterialApp( @@ -124,22 +97,14 @@ void main() { ); final imageFooterTheme = StreamGalleryFooterTheme.of(_context); - expect(imageFooterTheme.backgroundColor, - _galleryFooterThemeDataControlDark.backgroundColor); - expect(imageFooterTheme.shareIconColor, - _galleryFooterThemeDataControlDark.shareIconColor); - expect(imageFooterTheme.titleTextStyle, - _galleryFooterThemeDataControlDark.titleTextStyle); - expect(imageFooterTheme.gridIconButtonColor, - _galleryFooterThemeDataControlDark.gridIconButtonColor); - expect(imageFooterTheme.bottomSheetBarrierColor, - _galleryFooterThemeDataControlDark.bottomSheetBarrierColor); - expect(imageFooterTheme.bottomSheetBackgroundColor, - _galleryFooterThemeDataControlDark.bottomSheetBackgroundColor); - expect(imageFooterTheme.bottomSheetCloseIconColor, - _galleryFooterThemeDataControlDark.bottomSheetCloseIconColor); - expect(imageFooterTheme.bottomSheetPhotosTextStyle, - _galleryFooterThemeDataControlDark.bottomSheetPhotosTextStyle); + expect(imageFooterTheme.backgroundColor, _galleryFooterThemeDataControlDark.backgroundColor); + expect(imageFooterTheme.shareIconColor, _galleryFooterThemeDataControlDark.shareIconColor); + expect(imageFooterTheme.titleTextStyle, _galleryFooterThemeDataControlDark.titleTextStyle); + expect(imageFooterTheme.gridIconButtonColor, _galleryFooterThemeDataControlDark.gridIconButtonColor); + expect(imageFooterTheme.bottomSheetBarrierColor, _galleryFooterThemeDataControlDark.bottomSheetBarrierColor); + expect(imageFooterTheme.bottomSheetBackgroundColor, _galleryFooterThemeDataControlDark.bottomSheetBackgroundColor); + expect(imageFooterTheme.bottomSheetCloseIconColor, _galleryFooterThemeDataControlDark.bottomSheetCloseIconColor); + expect(imageFooterTheme.bottomSheetPhotosTextStyle, _galleryFooterThemeDataControlDark.bottomSheetPhotosTextStyle); }); } diff --git a/packages/stream_chat_flutter/test/src/theme/gallery_header_theme_test.dart b/packages/stream_chat_flutter/test/src/theme/gallery_header_theme_test.dart index 6c080193ed..f3bd223539 100644 --- a/packages/stream_chat_flutter/test/src/theme/gallery_header_theme_test.dart +++ b/packages/stream_chat_flutter/test/src/theme/gallery_header_theme_test.dart @@ -7,26 +7,18 @@ class MockStreamChatClient extends Mock implements StreamChatClient {} void main() { test('GalleryHeaderThemeData copyWith, ==, hashCode basics', () { - expect(const StreamGalleryHeaderThemeData(), - const StreamGalleryHeaderThemeData().copyWith()); - expect(const StreamGalleryHeaderThemeData().hashCode, - const StreamGalleryHeaderThemeData().copyWith().hashCode); + expect(const StreamGalleryHeaderThemeData(), const StreamGalleryHeaderThemeData().copyWith()); + expect(const StreamGalleryHeaderThemeData().hashCode, const StreamGalleryHeaderThemeData().copyWith().hashCode); }); - test( - '''Light GalleryHeaderThemeData lerps completely to dark GalleryHeaderThemeData''', - () { + test('''Light GalleryHeaderThemeData lerps completely to dark GalleryHeaderThemeData''', () { expect( - const StreamGalleryHeaderThemeData().lerp( - _galleryHeaderThemeDataControl, - _galleryHeaderThemeDataDarkControl, - 1), - _galleryHeaderThemeDataDarkControl); + const StreamGalleryHeaderThemeData().lerp(_galleryHeaderThemeDataControl, _galleryHeaderThemeDataDarkControl, 1), + _galleryHeaderThemeDataDarkControl, + ); }); - test( - '''Light GalleryHeaderThemeData lerps halfway to dark GalleryHeaderThemeData''', - () { + test('''Light GalleryHeaderThemeData lerps halfway to dark GalleryHeaderThemeData''', () { expect( const StreamGalleryHeaderThemeData().lerp( _galleryHeaderThemeDataControl, @@ -40,27 +32,21 @@ void main() { ); }); - test( - '''Dark GalleryHeaderThemeData lerps completely to light GalleryHeaderThemeData''', - () { + test('''Dark GalleryHeaderThemeData lerps completely to light GalleryHeaderThemeData''', () { expect( - const StreamGalleryHeaderThemeData().lerp( - _galleryHeaderThemeDataDarkControl, - _galleryHeaderThemeDataControl, - 1), - _galleryHeaderThemeDataControl); + const StreamGalleryHeaderThemeData().lerp(_galleryHeaderThemeDataDarkControl, _galleryHeaderThemeDataControl, 1), + _galleryHeaderThemeDataControl, + ); }); test('Merging dark and light themes results in a dark theme', () { expect( - _galleryHeaderThemeDataControl - .merge(_galleryHeaderThemeDataDarkControl), - _galleryHeaderThemeDataDarkControl); + _galleryHeaderThemeDataControl.merge(_galleryHeaderThemeDataDarkControl), + _galleryHeaderThemeDataDarkControl, + ); }); - testWidgets( - 'Passing no GalleryHeaderThemeData returns default light theme values', - (WidgetTester tester) async { + testWidgets('Passing no GalleryHeaderThemeData returns default light theme values', (WidgetTester tester) async { late BuildContext _context; await tester.pumpWidget( MaterialApp( @@ -78,23 +64,15 @@ void main() { ); final imageHeaderTheme = StreamGalleryHeaderTheme.of(_context); - expect(imageHeaderTheme.closeButtonColor, - _galleryHeaderThemeDataControl.closeButtonColor); - expect(imageHeaderTheme.backgroundColor, - _galleryHeaderThemeDataControl.backgroundColor); - expect(imageHeaderTheme.iconMenuPointColor, - _galleryHeaderThemeDataControl.iconMenuPointColor); - expect(imageHeaderTheme.titleTextStyle, - _galleryHeaderThemeDataControl.titleTextStyle); - expect(imageHeaderTheme.subtitleTextStyle, - _galleryHeaderThemeDataControl.subtitleTextStyle); - expect(imageHeaderTheme.bottomSheetBarrierColor, - _galleryHeaderThemeDataControl.bottomSheetBarrierColor); + expect(imageHeaderTheme.closeButtonColor, _galleryHeaderThemeDataControl.closeButtonColor); + expect(imageHeaderTheme.backgroundColor, _galleryHeaderThemeDataControl.backgroundColor); + expect(imageHeaderTheme.iconMenuPointColor, _galleryHeaderThemeDataControl.iconMenuPointColor); + expect(imageHeaderTheme.titleTextStyle, _galleryHeaderThemeDataControl.titleTextStyle); + expect(imageHeaderTheme.subtitleTextStyle, _galleryHeaderThemeDataControl.subtitleTextStyle); + expect(imageHeaderTheme.bottomSheetBarrierColor, _galleryHeaderThemeDataControl.bottomSheetBarrierColor); }); - testWidgets( - 'Passing no GalleryHeaderThemeData returns default dark theme values', - (WidgetTester tester) async { + testWidgets('Passing no GalleryHeaderThemeData returns default dark theme values', (WidgetTester tester) async { late BuildContext _context; await tester.pumpWidget( MaterialApp( @@ -113,18 +91,12 @@ void main() { ); final imageHeaderTheme = StreamGalleryHeaderTheme.of(_context); - expect(imageHeaderTheme.closeButtonColor, - _galleryHeaderThemeDataDarkControl.closeButtonColor); - expect(imageHeaderTheme.backgroundColor, - _galleryHeaderThemeDataDarkControl.backgroundColor); - expect(imageHeaderTheme.iconMenuPointColor, - _galleryHeaderThemeDataDarkControl.iconMenuPointColor); - expect(imageHeaderTheme.titleTextStyle, - _galleryHeaderThemeDataDarkControl.titleTextStyle); - expect(imageHeaderTheme.subtitleTextStyle, - _galleryHeaderThemeDataDarkControl.subtitleTextStyle); - expect(imageHeaderTheme.bottomSheetBarrierColor, - _galleryHeaderThemeDataDarkControl.bottomSheetBarrierColor); + expect(imageHeaderTheme.closeButtonColor, _galleryHeaderThemeDataDarkControl.closeButtonColor); + expect(imageHeaderTheme.backgroundColor, _galleryHeaderThemeDataDarkControl.backgroundColor); + expect(imageHeaderTheme.iconMenuPointColor, _galleryHeaderThemeDataDarkControl.iconMenuPointColor); + expect(imageHeaderTheme.titleTextStyle, _galleryHeaderThemeDataDarkControl.titleTextStyle); + expect(imageHeaderTheme.subtitleTextStyle, _galleryHeaderThemeDataDarkControl.subtitleTextStyle); + expect(imageHeaderTheme.bottomSheetBarrierColor, _galleryHeaderThemeDataDarkControl.bottomSheetBarrierColor); }); } @@ -138,13 +110,14 @@ final _galleryHeaderThemeDataControl = StreamGalleryHeaderThemeData( fontWeight: FontWeight.w500, color: Colors.black, ), - subtitleTextStyle: const TextStyle( - fontSize: 12, - color: Colors.black, - fontWeight: FontWeight.w400, - ).copyWith( - color: const Color(0xff7A7A7A), - ), + subtitleTextStyle: + const TextStyle( + fontSize: 12, + color: Colors.black, + fontWeight: FontWeight.w400, + ).copyWith( + color: const Color(0xff7A7A7A), + ), bottomSheetBarrierColor: const Color.fromRGBO(0, 0, 0, 0.2), ); @@ -158,13 +131,14 @@ final _galleryHeaderThemeDataHalfLerpControl = StreamGalleryHeaderThemeData( fontWeight: FontWeight.w500, color: Color(0xff7f7f7f), ), - subtitleTextStyle: const TextStyle( - fontSize: 12, - color: Color(0xff7a7a7a), - fontWeight: FontWeight.w400, - ).copyWith( - color: const Color(0xff7A7A7A), - ), + subtitleTextStyle: + const TextStyle( + fontSize: 12, + color: Color(0xff7a7a7a), + fontWeight: FontWeight.w400, + ).copyWith( + color: const Color(0xff7A7A7A), + ), bottomSheetBarrierColor: const Color(0x4c000000), ); @@ -178,12 +152,13 @@ final _galleryHeaderThemeDataDarkControl = StreamGalleryHeaderThemeData( fontWeight: FontWeight.w500, color: Colors.white, ), - subtitleTextStyle: const TextStyle( - fontSize: 12, - color: Colors.white, - fontWeight: FontWeight.w400, - ).copyWith( - color: const Color(0xff7A7A7A), - ), + subtitleTextStyle: + const TextStyle( + fontSize: 12, + color: Colors.white, + fontWeight: FontWeight.w400, + ).copyWith( + color: const Color(0xff7A7A7A), + ), bottomSheetBarrierColor: const Color.fromRGBO(0, 0, 0, 0.4), ); diff --git a/packages/stream_chat_flutter/test/src/theme/message_input_theme_test.dart b/packages/stream_chat_flutter/test/src/theme/message_input_theme_test.dart index 431a113b0d..96cb7fd979 100644 --- a/packages/stream_chat_flutter/test/src/theme/message_input_theme_test.dart +++ b/packages/stream_chat_flutter/test/src/theme/message_input_theme_test.dart @@ -4,18 +4,16 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; void main() { test('MessageInputThemeData copyWith, ==, hashCode basics', () { - expect(const StreamMessageInputThemeData(), - const StreamMessageInputThemeData().copyWith()); - expect(const StreamMessageInputThemeData().hashCode, - const StreamMessageInputThemeData().copyWith().hashCode); + expect(const StreamMessageInputThemeData(), const StreamMessageInputThemeData().copyWith()); + expect(const StreamMessageInputThemeData().hashCode, const StreamMessageInputThemeData().copyWith().hashCode); }); group('MessageInputThemeData lerps correctly', () { test('Lerp completely from light to dark', () { expect( - const StreamMessageInputThemeData().lerp( - _messageInputThemeControl, _messageInputThemeControlDark, 1), - _messageInputThemeControlDark); + const StreamMessageInputThemeData().lerp(_messageInputThemeControl, _messageInputThemeControlDark, 1), + _messageInputThemeControlDark, + ); }); test('Lerp halfway from light to dark', () { @@ -34,15 +32,14 @@ void main() { test('Lerp completely from dark to light', () { expect( - const StreamMessageInputThemeData().lerp( - _messageInputThemeControlDark, _messageInputThemeControl, 1), - _messageInputThemeControl); + const StreamMessageInputThemeData().lerp(_messageInputThemeControlDark, _messageInputThemeControl, 1), + _messageInputThemeControl, + ); }); }); test('Merging two MessageInputThemeData results in the latter', () { - expect(_messageInputThemeControl.merge(_messageInputThemeControlDark), - _messageInputThemeControlDark); + expect(_messageInputThemeControl.merge(_messageInputThemeControlDark), _messageInputThemeControlDark); }); } diff --git a/packages/stream_chat_flutter/test/src/theme/message_list_view_theme_test.dart b/packages/stream_chat_flutter/test/src/theme/message_list_view_theme_test.dart index 8123029912..b1e8f4842b 100644 --- a/packages/stream_chat_flutter/test/src/theme/message_list_view_theme_test.dart +++ b/packages/stream_chat_flutter/test/src/theme/message_list_view_theme_test.dart @@ -9,26 +9,22 @@ class MockStreamChatClient extends Mock implements StreamChatClient {} void main() { test('MessageListViewThemeData copyWith, ==, hashCode basics', () { - expect(const StreamMessageListViewThemeData(), - const StreamMessageListViewThemeData().copyWith()); - expect(const StreamMessageListViewThemeData().hashCode, - const StreamMessageListViewThemeData().copyWith().hashCode); + expect(const StreamMessageListViewThemeData(), const StreamMessageListViewThemeData().copyWith()); + expect(const StreamMessageListViewThemeData().hashCode, const StreamMessageListViewThemeData().copyWith().hashCode); }); - test( - '''Light MessageListViewThemeData lerps completely to dark MessageListViewThemeData''', - () { + test('''Light MessageListViewThemeData lerps completely to dark MessageListViewThemeData''', () { expect( - const StreamMessageListViewThemeData().lerp( - _messageListViewThemeDataControl, - _messageListViewThemeDataControlDark, - 1), - _messageListViewThemeDataControlDark); + const StreamMessageListViewThemeData().lerp( + _messageListViewThemeDataControl, + _messageListViewThemeDataControlDark, + 1, + ), + _messageListViewThemeDataControlDark, + ); }); - test( - '''Light MessageListViewThemeData lerps halfway to dark MessageListViewThemeData''', - () { + test('''Light MessageListViewThemeData lerps halfway to dark MessageListViewThemeData''', () { expect( const StreamMessageListViewThemeData().lerp( _messageListViewThemeDataControl, @@ -42,27 +38,25 @@ void main() { ); }); - test( - '''Dark MessageListViewThemeData lerps completely to light MessageListViewThemeData''', - () { + test('''Dark MessageListViewThemeData lerps completely to light MessageListViewThemeData''', () { expect( - const StreamMessageListViewThemeData().lerp( - _messageListViewThemeDataControlDark, - _messageListViewThemeDataControl, - 1), - _messageListViewThemeDataControl); + const StreamMessageListViewThemeData().lerp( + _messageListViewThemeDataControlDark, + _messageListViewThemeDataControl, + 1, + ), + _messageListViewThemeDataControl, + ); }); test('Merging dark and light themes results in a dark theme', () { expect( - _messageListViewThemeDataControl - .merge(_messageListViewThemeDataControlDark), - _messageListViewThemeDataControlDark); + _messageListViewThemeDataControl.merge(_messageListViewThemeDataControlDark), + _messageListViewThemeDataControlDark, + ); }); - testWidgets( - 'Passing no MessageListViewThemeData returns default light theme values', - (WidgetTester tester) async { + testWidgets('Passing no MessageListViewThemeData returns default light theme values', (WidgetTester tester) async { late BuildContext _context; await tester.pumpWidget( MaterialApp( @@ -80,13 +74,10 @@ void main() { ); final messageListViewTheme = StreamMessageListViewTheme.of(_context); - expect(messageListViewTheme.backgroundColor, - _messageListViewThemeDataControl.backgroundColor); + expect(messageListViewTheme.backgroundColor, _messageListViewThemeDataControl.backgroundColor); }); - testWidgets( - 'Passing no MessageListViewThemeData returns default dark theme values', - (WidgetTester tester) async { + testWidgets('Passing no MessageListViewThemeData returns default dark theme values', (WidgetTester tester) async { late BuildContext _context; await tester.pumpWidget( MaterialApp( @@ -105,20 +96,18 @@ void main() { ); final messageListViewTheme = StreamMessageListViewTheme.of(_context); - expect(messageListViewTheme.backgroundColor, - _messageListViewThemeDataControlDark.backgroundColor); + expect(messageListViewTheme.backgroundColor, _messageListViewThemeDataControlDark.backgroundColor); }); - testWidgets( - 'Pass backgroundImage to MessageListViewThemeData return backgroundImage', - (WidgetTester tester) async { + testWidgets('Pass backgroundImage to MessageListViewThemeData return backgroundImage', (WidgetTester tester) async { late BuildContext _context; await tester.pumpWidget( MaterialApp( builder: (context, child) => StreamChat( client: MockStreamChatClient(), - streamChatThemeData: StreamChatThemeData.light() - .copyWith(messageListViewTheme: _messageListViewThemeDataImage), + streamChatThemeData: StreamChatThemeData.light().copyWith( + messageListViewTheme: _messageListViewThemeDataImage, + ), child: child, ), home: Builder( @@ -136,8 +125,7 @@ void main() { ); final messageListViewTheme = StreamMessageListViewTheme.of(_context); - expect(messageListViewTheme.backgroundImage, - _messageListViewThemeDataImage.backgroundImage); + expect(messageListViewTheme.backgroundImage, _messageListViewThemeDataImage.backgroundImage); }); } diff --git a/packages/stream_chat_flutter/test/src/theme/message_theme_test.dart b/packages/stream_chat_flutter/test/src/theme/message_theme_test.dart index a7cb68df2f..d8759b2be3 100644 --- a/packages/stream_chat_flutter/test/src/theme/message_theme_test.dart +++ b/packages/stream_chat_flutter/test/src/theme/message_theme_test.dart @@ -6,48 +6,43 @@ String _dummyFormatter(BuildContext context, DateTime date) => 'formatted'; void main() { test('MessageThemeData copyWith, ==, hashCode basics', () { - expect(const StreamMessageThemeData(), - const StreamMessageThemeData().copyWith()); - expect(const StreamMessageThemeData().hashCode, - const StreamMessageThemeData().copyWith().hashCode); + expect(const StreamMessageThemeData(), const StreamMessageThemeData().copyWith()); + expect(const StreamMessageThemeData().hashCode, const StreamMessageThemeData().copyWith().hashCode); }); group('MessageThemeData lerps', () { - test('''Light MessageThemeData lerps completely to dark MessageThemeData''', - () { + test('''Light MessageThemeData lerps completely to dark MessageThemeData''', () { expect( - const StreamMessageThemeData() - .lerp(_messageThemeControl, _messageThemeControlDark, 1), - _messageThemeControlDark); + const StreamMessageThemeData().lerp(_messageThemeControl, _messageThemeControlDark, 1), + _messageThemeControlDark, + ); }); - test('''Dark MessageThemeData lerps completely to light MessageThemeData''', - () { + test('''Dark MessageThemeData lerps completely to light MessageThemeData''', () { expect( - const StreamMessageThemeData() - .lerp(_messageThemeControlDark, _messageThemeControl, 1), - _messageThemeControl); + const StreamMessageThemeData().lerp(_messageThemeControlDark, _messageThemeControl, 1), + _messageThemeControl, + ); }); }); test('Merging dark and light themes results in a dark theme', () { - expect(_messageThemeControl.merge(_messageThemeControlDark), - _messageThemeControlDark); + expect(_messageThemeControl.merge(_messageThemeControlDark), _messageThemeControlDark); }); } final _messageThemeControl = StreamMessageThemeData( messageAuthorStyle: const StreamTextTheme.light().footnote.copyWith( - color: const StreamColorTheme.light().textLowEmphasis, - ), + color: const StreamColorTheme.light().textLowEmphasis, + ), messageTextStyle: const StreamTextTheme.light().body, createdAtStyle: const StreamTextTheme.light().footnote.copyWith( - color: const StreamColorTheme.light().textLowEmphasis, - ), + color: const StreamColorTheme.light().textLowEmphasis, + ), createdAtFormatter: _dummyFormatter, repliesStyle: const StreamTextTheme.light().footnoteBold.copyWith( - color: const StreamColorTheme.light().accentPrimary, - ), + color: const StreamColorTheme.light().accentPrimary, + ), messageBackgroundColor: const StreamColorTheme.light().disabled, reactionsBackgroundColor: const StreamColorTheme.light().barsBg, reactionsBorderColor: const StreamColorTheme.light().borders, @@ -68,16 +63,16 @@ final _messageThemeControl = StreamMessageThemeData( final _messageThemeControlDark = StreamMessageThemeData( messageAuthorStyle: const StreamTextTheme.dark().footnote.copyWith( - color: const StreamColorTheme.dark().textLowEmphasis, - ), + color: const StreamColorTheme.dark().textLowEmphasis, + ), messageTextStyle: const StreamTextTheme.dark().body, createdAtStyle: const StreamTextTheme.dark().footnote.copyWith( - color: const StreamColorTheme.dark().textLowEmphasis, - ), + color: const StreamColorTheme.dark().textLowEmphasis, + ), createdAtFormatter: _dummyFormatter, repliesStyle: const StreamTextTheme.dark().footnoteBold.copyWith( - color: const StreamColorTheme.dark().accentPrimary, - ), + color: const StreamColorTheme.dark().accentPrimary, + ), messageBackgroundColor: const StreamColorTheme.dark().disabled, reactionsBackgroundColor: const StreamColorTheme.dark().barsBg, reactionsBorderColor: const StreamColorTheme.dark().borders, diff --git a/packages/stream_chat_flutter/test/src/theme/thread_list_tile_theme_test.dart b/packages/stream_chat_flutter/test/src/theme/thread_list_tile_theme_test.dart index dd857151f6..d4c38dece2 100644 --- a/packages/stream_chat_flutter/test/src/theme/thread_list_tile_theme_test.dart +++ b/packages/stream_chat_flutter/test/src/theme/thread_list_tile_theme_test.dart @@ -5,8 +5,7 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; String _dummyFormatter(BuildContext context, DateTime date) => 'formatted'; void main() { - testWidgets('StreamThreadListTileTheme merges with ancestor theme', - (tester) async { + testWidgets('StreamThreadListTileTheme merges with ancestor theme', (tester) async { const backgroundColor = Colors.blue; const childBackgroundColor = Colors.red; @@ -116,12 +115,9 @@ void main() { expect(copied.padding, newPadding); // Unchanged properties should remain the same expect(copied.threadChannelNameStyle, original.threadChannelNameStyle); - expect( - copied.threadReplyToMessageStyle, original.threadReplyToMessageStyle); - expect(copied.threadLatestReplyTimestampStyle, - original.threadLatestReplyTimestampStyle); - expect(copied.threadLatestReplyTimestampFormatter, - original.threadLatestReplyTimestampFormatter); + expect(copied.threadReplyToMessageStyle, original.threadReplyToMessageStyle); + expect(copied.threadLatestReplyTimestampStyle, original.threadLatestReplyTimestampStyle); + expect(copied.threadLatestReplyTimestampFormatter, original.threadLatestReplyTimestampFormatter); }); test('StreamThreadListTileThemeData merge', () { @@ -147,12 +143,9 @@ void main() { expect(merged.padding, other.padding); // Null properties in 'other' should not override 'original' expect(merged.threadChannelNameStyle, original.threadChannelNameStyle); - expect( - merged.threadReplyToMessageStyle, original.threadReplyToMessageStyle); - expect(merged.threadLatestReplyTimestampStyle, - original.threadLatestReplyTimestampStyle); - expect(merged.threadLatestReplyTimestampFormatter, - original.threadLatestReplyTimestampFormatter); + expect(merged.threadReplyToMessageStyle, original.threadReplyToMessageStyle); + expect(merged.threadLatestReplyTimestampStyle, original.threadLatestReplyTimestampStyle); + expect(merged.threadLatestReplyTimestampFormatter, original.threadLatestReplyTimestampFormatter); // Merging with null should return original final mergedWithNull = original.merge(null); @@ -176,29 +169,26 @@ void main() { final lerpedAt0 = data1.lerp(data1, data2, 0); expect(lerpedAt0.backgroundColor, data1.backgroundColor); expect(lerpedAt0.padding, data1.padding); - expect(lerpedAt0.threadLatestReplyTimestampFormatter, - data1.threadLatestReplyTimestampFormatter); + expect(lerpedAt0.threadLatestReplyTimestampFormatter, data1.threadLatestReplyTimestampFormatter); // t = 1 should return data2 final lerpedAt1 = data1.lerp(data1, data2, 1); expect(lerpedAt1.backgroundColor, data2.backgroundColor); expect(lerpedAt1.padding, data2.padding); - expect(lerpedAt1.threadLatestReplyTimestampFormatter, - data2.threadLatestReplyTimestampFormatter); + expect(lerpedAt1.threadLatestReplyTimestampFormatter, data2.threadLatestReplyTimestampFormatter); // t = 0.5 should return something in between final lerpedAt05 = data1.lerp(data1, data2, 0.5); - expect(lerpedAt05.backgroundColor, - Color.lerp(Colors.black, Colors.white, 0.5)); + expect(lerpedAt05.backgroundColor, Color.lerp(Colors.black, Colors.white, 0.5)); expect( - lerpedAt05.padding, - EdgeInsetsGeometry.lerp( - const EdgeInsets.all(8), - const EdgeInsets.all(16), - 0.5, - )); + lerpedAt05.padding, + EdgeInsetsGeometry.lerp( + const EdgeInsets.all(8), + const EdgeInsets.all(16), + 0.5, + ), + ); // For t < 0.5, should use data1's formatter - expect(lerpedAt05.threadLatestReplyTimestampFormatter, - data1.threadLatestReplyTimestampFormatter); + expect(lerpedAt05.threadLatestReplyTimestampFormatter, data1.threadLatestReplyTimestampFormatter); }); } diff --git a/packages/stream_chat_flutter/test/src/utils/extension_test.dart b/packages/stream_chat_flutter/test/src/utils/extension_test.dart index badab2e9d9..77953c1bd3 100644 --- a/packages/stream_chat_flutter/test/src/utils/extension_test.dart +++ b/packages/stream_chat_flutter/test/src/utils/extension_test.dart @@ -325,8 +325,7 @@ void main() { ); }); - test('should handle both userId and userName with special characters', - () { + test('should handle both userId and userName with special characters', () { final user = User(id: 'user[123]', name: 'Test (X)'); final message = Message( diff --git a/packages/stream_chat_flutter/test/src/utils/stream_image_cdn_test.dart b/packages/stream_chat_flutter/test/src/utils/stream_image_cdn_test.dart new file mode 100644 index 0000000000..967ba5a9ed --- /dev/null +++ b/packages/stream_chat_flutter/test/src/utils/stream_image_cdn_test.dart @@ -0,0 +1,237 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:stream_chat_flutter/src/utils/stream_image_cdn.dart'; + +void main() { + const cdn = StreamImageCDN(); + + group('StreamImageCDN.resolveUrl', () { + group('Stream CDN URLs', () { + test('returns unchanged URL when resize is null', () { + const url = + 'https://us-east.stream-io-cdn.com/102400/images/photo.jpg' + '?Policy=abc&Signature=xyz&Key-Pair-Id=123'; + + expect(cdn.resolveUrl(url), equals(url)); + }); + + test('adds resize params when none exist', () { + const url = 'https://us-east.stream-io-cdn.com/102400/images/photo.jpg'; + const resize = ImageResize(width: 200, height: 300); + + final result = cdn.resolveUrl(url, resize: resize); + + expect(result, contains('w=200')); + expect(result, contains('h=300')); + expect(result, contains('resize=clip')); + expect(result, contains('ro=0')); + expect(result, isNot(contains('crop='))); + }); + + test('includes crop param only when mode is crop', () { + const url = 'https://us-east.stream-io-cdn.com/102400/images/photo.jpg'; + const resize = ImageResize( + width: 400, + height: 400, + mode: ResizeMode.crop, + crop: CropMode.top, + ); + + final result = cdn.resolveUrl(url, resize: resize); + + expect(result, contains('resize=crop')); + expect(result, contains('crop=top')); + expect(result, contains('ro=0')); + }); + + test('does not include crop param when mode is not crop', () { + const url = 'https://us-east.stream-io-cdn.com/102400/images/photo.jpg'; + + for (final mode in [ + ResizeMode.clip, + ResizeMode.scale, + ResizeMode.fill, + ]) { + final result = cdn.resolveUrl( + url, + resize: ImageResize(width: 200, height: 200, mode: mode), + ); + + expect( + result, + isNot(contains('crop=')), + reason: 'crop should not be present for mode ${mode.value}', + ); + } + }); + + test('always overrides existing resize params', () { + const url = + 'https://us-east.stream-io-cdn.com/102400/images/photo.jpg' + '?w=100&h=100&resize=fill'; + const resize = ImageResize( + width: 200, + height: 300, + mode: ResizeMode.crop, + crop: CropMode.left, + ); + + final result = cdn.resolveUrl(url, resize: resize); + + expect(result, contains('w=200')); + expect(result, contains('h=300')); + expect(result, contains('resize=crop')); + expect(result, contains('crop=left')); + }); + + test('preserves existing non-resize query parameters', () { + const url = + 'https://us-east.stream-io-cdn.com/102400/images/photo.jpg' + '?Policy=abc&Signature=xyz&Key-Pair-Id=123'; + const resize = ImageResize(width: 200, height: 300); + + final result = cdn.resolveUrl(url, resize: resize); + + expect(result, contains('Policy=abc')); + expect(result, contains('Signature=xyz')); + expect(result, contains('Key-Pair-Id=123')); + expect(result, contains('w=200')); + }); + + test('floors fractional dimensions', () { + const url = 'https://us-east.stream-io-cdn.com/102400/images/photo.jpg'; + const resize = ImageResize(width: 199.7, height: 300.3); + + final result = cdn.resolveUrl(url, resize: resize); + + expect(result, contains('w=199')); + expect(result, contains('h=300')); + }); + + test('uses wildcard for zero dimensions', () { + const url = 'https://us-east.stream-io-cdn.com/102400/images/photo.jpg'; + const resize = ImageResize(width: 0, height: 300); + + final result = cdn.resolveUrl(url, resize: resize); + + expect(result, contains('w=%2A')); + expect(result, contains('h=300')); + }); + }); + + group('non-Stream URLs', () { + test('returns URL unchanged regardless of resize', () { + const url = 'https://example.com/photo.jpg'; + const resize = ImageResize(width: 200, height: 300); + + expect(cdn.resolveUrl(url, resize: resize), equals(url)); + }); + + test('returns URL unchanged when resize is null', () { + const url = 'https://example.com/photo.jpg?token=abc'; + + expect(cdn.resolveUrl(url), equals(url)); + }); + }); + }); + + group('StreamImageCDN.cacheKey', () { + group('Stream CDN URLs', () { + test('strips signing parameters', () { + const url = + 'https://us-east.stream-io-cdn.com/102400/images/photo.jpg' + '?Key-Pair-Id=APKAIHG&Policy=eyJTdGF0&Signature=OeMK5' + '&w=200&h=300&resize=clip&crop=center'; + + final key = cdn.cacheKey(url); + + expect(key, contains('w=200')); + expect(key, contains('h=300')); + expect(key, contains('resize=clip')); + expect(key, contains('crop=center')); + expect(key, isNot(contains('Key-Pair-Id'))); + expect(key, isNot(contains('Policy'))); + expect(key, isNot(contains('Signature'))); + }); + + test('returns URL path only when no resize params exist', () { + const url = + 'https://us-east.stream-io-cdn.com/102400/images/photo.jpg' + '?Key-Pair-Id=APKAIHG&Policy=eyJTdGF0&Signature=OeMK5'; + + final key = cdn.cacheKey(url); + + expect(key, isNot(contains('Key-Pair-Id'))); + expect(key, isNot(contains('Policy'))); + expect(key, isNot(contains('Signature'))); + expect( + key, + 'https://us-east.stream-io-cdn.com/102400/images/photo.jpg?', + ); + }); + + test('produces same key for same image with different signatures', () { + const url1 = + 'https://us-east.stream-io-cdn.com/102400/images/photo.jpg' + '?Key-Pair-Id=APKAIHG&Policy=policy1&Signature=sig1' + '&w=200&h=300'; + const url2 = + 'https://us-east.stream-io-cdn.com/102400/images/photo.jpg' + '?Key-Pair-Id=APKAIHG&Policy=policy2&Signature=sig2' + '&w=200&h=300'; + + expect(cdn.cacheKey(url1), equals(cdn.cacheKey(url2))); + }); + + test('produces different keys for different resize dimensions', () { + const url1 = + 'https://us-east.stream-io-cdn.com/102400/images/photo.jpg' + '?w=200&h=300'; + const url2 = + 'https://us-east.stream-io-cdn.com/102400/images/photo.jpg' + '?w=400&h=600'; + + expect(cdn.cacheKey(url1), isNot(equals(cdn.cacheKey(url2)))); + }); + + test('strips oh and ow parameters', () { + const url = + 'https://us-east.stream-io-cdn.com/102400/images/photo.jpg' + '?oh=4032&ow=3024&w=200&h=300'; + + final key = cdn.cacheKey(url); + + expect(key, isNot(contains('oh='))); + expect(key, isNot(contains('ow='))); + expect(key, contains('w=200')); + expect(key, contains('h=300')); + }); + }); + + group('non-Stream URLs', () { + test('returns full URL string unchanged', () { + const url = 'https://example.com/photo.jpg?token=abc'; + + expect(cdn.cacheKey(url), equals(url)); + }); + }); + }); + + group('ResizeMode', () { + test('all modes have correct string values', () { + expect(ResizeMode.clip.value, 'clip'); + expect(ResizeMode.crop.value, 'crop'); + expect(ResizeMode.scale.value, 'scale'); + expect(ResizeMode.fill.value, 'fill'); + }); + }); + + group('CropMode', () { + test('all modes have correct string values', () { + expect(CropMode.center.value, 'center'); + expect(CropMode.top.value, 'top'); + expect(CropMode.bottom.value, 'bottom'); + expect(CropMode.left.value, 'left'); + expect(CropMode.right.value, 'right'); + }); + }); +} diff --git a/packages/stream_chat_flutter/test/test_utils/data_generator.dart b/packages/stream_chat_flutter/test/test_utils/data_generator.dart index 5759136ba6..94f32285e3 100644 --- a/packages/stream_chat_flutter/test/test_utils/data_generator.dart +++ b/packages/stream_chat_flutter/test/test_utils/data_generator.dart @@ -8,9 +8,10 @@ List generateConversation( int unreadCount = 0, }) { assert( - users == null || noOfUsers == null, - 'Only one of users or noOfUsers ' - 'should be provided'); + users == null || noOfUsers == null, + 'Only one of users or noOfUsers ' + 'should be provided', + ); assert(count > 0, 'Count should be greater than 0'); assert(count > unreadCount, 'Count should be greater than unreadCount'); @@ -38,8 +39,7 @@ List generateConversation( id: faker.datatype.uuid(), text: faker.lorem.sentence(), user: user, - createdAt: - DateTime.now().subtract(Duration(minutes: i + count - unreadCount)), + createdAt: DateTime.now().subtract(Duration(minutes: i + count - unreadCount)), ), ); } diff --git a/packages/stream_chat_flutter_core/example/lib/main.dart b/packages/stream_chat_flutter_core/example/lib/main.dart index 0a523f81ac..40392a8c19 100644 --- a/packages/stream_chat_flutter_core/example/lib/main.dart +++ b/packages/stream_chat_flutter_core/example/lib/main.dart @@ -19,11 +19,7 @@ Future main() async { '''eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiY29vbC1zaGFkb3ctNyJ9.gkOlCRb1qgy4joHPaxFwPOdXcGvSPvp6QY0S4mpRkVo''', ); - runApp( - StreamExample( - client: client, - ), - ); + runApp(StreamExample(client: client)); } /// Example application using Stream Chat core widgets. @@ -37,10 +33,7 @@ class StreamExample extends StatelessWidget { /// /// If you'd prefer using pre-made UI widgets for your app, please see our /// other package, `stream_chat_flutter`. - const StreamExample({ - Key? key, - required this.client, - }) : super(key: key); + const StreamExample({Key? key, required this.client}) : super(key: key); /// Instance of Stream Client. /// Stream's [StreamChatClient] can be used to connect to our servers and @@ -50,13 +43,10 @@ class StreamExample extends StatelessWidget { @override Widget build(BuildContext context) => MaterialApp( - title: 'Stream Chat Core Example', - home: HomeScreen(), - builder: (context, child) => StreamChatCore( - client: client, - child: child!, - ), - ); + title: 'Stream Chat Core Example', + home: HomeScreen(), + builder: (context, child) => StreamChatCore(client: client, child: child!), + ); } /// Basic layout displaying a list of [Channel]s the user is a part of. @@ -80,12 +70,7 @@ class _HomeScreenState extends State { client: StreamChatCore.of(context).client, filter: Filter.and([ Filter.equal('type', 'messaging'), - Filter.in_( - 'members', - [ - StreamChatCore.of(context).currentUser!.id, - ], - ), + Filter.in_('members', [StreamChatCore.of(context).currentUser!.id]), ]), ); @@ -103,91 +88,89 @@ class _HomeScreenState extends State { @override Widget build(BuildContext context) => Scaffold( - appBar: AppBar( - title: const Text('Channels'), - ), - body: PagedValueListenableBuilder( - valueListenable: channelListController, - builder: (context, value, child) { - return value.when( - (channels, nextPageKey, error) => LazyLoadScrollView( - onEndOfPage: () async { - if (nextPageKey != null) { - channelListController.loadMore(nextPageKey); + appBar: AppBar(title: const Text('Channels')), + body: PagedValueListenableBuilder( + valueListenable: channelListController, + builder: (context, value, child) { + return value.when( + (channels, nextPageKey, error) => LazyLoadScrollView( + onEndOfPage: () async { + if (nextPageKey != null) { + channelListController.loadMore(nextPageKey); + } + }, + child: ListView.builder( + /// We're using the channels length when there are no more + /// pages to load and there are no errors with pagination. + /// In case we need to show a loading indicator or and error + /// tile we're increasing the count by 1. + itemCount: (nextPageKey != null || error != null) + ? channels.length + 1 + : channels.length, + itemBuilder: (BuildContext context, int index) { + if (index == channels.length) { + if (error != null) { + return TextButton( + onPressed: () { + channelListController.retry(); + }, + child: Text(error.message), + ); } - }, - child: ListView.builder( - /// We're using the channels length when there are no more - /// pages to load and there are no errors with pagination. - /// In case we need to show a loading indicator or and error - /// tile we're increasing the count by 1. - itemCount: (nextPageKey != null || error != null) - ? channels.length + 1 - : channels.length, - itemBuilder: (BuildContext context, int index) { - if (index == channels.length) { - if (error != null) { - return TextButton( - onPressed: () { - channelListController.retry(); - }, - child: Text(error.message), - ); - } - return CircularProgressIndicator(); - } + return CircularProgressIndicator(); + } - final _item = channels[index]; - return ListTile( - title: Text(_item.name ?? ''), - subtitle: StreamBuilder( - stream: _item.state!.lastMessageStream, - initialData: _item.state!.lastMessage, - builder: (context, snapshot) { - if (snapshot.hasData) { - return Text(snapshot.data!.text!); - } + final _item = channels[index]; + return ListTile( + title: Text(_item.name ?? ''), + subtitle: StreamBuilder( + stream: _item.state!.lastMessageStream, + initialData: _item.state!.lastMessage, + builder: (context, snapshot) { + if (snapshot.hasData) { + return Text(snapshot.data!.text!); + } - return const SizedBox(); - }, + return const SizedBox(); + }, + ), + onTap: () { + /// Display a list of messages when the user taps on + /// an item. We can use [StreamChannel] to wrap our + /// [MessageScreen] screen with the selected channel. + /// + /// This allows us to use a built-in inherited widget + /// for accessing our `channel` later on. + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => StreamChannel( + channel: _item, + child: const MessageScreen(), + ), ), - onTap: () { - /// Display a list of messages when the user taps on - /// an item. We can use [StreamChannel] to wrap our - /// [MessageScreen] screen with the selected channel. - /// - /// This allows us to use a built-in inherited widget - /// for accessing our `channel` later on. - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => StreamChannel( - channel: _item, - child: const MessageScreen(), - ), - ), - ); - }, ); }, - ), - ), - loading: () => const Center( - child: SizedBox( - height: 100, - width: 100, - child: CircularProgressIndicator(), - ), - ), - error: (e) => Center( - child: Text( - 'Oh no, something went wrong. ' - 'Please check your config. $e', - ), - ), - ); - }, - ), - ); + ); + }, + ), + ), + loading: () => const Center( + child: SizedBox( + height: 100, + width: 100, + child: CircularProgressIndicator(), + ), + ), + error: (e) => Center( + child: Text( + 'Oh no, something went wrong. ' + 'Please check your config. $e', + ), + ), + ); + }, + ), + ); } /// A list of messages sent in the current channel. @@ -259,9 +242,8 @@ class _MessageScreenState extends State { }, child: MessageListCore( messageListController: messageListController, - emptyBuilder: (BuildContext context) => const Center( - child: Text('Nothing here yet'), - ), + emptyBuilder: (BuildContext context) => + const Center(child: Text('Nothing here yet')), loadingBuilder: (BuildContext context) => const Center( child: SizedBox( height: 100, @@ -269,44 +251,43 @@ class _MessageScreenState extends State { child: CircularProgressIndicator(), ), ), - messageListBuilder: ( - BuildContext context, - List messages, - ) => - ListView.builder( - controller: _scrollController, - itemCount: messages.length, - reverse: true, - itemBuilder: (BuildContext context, int index) { - final item = messages[index]; - final client = StreamChatCore.of(context).client; - if (item.user!.id == client.uid) { - return Align( - alignment: Alignment.centerRight, - child: Padding( - padding: const EdgeInsets.all(8), - child: Text(item.text!), + messageListBuilder: + (BuildContext context, List messages) => + ListView.builder( + controller: _scrollController, + itemCount: messages.length, + reverse: true, + itemBuilder: (BuildContext context, int index) { + final item = messages[index]; + final client = StreamChatCore.of(context).client; + if (item.user!.id == client.uid) { + return Align( + alignment: Alignment.centerRight, + child: Padding( + padding: const EdgeInsets.all(8), + child: Text(item.text!), + ), + ); + } else { + return Align( + alignment: Alignment.centerLeft, + child: Padding( + padding: const EdgeInsets.all(8), + child: Text(item.text!), + ), + ); + } + }, ), - ); - } else { - return Align( - alignment: Alignment.centerLeft, - child: Padding( - padding: const EdgeInsets.all(8), - child: Text(item.text!), - ), - ); - } - }, - ), errorBuilder: (BuildContext context, error) { print(error.toString()); return const Center( child: SizedBox( height: 100, width: 100, - child: - Text('Oh no, an error occured. Please see logs.'), + child: Text( + 'Oh no, an error occured. Please see logs.', + ), ), ); }, @@ -344,10 +325,7 @@ class _MessageScreenState extends State { child: const Padding( padding: EdgeInsets.all(8), child: Center( - child: Icon( - Icons.send, - color: Colors.white, - ), + child: Icon(Icons.send, color: Colors.white), ), ), ), diff --git a/packages/stream_chat_flutter_core/example/pubspec.yaml b/packages/stream_chat_flutter_core/example/pubspec.yaml index c2821a10ae..95fdf094f7 100644 --- a/packages/stream_chat_flutter_core/example/pubspec.yaml +++ b/packages/stream_chat_flutter_core/example/pubspec.yaml @@ -16,8 +16,8 @@ version: 1.0.0+1 # 2. Add it to the melos.yaml file for future updates. environment: - sdk: ^3.6.2 - flutter: ">=3.27.4" + sdk: ^3.10.0 + flutter: ">=3.38.1" dependencies: cupertino_icons: ^1.0.3 diff --git a/packages/stream_chat_flutter_core/lib/src/better_stream_builder.dart b/packages/stream_chat_flutter_core/lib/src/better_stream_builder.dart index 6305224b61..22344c81ef 100644 --- a/packages/stream_chat_flutter_core/lib/src/better_stream_builder.dart +++ b/packages/stream_chat_flutter_core/lib/src/better_stream_builder.dart @@ -40,8 +40,7 @@ class BetterStreamBuilder extends StatefulWidget { _BetterStreamBuilderState createState() => _BetterStreamBuilderState(); } -class _BetterStreamBuilderState - extends State> { +class _BetterStreamBuilderState extends State> { T? _lastEvent; StreamSubscription? _subscription; Object? _lastError; @@ -102,8 +101,7 @@ class _BetterStreamBuilderState void _onEvent(T? event) { _lastError = null; - final isEqual = - widget.comparator?.call(_lastEvent, event) ?? event == _lastEvent; + final isEqual = widget.comparator?.call(_lastEvent, event) ?? event == _lastEvent; if (!isEqual) { _lastEvent = event; if (mounted) { diff --git a/packages/stream_chat_flutter_core/lib/src/lazy_load_scroll_view.dart b/packages/stream_chat_flutter_core/lib/src/lazy_load_scroll_view.dart index d56d5f239a..1495a94c9b 100644 --- a/packages/stream_chat_flutter_core/lib/src/lazy_load_scroll_view.dart +++ b/packages/stream_chat_flutter_core/lib/src/lazy_load_scroll_view.dart @@ -53,11 +53,10 @@ class _LazyLoadScrollViewState extends State { double _scrollPosition = 0; @override - Widget build(BuildContext context) => - NotificationListener( - onNotification: _onNotification, - child: widget.child, - ); + Widget build(BuildContext context) => NotificationListener( + onNotification: _onNotification, + child: widget.child, + ); bool _onNotification(ScrollNotification notification) { if (notification is ScrollStartNotification) { @@ -78,8 +77,7 @@ class _LazyLoadScrollViewState extends State { final minScrollExtent = notification.metrics.minScrollExtent; final scrollOffset = widget.scrollOffset; - if (pixels > (minScrollExtent + scrollOffset) && - pixels < (maxScrollExtent - scrollOffset)) { + if (pixels > (minScrollExtent + scrollOffset) && pixels < (maxScrollExtent - scrollOffset)) { if (widget.onInBetweenOfPage != null) { widget.onInBetweenOfPage!(); return !widget.allowNotificationBubbling; diff --git a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart index 44b3c2619c..7a9b3d6c0e 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart @@ -9,12 +9,11 @@ import 'package:stream_chat_flutter_core/src/stream_channel.dart'; import 'package:stream_chat_flutter_core/src/typedef.dart'; /// Default filter for the message list -bool Function(Message) defaultMessageFilter(String currentUserId) => - (Message m) { - final isMyMessage = m.user?.id == currentUserId; - if (m.shadowed && !isMyMessage) return false; - return true; - }; +bool Function(Message) defaultMessageFilter(String currentUserId) => (Message m) { + final isMyMessage = m.user?.id == currentUserId; + if (m.shadowed && !isMyMessage) return false; + return true; +}; /// [MessageListCore] is a simplified class that allows fetching a list of /// messages while exposing UI builders. @@ -131,8 +130,8 @@ class MessageListCoreState extends State { Widget build(BuildContext context) { final messagesStream = _isThreadConversation ? _streamChannel!.channel.state?.threadsStream - .where((threads) => threads.containsKey(widget.parentMessage!.id)) - .map((threads) => threads[widget.parentMessage!.id]) + .where((threads) => threads.containsKey(widget.parentMessage!.id)) + .map((threads) => threads[widget.parentMessage!.id]) : _streamChannel!.channel.state?.messagesStream; final initialData = _isThreadConversation diff --git a/packages/stream_chat_flutter_core/lib/src/message_text_field_controller.dart b/packages/stream_chat_flutter_core/lib/src/message_text_field_controller.dart index ae730c06b8..f485b75c5a 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_text_field_controller.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_text_field_controller.dart @@ -1,10 +1,11 @@ import 'package:flutter/material.dart'; /// A function that takes a [BuildContext] and returns a [TextStyle]. -typedef TextStyleBuilder = TextStyle? Function( - BuildContext context, - String text, -); +typedef TextStyleBuilder = + TextStyle? Function( + BuildContext context, + String text, + ); /// Controller for the [StreamTextField] widget. class MessageTextFieldController extends TextEditingController { diff --git a/packages/stream_chat_flutter_core/lib/src/paged_value_notifier.dart b/packages/stream_chat_flutter_core/lib/src/paged_value_notifier.dart index 83643a64d0..38bfa98756 100644 --- a/packages/stream_chat_flutter_core/lib/src/paged_value_notifier.dart +++ b/packages/stream_chat_flutter_core/lib/src/paged_value_notifier.dart @@ -8,8 +8,7 @@ part 'paged_value_notifier.freezed.dart'; const defaultInitialPagedLimitMultiplier = 3; /// Value listenable for paged data. -typedef PagedValueListenableBuilder - = ValueListenableBuilder>; +typedef PagedValueListenableBuilder = ValueListenableBuilder>; /// A [PagedValueNotifier] that uses a [PagedListenable] to load data. /// @@ -19,8 +18,7 @@ typedef PagedValueListenableBuilder /// /// [PagedValueNotifier] is a [ValueNotifier] that emits a [PagedValue] /// whenever the data is loaded or an error occurs. -abstract class PagedValueNotifier - extends ValueNotifier> { +abstract class PagedValueNotifier extends ValueNotifier> { /// Creates a [PagedValueNotifier] PagedValueNotifier(this._initialValue) : super(_initialValue); @@ -148,17 +146,18 @@ extension PagedValuePatternMatching on PagedValue { List items, Key? nextPageKey, StreamChatError? error, - ) success, { + ) + success, { required TResult Function() loading, required TResult Function(StreamChatError error) error, }) { final pagedValue = this; return switch (pagedValue) { Success() => success( - pagedValue.items, - pagedValue.nextPageKey, - pagedValue.error, - ), + pagedValue.items, + pagedValue.nextPageKey, + pagedValue.error, + ), Loading() => loading(), Error() => error(pagedValue.error), }; @@ -171,17 +170,18 @@ extension PagedValuePatternMatching on PagedValue { List items, Key? nextPageKey, StreamChatError? error, - )? success, { + )? + success, { TResult? Function()? loading, TResult? Function(StreamChatError error)? error, }) { final pagedValue = this; return switch (pagedValue) { Success() => success?.call( - pagedValue.items, - pagedValue.nextPageKey, - pagedValue.error, - ), + pagedValue.items, + pagedValue.nextPageKey, + pagedValue.error, + ), Loading() => loading?.call(), Error() => error?.call(pagedValue.error), }; @@ -194,7 +194,8 @@ extension PagedValuePatternMatching on PagedValue { List items, Key? nextPageKey, StreamChatError? error, - )? success, { + )? + success, { TResult Function()? loading, TResult Function(StreamChatError error)? error, required TResult orElse(), @@ -202,10 +203,10 @@ extension PagedValuePatternMatching on PagedValue { final pagedValue = this; final result = switch (pagedValue) { Success() => success?.call( - pagedValue.items, - pagedValue.nextPageKey, - pagedValue.error, - ), + pagedValue.items, + pagedValue.nextPageKey, + pagedValue.error, + ), Loading() => loading?.call(), Error() => error?.call(pagedValue.error), }; diff --git a/packages/stream_chat_flutter_core/lib/src/paged_value_scroll_view.dart b/packages/stream_chat_flutter_core/lib/src/paged_value_scroll_view.dart index a078f471c7..ce60e68bb0 100644 --- a/packages/stream_chat_flutter_core/lib/src/paged_value_scroll_view.dart +++ b/packages/stream_chat_flutter_core/lib/src/paged_value_scroll_view.dart @@ -5,18 +5,20 @@ import 'package:stream_chat_flutter_core/src/paged_value_notifier.dart'; /// Signature for a function that creates a widget for a given index, e.g., in a /// [PagedValueListView] and [PagedValueGridView]. -typedef PagedValueScrollViewIndexedWidgetBuilder = Widget Function( - BuildContext context, - List values, - int index, -); +typedef PagedValueScrollViewIndexedWidgetBuilder = + Widget Function( + BuildContext context, + List values, + int index, + ); /// Signature for the item builder that creates the children of the /// [PagedValueListView] and [PagedValueGridView]. -typedef PagedValueScrollViewLoadMoreErrorBuilder = Widget Function( - BuildContext context, - StreamChatError error, -); +typedef PagedValueScrollViewLoadMoreErrorBuilder = + Widget Function( + BuildContext context, + StreamChatError error, + ); /// A [ListView] that loads more pages when the user scrolls to the end of the /// list. @@ -260,8 +262,7 @@ class PagedValueListView extends StatefulWidget { final Clip clipBehavior; @override - State> createState() => - _PagedValueListViewState(); + State> createState() => _PagedValueListViewState(); } class _PagedValueListViewState extends State> { @@ -288,65 +289,62 @@ class _PagedValueListViewState extends State> { @override Widget build(BuildContext context) => PagedValueListenableBuilder( - valueListenable: _controller, - builder: (context, value, _) => value.when( - (items, nextPageKey, error) { - if (items.isEmpty) { - return widget.emptyBuilder(context); - } - - return ListView.separated( - scrollDirection: widget.scrollDirection, - padding: widget.padding, - physics: widget.physics, - reverse: widget.reverse, - controller: widget.scrollController, - primary: widget.primary, - shrinkWrap: widget.shrinkWrap, - addAutomaticKeepAlives: widget.addAutomaticKeepAlives, - addRepaintBoundaries: widget.addRepaintBoundaries, - addSemanticIndexes: widget.addSemanticIndexes, - keyboardDismissBehavior: widget.keyboardDismissBehavior, - restorationId: widget.restorationId, - dragStartBehavior: widget.dragStartBehavior, - cacheExtent: widget.cacheExtent, - clipBehavior: widget.clipBehavior, - itemCount: value.itemCount, - separatorBuilder: (context, index) => - widget.separatorBuilder(context, items, index), - itemBuilder: (context, index) { - if (!_hasRequestedNextPage) { - final newPageRequestTriggerIndex = - items.length - widget.loadMoreTriggerIndex; - final isBuildingTriggerIndexItem = - index == newPageRequestTriggerIndex; - if (nextPageKey != null && isBuildingTriggerIndexItem) { - // Schedules the request for the end of this frame. - WidgetsBinding.instance.addPostFrameCallback((_) async { - if (error == null) { - await _controller.loadMore(nextPageKey); - } - _hasRequestedNextPage = false; - }); - _hasRequestedNextPage = true; + valueListenable: _controller, + builder: (context, value, _) => value.when( + (items, nextPageKey, error) { + if (items.isEmpty) { + return widget.emptyBuilder(context); + } + + return ListView.separated( + scrollDirection: widget.scrollDirection, + padding: widget.padding, + physics: widget.physics, + reverse: widget.reverse, + controller: widget.scrollController, + primary: widget.primary, + shrinkWrap: widget.shrinkWrap, + addAutomaticKeepAlives: widget.addAutomaticKeepAlives, + addRepaintBoundaries: widget.addRepaintBoundaries, + addSemanticIndexes: widget.addSemanticIndexes, + keyboardDismissBehavior: widget.keyboardDismissBehavior, + restorationId: widget.restorationId, + dragStartBehavior: widget.dragStartBehavior, + cacheExtent: widget.cacheExtent, + clipBehavior: widget.clipBehavior, + itemCount: value.itemCount, + separatorBuilder: (context, index) => widget.separatorBuilder(context, items, index), + itemBuilder: (context, index) { + if (!_hasRequestedNextPage) { + final newPageRequestTriggerIndex = items.length - widget.loadMoreTriggerIndex; + final isBuildingTriggerIndexItem = index == newPageRequestTriggerIndex; + if (nextPageKey != null && isBuildingTriggerIndexItem) { + // Schedules the request for the end of this frame. + WidgetsBinding.instance.addPostFrameCallback((_) async { + if (error == null) { + await _controller.loadMore(nextPageKey); } - } + _hasRequestedNextPage = false; + }); + _hasRequestedNextPage = true; + } + } - if (index == items.length) { - if (error != null) { - return widget.loadMoreErrorBuilder(context, error); - } - return widget.loadMoreIndicatorBuilder(context); - } + if (index == items.length) { + if (error != null) { + return widget.loadMoreErrorBuilder(context, error); + } + return widget.loadMoreIndicatorBuilder(context); + } - return widget.itemBuilder(context, items, index); - }, - ); + return widget.itemBuilder(context, items, index); }, - loading: () => widget.loadingBuilder(context), - error: (error) => widget.errorBuilder(context, error), - ), - ); + ); + }, + loading: () => widget.loadingBuilder(context), + error: (error) => widget.errorBuilder(context, error), + ), + ); } /// A [GridView] that loads more pages when the user scrolls to the end of the @@ -367,6 +365,7 @@ class PagedValueGridView extends StatefulWidget { required this.loadingBuilder, required this.errorBuilder, this.loadMoreTriggerIndex = 3, + this.leadingItemBuilder, this.scrollDirection = Axis.vertical, this.reverse = false, this.scrollController, @@ -415,6 +414,13 @@ class PagedValueGridView extends StatefulWidget { /// The index to take into account when triggering [controller.loadMore]. final int loadMoreTriggerIndex; + /// An optional builder for a single item prepended before the paged items. + /// + /// When provided, [itemBuilder] still receives regular item indices starting + /// at 0 — the leading item is handled separately, similar to + /// [loadMoreIndicatorBuilder]. + final WidgetBuilder? leadingItemBuilder; + /// {@template flutter.widgets.scroll_view.scrollDirection} /// The axis along which the scroll view scrolls. /// @@ -616,8 +622,7 @@ class PagedValueGridView extends StatefulWidget { final Clip clipBehavior; @override - State> createState() => - _PagedValueGridViewState(); + State> createState() => _PagedValueGridViewState(); } class _PagedValueGridViewState extends State> { @@ -644,63 +649,66 @@ class _PagedValueGridViewState extends State> { @override Widget build(BuildContext context) => PagedValueListenableBuilder( - valueListenable: _controller, - builder: (context, value, _) => value.when( - (items, nextPageKey, error) { - if (items.isEmpty) { - return widget.emptyBuilder(context); + valueListenable: _controller, + builder: (context, value, _) => value.when( + (items, nextPageKey, error) { + if (items.isEmpty) { + return widget.emptyBuilder(context); + } + + return GridView.builder( + scrollDirection: widget.scrollDirection, + reverse: widget.reverse, + controller: widget.scrollController, + primary: widget.primary, + physics: widget.physics, + shrinkWrap: widget.shrinkWrap, + padding: widget.padding, + addAutomaticKeepAlives: widget.addAutomaticKeepAlives, + addRepaintBoundaries: widget.addRepaintBoundaries, + addSemanticIndexes: widget.addSemanticIndexes, + cacheExtent: widget.cacheExtent, + semanticChildCount: widget.semanticChildCount, + dragStartBehavior: widget.dragStartBehavior, + keyboardDismissBehavior: widget.keyboardDismissBehavior, + restorationId: widget.restorationId, + clipBehavior: widget.clipBehavior, + itemCount: value.itemCount + (widget.leadingItemBuilder != null ? 1 : 0), + gridDelegate: widget.gridDelegate, + itemBuilder: (context, index) { + var adjustedIndex = index; + if (widget.leadingItemBuilder != null) { + if (index == 0) return widget.leadingItemBuilder!(context); + adjustedIndex = index - 1; } - return GridView.builder( - scrollDirection: widget.scrollDirection, - reverse: widget.reverse, - controller: widget.scrollController, - primary: widget.primary, - physics: widget.physics, - shrinkWrap: widget.shrinkWrap, - padding: widget.padding, - addAutomaticKeepAlives: widget.addAutomaticKeepAlives, - addRepaintBoundaries: widget.addRepaintBoundaries, - addSemanticIndexes: widget.addSemanticIndexes, - cacheExtent: widget.cacheExtent, - semanticChildCount: widget.semanticChildCount, - dragStartBehavior: widget.dragStartBehavior, - keyboardDismissBehavior: widget.keyboardDismissBehavior, - restorationId: widget.restorationId, - clipBehavior: widget.clipBehavior, - itemCount: value.itemCount, - gridDelegate: widget.gridDelegate, - itemBuilder: (context, index) { - if (!_hasRequestedNextPage) { - final newPageRequestTriggerIndex = - items.length - widget.loadMoreTriggerIndex; - final isBuildingTriggerIndexItem = - index == newPageRequestTriggerIndex; - if (nextPageKey != null && isBuildingTriggerIndexItem) { - // Schedules the request for the end of this frame. - WidgetsBinding.instance.addPostFrameCallback((_) async { - if (error == null) { - await _controller.loadMore(nextPageKey); - } - _hasRequestedNextPage = false; - }); - _hasRequestedNextPage = true; + if (!_hasRequestedNextPage) { + final newPageRequestTriggerIndex = items.length - widget.loadMoreTriggerIndex; + if (nextPageKey != null && adjustedIndex == newPageRequestTriggerIndex) { + // Schedules the request for the end of this frame. + WidgetsBinding.instance.addPostFrameCallback((_) async { + if (error == null) { + await _controller.loadMore(nextPageKey); } - } + _hasRequestedNextPage = false; + }); + _hasRequestedNextPage = true; + } + } - if (index == items.length) { - if (error != null) { - return widget.loadMoreErrorBuilder(context, error); - } - return widget.loadMoreIndicatorBuilder(context); - } + if (adjustedIndex == items.length) { + if (error != null) { + return widget.loadMoreErrorBuilder(context, error); + } + return widget.loadMoreIndicatorBuilder(context); + } - return widget.itemBuilder(context, items, index); - }, - ); + return widget.itemBuilder(context, items, adjustedIndex); }, - loading: () => widget.loadingBuilder(context), - error: (error) => widget.errorBuilder(context, error), - ), - ); + ); + }, + loading: () => widget.loadingBuilder(context), + error: (error) => widget.errorBuilder(context, error), + ), + ); } diff --git a/packages/stream_chat_flutter_core/lib/src/stream_channel.dart b/packages/stream_chat_flutter_core/lib/src/stream_channel.dart index 77ef984bc9..ee6edd0c2b 100644 --- a/packages/stream_chat_flutter_core/lib/src/stream_channel.dart +++ b/packages/stream_chat_flutter_core/lib/src/stream_channel.dart @@ -17,11 +17,12 @@ enum QueryDirection { /// Signature used by [StreamChannel.errorBuilder] to create a replacement /// widget for an error that occurs while asynchronously building the channel. // TODO: Remove once ErrorBuilder supports passing stacktrace. -typedef ErrorWidgetBuilder = Widget Function( - BuildContext context, - Object error, - StackTrace? stackTrace, -); +typedef ErrorWidgetBuilder = + Widget Function( + BuildContext context, + Object error, + StackTrace? stackTrace, + ); Color _getDefaultBackgroundColor(BuildContext context) { final brightness = Theme.of(context).brightness; @@ -95,8 +96,7 @@ class StreamChannel extends StatefulWidget { color: backgroundColor, child: Center( child: switch (exception) { - DioException(type: DioExceptionType.badResponse) => - Text(exception.message ?? 'Bad response'), + DioException(type: DioExceptionType.badResponse) => Text(exception.message ?? 'Bad response'), DioException() => const Text('Check your connection and retry'), _ => Text(exception.toString()), }, @@ -180,8 +180,7 @@ class StreamChannelState extends State { String? get initialMessageId => widget.initialMessageId; /// Current channel state stream - Stream? get channelStateStream => - widget.channel.state?.channelStateStream; + Stream? get channelStateStream => widget.channel.state?.channelStateStream; final _queryTopMessagesController = BehaviorSubject.seeded(false); final _queryBottomMessagesController = BehaviorSubject.seeded(false); @@ -427,31 +426,30 @@ class StreamChannelState extends State { String? messageId, { int limit = 30, bool preferOffline = false, - }) => - _queryAtMessage( - messageId: messageId, - limit: limit, - preferOffline: preferOffline, - ); + }) => _queryAtMessage( + messageId: messageId, + limit: limit, + preferOffline: preferOffline, + ); /// Loads channel at specific message Future loadChannelAtTimestamp( DateTime timestamp, { int limit = 30, bool preferOffline = false, - }) => - _queryAtTimestamp( - timestamp: timestamp, - limit: limit, - preferOffline: preferOffline, - ); + }) => _queryAtTimestamp( + timestamp: timestamp, + limit: limit, + preferOffline: preferOffline, + ); // If we are jumping to a message we can determine if we loaded the oldest // page or the newest page, depending on where the aroundMessageId is located. ({ bool endOfPrependReached, bool endOfAppendReached, - }) _inferBoundariesFromAnchorId( + }) + _inferBoundariesFromAnchorId( String anchorId, List loadedMessages, ) { @@ -539,7 +537,8 @@ class StreamChannelState extends State { ({ bool endOfPrependReached, bool endOfAppendReached, - }) _inferBoundariesFromAnchorTimestamp( + }) + _inferBoundariesFromAnchorTimestamp( DateTime anchorTimestamp, List loadedMessages, ) { @@ -564,9 +563,11 @@ class StreamChannelState extends State { DateTime anchorTimestamp, List loadedMessages, ) { - final messageTimestamps = loadedMessages.map((it) { - return it.createdAt.millisecondsSinceEpoch; - }).toList(growable: false); + final messageTimestamps = loadedMessages + .map((it) { + return it.createdAt.millisecondsSinceEpoch; + }) + .toList(growable: false); return messageTimestamps.lowerBoundBy( anchorTimestamp.millisecondsSinceEpoch, @@ -820,8 +821,7 @@ class StreamChannelState extends State { @override void didUpdateWidget(StreamChannel oldWidget) { super.didUpdateWidget(oldWidget); - if (oldWidget.channel.cid != widget.channel.cid || - oldWidget.initialMessageId != widget.initialMessageId) { + if (oldWidget.channel.cid != widget.channel.cid || oldWidget.initialMessageId != widget.initialMessageId) { // Re-initialize channel if the channel CID or initial message ID changes. _channelInitFuture = [_maybeInitChannel(), channel.initialized].wait; } diff --git a/packages/stream_chat_flutter_core/lib/src/stream_channel_list_controller.dart b/packages/stream_chat_flutter_core/lib/src/stream_channel_list_controller.dart index db2ff2c4c6..bd671a0bcc 100644 --- a/packages/stream_chat_flutter_core/lib/src/stream_channel_list_controller.dart +++ b/packages/stream_chat_flutter_core/lib/src/stream_channel_list_controller.dart @@ -55,8 +55,8 @@ class StreamChannelListController extends PagedValueNotifier { this.limit = defaultChannelPagedLimit, this.messageLimit, this.memberLimit, - }) : _eventHandler = eventHandler ?? StreamChannelListEventHandler(), - super(const PagedValue.loading()); + }) : _eventHandler = eventHandler ?? StreamChannelListEventHandler(), + super(const PagedValue.loading()); /// Creates a [StreamChannelListController] from the passed [value]. StreamChannelListController.fromValue( @@ -113,14 +113,14 @@ class StreamChannelListController extends PagedValueNotifier { super.value = switch (channelStateSort) { null => newValue, final channelSort => newValue.maybeMap( - orElse: () => newValue, - (success) => success.copyWith( - items: success.items.sortedByCompare( - (it) => it.state!.channelState, - channelSort.compare, - ), + orElse: () => newValue, + (success) => success.copyWith( + items: success.items.sortedByCompare( + (it) => it.state!.channelState, + channelSort.compare, ), ), + ), }; } @@ -269,8 +269,7 @@ class StreamChannelListController extends PagedValueNotifier { _eventHandler.onNotificationMessageNew(event, this); } else if (eventType == EventType.notificationRemovedFromChannel) { _eventHandler.onNotificationRemovedFromChannel(event, this); - } else if (eventType == 'user.presence.changed' || - eventType == EventType.userUpdated) { + } else if (eventType == 'user.presence.changed' || eventType == EventType.userUpdated) { _eventHandler.onUserPresenceChanged(event, this); } else if (eventType == EventType.memberUpdated) { _eventHandler.onMemberUpdated(event, this); diff --git a/packages/stream_chat_flutter_core/lib/src/stream_channel_list_event_handler.dart b/packages/stream_chat_flutter_core/lib/src/stream_channel_list_event_handler.dart index b59d16e67c..59a21f13c4 100644 --- a/packages/stream_chat_flutter_core/lib/src/stream_channel_list_event_handler.dart +++ b/packages/stream_chat_flutter_core/lib/src/stream_channel_list_event_handler.dart @@ -171,8 +171,7 @@ mixin class StreamChannelListEventHandler { StreamChannelListController controller, ) { final channels = [...controller.currentItems]; - final updatedChannels = - channels.where((it) => it.cid != event.channel?.cid); + final updatedChannels = channels.where((it) => it.cid != event.channel?.cid); final listChanged = channels.length != updatedChannels.length; if (!listChanged) return; diff --git a/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart b/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart index 252ffee341..44d77f156b 100644 --- a/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart @@ -137,8 +137,7 @@ class StreamChatCore extends StatefulWidget { } /// State class associated with [StreamChatCore]. -class StreamChatCoreState extends State - with WidgetsBindingObserver { +class StreamChatCoreState extends State with WidgetsBindingObserver { /// The current user User? get currentUser => client.state.currentUser; @@ -168,15 +167,13 @@ class StreamChatCoreState extends State case PlatformType.macOS: final info = await DeviceInfoPlugin().macOsInfo; - osVersion = [info.majorVersion, info.minorVersion, info.patchVersion] - .join('.'); + osVersion = [info.majorVersion, info.minorVersion, info.patchVersion].join('.'); deviceModel = info.model; break; case PlatformType.windows: final info = await DeviceInfoPlugin().windowsInfo; - osVersion = [info.majorVersion, info.minorVersion, info.buildNumber] - .join('.'); + osVersion = [info.majorVersion, info.minorVersion, info.buildNumber].join('.'); deviceModel = null; break; diff --git a/packages/stream_chat_flutter_core/lib/src/stream_draft_list_controller.dart b/packages/stream_chat_flutter_core/lib/src/stream_draft_list_controller.dart index 8845ec93f1..cebe6c965a 100644 --- a/packages/stream_chat_flutter_core/lib/src/stream_draft_list_controller.dart +++ b/packages/stream_chat_flutter_core/lib/src/stream_draft_list_controller.dart @@ -34,10 +34,10 @@ class StreamDraftListController extends PagedValueNotifier { this.filter, this.sort = defaultDraftListSort, this.limit = defaultDraftPagedLimit, - }) : _activeFilter = filter, - _activeSort = sort, - _eventHandler = eventHandler ?? StreamDraftListEventHandler(), - super(const PagedValue.loading()); + }) : _activeFilter = filter, + _activeSort = sort, + _eventHandler = eventHandler ?? StreamDraftListEventHandler(), + super(const PagedValue.loading()); /// Creates a [StreamThreadListController] from the passed [value]. StreamDraftListController.fromValue( @@ -47,9 +47,9 @@ class StreamDraftListController extends PagedValueNotifier { this.filter, this.sort = defaultDraftListSort, this.limit = defaultDraftPagedLimit, - }) : _activeFilter = filter, - _activeSort = sort, - _eventHandler = eventHandler ?? StreamDraftListEventHandler(); + }) : _activeFilter = filter, + _activeSort = sort, + _eventHandler = eventHandler ?? StreamDraftListEventHandler(); /// The Stream client used to perform the queries. final StreamChatClient client; @@ -94,11 +94,11 @@ class StreamDraftListController extends PagedValueNotifier { super.value = switch (_activeSort) { null => newValue, final draftSort => newValue.maybeMap( - orElse: () => newValue, - (success) => success.copyWith( - items: success.items.sorted(draftSort.compare), - ), + orElse: () => newValue, + (success) => success.copyWith( + items: success.items.sorted(draftSort.compare), ), + ), }; } diff --git a/packages/stream_chat_flutter_core/lib/src/stream_member_list_controller.dart b/packages/stream_chat_flutter_core/lib/src/stream_member_list_controller.dart index 16185685d9..0bc07a4c43 100644 --- a/packages/stream_chat_flutter_core/lib/src/stream_member_list_controller.dart +++ b/packages/stream_chat_flutter_core/lib/src/stream_member_list_controller.dart @@ -36,9 +36,9 @@ class StreamMemberListController extends PagedValueNotifier { this.filter, this.sort = defaultMemberListSort, this.limit = defaultMemberPagedLimit, - }) : _activeFilter = filter, - _activeSort = sort, - super(const PagedValue.loading()); + }) : _activeFilter = filter, + _activeSort = sort, + super(const PagedValue.loading()); /// Creates a [StreamMemberListController] from the passed [value]. StreamMemberListController.fromValue( @@ -47,8 +47,8 @@ class StreamMemberListController extends PagedValueNotifier { this.filter, this.sort = defaultMemberListSort, this.limit = defaultMemberPagedLimit, - }) : _activeFilter = filter, - _activeSort = sort; + }) : _activeFilter = filter, + _activeSort = sort; /// The client to use for the channels list. final Channel channel; @@ -97,11 +97,11 @@ class StreamMemberListController extends PagedValueNotifier { super.value = switch (_activeSort) { null => newValue, final memberSort => newValue.maybeMap( - orElse: () => newValue, - (success) => success.copyWith( - items: success.items.sorted(memberSort.compare), - ), + orElse: () => newValue, + (success) => success.copyWith( + items: success.items.sorted(memberSort.compare), ), + ), }; } diff --git a/packages/stream_chat_flutter_core/lib/src/stream_message_input_controller.dart b/packages/stream_chat_flutter_core/lib/src/stream_message_input_controller.dart index 478fdacce7..78576690da 100644 --- a/packages/stream_chat_flutter_core/lib/src/stream_message_input_controller.dart +++ b/packages/stream_chat_flutter_core/lib/src/stream_message_input_controller.dart @@ -23,42 +23,39 @@ class StreamMessageInputController extends ValueNotifier { factory StreamMessageInputController({ Message? message, Map? textPatternStyle, - }) => - StreamMessageInputController._( - initialMessage: message ?? Message(), - textPatternStyle: textPatternStyle, - ); + }) => StreamMessageInputController._( + initialMessage: message ?? Message(), + textPatternStyle: textPatternStyle, + ); /// Creates a controller for an editable text field from an initial [text]. factory StreamMessageInputController.fromText( String? text, { Map? textPatternStyle, - }) => - StreamMessageInputController._( - initialMessage: Message(text: text), - textPatternStyle: textPatternStyle, - ); + }) => StreamMessageInputController._( + initialMessage: Message(text: text), + textPatternStyle: textPatternStyle, + ); /// Creates a controller for an editable text field from initial /// [attachments]. factory StreamMessageInputController.fromAttachments( List attachments, { Map? textPatternStyle, - }) => - StreamMessageInputController._( - initialMessage: Message(attachments: attachments), - textPatternStyle: textPatternStyle, - ); + }) => StreamMessageInputController._( + initialMessage: Message(attachments: attachments), + textPatternStyle: textPatternStyle, + ); StreamMessageInputController._({ required Message initialMessage, Map? textPatternStyle, - }) : _initialMessage = initialMessage, - _textFieldController = MessageTextFieldController.fromValue( - _textEditingValueFromMessage(initialMessage), - textPatternStyle: textPatternStyle, - ), - super(initialMessage) { + }) : _initialMessage = initialMessage, + _textFieldController = MessageTextFieldController.fromValue( + _textEditingValueFromMessage(initialMessage), + textPatternStyle: textPatternStyle, + ), + super(initialMessage) { _textFieldController.addListener(_textFieldListener); } @@ -185,7 +182,7 @@ class StreamMessageInputController extends ValueNotifier { } /// Sets a command for the message. - set command(String command) { + set command(String? command) { // Setting the command should also clear the text and attachments. message = message.copyWith( text: '', @@ -318,6 +315,32 @@ class StreamMessageInputController extends ValueNotifier { message = Message(); } + /// The original message being edited, before any user changes. + /// + /// This is set by [editMessage] and cleared by [cancelEditMessage]. + /// Use this to display a stable preview of the original message while the + /// user is typing their edits. + Message? get editingOriginalMessage => _editingOriginalMessage; + Message? _editingOriginalMessage; + + /// Sets the controller to edit an existing [message]. + /// + /// Stores a snapshot of [message] in [editingOriginalMessage] so the + /// original content stays visible while the user types. + /// Resets [_initialMessage] to an empty message so that [cancelEditMessage] + /// returns to a clean empty state. + void editMessage(Message message) { + _editingOriginalMessage = message; + _initialMessage = Message(); + this.message = message.copyWith(state: MessageState.updating); + } + + /// Cancels the current edit and resets the controller to an empty state. + void cancelEditMessage() { + _editingOriginalMessage = null; + reset(); + } + /// Sets the [message] to the initial [Message] value. void reset({bool resetId = true}) { if (resetId) { @@ -347,14 +370,12 @@ class StreamMessageInputController extends ValueNotifier { /// the property will restore [StreamMessageInputController.message] /// to the value it had when the restoration data it is getting restored from /// was collected. -class StreamRestorableMessageInputController - extends RestorableChangeNotifier { +class StreamRestorableMessageInputController extends RestorableChangeNotifier { /// Creates a [StreamRestorableMessageInputController]. /// /// This constructor creates a default [Message] when no `message` argument /// is supplied. - StreamRestorableMessageInputController({Message? message}) - : _initialValue = message ?? Message(); + StreamRestorableMessageInputController({Message? message}) : _initialValue = message ?? Message(); /// Creates a [StreamRestorableMessageInputController] from an initial /// [text] value. @@ -364,8 +385,7 @@ class StreamRestorableMessageInputController final Message _initialValue; @override - StreamMessageInputController createDefaultValue() => - StreamMessageInputController(message: _initialValue); + StreamMessageInputController createDefaultValue() => StreamMessageInputController(message: _initialValue); @override StreamMessageInputController fromPrimitives(Object? data) { diff --git a/packages/stream_chat_flutter_core/lib/src/stream_message_reminder_list_controller.dart b/packages/stream_chat_flutter_core/lib/src/stream_message_reminder_list_controller.dart index a2fa4f156e..242bd9c4b1 100644 --- a/packages/stream_chat_flutter_core/lib/src/stream_message_reminder_list_controller.dart +++ b/packages/stream_chat_flutter_core/lib/src/stream_message_reminder_list_controller.dart @@ -29,8 +29,7 @@ const _kDefaultBackendPaginationLimit = 30; /// This controller is typically used in conjunction with UI components /// to display and interact with a list of message reminders. /// {@endtemplate} -class StreamMessageReminderListController - extends PagedValueNotifier { +class StreamMessageReminderListController extends PagedValueNotifier { /// {@macro streamMessageReminderListController} StreamMessageReminderListController({ required this.client, @@ -38,10 +37,10 @@ class StreamMessageReminderListController this.filter, this.sort = defaultMessageReminderListSort, this.limit = defaultMessageReminderPagedLimit, - }) : _activeFilter = filter, - _activeSort = sort, - _eventHandler = eventHandler ?? StreamMessageReminderListEventHandler(), - super(const PagedValue.loading()); + }) : _activeFilter = filter, + _activeSort = sort, + _eventHandler = eventHandler ?? StreamMessageReminderListEventHandler(), + super(const PagedValue.loading()); /// Creates a [StreamMessageReminderListController] from the passed [value]. StreamMessageReminderListController.fromValue( @@ -51,9 +50,9 @@ class StreamMessageReminderListController this.filter, this.sort = defaultMessageReminderListSort, this.limit = defaultMessageReminderPagedLimit, - }) : _activeFilter = filter, - _activeSort = sort, - _eventHandler = eventHandler ?? StreamMessageReminderListEventHandler(); + }) : _activeFilter = filter, + _activeSort = sort, + _eventHandler = eventHandler ?? StreamMessageReminderListEventHandler(); /// The Stream client used to perform the queries. final StreamChatClient client; @@ -98,11 +97,11 @@ class StreamMessageReminderListController super.value = switch (_activeSort) { null => newValue, final reminderSort => newValue.maybeMap( - orElse: () => newValue, - (success) => success.copyWith( - items: success.items.sorted(reminderSort.compare), - ), + orElse: () => newValue, + (success) => success.copyWith( + items: success.items.sorted(reminderSort.compare), ), + ), }; } diff --git a/packages/stream_chat_flutter_core/lib/src/stream_message_search_list_controller.dart b/packages/stream_chat_flutter_core/lib/src/stream_message_search_list_controller.dart index 5f8b369c70..3666f7d0f4 100644 --- a/packages/stream_chat_flutter_core/lib/src/stream_message_search_list_controller.dart +++ b/packages/stream_chat_flutter_core/lib/src/stream_message_search_list_controller.dart @@ -15,8 +15,7 @@ const _kDefaultBackendPaginationLimit = 30; /// * Load initial data. /// * Load more data using [loadMore]. /// * Replace the previously loaded users. -class StreamMessageSearchListController - extends PagedValueNotifier { +class StreamMessageSearchListController extends PagedValueNotifier { /// Creates a Stream user list controller. /// /// * `client` is the Stream chat client to use for the channels list. @@ -36,19 +35,19 @@ class StreamMessageSearchListController this.searchQuery, this.sort, this.limit = defaultMessageSearchPagedLimit, - }) : assert( - messageFilter != null || searchQuery != null, - 'Either messageFilter or searchQuery must be provided', - ), - assert( - messageFilter == null || searchQuery == null, - 'Only one of messageFilter or searchQuery can be provided', - ), - _activeFilter = filter, - _activeMessageFilter = messageFilter, - _activeSearchQuery = searchQuery, - _activeSort = sort, - super(const PagedValue.loading()); + }) : assert( + messageFilter != null || searchQuery != null, + 'Either messageFilter or searchQuery must be provided', + ), + assert( + messageFilter == null || searchQuery == null, + 'Only one of messageFilter or searchQuery can be provided', + ), + _activeFilter = filter, + _activeMessageFilter = messageFilter, + _activeSearchQuery = searchQuery, + _activeSort = sort, + super(const PagedValue.loading()); /// Creates a [StreamUserListController] from the passed [value]. StreamMessageSearchListController.fromValue( @@ -59,18 +58,18 @@ class StreamMessageSearchListController this.searchQuery, this.sort, this.limit = defaultMessageSearchPagedLimit, - }) : assert( - messageFilter != null || searchQuery != null, - 'Either messageFilter or searchQuery must be provided', - ), - assert( - messageFilter == null || searchQuery == null, - 'Only one of messageFilter or searchQuery can be provided', - ), - _activeFilter = filter, - _activeMessageFilter = messageFilter, - _activeSearchQuery = searchQuery, - _activeSort = sort; + }) : assert( + messageFilter != null || searchQuery != null, + 'Either messageFilter or searchQuery must be provided', + ), + assert( + messageFilter == null || searchQuery == null, + 'Only one of messageFilter or searchQuery can be provided', + ), + _activeFilter = filter, + _activeMessageFilter = messageFilter, + _activeSearchQuery = searchQuery, + _activeSort = sort; /// The client to use for the channels list. final StreamChatClient client; diff --git a/packages/stream_chat_flutter_core/lib/src/stream_poll_controller.dart b/packages/stream_chat_flutter_core/lib/src/stream_poll_controller.dart index 749fb77e0e..d63f7c3569 100644 --- a/packages/stream_chat_flutter_core/lib/src/stream_poll_controller.dart +++ b/packages/stream_chat_flutter_core/lib/src/stream_poll_controller.dart @@ -53,11 +53,14 @@ class StreamPollController extends ValueNotifier { factory StreamPollController({ Poll? poll, PollConfig? config, - }) => - StreamPollController._( - config ?? const PollConfig(), - poll ?? Poll(name: '', options: const [PollOption(text: '')]), - ); + }) => StreamPollController._( + config ?? const PollConfig(), + poll ?? + Poll( + name: '', + options: const [PollOption(text: '')], + ), + ); StreamPollController._(this.config, super.poll) : _initialValue = poll; @@ -84,7 +87,7 @@ class StreamPollController extends ValueNotifier { // Remove the id from the new added options. return option.copyWith(id: null); - }) + }), ], ); } @@ -122,8 +125,7 @@ class StreamPollController extends ValueNotifier { final name = value.name; final (:min, :max) = nameRange; - if (min != null && name.length < min || - max != null && name.length > max) { + if (min != null && name.length < min || max != null && name.length > max) { invalidErrors.add( PollValidationError.nameRange(name, range: nameRange), ); @@ -147,8 +149,7 @@ class StreamPollController extends ValueNotifier { final nonEmptyOptions = [...options.where((it) => it.text.isNotEmpty)]; final (:min, :max) = optionsRange; - if (min != null && nonEmptyOptions.length < min || - max != null && nonEmptyOptions.length > max) { + if (min != null && nonEmptyOptions.length < min || max != null && nonEmptyOptions.length > max) { invalidErrors.add( PollValidationError.optionsRange(options, range: optionsRange), ); @@ -161,8 +162,7 @@ class StreamPollController extends ValueNotifier { if (config.allowedVotesRange case final allowedVotesRange?) { final (:min, :max) = allowedVotesRange; - if (min != null && maxVotesAllowed < min || - max != null && maxVotesAllowed > max) { + if (min != null && maxVotesAllowed < min || max != null && maxVotesAllowed > max) { invalidErrors.add( PollValidationError.maxVotesAllowed( maxVotesAllowed, @@ -292,19 +292,15 @@ extension PollValidationErrorPatternMatching on PollValidationError { TResult when({ required TResult Function(List options) duplicateOptions, required TResult Function(String name, Range range) nameRange, - required TResult Function(List options, Range range) - optionsRange, - required TResult Function(int maxVotesAllowed, Range range) - maxVotesAllowed, + required TResult Function(List options, Range range) optionsRange, + required TResult Function(int maxVotesAllowed, Range range) maxVotesAllowed, }) { final error = this; return switch (error) { _PollValidationErrorDuplicateOptions() => duplicateOptions(error.options), _PollValidationErrorNameRange() => nameRange(error.name, error.range), - _PollValidationErrorOptionsRange() => - optionsRange(error.options, error.range), - _PollValidationErrorMaxVotesAllowed() => - maxVotesAllowed(error.maxVotesAllowed, error.range), + _PollValidationErrorOptionsRange() => optionsRange(error.options, error.range), + _PollValidationErrorMaxVotesAllowed() => maxVotesAllowed(error.maxVotesAllowed, error.range), }; } @@ -318,14 +314,10 @@ extension PollValidationErrorPatternMatching on PollValidationError { }) { final error = this; return switch (error) { - _PollValidationErrorDuplicateOptions() => - duplicateOptions?.call(error.options), - _PollValidationErrorNameRange() => - nameRange?.call(error.name, error.range), - _PollValidationErrorOptionsRange() => - optionsRange?.call(error.options, error.range), - _PollValidationErrorMaxVotesAllowed() => - maxVotesAllowed?.call(error.maxVotesAllowed, error.range), + _PollValidationErrorDuplicateOptions() => duplicateOptions?.call(error.options), + _PollValidationErrorNameRange() => nameRange?.call(error.name, error.range), + _PollValidationErrorOptionsRange() => optionsRange?.call(error.options, error.range), + _PollValidationErrorMaxVotesAllowed() => maxVotesAllowed?.call(error.maxVotesAllowed, error.range), }; } @@ -340,14 +332,10 @@ extension PollValidationErrorPatternMatching on PollValidationError { }) { final error = this; final result = switch (error) { - _PollValidationErrorDuplicateOptions() => - duplicateOptions?.call(error.options), - _PollValidationErrorNameRange() => - nameRange?.call(error.name, error.range), - _PollValidationErrorOptionsRange() => - optionsRange?.call(error.options, error.range), - _PollValidationErrorMaxVotesAllowed() => - maxVotesAllowed?.call(error.maxVotesAllowed, error.range), + _PollValidationErrorDuplicateOptions() => duplicateOptions?.call(error.options), + _PollValidationErrorNameRange() => nameRange?.call(error.name, error.range), + _PollValidationErrorOptionsRange() => optionsRange?.call(error.options, error.range), + _PollValidationErrorMaxVotesAllowed() => maxVotesAllowed?.call(error.maxVotesAllowed, error.range), }; return result ?? orElse(); @@ -356,13 +344,10 @@ extension PollValidationErrorPatternMatching on PollValidationError { /// @nodoc @optionalTypeArgs TResult map({ - required TResult Function(_PollValidationErrorDuplicateOptions value) - duplicateOptions, + required TResult Function(_PollValidationErrorDuplicateOptions value) duplicateOptions, required TResult Function(_PollValidationErrorNameRange value) nameRange, - required TResult Function(_PollValidationErrorOptionsRange value) - optionsRange, - required TResult Function(_PollValidationErrorMaxVotesAllowed value) - maxVotesAllowed, + required TResult Function(_PollValidationErrorOptionsRange value) optionsRange, + required TResult Function(_PollValidationErrorMaxVotesAllowed value) maxVotesAllowed, }) { final error = this; return switch (error) { @@ -376,12 +361,10 @@ extension PollValidationErrorPatternMatching on PollValidationError { /// @nodoc @optionalTypeArgs TResult? mapOrNull({ - TResult? Function(_PollValidationErrorDuplicateOptions value)? - duplicateOptions, + TResult? Function(_PollValidationErrorDuplicateOptions value)? duplicateOptions, TResult? Function(_PollValidationErrorNameRange value)? nameRange, TResult? Function(_PollValidationErrorOptionsRange value)? optionsRange, - TResult? Function(_PollValidationErrorMaxVotesAllowed value)? - maxVotesAllowed, + TResult? Function(_PollValidationErrorMaxVotesAllowed value)? maxVotesAllowed, }) { final error = this; return switch (error) { @@ -395,12 +378,10 @@ extension PollValidationErrorPatternMatching on PollValidationError { /// @nodoc @optionalTypeArgs TResult maybeMap({ - TResult Function(_PollValidationErrorDuplicateOptions value)? - duplicateOptions, + TResult Function(_PollValidationErrorDuplicateOptions value)? duplicateOptions, TResult Function(_PollValidationErrorNameRange value)? nameRange, TResult Function(_PollValidationErrorOptionsRange value)? optionsRange, - TResult Function(_PollValidationErrorMaxVotesAllowed value)? - maxVotesAllowed, + TResult Function(_PollValidationErrorMaxVotesAllowed value)? maxVotesAllowed, required TResult orElse(), }) { final error = this; diff --git a/packages/stream_chat_flutter_core/lib/src/stream_poll_vote_list_controller.dart b/packages/stream_chat_flutter_core/lib/src/stream_poll_vote_list_controller.dart index 3b29d687dc..a55d48fbd5 100644 --- a/packages/stream_chat_flutter_core/lib/src/stream_poll_vote_list_controller.dart +++ b/packages/stream_chat_flutter_core/lib/src/stream_poll_vote_list_controller.dart @@ -21,8 +21,7 @@ const _kDefaultBackendPaginationLimit = 30; /// * Load initial data. /// * Load more data using [loadMore]. /// * Replace the previously loaded poll votes. -class StreamPollVoteListController - extends PagedValueNotifier { +class StreamPollVoteListController extends PagedValueNotifier { /// Creates a Stream poll vote list controller. /// * `channel` is the Stream chat channel to use for the poll votes list. /// * `pollId` is the poll id to use for the poll votes list. @@ -36,10 +35,10 @@ class StreamPollVoteListController this.filter, this.sort = defaultPollVoteListSort, this.limit = defaultPollVotePagedLimit, - }) : _eventHandler = eventHandler ?? StreamPollVoteEventHandler(), - _activeFilter = filter, - _activeSort = sort, - super(const PagedValue.loading()); + }) : _eventHandler = eventHandler ?? StreamPollVoteEventHandler(), + _activeFilter = filter, + _activeSort = sort, + super(const PagedValue.loading()); /// Creates a [StreamPollVoteListController] from the passed [value]. StreamPollVoteListController.fromValue( @@ -50,9 +49,9 @@ class StreamPollVoteListController this.filter, this.sort = defaultPollVoteListSort, this.limit = defaultPollVotePagedLimit, - }) : _eventHandler = eventHandler ?? StreamPollVoteEventHandler(), - _activeFilter = filter, - _activeSort = sort; + }) : _eventHandler = eventHandler ?? StreamPollVoteEventHandler(), + _activeFilter = filter, + _activeSort = sort; /// The channel to use for the poll votes list. final Channel channel; @@ -106,11 +105,11 @@ class StreamPollVoteListController super.value = switch (_activeSort) { null => newValue, final pollVoteSort => newValue.maybeMap( - orElse: () => newValue, - (success) => success.copyWith( - items: success.items.sorted(pollVoteSort.compare), - ), + orElse: () => newValue, + (success) => success.copyWith( + items: success.items.sorted(pollVoteSort.compare), ), + ), }; } @@ -216,13 +215,11 @@ class StreamPollVoteListController if (eventListener?.call(event) ?? false) return; final eventType = event.type; - if (eventType == EventType.pollVoteCasted || - eventType == EventType.pollAnswerCasted) { + if (eventType == EventType.pollVoteCasted || eventType == EventType.pollAnswerCasted) { _eventHandler.onPollVoteCasted(event, this); } else if (eventType == EventType.pollVoteChanged) { _eventHandler.onPollVoteChanged(event, this); - } else if (eventType == EventType.pollVoteRemoved || - eventType == EventType.pollAnswerRemoved) { + } else if (eventType == EventType.pollVoteRemoved || eventType == EventType.pollAnswerRemoved) { _eventHandler.onPollVoteRemoved(event, this); } }); diff --git a/packages/stream_chat_flutter_core/lib/src/stream_reaction_list_controller.dart b/packages/stream_chat_flutter_core/lib/src/stream_reaction_list_controller.dart new file mode 100644 index 0000000000..2e7dc0dda6 --- /dev/null +++ b/packages/stream_chat_flutter_core/lib/src/stream_reaction_list_controller.dart @@ -0,0 +1,182 @@ +import 'dart:async'; +import 'dart:math'; + +import 'package:collection/collection.dart'; +import 'package:stream_chat/stream_chat.dart' hide Success; +import 'package:stream_chat_flutter_core/src/paged_value_notifier.dart'; + +/// The default reaction list page limit to load. +const defaultReactionPagedLimit = 25; + +const _kDefaultBackendPaginationLimit = 30; + +/// {@template streamReactionListController} +/// A controller for managing and displaying a paginated list of reactions. +/// +/// The `StreamReactionListController` extends [PagedValueNotifier] to handle +/// paginated data for reactions. It provides functionality for querying +/// reactions and managing filters and sorting. +/// +/// This controller uses cursor-based pagination via the `queryReactions` API, +/// which supports filtering by reaction type, user ID, or creation date. +/// +/// This controller is typically used in conjunction with UI components +/// to display and interact with a list of reactions for a message. +/// {@endtemplate} +class StreamReactionListController extends PagedValueNotifier { + /// {@macro streamReactionListController} + StreamReactionListController({ + required this.client, + required this.messageId, + this.filter, + this.sort, + this.limit = defaultReactionPagedLimit, + }) : _activeFilter = filter, + _activeSort = sort, + super(const PagedValue.loading()); + + /// Creates a [StreamReactionListController] from the passed [value]. + StreamReactionListController.fromValue( + super.value, { + required this.client, + required this.messageId, + this.filter, + this.sort, + this.limit = defaultReactionPagedLimit, + }) : _activeFilter = filter, + _activeSort = sort; + + /// The Stream chat client used to query reactions. + final StreamChatClient client; + + /// The ID of the message whose reactions are being listed. + final String messageId; + + /// The query filters to use. + /// + /// Supported filter fields: `type`, `user_id`, `created_at`. + final Filter? filter; + Filter? _activeFilter; + + /// The sorting used for the reactions matching the filters. + /// + /// Sorting is based on field and direction. The only backend-supported sort + /// field is `created_at` (see [ReactionSortKey]). + /// + /// Direction can be ascending or descending. + final SortOrder? sort; + SortOrder? _activeSort; + + /// The limit to apply to the reaction list. + /// + /// The default is set to [defaultReactionPagedLimit]. + final int limit; + + /// Allows for the change of filters used for reaction queries. + /// + /// Use this if you need to support runtime filter changes, + /// such as switching between reaction type tabs. + /// + /// Note: This will not trigger a new query. Make sure to call + /// [doInitialLoad] or [refresh] after setting a new filter. + set filter(Filter? value) => _activeFilter = value; + + /// Allows for the change of the query sort used for reaction queries. + /// + /// Use this if you need to support runtime sort changes, + /// through custom sort UI. + /// + /// Note: This will not trigger a new query. Make sure to call + /// [doInitialLoad] or [refresh] after setting a new sort. + set sort(SortOrder? value) => _activeSort = value; + + @override + set value(PagedValue newValue) { + super.value = switch (_activeSort) { + null => newValue, + final reactionSort => newValue.maybeMap( + orElse: () => newValue, + (success) => success.copyWith( + items: success.items.sorted(reactionSort.compare), + ), + ), + }; + } + + @override + Future doInitialLoad() async { + final limit = min( + this.limit * defaultInitialPagedLimitMultiplier, + _kDefaultBackendPaginationLimit, + ); + try { + final response = await client.queryReactions( + messageId, + filter: _activeFilter, + sort: _activeSort, + pagination: PaginationParams(limit: limit), + ); + + final reactions = response.reactions; + final next = response.next; + final nextKey = next != null && next.isNotEmpty ? next : null; + value = PagedValue( + items: reactions, + nextPageKey: nextKey, + ); + } on StreamChatError catch (error) { + value = PagedValue.error(error); + } catch (error) { + final chatError = StreamChatError(error.toString()); + value = PagedValue.error(chatError); + } + } + + @override + Future loadMore(String? nextPageKey) async { + final previousValue = value.asSuccess; + + try { + final response = await client.queryReactions( + messageId, + filter: _activeFilter, + sort: _activeSort, + pagination: PaginationParams(limit: limit, next: nextPageKey), + ); + + final reactions = response.reactions; + final previousItems = previousValue.items; + final newItems = previousItems + reactions; + final next = response.next; + final nextKey = next != null && next.isNotEmpty ? next : null; + value = PagedValue( + items: newItems, + nextPageKey: nextKey, + ); + } on StreamChatError catch (error) { + value = previousValue.copyWith(error: error); + } catch (error) { + final chatError = StreamChatError(error.toString()); + value = previousValue.copyWith(error: chatError); + } + } + + @override + Future refresh({bool resetValue = true}) { + if (resetValue) { + _activeFilter = filter; + _activeSort = sort; + } + return super.refresh(resetValue: resetValue); + } + + /// Replaces the previously loaded reactions with [reactions]. + set reactions(List reactions) { + if (value.isSuccess) { + final currentValue = value.asSuccess; + value = currentValue.copyWith(items: reactions); + } else { + value = PagedValue(items: reactions); + } + } +} diff --git a/packages/stream_chat_flutter_core/lib/src/stream_thread_list_controller.dart b/packages/stream_chat_flutter_core/lib/src/stream_thread_list_controller.dart index fe30762ed7..691c4e7f2b 100644 --- a/packages/stream_chat_flutter_core/lib/src/stream_thread_list_controller.dart +++ b/packages/stream_chat_flutter_core/lib/src/stream_thread_list_controller.dart @@ -29,11 +29,11 @@ class StreamThreadListController extends PagedValueNotifier { this.sort, this.options = const ThreadOptions(), this.limit = defaultThreadsPagedLimit, - }) : _activeFilter = filter, - _activeSort = sort, - _activeOptions = options, - _eventHandler = eventHandler ?? StreamThreadListEventHandler(), - super(const PagedValue.loading()); + }) : _activeFilter = filter, + _activeSort = sort, + _activeOptions = options, + _eventHandler = eventHandler ?? StreamThreadListEventHandler(), + super(const PagedValue.loading()); /// Creates a [StreamThreadListController] from the passed [value]. StreamThreadListController.fromValue( @@ -44,10 +44,10 @@ class StreamThreadListController extends PagedValueNotifier { this.sort, this.options = const ThreadOptions(), this.limit = defaultThreadsPagedLimit, - }) : _activeFilter = filter, - _activeSort = sort, - _activeOptions = options, - _eventHandler = eventHandler ?? StreamThreadListEventHandler(); + }) : _activeFilter = filter, + _activeSort = sort, + _activeOptions = options, + _eventHandler = eventHandler ?? StreamThreadListEventHandler(); /// The Stream client used to perform the queries. final StreamChatClient client; @@ -129,11 +129,11 @@ class StreamThreadListController extends PagedValueNotifier { super.value = switch (_activeSort) { null => newValue, final threadSort => newValue.maybeMap( - orElse: () => newValue, - (success) => success.copyWith( - items: success.items.sorted(threadSort.compare), - ), + orElse: () => newValue, + (success) => success.copyWith( + items: success.items.sorted(threadSort.compare), ), + ), }; } @@ -338,11 +338,9 @@ class StreamThreadListController extends PagedValueNotifier { final handlerFunc = switch (event.type) { EventType.threadUpdated => _eventHandler.onThreadUpdated, EventType.connectionRecovered => _eventHandler.onConnectionRecovered, - EventType.notificationThreadMessageNew => - _eventHandler.onNotificationThreadMessageNew, + EventType.notificationThreadMessageNew => _eventHandler.onNotificationThreadMessageNew, EventType.messageRead => _eventHandler.onMessageRead, - EventType.notificationMarkUnread => - _eventHandler.onNotificationMarkUnread, + EventType.notificationMarkUnread => _eventHandler.onNotificationMarkUnread, EventType.channelDeleted => _eventHandler.onChannelDeleted, EventType.channelTruncated => _eventHandler.onChannelTruncated, EventType.messageNew => _eventHandler.onMessageNew, diff --git a/packages/stream_chat_flutter_core/lib/src/stream_user_list_controller.dart b/packages/stream_chat_flutter_core/lib/src/stream_user_list_controller.dart index 9dc25635eb..fa22bc49a1 100644 --- a/packages/stream_chat_flutter_core/lib/src/stream_user_list_controller.dart +++ b/packages/stream_chat_flutter_core/lib/src/stream_user_list_controller.dart @@ -40,9 +40,9 @@ class StreamUserListController extends PagedValueNotifier { this.sort = defaultUserListSort, this.presence = true, this.limit = defaultUserPagedLimit, - }) : _activeFilter = filter, - _activeSort = sort, - super(const PagedValue.loading()); + }) : _activeFilter = filter, + _activeSort = sort, + super(const PagedValue.loading()); /// Creates a [StreamUserListController] from the passed [value]. StreamUserListController.fromValue( @@ -52,8 +52,8 @@ class StreamUserListController extends PagedValueNotifier { this.sort = defaultUserListSort, this.presence = true, this.limit = defaultUserPagedLimit, - }) : _activeFilter = filter, - _activeSort = sort; + }) : _activeFilter = filter, + _activeSort = sort; /// The client to use for the channels list. final StreamChatClient client; @@ -105,11 +105,11 @@ class StreamUserListController extends PagedValueNotifier { super.value = switch (_activeSort) { null => newValue, final userSort => newValue.maybeMap( - orElse: () => newValue, - (success) => success.copyWith( - items: success.items.sorted(userSort.compare), - ), + orElse: () => newValue, + (success) => success.copyWith( + items: success.items.sorted(userSort.compare), ), + ), }; } diff --git a/packages/stream_chat_flutter_core/lib/stream_chat_flutter_core.dart b/packages/stream_chat_flutter_core/lib/stream_chat_flutter_core.dart index 444362bbb7..2bdd25a06c 100644 --- a/packages/stream_chat_flutter_core/lib/stream_chat_flutter_core.dart +++ b/packages/stream_chat_flutter_core/lib/stream_chat_flutter_core.dart @@ -8,11 +8,7 @@ export 'src/lazy_load_scroll_view.dart'; export 'src/message_list_core.dart' hide MessageListCoreState; export 'src/message_text_field_controller.dart'; export 'src/paged_value_notifier.dart' - show - PagedValueListenableBuilder, - PagedValue, - PagedValueNotifier, - PagedValuePatternMatching; + show PagedValueListenableBuilder, PagedValue, PagedValueNotifier, PagedValuePatternMatching; export 'src/paged_value_scroll_view.dart'; export 'src/stream_channel.dart'; export 'src/stream_channel_list_controller.dart'; @@ -27,6 +23,7 @@ export 'src/stream_message_reminder_list_event_handler.dart'; export 'src/stream_message_search_list_controller.dart'; export 'src/stream_poll_controller.dart'; export 'src/stream_poll_vote_list_controller.dart'; +export 'src/stream_reaction_list_controller.dart'; export 'src/stream_thread_list_controller.dart'; export 'src/stream_thread_list_event_handler.dart'; export 'src/stream_user_list_controller.dart'; diff --git a/packages/stream_chat_flutter_core/pubspec.yaml b/packages/stream_chat_flutter_core/pubspec.yaml index e0029c4ac7..dc3124e751 100644 --- a/packages/stream_chat_flutter_core/pubspec.yaml +++ b/packages/stream_chat_flutter_core/pubspec.yaml @@ -18,8 +18,8 @@ issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues # 2. Add it to the melos.yaml file for future updates. environment: - sdk: ^3.6.2 - flutter: ">=3.27.4" + sdk: ^3.10.0 + flutter: ">=3.38.1" dependencies: collection: ^1.17.2 diff --git a/packages/stream_chat_flutter_core/test/message_list_core_test.dart b/packages/stream_chat_flutter_core/test/message_list_core_test.dart index d49a31e8f5..5392f7db16 100644 --- a/packages/stream_chat_flutter_core/test/message_list_core_test.dart +++ b/packages/stream_chat_flutter_core/test/message_list_core_test.dart @@ -14,14 +14,14 @@ void main() { int offset = 0, bool threads = false, }) { - final users = List.generate(count, (index) { - index = count + offset; + final users = List.generate(count, (i) { + final index = i + offset; return User(id: 'testUserId$index'); }); final messages = List.generate( count, - (index) { - index = index + offset; + (i) { + final index = i + offset; return Message( id: 'testMessageId$index', type: 'testType', @@ -39,8 +39,8 @@ void main() { ); final threadMessages = List.generate( count, - (index) { - index = index + offset; + (i) { + final index = i + offset; return Message( id: 'testThreadMessageId$index', type: 'testType', @@ -94,8 +94,7 @@ void main() { final mockChannel = MockChannel(); when(() => mockChannel.state.unreadCount).thenReturn(0); when(() => mockChannel.state.isUpToDate).thenReturn(true); - when(() => mockChannel.state.messagesStream) - .thenAnswer((_) => Stream.value([])); + when(() => mockChannel.state.messagesStream).thenAnswer((_) => Stream.value([])); when(() => mockChannel.state.messages).thenReturn([]); await tester.pumpWidget( @@ -131,8 +130,7 @@ void main() { when(() => mockChannel.state.isUpToDate).thenReturn(true); when(() => mockChannel.state.unreadCount).thenReturn(0); - when(() => mockChannel.state.messagesStream) - .thenAnswer((_) => Stream.value([])); + when(() => mockChannel.state.messagesStream).thenAnswer((_) => Stream.value([])); when(() => mockChannel.state.messages).thenReturn([]); await tester.pumpWidget( @@ -173,8 +171,7 @@ void main() { when(() => mockChannel.state.unreadCount).thenReturn(0); final messages = _generateMessages(); when(() => mockChannel.state.messages).thenReturn(messages); - when(() => mockChannel.state.messagesStream) - .thenAnswer((_) => Stream.value(messages)); + when(() => mockChannel.state.messagesStream).thenAnswer((_) => Stream.value(messages)); when(() => mockChannel.state.messages).thenReturn(messages); await tester.pumpWidget( @@ -193,13 +190,15 @@ void main() { await coreState.paginateData(); - verify(() => mockChannel.query( - messagesPagination: any( - named: 'messagesPagination', - that: wrapMatcher((it) => it.limit == paginationLimit), - ), - preferOffline: any(named: 'preferOffline'), - )).called(1); + verify( + () => mockChannel.query( + messagesPagination: any( + named: 'messagesPagination', + that: wrapMatcher((it) => it.limit == paginationLimit), + ), + preferOffline: any(named: 'preferOffline'), + ), + ).called(1); }, ); @@ -223,8 +222,7 @@ void main() { when(() => mockChannel.state.isUpToDate).thenReturn(true); const error = 'Error! Error! Error!'; - when(() => mockChannel.state.messagesStream) - .thenAnswer((_) => Stream.error(error)); + when(() => mockChannel.state.messagesStream).thenAnswer((_) => Stream.error(error)); when(() => mockChannel.state.messages).thenReturn([]); when(() => mockChannel.state.unreadCount).thenReturn(0); @@ -254,8 +252,7 @@ void main() { key: messageListCoreKey, messageListBuilder: (_, __) => const Offstage(), loadingBuilder: (BuildContext context) => const Offstage(), - emptyBuilder: (BuildContext context) => - const Offstage(key: emptyWidgetKey), + emptyBuilder: (BuildContext context) => const Offstage(key: emptyWidgetKey), errorBuilder: (BuildContext context, Object error) => const Offstage(), ); @@ -264,8 +261,7 @@ void main() { when(() => mockChannel.state.isUpToDate).thenReturn(true); const messages = []; - when(() => mockChannel.state.messagesStream) - .thenAnswer((_) => Stream.value(messages)); + when(() => mockChannel.state.messagesStream).thenAnswer((_) => Stream.value(messages)); when(() => mockChannel.state.messages).thenReturn(messages); when(() => mockChannel.state.unreadCount).thenReturn(0); @@ -302,19 +298,20 @@ void main() { final mockChannel = MockChannel(); when(() => mockChannel.state.isUpToDate).thenReturn(false); - when(() => mockChannel.query( - state: any(named: 'state'), - watch: any(named: 'watch'), - presence: any(named: 'presence'), - membersPagination: any(named: 'membersPagination'), - messagesPagination: any(named: 'messagesPagination'), - preferOffline: any(named: 'preferOffline'), - watchersPagination: any(named: 'watchersPagination'), - )).thenAnswer((_) async => const ChannelState()); + when( + () => mockChannel.query( + state: any(named: 'state'), + watch: any(named: 'watch'), + presence: any(named: 'presence'), + membersPagination: any(named: 'membersPagination'), + messagesPagination: any(named: 'messagesPagination'), + preferOffline: any(named: 'preferOffline'), + watchersPagination: any(named: 'watchersPagination'), + ), + ).thenAnswer((_) async => const ChannelState()); const messages = []; - when(() => mockChannel.state.messagesStream) - .thenAnswer((_) => Stream.value(messages)); + when(() => mockChannel.state.messagesStream).thenAnswer((_) => Stream.value(messages)); when(() => mockChannel.state.messages).thenReturn(messages); when(() => mockChannel.state.unreadCount).thenReturn(0); @@ -358,8 +355,7 @@ void main() { when(() => mockChannel.state.isUpToDate).thenReturn(true); final messages = _generateMessages(); - when(() => mockChannel.state.messagesStream) - .thenAnswer((_) => Stream.value(messages)); + when(() => mockChannel.state.messagesStream).thenAnswer((_) => Stream.value(messages)); when(() => mockChannel.state.messages).thenReturn(messages); when(() => mockChannel.state.unreadCount).thenReturn(0); @@ -408,8 +404,7 @@ void main() { final threads = {parentMessage.id: messages}; when(() => mockChannel.state.threads).thenReturn(threads); - when(() => mockChannel.state.threadsStream) - .thenAnswer((_) => Stream.value(threads)); + when(() => mockChannel.state.threadsStream).thenAnswer((_) => Stream.value(threads)); when(() => mockChannel.state.unreadCount).thenReturn(0); when( diff --git a/packages/stream_chat_flutter_core/test/mocks.dart b/packages/stream_chat_flutter_core/test/mocks.dart index 7235202250..cc705f9838 100644 --- a/packages/stream_chat_flutter_core/test/mocks.dart +++ b/packages/stream_chat_flutter_core/test/mocks.dart @@ -22,11 +22,11 @@ class MockClientState extends Mock implements ClientState { @override OwnUser get currentUser => _currentUser ??= OwnUser( - id: 'testUserId', - role: 'admin', - createdAt: DateTime.now(), - updatedAt: DateTime.now(), - ); + id: 'testUserId', + role: 'admin', + createdAt: DateTime.now(), + updatedAt: DateTime.now(), + ); } class NonInitializedMockChannel extends Mock implements Channel { diff --git a/packages/stream_chat_flutter_core/test/paged_value_grid_view_test.dart b/packages/stream_chat_flutter_core/test/paged_value_grid_view_test.dart new file mode 100644 index 0000000000..c2877cdaf5 --- /dev/null +++ b/packages/stream_chat_flutter_core/test/paged_value_grid_view_test.dart @@ -0,0 +1,147 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; + +class _TestController extends PagedValueNotifier { + _TestController(List items, {int? nextPageKey}) : super(PagedValue(items: items, nextPageKey: nextPageKey)); + + @override + Future doInitialLoad() async {} + + @override + Future loadMore(int nextPageKey) async {} +} + +Widget _wrap(Widget child) => MaterialApp(home: Scaffold(body: child)); + +PagedValueGridView _buildGrid( + _TestController controller, { + WidgetBuilder? leadingItemBuilder, + required List builtIndices, +}) { + return PagedValueGridView( + controller: controller, + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3), + leadingItemBuilder: leadingItemBuilder, + itemBuilder: (context, items, index) { + builtIndices.add(index); + return Text('item-$index'); + }, + emptyBuilder: (_) => const Text('empty'), + loadMoreErrorBuilder: (_, __) => const Text('load-more-error'), + loadMoreIndicatorBuilder: (_) => const Text('load-more-indicator'), + loadingBuilder: (_) => const Text('loading'), + errorBuilder: (_, __) => const Text('error'), + ); +} + +void main() { + group('PagedValueGridView without leadingItemBuilder', () { + testWidgets('renders items starting at index 0', (tester) async { + final controller = _TestController(['a', 'b', 'c']); + final builtIndices = []; + + await tester.pumpWidget(_wrap(_buildGrid(controller, builtIndices: builtIndices))); + await tester.pump(); + + expect(find.text('item-0'), findsOneWidget); + expect(find.text('item-1'), findsOneWidget); + expect(find.text('item-2'), findsOneWidget); + expect(builtIndices, [0, 1, 2]); + }); + + testWidgets('does not render a leading item', (tester) async { + final controller = _TestController(['a']); + final builtIndices = []; + + await tester.pumpWidget(_wrap(_buildGrid(controller, builtIndices: builtIndices))); + await tester.pump(); + + expect(find.text('leading'), findsNothing); + expect(builtIndices, [0]); + }); + }); + + group('PagedValueGridView with leadingItemBuilder', () { + testWidgets('renders the leading item before the paged items', (tester) async { + final controller = _TestController(['a', 'b', 'c']); + final builtIndices = []; + + await tester.pumpWidget( + _wrap( + _buildGrid( + controller, + leadingItemBuilder: (_) => const Text('leading'), + builtIndices: builtIndices, + ), + ), + ); + await tester.pump(); + + expect(find.text('leading'), findsOneWidget); + expect(find.text('item-0'), findsOneWidget); + expect(find.text('item-1'), findsOneWidget); + expect(find.text('item-2'), findsOneWidget); + }); + + testWidgets('itemBuilder receives item indices starting at 0, not offset', (tester) async { + final controller = _TestController(['a', 'b', 'c']); + final builtIndices = []; + + await tester.pumpWidget( + _wrap( + _buildGrid( + controller, + leadingItemBuilder: (_) => const Text('leading'), + builtIndices: builtIndices, + ), + ), + ); + await tester.pump(); + + expect(builtIndices, [0, 1, 2]); + }); + + testWidgets('renders leading item even with a single paged item', (tester) async { + final controller = _TestController(['a']); + final builtIndices = []; + + await tester.pumpWidget( + _wrap( + _buildGrid( + controller, + leadingItemBuilder: (_) => const Text('leading'), + builtIndices: builtIndices, + ), + ), + ); + await tester.pump(); + + expect(find.text('leading'), findsOneWidget); + expect(find.text('item-0'), findsOneWidget); + expect(builtIndices, [0]); + }); + + testWidgets('renders load-more indicator at correct position with leading item', (tester) async { + final controller = _TestController(['item-0', 'item-1'], nextPageKey: 1); + final builtIndices = []; + + await tester.pumpWidget( + _wrap( + _buildGrid( + controller, + leadingItemBuilder: (_) => const Text('leading'), + builtIndices: builtIndices, + ), + ), + ); + await tester.pump(); + + expect(find.text('leading'), findsOneWidget); + expect(find.text('item-0'), findsOneWidget); + expect(find.text('item-1'), findsOneWidget); + expect(find.text('load-more-indicator'), findsOneWidget); + expect(builtIndices, [0, 1]); + }); + }); +} diff --git a/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart b/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart index 8f56ce1c1d..80c95ad425 100644 --- a/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart +++ b/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart @@ -145,8 +145,7 @@ void main() { when( mockClient.openConnection, ).thenAnswer((_) async => OwnUser(id: 'test-user')); - when(() => mockClient.wsConnectionStatus) - .thenReturn(ConnectionStatus.connected); + when(() => mockClient.wsConnectionStatus).thenReturn(ConnectionStatus.connected); }); tearDown(() { @@ -202,8 +201,7 @@ void main() { ).thenReturn(ConnectionStatus.disconnected); // Act - bring app to foreground - tester.binding - .handleAppLifecycleStateChanged(AppLifecycleState.resumed); + tester.binding.handleAppLifecycleStateChanged(AppLifecycleState.resumed); await tester.pumpAndSettle(); // Assert @@ -251,8 +249,7 @@ void main() { ); // Act - tester.binding - .handleAppLifecycleStateChanged(AppLifecycleState.paused); + tester.binding.handleAppLifecycleStateChanged(AppLifecycleState.paused); await tester.pumpAndSettle(); // Wait for timer to expire diff --git a/packages/stream_chat_flutter_core/test/stream_draft_list_controller_test.dart b/packages/stream_chat_flutter_core/test/stream_draft_list_controller_test.dart index ff07096332..87aa10cfa8 100644 --- a/packages/stream_chat_flutter_core/test/stream_draft_list_controller_test.dart +++ b/packages/stream_chat_flutter_core/test/stream_draft_list_controller_test.dart @@ -36,9 +36,7 @@ List generateDrafts({ final baseId = startId ?? 123; return List.generate(count, (index) { - final text = texts != null && index < texts.length - ? texts[index] - : 'Draft ${index + 1}'; + final text = texts != null && index < texts.length ? texts[index] : 'Draft ${index + 1}'; return generateDraft( channelCid: 'messaging:${baseId + index}', @@ -86,22 +84,26 @@ void main() { ..drafts = drafts ..next = ''; - when(() => client.queryDrafts( - filter: any(named: 'filter'), - sort: any(named: 'sort'), - pagination: any(named: 'pagination'), - )).thenAnswer((_) async => response); + when( + () => client.queryDrafts( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenAnswer((_) async => response); final controller = StreamDraftListController(client: client); await controller.doInitialLoad(); await pumpEventQueue(); - verify(() => client.queryDrafts( - filter: any(named: 'filter'), - sort: any(named: 'sort'), - pagination: any(named: 'pagination'), - )).called(1); + verify( + () => client.queryDrafts( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).called(1); expect(controller.value, isA>()); expect(controller.value.asSuccess.items, equals(drafts)); @@ -109,11 +111,13 @@ void main() { test('handles API exceptions by transitioning to error state', () async { final exception = Exception('API unavailable'); - when(() => client.queryDrafts( - filter: any(named: 'filter'), - sort: any(named: 'sort'), - pagination: any(named: 'pagination'), - )).thenThrow(exception); + when( + () => client.queryDrafts( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenThrow(exception); final controller = StreamDraftListController(client: client); @@ -142,11 +146,13 @@ void main() { ..drafts = additionalDrafts ..next = ''; - when(() => client.queryDrafts( - filter: any(named: 'filter'), - sort: any(named: 'sort'), - pagination: any(named: 'pagination'), - )).thenAnswer((_) async => response); + when( + () => client.queryDrafts( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenAnswer((_) async => response); final controller = StreamDraftListController.fromValue( PagedValue( @@ -171,9 +177,9 @@ void main() { for (final draft in mergedDrafts) { expect( - controller.value.asSuccess.items.any((d) => - d.channelCid == draft.channelCid && - d.message.text == draft.message.text), + controller.value.asSuccess.items.any( + (d) => d.channelCid == draft.channelCid && d.message.text == draft.message.text, + ), isTrue, ); } @@ -181,17 +187,18 @@ void main() { expect(controller.value.asSuccess.nextPageKey, isNull); }); - test('loadMore preserves existing items when API throws exception', - () async { + test('loadMore preserves existing items when API throws exception', () async { const nextKey = 'next_page_token'; final existingDrafts = generateDrafts(); final exception = Exception('Network error'); - when(() => client.queryDrafts( - filter: any(named: 'filter'), - sort: any(named: 'sort'), - pagination: any(named: 'pagination'), - )).thenThrow(exception); + when( + () => client.queryDrafts( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenThrow(exception); final controller = StreamDraftListController.fromValue( PagedValue( @@ -331,9 +338,7 @@ void main() { equals(allDrafts.length - 1), ); - final remainingDraftTexts = [ - ...controller.value.asSuccess.items.map((d) => d.message.text) - ]; + final remainingDraftTexts = [...controller.value.asSuccess.items.map((d) => d.message.text)]; expect(remainingDraftTexts, contains('Thread Draft 2')); expect(remainingDraftTexts, isNot(contains('Thread Draft 1'))); @@ -393,11 +398,13 @@ void main() { ..drafts = drafts ..next = ''; - when(() => client.queryDrafts( - filter: any(named: 'filter'), - sort: any(named: 'sort'), - pagination: any(named: 'pagination'), - )).thenAnswer((_) async => queryResponse); + when( + () => client.queryDrafts( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenAnswer((_) async => queryResponse); final controller = StreamDraftListController.fromValue( PagedValue(items: drafts), @@ -432,11 +439,13 @@ void main() { ..drafts = drafts ..next = ''; - when(() => client.queryDrafts( - filter: any(named: 'filter'), - sort: any(named: 'sort'), - pagination: any(named: 'pagination'), - )).thenAnswer((_) async => queryResponse); + when( + () => client.queryDrafts( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenAnswer((_) async => queryResponse); final controller = StreamDraftListController.fromValue( PagedValue(items: drafts), @@ -473,11 +482,13 @@ void main() { final drafts = generateDrafts(); var queryCallCount = 0; - when(() => client.queryDrafts( - filter: any(named: 'filter'), - sort: any(named: 'sort'), - pagination: any(named: 'pagination'), - )).thenAnswer((_) async { + when( + () => client.queryDrafts( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenAnswer((_) async { queryCallCount++; return QueryDraftsResponse() ..drafts = drafts @@ -512,11 +523,13 @@ void main() { ..drafts = drafts ..next = ''; - when(() => client.queryDrafts( - filter: any(named: 'filter'), - sort: any(named: 'sort'), - pagination: any(named: 'pagination'), - )).thenAnswer((_) async => queryResponse); + when( + () => client.queryDrafts( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenAnswer((_) async => queryResponse); final controller = StreamDraftListController.fromValue( PagedValue(items: drafts), @@ -569,11 +582,13 @@ void main() { ..drafts = drafts ..next = ''; - when(() => client.queryDrafts( - filter: any(named: 'filter'), - sort: any(named: 'sort'), - pagination: any(named: 'pagination'), - )).thenAnswer((_) async => response); + when( + () => client.queryDrafts( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenAnswer((_) async => response); final controller = StreamDraftListController.fromValue( PagedValue(items: drafts), @@ -599,11 +614,13 @@ void main() { final apiCalls = >[]; - when(() => client.queryDrafts( - filter: any(named: 'filter'), - sort: any(named: 'sort'), - pagination: any(named: 'pagination'), - )).thenAnswer((invocation) async { + when( + () => client.queryDrafts( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenAnswer((invocation) async { apiCalls.add({ 'filter': invocation.namedArguments[const Symbol('filter')], 'sort': invocation.namedArguments[const Symbol('sort')], @@ -647,18 +664,22 @@ void main() { final apiCalls = >[]; - when(() => client.queryDrafts( - filter: any(named: 'filter'), - sort: any(named: 'sort'), - pagination: any(named: 'pagination'), - )).thenAnswer((invocation) { + when( + () => client.queryDrafts( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenAnswer((invocation) { apiCalls.add({ 'filter': invocation.namedArguments[const Symbol('filter')], 'sort': invocation.namedArguments[const Symbol('sort')], }); - return Future.value(QueryDraftsResponse() - ..drafts = drafts - ..next = ''); + return Future.value( + QueryDraftsResponse() + ..drafts = drafts + ..next = '', + ); }); final controller = StreamDraftListController( @@ -688,8 +709,7 @@ void main() { group('Disposal', () { test('dispose cancels subscriptions without errors', () { - final controller = StreamDraftListController(client: client) - ..doInitialLoad(); + final controller = StreamDraftListController(client: client)..doInitialLoad(); expect(controller.dispose, returnsNormally); }); diff --git a/packages/stream_chat_flutter_core/test/stream_message_input_controller_test.dart b/packages/stream_chat_flutter_core/test/stream_message_input_controller_test.dart index ba1eadfa9f..f3cae092ca 100644 --- a/packages/stream_chat_flutter_core/test/stream_message_input_controller_test.dart +++ b/packages/stream_chat_flutter_core/test/stream_message_input_controller_test.dart @@ -241,12 +241,10 @@ void main() { }); test('setOGAttachment replaces existing OG attachment', () { - final oldOGAttachment = - Attachment(ogScrapeUrl: 'https://old.example.com'); + final oldOGAttachment = Attachment(ogScrapeUrl: 'https://old.example.com'); controller.addAttachment(oldOGAttachment); - final newOGAttachment = - Attachment(ogScrapeUrl: 'https://new.example.com'); + final newOGAttachment = Attachment(ogScrapeUrl: 'https://new.example.com'); controller.setOGAttachment(newOGAttachment); expect(controller.attachments.length, 1); @@ -382,6 +380,63 @@ void main() { }); }); + group('Edit Message', () { + test('editMessage sets state to MessageState.updating', () { + final existingMessage = Message(id: 'msg-1', text: 'Original text'); + controller.editMessage(existingMessage); + + expect(controller.message.state.isInitial, isFalse); + expect(controller.message.state.isUpdating, isTrue); + }); + + test('editMessage preserves the message id and text', () { + final existingMessage = Message(id: 'msg-1', text: 'Original text'); + controller.editMessage(existingMessage); + + expect(controller.message.id, 'msg-1'); + expect(controller.message.text, 'Original text'); + }); + + test('editMessage stores original in editingOriginalMessage', () { + final existingMessage = Message(id: 'msg-1', text: 'Original text'); + controller.editMessage(existingMessage); + + expect(controller.editingOriginalMessage, isNotNull); + expect(controller.editingOriginalMessage!.id, 'msg-1'); + expect(controller.editingOriginalMessage!.text, 'Original text'); + }); + + test('editingOriginalMessage text is not affected by subsequent typing', () { + final existingMessage = Message(id: 'msg-1', text: 'Original text'); + controller.editMessage(existingMessage); + + controller.text = 'Edited text'; + + expect(controller.editingOriginalMessage!.text, 'Original text'); + expect(controller.message.text, 'Edited text'); + }); + + test('cancelEditMessage clears editingOriginalMessage', () { + final existingMessage = Message(id: 'msg-1', text: 'Original text'); + controller.editMessage(existingMessage); + + controller.cancelEditMessage(); + + expect(controller.editingOriginalMessage, isNull); + }); + + test('cancelEditMessage resets controller to empty state, not the edited message', () { + final existingMessage = Message(id: 'msg-1', text: 'Original text'); + controller.editMessage(existingMessage); + controller.text = 'Edited text'; + + controller.cancelEditMessage(); + + expect(controller.text, isEmpty); + expect(controller.message.state.isInitial, isTrue); + }); + }); + group('Reset and Clear', () { test('clear resets the message to empty state', () { controller.text = 'Some text'; @@ -501,8 +556,7 @@ class _RestorableWidget extends StatefulWidget { State<_RestorableWidget> createState() => _RestorableWidgetState(); } -class _RestorableWidgetState extends State<_RestorableWidget> - with RestorationMixin { +class _RestorableWidgetState extends State<_RestorableWidget> with RestorationMixin { final controller = StreamRestorableMessageInputController(); @override diff --git a/packages/stream_chat_flutter_core/test/stream_message_reminder_list_controller_test.dart b/packages/stream_chat_flutter_core/test/stream_message_reminder_list_controller_test.dart index 86d6767df1..cbbd5924b6 100644 --- a/packages/stream_chat_flutter_core/test/stream_message_reminder_list_controller_test.dart +++ b/packages/stream_chat_flutter_core/test/stream_message_reminder_list_controller_test.dart @@ -48,15 +48,9 @@ List generateMessageReminders({ final channelCid = channelCids != null && index < channelCids.length ? channelCids[index] : 'messaging:${baseId + index}'; - final messageId = messageIds != null && index < messageIds.length - ? messageIds[index] - : 'message_${baseId + index}'; - final userId = userIds != null && index < userIds.length - ? userIds[index] - : 'user_${baseId + index}'; - final text = texts != null && index < texts.length - ? texts[index] - : 'Reminder ${index + 1}'; + final messageId = messageIds != null && index < messageIds.length ? messageIds[index] : 'message_${baseId + index}'; + final userId = userIds != null && index < userIds.length ? userIds[index] : 'user_${baseId + index}'; + final text = texts != null && index < texts.length ? texts[index] : 'Reminder ${index + 1}'; return generateMessageReminder( channelCid: channelCid, @@ -110,22 +104,26 @@ void main() { ..reminders = reminders ..next = null; - when(() => client.queryReminders( - filter: any(named: 'filter'), - sort: any(named: 'sort'), - pagination: any(named: 'pagination'), - )).thenAnswer((_) async => response); + when( + () => client.queryReminders( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenAnswer((_) async => response); final controller = StreamMessageReminderListController(client: client); await controller.doInitialLoad(); await pumpEventQueue(); - verify(() => client.queryReminders( - filter: any(named: 'filter'), - sort: any(named: 'sort'), - pagination: any(named: 'pagination'), - )).called(1); + verify( + () => client.queryReminders( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).called(1); expect(controller.value, isA>()); expect(controller.value.asSuccess.items, equals(reminders)); @@ -133,11 +131,13 @@ void main() { test('handles StreamChatError exceptions properly', () async { const chatError = StreamChatError('Network error'); - when(() => client.queryReminders( - filter: any(named: 'filter'), - sort: any(named: 'sort'), - pagination: any(named: 'pagination'), - )).thenThrow(chatError); + when( + () => client.queryReminders( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenThrow(chatError); final controller = StreamMessageReminderListController(client: client); @@ -166,11 +166,13 @@ void main() { ..reminders = additionalReminders ..next = null; - when(() => client.queryReminders( - filter: any(named: 'filter'), - sort: any(named: 'sort'), - pagination: any(named: 'pagination'), - )).thenAnswer((_) async => response); + when( + () => client.queryReminders( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenAnswer((_) async => response); final controller = StreamMessageReminderListController.fromValue( PagedValue( @@ -198,11 +200,13 @@ void main() { final existingReminders = generateMessageReminders(); const chatError = StreamChatError('Network error'); - when(() => client.queryReminders( - filter: any(named: 'filter'), - sort: any(named: 'sort'), - pagination: any(named: 'pagination'), - )).thenThrow(chatError); + when( + () => client.queryReminders( + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenThrow(chatError); final controller = StreamMessageReminderListController.fromValue( PagedValue( @@ -275,9 +279,7 @@ void main() { ); }); - test( - 'deleteReminder removes reminder and returns true when reminder exists', - () { + test('deleteReminder removes reminder and returns true when reminder exists', () { final reminders = generateMessageReminders(); final controller = StreamMessageReminderListController.fromValue( PagedValue(items: reminders), @@ -292,9 +294,9 @@ void main() { equals(reminders.length - 1), ); expect( - controller.value.asSuccess.items.any((r) => - r.messageId == reminders[0].messageId && - r.userId == reminders[0].userId), + controller.value.asSuccess.items.any( + (r) => r.messageId == reminders[0].messageId && r.userId == reminders[0].userId, + ), isFalse, ); }); @@ -438,9 +440,7 @@ void main() { expect( controller.value.asSuccess.items.any( - (r) => - r.messageId == initialReminders[0].messageId && - r.userId == initialReminders[0].userId, + (r) => r.messageId == initialReminders[0].messageId && r.userId == initialReminders[0].userId, ), isFalse, ); diff --git a/packages/stream_chat_flutter_core/test/stream_poll_controller_test.dart b/packages/stream_chat_flutter_core/test/stream_poll_controller_test.dart index ed5282eb63..408b4c9c29 100644 --- a/packages/stream_chat_flutter_core/test/stream_poll_controller_test.dart +++ b/packages/stream_chat_flutter_core/test/stream_poll_controller_test.dart @@ -11,10 +11,13 @@ void main() { }); test('Initialization with Custom Poll and Config', () { - final poll = Poll(name: 'Initial Poll', options: const [ - PollOption(text: 'Option 1'), - PollOption(text: 'Option 2'), - ]); + final poll = Poll( + name: 'Initial Poll', + options: const [ + PollOption(text: 'Option 1'), + PollOption(text: 'Option 2'), + ], + ); const config = PollConfig(nameRange: (min: 2, max: 50)); final pollController = StreamPollController(poll: poll, config: config); @@ -101,10 +104,7 @@ void main() { final errors = pollController.validateGranularly(); expect(errors.isEmpty, isFalse); - final containsNameRangeError = errors - .map((e) => e.mapOrNull(nameRange: (e) => e)) - .nonNulls - .isNotEmpty; + final containsNameRangeError = errors.map((e) => e.mapOrNull(nameRange: (e) => e)).nonNulls.isNotEmpty; expect(containsNameRangeError, isTrue); }); @@ -117,10 +117,7 @@ void main() { final errors = pollController.validateGranularly(); expect(errors.isEmpty, isFalse); - final containsDuplicateOptions = errors - .map((e) => e.mapOrNull(duplicateOptions: (e) => e)) - .nonNulls - .isNotEmpty; + final containsDuplicateOptions = errors.map((e) => e.mapOrNull(duplicateOptions: (e) => e)).nonNulls.isNotEmpty; expect(containsDuplicateOptions, isTrue); }); @@ -130,10 +127,7 @@ void main() { final errors = pollController.validateGranularly(); expect(errors.isEmpty, isFalse); - final containsOptionsRangeError = errors - .map((e) => e.mapOrNull(optionsRange: (e) => e)) - .nonNulls - .isNotEmpty; + final containsOptionsRangeError = errors.map((e) => e.mapOrNull(optionsRange: (e) => e)).nonNulls.isNotEmpty; expect(containsOptionsRangeError, isTrue); }); @@ -238,10 +232,7 @@ void main() { )..question = 'A' * 200; final errors = pollController.validateGranularly(); - final containsNameRangeError = errors - .map((e) => e.mapOrNull(nameRange: (e) => e)) - .nonNulls - .isNotEmpty; + final containsNameRangeError = errors.map((e) => e.mapOrNull(nameRange: (e) => e)).nonNulls.isNotEmpty; expect(containsNameRangeError, isFalse); }); @@ -256,10 +247,7 @@ void main() { } final errors = pollController.validateGranularly(); - final containsOptionsRangeError = errors - .map((e) => e.mapOrNull(optionsRange: (e) => e)) - .nonNulls - .isNotEmpty; + final containsOptionsRangeError = errors.map((e) => e.mapOrNull(optionsRange: (e) => e)).nonNulls.isNotEmpty; expect(containsOptionsRangeError, isFalse); }); diff --git a/packages/stream_chat_flutter_core/test/stream_reaction_list_controller_test.dart b/packages/stream_chat_flutter_core/test/stream_reaction_list_controller_test.dart new file mode 100644 index 0000000000..983d82b09b --- /dev/null +++ b/packages/stream_chat_flutter_core/test/stream_reaction_list_controller_test.dart @@ -0,0 +1,579 @@ +// ignore_for_file: avoid_redundant_argument_values + +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:stream_chat/stream_chat.dart' hide Success; +import 'package:stream_chat_flutter_core/src/paged_value_notifier.dart'; +import 'package:stream_chat_flutter_core/src/stream_reaction_list_controller.dart'; + +import 'mocks.dart'; + +Reaction generateReaction({ + String? messageId, + String? type, + String? userId, + DateTime? createdAt, +}) { + return Reaction( + messageId: messageId ?? 'message_123', + type: type ?? 'like', + userId: userId ?? 'user_123', + createdAt: createdAt ?? DateTime.now(), + ); +} + +List generateReactions({ + int count = 2, + String? messageId, + List? types, + List? userIds, + int? startId, +}) { + final now = DateTime.now(); + final baseId = startId ?? 1; + + return List.generate(count, (index) { + final type = types != null && index < types.length ? types[index] : 'like'; + final userId = userIds != null && index < userIds.length ? userIds[index] : 'user_${baseId + index}'; + + return generateReaction( + messageId: messageId ?? 'message_123', + type: type, + userId: userId, + createdAt: now.subtract(Duration(minutes: index)), + ); + }); +} + +void main() { + const messageId = 'message_123'; + + final client = MockClient(); + + setUpAll(() { + registerFallbackValue(const PaginationParams()); + registerFallbackValue(Filter.equal('type', 'like')); + }); + + setUp(() { + when(client.on).thenAnswer((_) => const Stream.empty()); + }); + + tearDown(() { + reset(client); + }); + + group('Initialization', () { + test('should start in loading state when created with client', () { + final controller = StreamReactionListController( + client: client, + messageId: messageId, + ); + expect(controller.value, isA()); + }); + + test('should preserve provided value when created with fromValue', () { + final reactions = generateReactions(); + final value = PagedValue(items: reactions); + final controller = StreamReactionListController.fromValue( + value, + client: client, + messageId: messageId, + ); + + expect(controller.value, same(value)); + expect(controller.value.asSuccess.items, equals(reactions)); + }); + }); + + group('Initial loading', () { + test('successfully loads reactions from API', () async { + final reactions = generateReactions(); + final response = QueryReactionsResponse() + ..reactions = reactions + ..next = null; + + when( + () => client.queryReactions( + any(), + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenAnswer((_) async => response); + + final controller = StreamReactionListController( + client: client, + messageId: messageId, + ); + + await controller.doInitialLoad(); + await pumpEventQueue(); + + verify( + () => client.queryReactions( + messageId, + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).called(1); + + expect(controller.value, isA>()); + expect(controller.value.asSuccess.items, equals(reactions)); + }); + + test('sets next page key when API returns next cursor', () async { + const nextCursor = 'next_cursor_token'; + final reactions = generateReactions(); + final response = QueryReactionsResponse() + ..reactions = reactions + ..next = nextCursor; + + when( + () => client.queryReactions( + any(), + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenAnswer((_) async => response); + + final controller = StreamReactionListController( + client: client, + messageId: messageId, + ); + + await controller.doInitialLoad(); + await pumpEventQueue(); + + expect(controller.value.asSuccess.nextPageKey, equals(nextCursor)); + }); + + test('sets null next page key when API returns empty next cursor', () async { + final reactions = generateReactions(); + final response = QueryReactionsResponse() + ..reactions = reactions + ..next = ''; + + when( + () => client.queryReactions( + any(), + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenAnswer((_) async => response); + + final controller = StreamReactionListController( + client: client, + messageId: messageId, + ); + + await controller.doInitialLoad(); + await pumpEventQueue(); + + expect(controller.value.asSuccess.nextPageKey, isNull); + }); + + test('handles StreamChatError by transitioning to error state', () async { + const chatError = StreamChatError('Network error'); + + when( + () => client.queryReactions( + any(), + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenThrow(chatError); + + final controller = StreamReactionListController( + client: client, + messageId: messageId, + ); + + await controller.doInitialLoad(); + await pumpEventQueue(); + + expect(controller.value, isA()); + expect((controller.value as Error).error, equals(chatError)); + }); + + test('wraps generic exceptions in StreamChatError', () async { + final exception = Exception('API unavailable'); + + when( + () => client.queryReactions( + any(), + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenThrow(exception); + + final controller = StreamReactionListController( + client: client, + messageId: messageId, + ); + + await controller.doInitialLoad(); + await pumpEventQueue(); + + expect(controller.value, isA()); + expect( + (controller.value as Error).error.message, + contains('API unavailable'), + ); + }); + }); + + group('Pagination', () { + test('loadMore appends new reactions to existing items', () async { + const nextKey = 'next_page_token'; + final existingReactions = generateReactions(); + final additionalReactions = generateReactions( + count: 1, + userIds: ['user_999'], + startId: 999, + ); + + final response = QueryReactionsResponse() + ..reactions = additionalReactions + ..next = null; + + when( + () => client.queryReactions( + any(), + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenAnswer((_) async => response); + + final controller = StreamReactionListController.fromValue( + PagedValue( + items: existingReactions, + nextPageKey: nextKey, + ), + client: client, + messageId: messageId, + ); + + await controller.loadMore(nextKey); + await pumpEventQueue(); + + final mergedReactions = [...existingReactions, ...additionalReactions]; + + expect( + controller.value.asSuccess.items.length, + equals(mergedReactions.length), + ); + expect(controller.value.asSuccess.nextPageKey, isNull); + }); + + test('loadMore passes next cursor to API', () async { + const nextKey = 'cursor_page_2'; + final existingReactions = generateReactions(); + + final response = QueryReactionsResponse() + ..reactions = [] + ..next = null; + + PaginationParams? capturedPagination; + when( + () => client.queryReactions( + any(), + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenAnswer((invocation) async { + capturedPagination = invocation.namedArguments[const Symbol('pagination')] as PaginationParams?; + return response; + }); + + final controller = StreamReactionListController.fromValue( + PagedValue( + items: existingReactions, + nextPageKey: nextKey, + ), + client: client, + messageId: messageId, + ); + + await controller.loadMore(nextKey); + await pumpEventQueue(); + + expect(capturedPagination?.next, equals(nextKey)); + }); + + test('loadMore preserves existing items on StreamChatError', () async { + const nextKey = 'next_page_token'; + final existingReactions = generateReactions(); + const chatError = StreamChatError('Network error'); + + when( + () => client.queryReactions( + any(), + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenThrow(chatError); + + final controller = StreamReactionListController.fromValue( + PagedValue( + items: existingReactions, + nextPageKey: nextKey, + ), + client: client, + messageId: messageId, + ); + + await controller.loadMore(nextKey); + await pumpEventQueue(); + + expect(controller.value.isSuccess, isTrue); + expect(controller.value.asSuccess.items, equals(existingReactions)); + expect(controller.value.asSuccess.error, equals(chatError)); + }); + + test('loadMore preserves existing items on generic error', () async { + const nextKey = 'next_page_token'; + final existingReactions = generateReactions(); + final exception = Exception('Network error'); + + when( + () => client.queryReactions( + any(), + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenThrow(exception); + + final controller = StreamReactionListController.fromValue( + PagedValue( + items: existingReactions, + nextPageKey: nextKey, + ), + client: client, + messageId: messageId, + ); + + await controller.loadMore(nextKey); + await pumpEventQueue(); + + expect(controller.value.isSuccess, isTrue); + expect(controller.value.asSuccess.items, equals(existingReactions)); + expect(controller.value.asSuccess.error, isNotNull); + expect( + controller.value.asSuccess.error!.message, + contains('Network error'), + ); + }); + }); + + group('reactions setter', () { + test('replaces reactions when in success state', () { + final initial = generateReactions(count: 3); + final replacement = generateReactions(count: 1, userIds: ['user_new']); + + final controller = StreamReactionListController.fromValue( + PagedValue(items: initial), + client: client, + messageId: messageId, + ); + + expect(controller.value.isSuccess, isTrue); + expect(controller.value.asSuccess.items, equals(initial)); + + controller.reactions = replacement; + + expect(controller.value.asSuccess.items, equals(replacement)); + expect(controller.value.asSuccess.items.length, equals(1)); + }); + + test('creates new success value when not in success state', () { + final reactions = generateReactions(); + final controller = StreamReactionListController( + client: client, + messageId: messageId, + ); + + // Controller is in loading state + expect(controller.value.isNotSuccess, isTrue); + + controller.reactions = reactions; + + expect(controller.value.isSuccess, isTrue); + expect(controller.value.asSuccess.items, equals(reactions)); + }); + + test('preserves nextPageKey when replacing reactions', () { + const nextKey = 'next_cursor'; + final initial = generateReactions(); + final replacement = generateReactions(count: 1, userIds: ['user_new']); + + final controller = StreamReactionListController.fromValue( + PagedValue(items: initial, nextPageKey: nextKey), + client: client, + messageId: messageId, + ); + + expect(controller.value.isSuccess, isTrue); + expect(controller.value.asSuccess.items, equals(initial)); + expect(controller.value.asSuccess.nextPageKey, equals(nextKey)); + + controller.reactions = replacement; + + expect(controller.value.asSuccess.items, equals(replacement)); + expect(controller.value.asSuccess.nextPageKey, equals(nextKey)); + }); + }); + + group('Filtering and sorting', () { + test('refresh resets filter and sort to initial values', () async { + final reactions = generateReactions(); + final initialFilter = Filter.equal('type', 'like'); + final sort = [const SortOption.desc(ReactionSortKey.createdAt)]; + + final apiCalls = >[]; + + when( + () => client.queryReactions( + any(), + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenAnswer((invocation) async { + apiCalls.add({ + 'filter': invocation.namedArguments[const Symbol('filter')], + 'sort': invocation.namedArguments[const Symbol('sort')], + }); + return QueryReactionsResponse() + ..reactions = reactions + ..next = null; + }); + + final controller = StreamReactionListController( + client: client, + messageId: messageId, + filter: initialFilter, + sort: sort, + ); + + await controller.doInitialLoad(); + await pumpEventQueue(); + + // Change filter and sort at runtime + controller + ..filter = Filter.equal('type', 'love') + ..sort = [const SortOption.asc(ReactionSortKey.createdAt)]; + + await controller.refresh(); + await pumpEventQueue(); + + expect(apiCalls.length, equals(2)); + + final refreshCall = apiCalls.last; + expect(refreshCall['filter'], equals(initialFilter)); + expect(refreshCall['sort'], equals(sort)); + }); + + test('refresh with resetValue=false preserves current filter and sort', () async { + final reactions = generateReactions(); + final initialFilter = Filter.equal('type', 'like'); + final initialSort = [const SortOption.desc(ReactionSortKey.createdAt)]; + final newFilter = Filter.equal('type', 'love'); + final newSort = [const SortOption.asc(ReactionSortKey.createdAt)]; + + final apiCalls = >[]; + + when( + () => client.queryReactions( + any(), + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenAnswer((invocation) async { + apiCalls.add({ + 'filter': invocation.namedArguments[const Symbol('filter')], + 'sort': invocation.namedArguments[const Symbol('sort')], + }); + return QueryReactionsResponse() + ..reactions = reactions + ..next = null; + }); + + final controller = StreamReactionListController( + client: client, + messageId: messageId, + filter: initialFilter, + sort: initialSort, + ); + + await controller.doInitialLoad(); + await pumpEventQueue(); + + controller + ..filter = newFilter + ..sort = newSort; + + await controller.refresh(resetValue: false); + await pumpEventQueue(); + + expect(apiCalls.length, equals(2)); + + final refreshCall = apiCalls.last; + expect(refreshCall['filter'], equals(newFilter)); + expect(refreshCall['sort'], equals(newSort)); + }); + + test('value setter sorts items when sort is provided', () async { + final now = DateTime.now(); + final older = generateReaction(userId: 'user_1', createdAt: now.subtract(const Duration(hours: 1))); + final newer = generateReaction(userId: 'user_2', createdAt: now); + + final response = QueryReactionsResponse() + ..reactions = [older, newer] + ..next = null; + + when( + () => client.queryReactions( + any(), + filter: any(named: 'filter'), + sort: any(named: 'sort'), + pagination: any(named: 'pagination'), + ), + ).thenAnswer((_) async => response); + + final controller = StreamReactionListController( + client: client, + messageId: messageId, + sort: [const SortOption.desc(ReactionSortKey.createdAt)], + ); + + await controller.doInitialLoad(); + await pumpEventQueue(); + + // desc order: newer first + expect(controller.value.asSuccess.items.first.userId, equals('user_2')); + expect(controller.value.asSuccess.items.last.userId, equals('user_1')); + }); + }); + + group('Disposal', () { + test('dispose completes without errors', () { + final controller = StreamReactionListController( + client: client, + messageId: messageId, + )..doInitialLoad(); + + expect(controller.dispose, returnsNormally); + }); + }); +} diff --git a/packages/stream_chat_flutter_core/test/stream_thread_list_event_handler_test.dart b/packages/stream_chat_flutter_core/test/stream_thread_list_event_handler_test.dart index 2dd163d6df..f4608b16f1 100644 --- a/packages/stream_chat_flutter_core/test/stream_thread_list_event_handler_test.dart +++ b/packages/stream_chat_flutter_core/test/stream_thread_list_event_handler_test.dart @@ -5,8 +5,7 @@ import 'package:stream_chat_flutter_core/src/stream_thread_list_controller.dart' import 'package:stream_chat_flutter_core/src/stream_thread_list_event_handler.dart'; // Mock classes -class MockStreamThreadListController extends Mock - implements StreamThreadListController {} +class MockStreamThreadListController extends Mock implements StreamThreadListController {} class MockEvent extends Mock implements Event {} @@ -83,8 +82,7 @@ void main() { () { when(() => mockEvent.message).thenReturn(mockMessage); when(() => mockMessage.parentId).thenReturn('parent-id'); - when(() => mockController.getThread( - parentMessageId: any(named: 'parentMessageId'))).thenReturn(null); + when(() => mockController.getThread(parentMessageId: any(named: 'parentMessageId'))).thenReturn(null); handler.onNotificationThreadMessageNew(mockEvent, mockController); verify(() => mockController.addUnseenThreadId('parent-id')); @@ -108,12 +106,10 @@ void main() { when(() => mockMessage.parentId).thenReturn(null); when(() => mockEvent.message).thenReturn(mockMessage); when(() => mockEvent.hardDelete).thenReturn(true); - when(() => mockController.deleteThread(parentMessageId: 'message-id')) - .thenReturn(true); + when(() => mockController.deleteThread(parentMessageId: 'message-id')).thenReturn(true); handler.onMessageDeleted(mockEvent, mockController); - verify( - () => mockController.deleteThread(parentMessageId: 'message-id')); + verify(() => mockController.deleteThread(parentMessageId: 'message-id')); verifyNever(() => mockController.deleteReply(any())); }, ); @@ -178,27 +174,23 @@ void main() { when(() => mockEvent.cid).thenReturn('channel-cid'); handler.onChannelDeleted(mockEvent, mockController); - verify(() => - mockController.deleteThreadByChannelCid(channelCid: 'channel-cid')); + verify(() => mockController.deleteThreadByChannelCid(channelCid: 'channel-cid')); }); test('onChannelTruncated deletes threads by channel cid', () { when(() => mockEvent.cid).thenReturn('channel-cid'); handler.onChannelTruncated(mockEvent, mockController); - verify(() => - mockController.deleteThreadByChannelCid(channelCid: 'channel-cid')); + verify(() => mockController.deleteThreadByChannelCid(channelCid: 'channel-cid')); }); test('onMessageRead marks thread as read', () { - when(() => mockThread.copyWith(read: any(named: 'read'))) - .thenReturn(mockThread); + when(() => mockThread.copyWith(read: any(named: 'read'))).thenReturn(mockThread); when(() => mockThread.parentMessageId).thenReturn('parent-id'); when(() => mockEvent.thread).thenReturn(mockThread); when(() => mockEvent.user).thenReturn(mockUser); when(() => mockEvent.createdAt).thenReturn(DateTime.now()); - when(() => mockController.getThread(parentMessageId: 'parent-id')) - .thenReturn(mockThread); + when(() => mockController.getThread(parentMessageId: 'parent-id')).thenReturn(mockThread); when(() => mockController.updateThread(mockThread)).thenReturn(true); handler.onMessageRead(mockEvent, mockController); @@ -208,14 +200,12 @@ void main() { }); test('onNotificationMarkUnread marks thread as unread', () { - when(() => mockThread.copyWith(read: any(named: 'read'))) - .thenReturn(mockThread); + when(() => mockThread.copyWith(read: any(named: 'read'))).thenReturn(mockThread); when(() => mockThread.parentMessageId).thenReturn('parent-id'); when(() => mockEvent.thread).thenReturn(mockThread); when(() => mockEvent.user).thenReturn(mockUser); when(() => mockEvent.createdAt).thenReturn(DateTime.now()); - when(() => mockController.getThread(parentMessageId: 'parent-id')) - .thenReturn(mockThread); + when(() => mockController.getThread(parentMessageId: 'parent-id')).thenReturn(mockThread); when(() => mockController.updateThread(mockThread)).thenReturn(true); handler.onNotificationMarkUnread(mockEvent, mockController); diff --git a/packages/stream_chat_localizations/example/lib/add_new_lang.dart b/packages/stream_chat_localizations/example/lib/add_new_lang.dart index c9592d4b71..36666ffdf2 100644 --- a/packages/stream_chat_localizations/example/lib/add_new_lang.dart +++ b/packages/stream_chat_localizations/example/lib/add_new_lang.dart @@ -3,16 +3,14 @@ import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_localizations/stream_chat_localizations.dart'; -class _NnStreamChatLocalizationsDelegate - extends LocalizationsDelegate { +class _NnStreamChatLocalizationsDelegate extends LocalizationsDelegate { const _NnStreamChatLocalizationsDelegate(); @override bool isSupported(Locale locale) => locale.languageCode == 'nn'; @override - Future load(Locale locale) => - SynchronousFuture(const NnStreamChatLocalizations()); + Future load(Locale locale) => SynchronousFuture(const NnStreamChatLocalizations()); @override bool shouldReload(_NnStreamChatLocalizationsDelegate old) => false; @@ -73,8 +71,7 @@ class NnStreamChatLocalizations extends GlobalStreamChatLocalizations { String attachmentsUploadProgressText({ required int remaining, required int total, - }) => - 'Uploading $remaining/$total ...'; + }) => 'Uploading $remaining/$total ...'; @override String pinnedByUserText({ @@ -87,8 +84,7 @@ class NnStreamChatLocalizations extends GlobalStreamChatLocalizations { } @override - String get sendMessagePermissionError => - "You don't have permission to send messages"; + String get sendMessagePermissionError => "You don't have permission to send messages"; @override String get emptyMessagesText => 'There are no messages currently'; @@ -122,8 +118,8 @@ class NnStreamChatLocalizations extends GlobalStreamChatLocalizations { @override String threadSeparatorText(int replyCount) { - if (replyCount == 1) return '1 Reply'; - return '$replyCount Replies'; + if (replyCount == 1) return '1 reply'; + return '$replyCount replies'; } @override @@ -136,7 +132,7 @@ class NnStreamChatLocalizations extends GlobalStreamChatLocalizations { String get reconnectingLabel => 'Reconnecting...'; @override - String get alsoSendAsDirectMessageLabel => 'Also send as direct message'; + String get alsoSendAsDirectMessageLabel => 'Also send in Channel'; @override String get addACommentOrSendLabel => 'Add a comment or send'; @@ -161,8 +157,7 @@ class NnStreamChatLocalizations extends GlobalStreamChatLocalizations { 'The file is too large to upload. The file size limit is $limitInMB MB.'; @override - String get couldNotReadBytesFromFileError => - 'Could not read bytes from file.'; + String get couldNotReadBytesFromFileError => 'Could not read bytes from file.'; @override String get addAFileLabel => 'Add a file'; @@ -189,7 +184,7 @@ class NnStreamChatLocalizations extends GlobalStreamChatLocalizations { String get somethingWentWrongError => 'Something went wrong'; @override - String get addMoreFilesLabel => 'Add more files'; + String get addMoreFilesLabel => 'Add more'; @override String get enablePhotoAndVideoAccessMessage => @@ -217,8 +212,7 @@ class NnStreamChatLocalizations extends GlobalStreamChatLocalizations { String get flagMessageSuccessfulLabel => 'Message flagged'; @override - String get flagMessageSuccessfulText => - 'The message has been reported to a moderator.'; + String get flagMessageSuccessfulText => 'The message has been reported to a moderator.'; @override String get deleteLabel => 'DELETE'; @@ -227,12 +221,10 @@ class NnStreamChatLocalizations extends GlobalStreamChatLocalizations { String get deleteMessageLabel => 'Delete Message'; @override - String get deleteMessageQuestion => - 'Are you sure you want to permanently delete this\nmessage?'; + String get deleteMessageQuestion => 'Are you sure you want to permanently delete this\nmessage?'; @override - String get operationCouldNotBeCompletedText => - "The operation couldn't be completed."; + String get operationCouldNotBeCompletedText => "The operation couldn't be completed."; @override String get replyLabel => 'Reply'; @@ -302,8 +294,7 @@ class NnStreamChatLocalizations extends GlobalStreamChatLocalizations { String get letsStartChattingLabel => 'Let’s start chatting!'; @override - String get sendingFirstMessageLabel => - 'How about sending your first message to a friend?'; + String get sendingFirstMessageLabel => 'How about sending your first message to a friend?'; @override String get startAChatLabel => 'Start a chat'; @@ -315,8 +306,7 @@ class NnStreamChatLocalizations extends GlobalStreamChatLocalizations { String get deleteConversationLabel => 'Delete Conversation'; @override - String get deleteConversationQuestion => - 'Are you sure you want to delete this conversation?'; + String get deleteConversationQuestion => 'Are you sure you want to delete this conversation?'; @override String get streamChatLabel => 'Stream Chat'; @@ -355,8 +345,7 @@ class NnStreamChatLocalizations extends GlobalStreamChatLocalizations { String get leaveConversationLabel => 'Leave conversation'; @override - String get leaveConversationQuestion => - 'Are you sure you want to leave this conversation?'; + String get leaveConversationQuestion => 'Are you sure you want to leave this conversation?'; @override String get showInChatLabel => 'Show in Chat'; @@ -392,8 +381,7 @@ class NnStreamChatLocalizations extends GlobalStreamChatLocalizations { String galleryPaginationText({ required int currentPage, required int totalPages, - }) => - '$currentPage of $totalPages'; + }) => '$currentPage of $totalPages'; @override String get fileText => 'File'; @@ -402,8 +390,7 @@ class NnStreamChatLocalizations extends GlobalStreamChatLocalizations { String get replyToMessageLabel => 'Reply to Message'; @override - String attachmentLimitExceedError(int limit) => - 'Attachment limit exceeded, limit: $limit'; + String attachmentLimitExceedError(int limit) => 'Attachment limit exceeded, limit: $limit'; @override String get slowModeOnLabel => 'Slow mode ON'; @@ -457,8 +444,7 @@ class NnStreamChatLocalizations extends GlobalStreamChatLocalizations { } @override - String get linkDisabledDetails => - 'Sending links is not allowed in this conversation.'; + String get linkDisabledDetails => 'Sending links is not allowed in this conversation.'; @override String get linkDisabledError => 'Links are disabled'; @@ -578,15 +564,13 @@ class NnStreamChatLocalizations extends GlobalStreamChatLocalizations { String get enterYourCommentLabel => 'Enter your comment'; @override - String get endVoteConfirmationText => - 'Are you sure you want to end the poll?'; + String get endVoteConfirmationText => 'Are you sure you want to end the poll?'; @override String get deletePollOptionLabel => 'Delete Option'; @override - String get deletePollOptionQuestion => - 'Are you sure you want to delete this option?'; + String get deletePollOptionQuestion => 'Are you sure you want to delete this option?'; @override String get createLabel => 'Create'; @@ -630,10 +614,10 @@ class NnStreamChatLocalizations extends GlobalStreamChatLocalizations { @override String voteCountLabel({int? count}) => switch (count) { - null || < 1 => '0 votes', - 1 => '1 vote', - _ => '$count votes', - }; + null || < 1 => '0 votes', + 1 => '1 vote', + _ => '$count votes', + }; @override String get noPollVotesLabel => 'There are no poll votes currently'; @@ -660,8 +644,7 @@ class NnStreamChatLocalizations extends GlobalStreamChatLocalizations { String get sendAnywayLabel => 'Send Anyway'; @override - String get moderatedMessageBlockedText => - 'Message was blocked by moderation policies'; + String get moderatedMessageBlockedText => 'Message was blocked by moderation policies'; @override String get moderationReviewModalTitle => 'Are you sure?'; @@ -705,6 +688,24 @@ class NnStreamChatLocalizations extends GlobalStreamChatLocalizations { if (isLive) return '📍 Live Location'; return '📍 Location'; } + + @override + String get fileAttachmentText => 'File'; + + @override + String filesAttachmentCountText(int count) { + return count == 1 ? 'File' : '$count files'; + } + + @override + String photosAttachmentCountText(int count) { + return count == 1 ? 'Photo' : '$count photos'; + } + + @override + String videosAttachmentCountText(int count) { + return count == 1 ? 'Video' : '$count videos'; + } } void main() async { diff --git a/packages/stream_chat_localizations/example/lib/main.dart b/packages/stream_chat_localizations/example/lib/main.dart index 22abb65fab..0d1b4fbd23 100644 --- a/packages/stream_chat_localizations/example/lib/main.dart +++ b/packages/stream_chat_localizations/example/lib/main.dart @@ -64,32 +64,32 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) => MaterialApp( - theme: ThemeData.light(), - darkTheme: ThemeData.dark(), - // Add all the supported locales - supportedLocales: const [ - Locale('en'), - Locale('hi'), - Locale('fr'), - Locale('it'), - Locale('es'), - Locale('ja'), - Locale('ko'), - Locale('pt'), - ], - // Add GlobalStreamChatLocalizations.delegates - localizationsDelegates: GlobalStreamChatLocalizations.delegates, - // Programatically set the locale (this is a global change) - locale: const Locale('fr'), - builder: (context, widget) => StreamChat( - client: client, - child: widget, - ), - home: StreamChannel( - channel: channel, - child: const ChannelPage(), - ), - ); + theme: ThemeData.light(), + darkTheme: ThemeData.dark(), + // Add all the supported locales + supportedLocales: const [ + Locale('en'), + Locale('hi'), + Locale('fr'), + Locale('it'), + Locale('es'), + Locale('ja'), + Locale('ko'), + Locale('pt'), + ], + // Add GlobalStreamChatLocalizations.delegates + localizationsDelegates: GlobalStreamChatLocalizations.delegates, + // Programatically set the locale (this is a global change) + locale: const Locale('fr'), + builder: (context, widget) => StreamChat( + client: client, + child: widget, + ), + home: StreamChannel( + channel: channel, + child: const ChannelPage(), + ), + ); } /// A list of messages sent in the current channel. diff --git a/packages/stream_chat_localizations/example/lib/override_lang.dart b/packages/stream_chat_localizations/example/lib/override_lang.dart index 1dc05f9100..9cd76c9849 100644 --- a/packages/stream_chat_localizations/example/lib/override_lang.dart +++ b/packages/stream_chat_localizations/example/lib/override_lang.dart @@ -3,16 +3,14 @@ import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_localizations/stream_chat_localizations.dart'; -class _CustomStreamChatLocalizationsDelegate - extends LocalizationsDelegate { +class _CustomStreamChatLocalizationsDelegate extends LocalizationsDelegate { const _CustomStreamChatLocalizationsDelegate(); @override bool isSupported(Locale locale) => locale.languageCode == 'en'; @override - Future load(Locale locale) => - SynchronousFuture(CustomStreamChatLocalizationsEn()); + Future load(Locale locale) => SynchronousFuture(CustomStreamChatLocalizationsEn()); @override bool shouldReload(_CustomStreamChatLocalizationsDelegate old) => false; @@ -89,34 +87,34 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) => MaterialApp( - theme: ThemeData.light(), - darkTheme: ThemeData.dark(), - // Add all the supported locales - supportedLocales: const [ - Locale('en'), - Locale('hi'), - Locale('fr'), - Locale('it'), - Locale('es'), - Locale('ja'), - Locale('ko'), - Locale('pt'), - ], - // Add overridden "CustomStreamChatLocalizationsEn.delegate" along with - // "GlobalStreamChatLocalizations.delegates" - localizationsDelegates: const [ - CustomStreamChatLocalizationsEn.delegate, - ...GlobalStreamChatLocalizations.delegates, - ], - builder: (context, widget) => StreamChat( - client: client, - child: widget, - ), - home: StreamChannel( - channel: channel, - child: const ChannelPage(), - ), - ); + theme: ThemeData.light(), + darkTheme: ThemeData.dark(), + // Add all the supported locales + supportedLocales: const [ + Locale('en'), + Locale('hi'), + Locale('fr'), + Locale('it'), + Locale('es'), + Locale('ja'), + Locale('ko'), + Locale('pt'), + ], + // Add overridden "CustomStreamChatLocalizationsEn.delegate" along with + // "GlobalStreamChatLocalizations.delegates" + localizationsDelegates: const [ + CustomStreamChatLocalizationsEn.delegate, + ...GlobalStreamChatLocalizations.delegates, + ], + builder: (context, widget) => StreamChat( + client: client, + child: widget, + ), + home: StreamChannel( + channel: channel, + child: const ChannelPage(), + ), + ); } /// A list of messages sent in the current channel. diff --git a/packages/stream_chat_localizations/example/pubspec.yaml b/packages/stream_chat_localizations/example/pubspec.yaml index 4c821fd4ac..73442357ee 100644 --- a/packages/stream_chat_localizations/example/pubspec.yaml +++ b/packages/stream_chat_localizations/example/pubspec.yaml @@ -17,8 +17,8 @@ version: 1.0.0+1 # 2. Add it to the melos.yaml file for future updates. environment: - sdk: ^3.6.2 - flutter: ">=3.27.4" + sdk: ^3.10.0 + flutter: ">=3.38.1" dependencies: cupertino_icons: ^1.0.3 diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations.dart index 62248df818..956b906cb4 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations.dart @@ -112,8 +112,7 @@ GlobalStreamChatLocalizations? getStreamChatTranslation(Locale locale) { /// ) /// ``` /// -abstract class GlobalStreamChatLocalizations - implements StreamChatLocalizations { +abstract class GlobalStreamChatLocalizations implements StreamChatLocalizations { /// Initializes an object that defines the StreamChat widget's localized /// strings for the given `localeName`. const GlobalStreamChatLocalizations({ @@ -129,8 +128,7 @@ abstract class GlobalStreamChatLocalizations /// [GlobalStreamChatLocalizations.delegates] as the value of /// [MaterialApp.localizationsDelegates] to include the localizations for both /// the flutter and stream chat widget libraries. - static const LocalizationsDelegate delegate = - _StreamChatLocalizationsDelegate(); + static const LocalizationsDelegate delegate = _StreamChatLocalizationsDelegate(); /// A value for [MaterialApp.localizationsDelegates] that's typically used by /// internationalized apps. @@ -160,16 +158,13 @@ abstract class GlobalStreamChatLocalizations ]; } -class _StreamChatLocalizationsDelegate - extends LocalizationsDelegate { +class _StreamChatLocalizationsDelegate extends LocalizationsDelegate { const _StreamChatLocalizationsDelegate(); @override - bool isSupported(Locale locale) => - kStreamChatSupportedLanguages.contains(locale.languageCode); + bool isSupported(Locale locale) => kStreamChatSupportedLanguages.contains(locale.languageCode); - static final _loadedTranslations = - >{}; + static final _loadedTranslations = >{}; @override Future load(Locale locale) { @@ -186,6 +181,7 @@ class _StreamChatLocalizationsDelegate bool shouldReload(_StreamChatLocalizationsDelegate old) => false; @override - String toString() => 'GlobalStreamChatLocalizations.delegate(' + String toString() => + 'GlobalStreamChatLocalizations.delegate(' '${kStreamChatSupportedLanguages.length} locales)'; } diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ca.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ca.dart index dbab4c9b59..d4b139fd34 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ca.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ca.dart @@ -51,8 +51,7 @@ class StreamChatLocalizationsCa extends GlobalStreamChatLocalizations { String attachmentsUploadProgressText({ required int remaining, required int total, - }) => - 'Transferència en curs $remaining/$total ...'; + }) => 'Transferència en curs $remaining/$total ...'; @override String pinnedByUserText({ @@ -65,8 +64,7 @@ class StreamChatLocalizationsCa extends GlobalStreamChatLocalizations { } @override - String get sendMessagePermissionError => - 'No tens permís per enviar missatges'; + String get sendMessagePermissionError => 'No tens permís per enviar missatges'; @override String get emptyMessagesText => 'Actualment no hi ha missatges'; @@ -75,8 +73,7 @@ class StreamChatLocalizationsCa extends GlobalStreamChatLocalizations { String get genericErrorText => 'Hi ha hagut un problema'; @override - String get loadingMessagesError => - 'Hi ha hagut un error mentre carregava el missatge'; + String get loadingMessagesError => 'Hi ha hagut un error mentre carregava el missatge'; @override String resultCountText(int count) => '$count resultats'; @@ -115,8 +112,7 @@ class StreamChatLocalizationsCa extends GlobalStreamChatLocalizations { String get reconnectingLabel => 'Reconnectant...'; @override - String get alsoSendAsDirectMessageLabel => - 'Enviar també com a missatge directe'; + String get alsoSendAsDirectMessageLabel => 'Enviar també com a missatge directe'; @override String get addACommentOrSendLabel => 'Afegir un comentari o enviar'; @@ -142,8 +138,7 @@ class StreamChatLocalizationsCa extends GlobalStreamChatLocalizations { 'La mida màxima del fitxer és de $limitInMB MB.'; @override - String get couldNotReadBytesFromFileError => - "No s'han pogut llegir els bytes del fitxer."; + String get couldNotReadBytesFromFileError => "No s'han pogut llegir els bytes del fitxer."; @override String get addAFileLabel => 'Afegeix un fitxer'; @@ -170,7 +165,7 @@ class StreamChatLocalizationsCa extends GlobalStreamChatLocalizations { String get somethingWentWrongError => 'Alguna cosa ha anat malament'; @override - String get addMoreFilesLabel => 'Afegir més fitxers'; + String get addMoreFilesLabel => 'Afegir més'; @override String get enablePhotoAndVideoAccessMessage => @@ -196,8 +191,7 @@ class StreamChatLocalizationsCa extends GlobalStreamChatLocalizations { String get flagMessageSuccessfulLabel => 'Missatge reportat'; @override - String get flagMessageSuccessfulText => - 'Aquest missatge ha estat reportat a un moderador'; + String get flagMessageSuccessfulText => 'Aquest missatge ha estat reportat a un moderador'; @override String get deleteLabel => 'ESBORRA'; @@ -206,12 +200,10 @@ class StreamChatLocalizationsCa extends GlobalStreamChatLocalizations { String get deleteMessageLabel => 'Esborra el missatge'; @override - String get deleteMessageQuestion => - 'Estàs segur que vols esborrar aquest missatge de forma permanent?'; + String get deleteMessageQuestion => 'Estàs segur que vols esborrar aquest missatge de forma permanent?'; @override - String get operationCouldNotBeCompletedText => - "L'operació no s'ha pogut completar"; + String get operationCouldNotBeCompletedText => "L'operació no s'ha pogut completar"; @override String get replyLabel => 'Respondre'; @@ -281,8 +273,7 @@ class StreamChatLocalizationsCa extends GlobalStreamChatLocalizations { String get letsStartChattingLabel => 'Comencem a parlar!'; @override - String get sendingFirstMessageLabel => - 'Què et sembla enviar el teu primer missatge?'; + String get sendingFirstMessageLabel => 'Què et sembla enviar el teu primer missatge?'; @override String get startAChatLabel => 'Inicia una conversa'; @@ -294,11 +285,10 @@ class StreamChatLocalizationsCa extends GlobalStreamChatLocalizations { String get deleteConversationLabel => 'Esborra la conversa'; @override - String get deleteConversationQuestion => - 'Estàs segur que vols esborrar aquesta conversa?'; + String get deleteConversationQuestion => 'Estàs segur que vols esborrar aquesta conversa?'; @override - String get streamChatLabel => 'Stream Chat'; + String get streamChatLabel => 'Converses'; @override String get searchingForNetworkText => 'Cercant xarxa'; @@ -334,8 +324,7 @@ class StreamChatLocalizationsCa extends GlobalStreamChatLocalizations { String get leaveConversationLabel => 'Surt de la conversa'; @override - String get leaveConversationQuestion => - "Estàs segur que vols sortir d'aquesta conversa?"; + String get leaveConversationQuestion => "Estàs segur que vols sortir d'aquesta conversa?"; @override String get showInChatLabel => 'Mostra al xat'; @@ -371,8 +360,7 @@ class StreamChatLocalizationsCa extends GlobalStreamChatLocalizations { String galleryPaginationText({ required int currentPage, required int totalPages, - }) => - '${currentPage + 1} de $totalPages'; + }) => '${currentPage + 1} de $totalPages'; @override String get fileText => 'Fitxer'; @@ -381,8 +369,7 @@ class StreamChatLocalizationsCa extends GlobalStreamChatLocalizations { String get replyToMessageLabel => 'Respondre al missatge'; @override - String attachmentLimitExceedError(int limit) => - 'No és possible afegir més de $limit fitxers adjunts'; + String attachmentLimitExceedError(int limit) => 'No és possible afegir més de $limit fitxers adjunts'; @override String get viewLibrary => 'Veure llibreria'; @@ -439,8 +426,7 @@ class StreamChatLocalizationsCa extends GlobalStreamChatLocalizations { } @override - String get linkDisabledDetails => - 'No es permet enviar enllaços a aquesta conversa'; + String get linkDisabledDetails => 'No es permet enviar enllaços a aquesta conversa'; @override String get linkDisabledError => 'Els enllaços estan deshabilitats'; @@ -449,8 +435,7 @@ class StreamChatLocalizationsCa extends GlobalStreamChatLocalizations { String unreadMessagesSeparatorText() => 'Missatges nous'; @override - String get enableFileAccessMessage => - "Habilita l'accés als fitxers per poder compartir-los amb amics"; + String get enableFileAccessMessage => "Habilita l'accés als fitxers per poder compartir-los amb amics"; @override String get allowFileAccessMessage => "Permet l'accés als fitxers"; @@ -559,15 +544,13 @@ class StreamChatLocalizationsCa extends GlobalStreamChatLocalizations { String get enterYourCommentLabel => 'Introdueix el teu comentari'; @override - String get endVoteConfirmationText => - 'Estàs segur que vols finalitzar la votació?'; + String get endVoteConfirmationText => 'Estàs segur que vols finalitzar la votació?'; @override String get deletePollOptionLabel => 'Eliminar opció'; @override - String get deletePollOptionQuestion => - 'Estàs segur que vols eliminar aquesta opció?'; + String get deletePollOptionQuestion => 'Estàs segur que vols eliminar aquesta opció?'; @override String get createLabel => 'Crear'; @@ -611,10 +594,10 @@ class StreamChatLocalizationsCa extends GlobalStreamChatLocalizations { @override String voteCountLabel({int? count}) => switch (count) { - null || < 1 => '0 vots', - 1 => '1 vot', - _ => '$count vots', - }; + null || < 1 => '0 vots', + 1 => '1 vot', + _ => '$count vots', + }; @override String get noPollVotesLabel => 'No hi ha vots en aquest moment'; @@ -635,15 +618,13 @@ class StreamChatLocalizationsCa extends GlobalStreamChatLocalizations { String get slideToCancelLabel => 'Llisca per cancel·lar'; @override - String get holdToRecordLabel => - 'Mantén premut per gravar, deixa anar per enviar'; + String get holdToRecordLabel => 'Mantén premut per gravar, deixa anar per enviar'; @override String get sendAnywayLabel => 'Enviar igualment'; @override - String get moderatedMessageBlockedText => - 'Missatge bloquejat per les polítiques de moderació'; + String get moderatedMessageBlockedText => 'Missatge bloquejat per les polítiques de moderació'; @override String get moderationReviewModalTitle => 'Estàs segur?'; @@ -667,6 +648,18 @@ class StreamChatLocalizationsCa extends GlobalStreamChatLocalizations { @override String get videoAttachmentText => 'Vídeo'; + @override + String get fileAttachmentText => 'Fitxer'; + + @override + String filesAttachmentCountText(int count) => count == 1 ? 'Fitxer' : '$count fitxers'; + + @override + String photosAttachmentCountText(int count) => count == 1 ? 'Foto' : '$count fotos'; + + @override + String videosAttachmentCountText(int count) => count == 1 ? 'Vídeo' : '$count vídeos'; + @override String get pollYouVotedText => 'Has votat'; diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_de.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_de.dart index ba18de1f32..01d25dd969 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_de.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_de.dart @@ -51,8 +51,7 @@ class StreamChatLocalizationsDe extends GlobalStreamChatLocalizations { String attachmentsUploadProgressText({ required int remaining, required int total, - }) => - 'Hochladen $remaining/$total ...'; + }) => 'Hochladen $remaining/$total ...'; @override String pinnedByUserText({ @@ -160,7 +159,7 @@ class StreamChatLocalizationsDe extends GlobalStreamChatLocalizations { String get somethingWentWrongError => 'Etwas ist schief gelaufen'; @override - String get addMoreFilesLabel => 'Weitere Dateien hinzufügen'; + String get addMoreFilesLabel => 'Mehr hinzufügen'; @override String get enablePhotoAndVideoAccessMessage => @@ -186,8 +185,7 @@ class StreamChatLocalizationsDe extends GlobalStreamChatLocalizations { String get flagMessageSuccessfulLabel => 'Nachricht gemeldet'; @override - String get flagMessageSuccessfulText => - 'Die Nachricht wurde an einen Moderator weitergeleitet.'; + String get flagMessageSuccessfulText => 'Die Nachricht wurde an einen Moderator weitergeleitet.'; @override String get deleteLabel => 'LÖSCHEN'; @@ -196,12 +194,10 @@ class StreamChatLocalizationsDe extends GlobalStreamChatLocalizations { String get deleteMessageLabel => 'Nachricht löschen'; @override - String get deleteMessageQuestion => - 'Sind Sie sicher, dass Sie diese Nachricht endgültig löschen wollen?'; + String get deleteMessageQuestion => 'Sind Sie sicher, dass Sie diese Nachricht endgültig löschen wollen?'; @override - String get operationCouldNotBeCompletedText => - 'Die Operation konnte nicht abgeschlossen werden.'; + String get operationCouldNotBeCompletedText => 'Die Operation konnte nicht abgeschlossen werden.'; @override String get replyLabel => 'Antwort'; @@ -271,7 +267,8 @@ class StreamChatLocalizationsDe extends GlobalStreamChatLocalizations { String get letsStartChattingLabel => 'Lass uns anfangen zu chatten!'; @override - String get sendingFirstMessageLabel => 'Wie wäre es, wenn Sie Ihre erste ' + String get sendingFirstMessageLabel => + 'Wie wäre es, wenn Sie Ihre erste ' 'Nachricht an einen Freund senden würden?'; @override @@ -284,11 +281,10 @@ class StreamChatLocalizationsDe extends GlobalStreamChatLocalizations { String get deleteConversationLabel => 'Unterhaltung löschen'; @override - String get deleteConversationQuestion => - 'Sind Sie sicher, dass Sie diese Unterhaltung löschen wollen?'; + String get deleteConversationQuestion => 'Sind Sie sicher, dass Sie diese Unterhaltung löschen wollen?'; @override - String get streamChatLabel => 'Stream Chat'; + String get streamChatLabel => 'Unterhaltungen'; @override String get searchingForNetworkText => 'Netzwerk wird gesucht'; @@ -323,8 +319,7 @@ class StreamChatLocalizationsDe extends GlobalStreamChatLocalizations { String get leaveConversationLabel => 'Unterhaltung verlassen'; @override - String get leaveConversationQuestion => - 'Sind Sie sicher, dass Sie diese Unterhaltung verlassen wollen?'; + String get leaveConversationQuestion => 'Sind Sie sicher, dass Sie diese Unterhaltung verlassen wollen?'; @override String get showInChatLabel => 'Im Chat anzeigen'; @@ -360,8 +355,7 @@ class StreamChatLocalizationsDe extends GlobalStreamChatLocalizations { String galleryPaginationText({ required int currentPage, required int totalPages, - }) => - '${currentPage + 1} von $totalPages'; + }) => '${currentPage + 1} von $totalPages'; @override String get fileText => 'Datei'; @@ -370,26 +364,22 @@ class StreamChatLocalizationsDe extends GlobalStreamChatLocalizations { String get replyToMessageLabel => 'Auf Nachricht antworten'; @override - String attachmentLimitExceedError(int limit) => - 'Dateigröße überschritten, Grenze: $limit'; + String attachmentLimitExceedError(int limit) => 'Dateigröße überschritten, Grenze: $limit'; @override String get slowModeOnLabel => 'Langsamer Modus: EIN'; @override - String get linkDisabledDetails => - 'Das Senden von Links ist in dieser Konversation nicht erlaubt.'; + String get linkDisabledDetails => 'Das Senden von Links ist in dieser Konversation nicht erlaubt.'; @override String get linkDisabledError => 'Verknüpfungen sind deaktiviert'; @override - String get sendMessagePermissionError => - 'Sie sind nicht berechtigt Nachrichten zu senden'; + String get sendMessagePermissionError => 'Sie sind nicht berechtigt Nachrichten zu senden'; @override - String get couldNotReadBytesFromFileError => - 'Kan bytes niet uit bestand lezen.'; + String get couldNotReadBytesFromFileError => 'Kan bytes niet uit bestand lezen.'; @override String get downloadLabel => 'Downloaden'; @@ -552,15 +542,13 @@ class StreamChatLocalizationsDe extends GlobalStreamChatLocalizations { String get enterYourCommentLabel => 'Geben Sie Ihren Kommentar ein'; @override - String get endVoteConfirmationText => - 'Sind Sie sicher, dass Sie die Abstimmung beenden möchten?'; + String get endVoteConfirmationText => 'Sind Sie sicher, dass Sie die Abstimmung beenden möchten?'; @override String get deletePollOptionLabel => 'Option löschen'; @override - String get deletePollOptionQuestion => - 'Sind Sie sicher, dass Sie diese Option löschen möchten?'; + String get deletePollOptionQuestion => 'Sind Sie sicher, dass Sie diese Option löschen möchten?'; @override String get createLabel => 'Erstellen'; @@ -604,10 +592,10 @@ class StreamChatLocalizationsDe extends GlobalStreamChatLocalizations { @override String voteCountLabel({int? count}) => switch (count) { - null || < 1 => '0 Stimmen', - 1 => '1 Stimme', - _ => '$count Stimmen', - }; + null || < 1 => '0 Stimmen', + 1 => '1 Stimme', + _ => '$count Stimmen', + }; @override String get noPollVotesLabel => 'Derzeit keine Umfrage-Stimmen'; @@ -634,8 +622,7 @@ class StreamChatLocalizationsDe extends GlobalStreamChatLocalizations { String get sendAnywayLabel => 'Trotzdem senden'; @override - String get moderatedMessageBlockedText => - 'Nachricht wurde durch Moderationsrichtlinien blockiert'; + String get moderatedMessageBlockedText => 'Nachricht wurde durch Moderationsrichtlinien blockiert'; @override String get moderationReviewModalTitle => 'Bist du sicher?'; @@ -659,6 +646,18 @@ class StreamChatLocalizationsDe extends GlobalStreamChatLocalizations { @override String get videoAttachmentText => 'Video'; + @override + String get fileAttachmentText => 'Datei'; + + @override + String filesAttachmentCountText(int count) => count == 1 ? 'Datei' : '$count Dateien'; + + @override + String photosAttachmentCountText(int count) => count == 1 ? 'Foto' : '$count Fotos'; + + @override + String videosAttachmentCountText(int count) => count == 1 ? 'Video' : '$count Videos'; + @override String get pollYouVotedText => 'Du hast abgestimmt'; diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_en.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_en.dart index b392b0ea4a..6d09eedef0 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_en.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_en.dart @@ -39,20 +39,19 @@ class StreamChatLocalizationsEn extends GlobalStreamChatLocalizations { } @override - String get threadReplyLabel => 'Thread Reply'; + String get threadReplyLabel => 'Thread'; @override String get onlyVisibleToYouText => 'Only visible to you'; @override - String threadReplyCountText(int count) => '$count Thread Replies'; + String threadReplyCountText(int count) => count == 1 ? '1 reply' : '$count replies'; @override String attachmentsUploadProgressText({ required int remaining, required int total, - }) => - 'Uploading $remaining/$total ...'; + }) => 'Uploading $remaining/$total ...'; @override String pinnedByUserText({ @@ -65,8 +64,7 @@ class StreamChatLocalizationsEn extends GlobalStreamChatLocalizations { } @override - String get sendMessagePermissionError => - "You don't have permission to send messages"; + String get sendMessagePermissionError => "You don't have permission to send messages"; @override String get emptyMessagesText => 'There are no messages currently'; @@ -100,8 +98,8 @@ class StreamChatLocalizationsEn extends GlobalStreamChatLocalizations { @override String threadSeparatorText(int replyCount) { - if (replyCount == 1) return '1 Reply'; - return '$replyCount Replies'; + if (replyCount == 1) return '1 reply'; + return '$replyCount replies'; } @override @@ -114,7 +112,7 @@ class StreamChatLocalizationsEn extends GlobalStreamChatLocalizations { String get reconnectingLabel => 'Reconnecting...'; @override - String get alsoSendAsDirectMessageLabel => 'Also send as direct message'; + String get alsoSendAsDirectMessageLabel => 'Also send in Channel'; @override String get addACommentOrSendLabel => 'Add a comment or send'; @@ -139,8 +137,7 @@ class StreamChatLocalizationsEn extends GlobalStreamChatLocalizations { 'The file is too large to upload. The file size limit is $limitInMB MB.'; @override - String get couldNotReadBytesFromFileError => - 'Could not read bytes from file.'; + String get couldNotReadBytesFromFileError => 'Could not read bytes from file.'; @override String get addAFileLabel => 'Add a file'; @@ -167,7 +164,7 @@ class StreamChatLocalizationsEn extends GlobalStreamChatLocalizations { String get somethingWentWrongError => 'Something went wrong'; @override - String get addMoreFilesLabel => 'Add more files'; + String get addMoreFilesLabel => 'Add more'; @override String get enablePhotoAndVideoAccessMessage => @@ -193,8 +190,7 @@ class StreamChatLocalizationsEn extends GlobalStreamChatLocalizations { String get flagMessageSuccessfulLabel => 'Message flagged'; @override - String get flagMessageSuccessfulText => - 'The message has been reported to a moderator.'; + String get flagMessageSuccessfulText => 'The message has been reported to a moderator.'; @override String get deleteLabel => 'DELETE'; @@ -203,12 +199,10 @@ class StreamChatLocalizationsEn extends GlobalStreamChatLocalizations { String get deleteMessageLabel => 'Delete Message'; @override - String get deleteMessageQuestion => - 'Are you sure you want to permanently delete this message?'; + String get deleteMessageQuestion => 'Are you sure you want to permanently delete this message?'; @override - String get operationCouldNotBeCompletedText => - "The operation couldn't be completed."; + String get operationCouldNotBeCompletedText => "The operation couldn't be completed."; @override String get replyLabel => 'Reply'; @@ -278,8 +272,7 @@ class StreamChatLocalizationsEn extends GlobalStreamChatLocalizations { String get letsStartChattingLabel => 'Let’s start chatting!'; @override - String get sendingFirstMessageLabel => - 'How about sending your first message to a friend?'; + String get sendingFirstMessageLabel => 'How about sending your first message to a friend?'; @override String get startAChatLabel => 'Start a chat'; @@ -291,11 +284,10 @@ class StreamChatLocalizationsEn extends GlobalStreamChatLocalizations { String get deleteConversationLabel => 'Delete Conversation'; @override - String get deleteConversationQuestion => - 'Are you sure you want to delete this conversation?'; + String get deleteConversationQuestion => 'Are you sure you want to delete this conversation?'; @override - String get streamChatLabel => 'Stream Chat'; + String get streamChatLabel => 'Chats'; @override String get searchingForNetworkText => 'Searching for Network'; @@ -331,8 +323,7 @@ class StreamChatLocalizationsEn extends GlobalStreamChatLocalizations { String get leaveConversationLabel => 'Leave conversation'; @override - String get leaveConversationQuestion => - 'Are you sure you want to leave this conversation?'; + String get leaveConversationQuestion => 'Are you sure you want to leave this conversation?'; @override String get showInChatLabel => 'Show in Chat'; @@ -368,8 +359,7 @@ class StreamChatLocalizationsEn extends GlobalStreamChatLocalizations { String galleryPaginationText({ required int currentPage, required int totalPages, - }) => - '${currentPage + 1} of $totalPages'; + }) => '${currentPage + 1} of $totalPages'; @override String get fileText => 'File'; @@ -378,8 +368,7 @@ class StreamChatLocalizationsEn extends GlobalStreamChatLocalizations { String get replyToMessageLabel => 'Reply to Message'; @override - String attachmentLimitExceedError(int limit) => - 'Attachment limit exceeded, limit: $limit'; + String attachmentLimitExceedError(int limit) => 'Attachment limit exceeded, limit: $limit'; @override String get slowModeOnLabel => 'Slow mode ON'; @@ -433,8 +422,7 @@ class StreamChatLocalizationsEn extends GlobalStreamChatLocalizations { } @override - String get linkDisabledDetails => - 'Sending links is not allowed in this conversation.'; + String get linkDisabledDetails => 'Sending links is not allowed in this conversation.'; @override String get linkDisabledError => 'Links are disabled'; @@ -446,8 +434,7 @@ class StreamChatLocalizationsEn extends GlobalStreamChatLocalizations { String unreadMessagesSeparatorText() => 'New messages'; @override - String get enableFileAccessMessage => - 'Please enable access to files so you can share them with friends.'; + String get enableFileAccessMessage => 'Please enable access to files so you can share them with friends.'; @override String get allowFileAccessMessage => 'Allow access to files'; @@ -555,15 +542,13 @@ class StreamChatLocalizationsEn extends GlobalStreamChatLocalizations { String get enterYourCommentLabel => 'Enter your comment'; @override - String get endVoteConfirmationText => - 'Are you sure you want to end the vote?'; + String get endVoteConfirmationText => 'Are you sure you want to end the vote?'; @override String get deletePollOptionLabel => 'Delete Option'; @override - String get deletePollOptionQuestion => - 'Are you sure you want to delete this option?'; + String get deletePollOptionQuestion => 'Are you sure you want to delete this option?'; @override String get createLabel => 'Create'; @@ -607,10 +592,10 @@ class StreamChatLocalizationsEn extends GlobalStreamChatLocalizations { @override String voteCountLabel({int? count}) => switch (count) { - null || < 1 => '0 votes', - 1 => '1 vote', - _ => '$count votes', - }; + null || < 1 => '0 votes', + 1 => '1 vote', + _ => '$count votes', + }; @override String get noPollVotesLabel => 'There are no poll votes currently'; @@ -637,8 +622,7 @@ class StreamChatLocalizationsEn extends GlobalStreamChatLocalizations { String get sendAnywayLabel => 'Send Anyway'; @override - String get moderatedMessageBlockedText => - 'Message was blocked by moderation policies'; + String get moderatedMessageBlockedText => 'Message was blocked by moderation policies'; @override String get moderationReviewModalTitle => 'Are you sure?'; @@ -662,6 +646,18 @@ class StreamChatLocalizationsEn extends GlobalStreamChatLocalizations { @override String get videoAttachmentText => 'Video'; + @override + String get fileAttachmentText => 'File'; + + @override + String filesAttachmentCountText(int count) => count == 1 ? 'File' : '$count files'; + + @override + String photosAttachmentCountText(int count) => count == 1 ? 'Photo' : '$count photos'; + + @override + String videosAttachmentCountText(int count) => count == 1 ? 'Video' : '$count videos'; + @override String get pollYouVotedText => 'You voted'; diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_es.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_es.dart index d81e4271cd..8cf7c799b9 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_es.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_es.dart @@ -45,15 +45,13 @@ class StreamChatLocalizationsEs extends GlobalStreamChatLocalizations { String get onlyVisibleToYouText => 'Sólo visible para usted'; @override - String threadReplyCountText(int count) => - '$count respuestas al hilo de discusión'; + String threadReplyCountText(int count) => '$count respuestas al hilo de discusión'; @override String attachmentsUploadProgressText({ required int remaining, required int total, - }) => - 'Transferencia en curso $remaining/$total ...'; + }) => 'Transferencia en curso $remaining/$total ...'; @override String pinnedByUserText({ @@ -66,8 +64,7 @@ class StreamChatLocalizationsEs extends GlobalStreamChatLocalizations { } @override - String get sendMessagePermissionError => - 'No tienes permiso para enviar mensajes'; + String get sendMessagePermissionError => 'No tienes permiso para enviar mensajes'; @override String get emptyMessagesText => 'Actualmente no hay mensajes'; @@ -76,8 +73,7 @@ class StreamChatLocalizationsEs extends GlobalStreamChatLocalizations { String get genericErrorText => 'Hubo un problema'; @override - String get loadingMessagesError => - 'Hubo un error mientras se cargaba el mensaje'; + String get loadingMessagesError => 'Hubo un error mientras se cargaba el mensaje'; @override String resultCountText(int count) => '$count resultados'; @@ -116,8 +112,7 @@ class StreamChatLocalizationsEs extends GlobalStreamChatLocalizations { String get reconnectingLabel => 'Reconectando...'; @override - String get alsoSendAsDirectMessageLabel => - 'Enviar también como mensaje directo'; + String get alsoSendAsDirectMessageLabel => 'Enviar también como mensaje directo'; @override String get addACommentOrSendLabel => 'Añadir un comentario o enviar'; @@ -143,8 +138,7 @@ class StreamChatLocalizationsEs extends GlobalStreamChatLocalizations { 'El límite de tamaño de los archivos es de $limitInMB MB.'; @override - String get couldNotReadBytesFromFileError => - 'No se pudieron leer los bytes del archivo.'; + String get couldNotReadBytesFromFileError => 'No se pudieron leer los bytes del archivo.'; @override String get addAFileLabel => 'Añadir un archivo'; @@ -171,7 +165,7 @@ class StreamChatLocalizationsEs extends GlobalStreamChatLocalizations { String get somethingWentWrongError => 'Algo ha salido mal'; @override - String get addMoreFilesLabel => 'Añadir más archivos'; + String get addMoreFilesLabel => 'Añadir más'; @override String get enablePhotoAndVideoAccessMessage => @@ -197,8 +191,7 @@ class StreamChatLocalizationsEs extends GlobalStreamChatLocalizations { String get flagMessageSuccessfulLabel => 'Mensaje reportado'; @override - String get flagMessageSuccessfulText => - 'Este mensaje ha sido reportado a un moderador.'; + String get flagMessageSuccessfulText => 'Este mensaje ha sido reportado a un moderador.'; @override String get deleteLabel => 'BORRAR'; @@ -207,12 +200,10 @@ class StreamChatLocalizationsEs extends GlobalStreamChatLocalizations { String get deleteMessageLabel => 'Borrar el mensaje'; @override - String get deleteMessageQuestion => - '¿Estás seguro de que quieres borrar este mensaje de forma permanente?'; + String get deleteMessageQuestion => '¿Estás seguro de que quieres borrar este mensaje de forma permanente?'; @override - String get operationCouldNotBeCompletedText => - 'La operación no pudo completarse.'; + String get operationCouldNotBeCompletedText => 'La operación no pudo completarse.'; @override String get replyLabel => 'Responder'; @@ -282,8 +273,7 @@ class StreamChatLocalizationsEs extends GlobalStreamChatLocalizations { String get letsStartChattingLabel => '¡Empecemos a charlar!'; @override - String get sendingFirstMessageLabel => - '¿Qué le parece enviar su primer mensaje a un amigo?'; + String get sendingFirstMessageLabel => '¿Qué le parece enviar su primer mensaje a un amigo?'; @override String get startAChatLabel => 'Iniciar una conversación'; @@ -295,11 +285,10 @@ class StreamChatLocalizationsEs extends GlobalStreamChatLocalizations { String get deleteConversationLabel => 'Borrar la conversación'; @override - String get deleteConversationQuestion => - '¿Estás seguro de que quieres borrar esta conversación?'; + String get deleteConversationQuestion => '¿Estás seguro de que quieres borrar esta conversación?'; @override - String get streamChatLabel => 'Stream Chat'; + String get streamChatLabel => 'Conversaciones'; @override String get searchingForNetworkText => 'Buscando red'; @@ -335,8 +324,7 @@ class StreamChatLocalizationsEs extends GlobalStreamChatLocalizations { String get leaveConversationLabel => 'Salir de la conversación'; @override - String get leaveConversationQuestion => - '¿Estás seguro de que quiere salir de esta conversación?'; + String get leaveConversationQuestion => '¿Estás seguro de que quiere salir de esta conversación?'; @override String get showInChatLabel => 'Mostrar en el chat'; @@ -372,8 +360,7 @@ class StreamChatLocalizationsEs extends GlobalStreamChatLocalizations { String galleryPaginationText({ required int currentPage, required int totalPages, - }) => - '${currentPage + 1} de $totalPages'; + }) => '${currentPage + 1} de $totalPages'; @override String get fileText => 'Archivo'; @@ -382,7 +369,8 @@ class StreamChatLocalizationsEs extends GlobalStreamChatLocalizations { String get replyToMessageLabel => 'Responder al Mensaje'; @override - String attachmentLimitExceedError(int limit) => ''' + String attachmentLimitExceedError(int limit) => + ''' No es posible añadir más de $limit archivos adjuntos '''; @@ -441,8 +429,7 @@ No es posible añadir más de $limit archivos adjuntos } @override - String get linkDisabledDetails => - 'No se permite enviar enlaces en esta conversación.'; + String get linkDisabledDetails => 'No se permite enviar enlaces en esta conversación.'; @override String get linkDisabledError => 'Los enlaces están deshabilitados'; @@ -451,8 +438,7 @@ No es posible añadir más de $limit archivos adjuntos String unreadMessagesSeparatorText() => 'Nuevos mensajes'; @override - String get enableFileAccessMessage => - 'Habilite el acceso a los archivos para poder compartirlos con amigos.'; + String get enableFileAccessMessage => 'Habilite el acceso a los archivos para poder compartirlos con amigos.'; @override String get allowFileAccessMessage => 'Permitir el acceso a los archivos'; @@ -560,15 +546,13 @@ No es posible añadir más de $limit archivos adjuntos String get enterYourCommentLabel => 'Ingresa tu comentario'; @override - String get endVoteConfirmationText => - '¿Estás seguro de que quieres finalizar la votación?'; + String get endVoteConfirmationText => '¿Estás seguro de que quieres finalizar la votación?'; @override String get deletePollOptionLabel => 'Eliminar opción'; @override - String get deletePollOptionQuestion => - '¿Estás seguro de que quieres eliminar esta opción?'; + String get deletePollOptionQuestion => '¿Estás seguro de que quieres eliminar esta opción?'; @override String get createLabel => 'Crear'; @@ -612,17 +596,16 @@ No es posible añadir más de $limit archivos adjuntos @override String voteCountLabel({int? count}) => switch (count) { - null || < 1 => '0 votos', - 1 => '1 voto', - _ => '$count votos', - }; + null || < 1 => '0 votos', + 1 => '1 voto', + _ => '$count votos', + }; @override String get noPollVotesLabel => 'No hay votos en la encuesta actualmente'; @override - String get loadingPollVotesError => - 'Error al cargar los votos de la encuesta'; + String get loadingPollVotesError => 'Error al cargar los votos de la encuesta'; @override String get repliedToLabel => 'respondido a:'; @@ -637,15 +620,13 @@ No es posible añadir más de $limit archivos adjuntos String get slideToCancelLabel => 'Desliza para cancelar'; @override - String get holdToRecordLabel => - 'Mantén pulsado para grabar, suelta para enviar'; + String get holdToRecordLabel => 'Mantén pulsado para grabar, suelta para enviar'; @override String get sendAnywayLabel => 'Enviar de todos modos'; @override - String get moderatedMessageBlockedText => - 'Mensaje bloqueado por políticas de moderación'; + String get moderatedMessageBlockedText => 'Mensaje bloqueado por políticas de moderación'; @override String get moderationReviewModalTitle => '¿Estás seguro?'; @@ -669,6 +650,18 @@ No es posible añadir más de $limit archivos adjuntos @override String get videoAttachmentText => 'Video'; + @override + String get fileAttachmentText => 'Archivo'; + + @override + String filesAttachmentCountText(int count) => count == 1 ? 'Archivo' : '$count archivos'; + + @override + String photosAttachmentCountText(int count) => count == 1 ? 'Foto' : '$count fotos'; + + @override + String videosAttachmentCountText(int count) => count == 1 ? 'Vídeo' : '$count vídeos'; + @override String get pollYouVotedText => 'Has votado'; diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_fr.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_fr.dart index a685fe77ae..a91ea1ae8e 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_fr.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_fr.dart @@ -45,15 +45,13 @@ class StreamChatLocalizationsFr extends GlobalStreamChatLocalizations { String get onlyVisibleToYouText => 'Seulement visible par vous'; @override - String threadReplyCountText(int count) => - '$count Réponses au fil de discussion'; + String threadReplyCountText(int count) => '$count Réponses au fil de discussion'; @override String attachmentsUploadProgressText({ required int remaining, required int total, - }) => - 'Transfert en cours $remaining/$total ...'; + }) => 'Transfert en cours $remaining/$total ...'; @override String pinnedByUserText({ @@ -66,8 +64,7 @@ class StreamChatLocalizationsFr extends GlobalStreamChatLocalizations { } @override - String get sendMessagePermissionError => - "Vous n'êtes pas autorisé à envoyer des messages"; + String get sendMessagePermissionError => "Vous n'êtes pas autorisé à envoyer des messages"; @override String get emptyMessagesText => "Il n'y a pas de messages actuellement"; @@ -115,8 +112,7 @@ class StreamChatLocalizationsFr extends GlobalStreamChatLocalizations { String get reconnectingLabel => 'Reconnexion...'; @override - String get alsoSendAsDirectMessageLabel => - 'Envoyer aussi comme message direct'; + String get alsoSendAsDirectMessageLabel => 'Envoyer aussi comme message direct'; @override String get addACommentOrSendLabel => 'Ajouter un commentaire ou envoyer'; @@ -142,8 +138,7 @@ class StreamChatLocalizationsFr extends GlobalStreamChatLocalizations { 'La taille limite du fichier est de $limitInMB Mo.'; @override - String get couldNotReadBytesFromFileError => - 'Impossible de lire les octets du fichier.'; + String get couldNotReadBytesFromFileError => 'Impossible de lire les octets du fichier.'; @override String get addAFileLabel => 'Ajouter un fichier'; @@ -170,7 +165,7 @@ class StreamChatLocalizationsFr extends GlobalStreamChatLocalizations { String get somethingWentWrongError => 'Quelque chose a mal tourné'; @override - String get addMoreFilesLabel => "Ajouter d'autres fichiers"; + String get addMoreFilesLabel => 'Ajouter plus'; @override String get enablePhotoAndVideoAccessMessage => @@ -196,8 +191,7 @@ class StreamChatLocalizationsFr extends GlobalStreamChatLocalizations { String get flagMessageSuccessfulLabel => 'Message signalé'; @override - String get flagMessageSuccessfulText => - 'Ce message a été signalé à un modérateur.'; + String get flagMessageSuccessfulText => 'Ce message a été signalé à un modérateur.'; @override String get deleteLabel => 'SUPPRIMER'; @@ -206,12 +200,10 @@ class StreamChatLocalizationsFr extends GlobalStreamChatLocalizations { String get deleteMessageLabel => 'Supprimer le message'; @override - String get deleteMessageQuestion => - 'Êtes-vous sûr de vouloir supprimer définitivement ce message ?'; + String get deleteMessageQuestion => 'Êtes-vous sûr de vouloir supprimer définitivement ce message ?'; @override - String get operationCouldNotBeCompletedText => - "L'opération n'a pas pu être terminée."; + String get operationCouldNotBeCompletedText => "L'opération n'a pas pu être terminée."; @override String get replyLabel => 'Répondre'; @@ -281,8 +273,7 @@ class StreamChatLocalizationsFr extends GlobalStreamChatLocalizations { String get letsStartChattingLabel => 'Commençons à discuter !'; @override - String get sendingFirstMessageLabel => - "Que diriez-vous d'envoyer votre premier message à un ami ?"; + String get sendingFirstMessageLabel => "Que diriez-vous d'envoyer votre premier message à un ami ?"; @override String get startAChatLabel => 'Commencer une discussion'; @@ -294,11 +285,10 @@ class StreamChatLocalizationsFr extends GlobalStreamChatLocalizations { String get deleteConversationLabel => 'Supprimer la conversation'; @override - String get deleteConversationQuestion => - 'Vous êtes sûr de vouloir supprimer cette conversation ?'; + String get deleteConversationQuestion => 'Vous êtes sûr de vouloir supprimer cette conversation ?'; @override - String get streamChatLabel => 'Stream Chat'; + String get streamChatLabel => 'Conversations'; @override String get searchingForNetworkText => 'Recherche de réseau'; @@ -334,8 +324,7 @@ class StreamChatLocalizationsFr extends GlobalStreamChatLocalizations { String get leaveConversationLabel => 'Quitter la conversation'; @override - String get leaveConversationQuestion => - 'Etes-vous sûr de vouloir quitter cette conversation ?'; + String get leaveConversationQuestion => 'Etes-vous sûr de vouloir quitter cette conversation ?'; @override String get showInChatLabel => 'Montrer dans la Discussion'; @@ -371,8 +360,7 @@ class StreamChatLocalizationsFr extends GlobalStreamChatLocalizations { String galleryPaginationText({ required int currentPage, required int totalPages, - }) => - '${currentPage + 1} de $totalPages'; + }) => '${currentPage + 1} de $totalPages'; @override String get fileText => 'Fichier'; @@ -381,7 +369,8 @@ class StreamChatLocalizationsFr extends GlobalStreamChatLocalizations { String get replyToMessageLabel => 'Répondre au Message'; @override - String attachmentLimitExceedError(int limit) => ''' + String attachmentLimitExceedError(int limit) => + ''' Limite de pièces jointes dépassée : il n'est pas possible d'ajouter plus de $limit pièces jointes '''; @@ -440,8 +429,7 @@ Limite de pièces jointes dépassée : il n'est pas possible d'ajouter plus de $ } @override - String get linkDisabledDetails => - "L'envoi de liens n'est pas autorisé dans cette conversation."; + String get linkDisabledDetails => "L'envoi de liens n'est pas autorisé dans cette conversation."; @override String get linkDisabledError => 'Les liens sont désactivés'; @@ -518,8 +506,7 @@ Limite de pièces jointes dépassée : il n'est pas possible d'ajouter plus de $ String get multipleAnswersLabel => 'Réponses multiples'; @override - String get maximumVotesPerPersonLabel => - 'Nombre maximum de votes par personne'; + String get maximumVotesPerPersonLabel => 'Nombre maximum de votes par personne'; @override String? maxVotesPerPersonValidationError(int votes, Range range) { @@ -561,15 +548,13 @@ Limite de pièces jointes dépassée : il n'est pas possible d'ajouter plus de $ String get enterYourCommentLabel => 'Entrez votre commentaire'; @override - String get endVoteConfirmationText => - 'Êtes-vous sûr de vouloir terminer le vote?'; + String get endVoteConfirmationText => 'Êtes-vous sûr de vouloir terminer le vote?'; @override String get deletePollOptionLabel => "Supprimer l'option"; @override - String get deletePollOptionQuestion => - 'Êtes-vous sûr de vouloir supprimer cette option ?'; + String get deletePollOptionQuestion => 'Êtes-vous sûr de vouloir supprimer cette option ?'; @override String get createLabel => 'Créer'; @@ -613,18 +598,16 @@ Limite de pièces jointes dépassée : il n'est pas possible d'ajouter plus de $ @override String voteCountLabel({int? count}) => switch (count) { - null || < 1 => '0 vote', - 1 => '1 vote', - _ => '$count votes', - }; + null || < 1 => '0 vote', + 1 => '1 vote', + _ => '$count votes', + }; @override - String get noPollVotesLabel => - "Il n'y a pas de votes de sondage actuellement"; + String get noPollVotesLabel => "Il n'y a pas de votes de sondage actuellement"; @override - String get loadingPollVotesError => - 'Erreur de chargement des votes du sondage'; + String get loadingPollVotesError => 'Erreur de chargement des votes du sondage'; @override String get repliedToLabel => 'répondu à:'; @@ -639,15 +622,13 @@ Limite de pièces jointes dépassée : il n'est pas possible d'ajouter plus de $ String get slideToCancelLabel => 'Glissez pour annuler'; @override - String get holdToRecordLabel => - 'Maintenez pour enregistrer, relâchez pour envoyer'; + String get holdToRecordLabel => 'Maintenez pour enregistrer, relâchez pour envoyer'; @override String get sendAnywayLabel => 'Envoyer quand même'; @override - String get moderatedMessageBlockedText => - 'Message bloqué par les politiques de modération'; + String get moderatedMessageBlockedText => 'Message bloqué par les politiques de modération'; @override String get moderationReviewModalTitle => 'Êtes-vous sûr ?'; @@ -671,6 +652,18 @@ Limite de pièces jointes dépassée : il n'est pas possible d'ajouter plus de $ @override String get videoAttachmentText => 'Vidéo'; + @override + String get fileAttachmentText => 'Fichier'; + + @override + String filesAttachmentCountText(int count) => count == 1 ? 'Fichier' : '$count fichiers'; + + @override + String photosAttachmentCountText(int count) => count == 1 ? 'Photo' : '$count photos'; + + @override + String videosAttachmentCountText(int count) => count == 1 ? 'Vidéo' : '$count vidéos'; + @override String get pollYouVotedText => 'Vous avez voté'; diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_hi.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_hi.dart index 1bea4031ee..756045a96d 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_hi.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_hi.dart @@ -51,8 +51,7 @@ class StreamChatLocalizationsHi extends GlobalStreamChatLocalizations { String attachmentsUploadProgressText({ required int remaining, required int total, - }) => - 'अपलोडिंग $remaining/$total ...'; + }) => 'अपलोडिंग $remaining/$total ...'; @override String pinnedByUserText({ @@ -165,7 +164,7 @@ class StreamChatLocalizationsHi extends GlobalStreamChatLocalizations { String get somethingWentWrongError => 'लोड करने में समस्या'; @override - String get addMoreFilesLabel => 'और फ़ाइलें जोड़ें'; + String get addMoreFilesLabel => 'और जोड़ें'; @override String get enablePhotoAndVideoAccessMessage => @@ -178,8 +177,7 @@ class StreamChatLocalizationsHi extends GlobalStreamChatLocalizations { String get flagMessageLabel => 'फ्लैग संदेश'; @override - String get flagMessageQuestion => - 'क्या आप आगे की जांच के लिए इस संदेश की एक प्रति मॉडरेटर को भेजना चाहते हैं?'; + String get flagMessageQuestion => 'क्या आप आगे की जांच के लिए इस संदेश की एक प्रति मॉडरेटर को भेजना चाहते हैं?'; @override String get flagLabel => 'फ्लैग'; @@ -191,8 +189,7 @@ class StreamChatLocalizationsHi extends GlobalStreamChatLocalizations { String get flagMessageSuccessfulLabel => 'संदेश फ्लैग हो गया'; @override - String get flagMessageSuccessfulText => - 'संदेश की रिपोर्ट एक मॉडरेटर को कर दी गई है।'; + String get flagMessageSuccessfulText => 'संदेश की रिपोर्ट एक मॉडरेटर को कर दी गई है।'; @override String get deleteLabel => 'हटाएँ'; @@ -201,12 +198,10 @@ class StreamChatLocalizationsHi extends GlobalStreamChatLocalizations { String get deleteMessageLabel => 'संदेश हटाएं'; @override - String get deleteMessageQuestion => - 'क्या आप वाकई इस संदेश को स्थायी रूप से हटाना चाहते हैं?'; + String get deleteMessageQuestion => 'क्या आप वाकई इस संदेश को स्थायी रूप से हटाना चाहते हैं?'; @override - String get operationCouldNotBeCompletedText => - 'कार्रवाई पूरी नहीं की जा सकी.'; + String get operationCouldNotBeCompletedText => 'कार्रवाई पूरी नहीं की जा सकी.'; @override String get replyLabel => 'जवाब दें'; @@ -276,8 +271,7 @@ class StreamChatLocalizationsHi extends GlobalStreamChatLocalizations { String get letsStartChattingLabel => 'चलो चैट करना शुरू करें!'; @override - String get sendingFirstMessageLabel => - 'किसी मित्र को अपना पहला संदेश भेजने के बारे में क्या विचार है?'; + String get sendingFirstMessageLabel => 'किसी मित्र को अपना पहला संदेश भेजने के बारे में क्या विचार है?'; @override String get startAChatLabel => 'चैट शुरू करें'; @@ -289,11 +283,10 @@ class StreamChatLocalizationsHi extends GlobalStreamChatLocalizations { String get deleteConversationLabel => 'वार्तालाप हटाए'; @override - String get deleteConversationQuestion => - 'क्या आप वाकई इस वार्तालाप को हटाना चाहते हैं?'; + String get deleteConversationQuestion => 'क्या आप वाकई इस वार्तालाप को हटाना चाहते हैं?'; @override - String get streamChatLabel => 'स्ट्रीम चैट'; + String get streamChatLabel => 'चैट'; @override String get searchingForNetworkText => 'नेटवर्क खोज रहे हैं'; @@ -329,8 +322,7 @@ class StreamChatLocalizationsHi extends GlobalStreamChatLocalizations { String get leaveConversationLabel => 'वार्तालाप छोड़े'; @override - String get leaveConversationQuestion => - 'क्या आप वाकई इस बातचीत को छोड़ना चाहते हैं?'; + String get leaveConversationQuestion => 'क्या आप वाकई इस बातचीत को छोड़ना चाहते हैं?'; @override String get showInChatLabel => 'चैट में दिखाएं'; @@ -366,8 +358,7 @@ class StreamChatLocalizationsHi extends GlobalStreamChatLocalizations { String galleryPaginationText({ required int currentPage, required int totalPages, - }) => - '${currentPage + 1} ऑफ़ $totalPages'; + }) => '${currentPage + 1} ऑफ़ $totalPages'; @override String get fileText => 'फ़ाइल'; @@ -376,7 +367,8 @@ class StreamChatLocalizationsHi extends GlobalStreamChatLocalizations { String get replyToMessageLabel => 'संदेश का जवाब'; @override - String attachmentLimitExceedError(int limit) => ''' + String attachmentLimitExceedError(int limit) => + ''' अटैचमेंट लिमिट: $limit अटैचमेंट से अधिक जोड़ना संभव नहीं है '''; @@ -435,8 +427,7 @@ class StreamChatLocalizationsHi extends GlobalStreamChatLocalizations { } @override - String get linkDisabledDetails => - 'इस बातचीत में लिंक भेजने की अनुमति नहीं है.'; + String get linkDisabledDetails => 'इस बातचीत में लिंक भेजने की अनुमति नहीं है.'; @override String get linkDisabledError => 'लिंक भेजना प्रतिबंधित'; @@ -445,8 +436,7 @@ class StreamChatLocalizationsHi extends GlobalStreamChatLocalizations { String unreadMessagesSeparatorText() => 'नए संदेश।'; @override - String get enableFileAccessMessage => - 'कृपया फ़ाइलों तक पहुंच सक्षम करें ताकि आप उन्हें मित्रों के साथ साझा कर सकें।'; + String get enableFileAccessMessage => 'कृपया फ़ाइलों तक पहुंच सक्षम करें ताकि आप उन्हें मित्रों के साथ साझा कर सकें।'; @override String get allowFileAccessMessage => 'फाइलों तक पहुंच की अनुमति दें'; @@ -548,15 +538,13 @@ class StreamChatLocalizationsHi extends GlobalStreamChatLocalizations { String get enterYourCommentLabel => 'अपनी टिप्पणी दर्ज करें'; @override - String get endVoteConfirmationText => - 'क्या आप वाकई मतदान समाप्त करना चाहते हैं?'; + String get endVoteConfirmationText => 'क्या आप वाकई मतदान समाप्त करना चाहते हैं?'; @override String get deletePollOptionLabel => 'विकल्प हटाएं'; @override - String get deletePollOptionQuestion => - 'क्या आप वाकई इस विकल्प को हटाना चाहते हैं?'; + String get deletePollOptionQuestion => 'क्या आप वाकई इस विकल्प को हटाना चाहते हैं?'; @override String get endLabel => 'समाप्त'; @@ -632,15 +620,13 @@ class StreamChatLocalizationsHi extends GlobalStreamChatLocalizations { String get slideToCancelLabel => 'रद्द करने के लिए स्लाइड करें'; @override - String get holdToRecordLabel => - 'रिकॉर्ड करने के लिए दबाए रखें, भेजने के लिए छोड़ें'; + String get holdToRecordLabel => 'रिकॉर्ड करने के लिए दबाए रखें, भेजने के लिए छोड़ें'; @override String get sendAnywayLabel => 'फिर भी भेजें'; @override - String get moderatedMessageBlockedText => - 'मॉडरेशन नीतियों द्वारा संदेश अवरुद्ध किया गया'; + String get moderatedMessageBlockedText => 'मॉडरेशन नीतियों द्वारा संदेश अवरुद्ध किया गया'; @override String get moderationReviewModalTitle => 'क्या आप निश्चित हैं?'; @@ -664,6 +650,18 @@ class StreamChatLocalizationsHi extends GlobalStreamChatLocalizations { @override String get videoAttachmentText => 'वीडियो'; + @override + String get fileAttachmentText => 'फ़ाइल'; + + @override + String filesAttachmentCountText(int count) => count == 1 ? 'फ़ाइल' : '$count फ़ाइलें'; + + @override + String photosAttachmentCountText(int count) => count == 1 ? 'फ़ोटो' : '$count फ़ोटो'; + + @override + String videosAttachmentCountText(int count) => count == 1 ? 'वीडियो' : '$count वीडियो'; + @override String get pollYouVotedText => 'आपने वोट दिया'; diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_it.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_it.dart index 39d56c44c1..5bbed46afa 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_it.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_it.dart @@ -56,8 +56,7 @@ class StreamChatLocalizationsIt extends GlobalStreamChatLocalizations { String attachmentsUploadProgressText({ required int remaining, required int total, - }) => - 'Caricamento $remaining/$total ...'; + }) => 'Caricamento $remaining/$total ...'; @override String pinnedByUserText({ @@ -70,8 +69,7 @@ class StreamChatLocalizationsIt extends GlobalStreamChatLocalizations { } @override - String get sendMessagePermissionError => - "Non hai l'autorizzazione per inviare messaggi"; + String get sendMessagePermissionError => "Non hai l'autorizzazione per inviare messaggi"; @override String get emptyMessagesText => "Non c'é nessun messaggio al momento"; @@ -80,8 +78,7 @@ class StreamChatLocalizationsIt extends GlobalStreamChatLocalizations { String get genericErrorText => 'Qualcosa è andato storto'; @override - String get loadingMessagesError => - 'Errore durante il caricamento dei messaggi'; + String get loadingMessagesError => 'Errore durante il caricamento dei messaggi'; @override String resultCountText(int count) => '$count risultati'; @@ -120,8 +117,7 @@ class StreamChatLocalizationsIt extends GlobalStreamChatLocalizations { String get reconnectingLabel => 'Riconnessione in corso...'; @override - String get alsoSendAsDirectMessageLabel => - 'Manda anche come messaggio diretto'; + String get alsoSendAsDirectMessageLabel => 'Manda anche come messaggio diretto'; @override String get addACommentOrSendLabel => 'Aggiungi un commento o invia'; @@ -146,8 +142,7 @@ class StreamChatLocalizationsIt extends GlobalStreamChatLocalizations { Il file è troppo grande per essere caricato. Il limite è di $limitInMB MB.'''; @override - String get couldNotReadBytesFromFileError => - 'Impossibile leggere i byte dal file.'; + String get couldNotReadBytesFromFileError => 'Impossibile leggere i byte dal file.'; @override String get addAFileLabel => 'Aggiungi un file'; @@ -174,7 +169,7 @@ Il file è troppo grande per essere caricato. Il limite è di $limitInMB MB.'''; String get somethingWentWrongError => 'Qualcosa è andato storto'; @override - String get addMoreFilesLabel => 'Aggiungi altri file'; + String get addMoreFilesLabel => 'Aggiungi altri'; @override String get enablePhotoAndVideoAccessMessage => @@ -187,8 +182,7 @@ Il file è troppo grande per essere caricato. Il limite è di $limitInMB MB.'''; String get flagMessageLabel => 'Segnala messaggio'; @override - String get flagMessageQuestion => - 'Vuoi mandare una copia di questo messaggio ad un moderatore?'; + String get flagMessageQuestion => 'Vuoi mandare una copia di questo messaggio ad un moderatore?'; @override String get flagLabel => 'SEGNALA'; @@ -200,8 +194,7 @@ Il file è troppo grande per essere caricato. Il limite è di $limitInMB MB.'''; String get flagMessageSuccessfulLabel => 'Messaggio segnalato'; @override - String get flagMessageSuccessfulText => - 'Questo messaggio è stato segnalato ad un moderatore.'; + String get flagMessageSuccessfulText => 'Questo messaggio è stato segnalato ad un moderatore.'; @override String get deleteLabel => 'CANCELLA'; @@ -210,12 +203,10 @@ Il file è troppo grande per essere caricato. Il limite è di $limitInMB MB.'''; String get deleteMessageLabel => 'Cancella messaggio'; @override - String get deleteMessageQuestion => - 'Sei sicuro di voler definitivamente cancellare questo messaggio?'; + String get deleteMessageQuestion => 'Sei sicuro di voler definitivamente cancellare questo messaggio?'; @override - String get operationCouldNotBeCompletedText => - 'Non è stato possibile completare questa operazione.'; + String get operationCouldNotBeCompletedText => 'Non è stato possibile completare questa operazione.'; @override String get replyLabel => 'Rispondi'; @@ -285,8 +276,7 @@ Il file è troppo grande per essere caricato. Il limite è di $limitInMB MB.'''; String get letsStartChattingLabel => 'Inizia una conversazione!'; @override - String get sendingFirstMessageLabel => - 'Che ne dici di mandare il tuo primo messaggio ad un amico?'; + String get sendingFirstMessageLabel => 'Che ne dici di mandare il tuo primo messaggio ad un amico?'; @override String get startAChatLabel => 'Inizia una conversazione'; @@ -298,11 +288,10 @@ Il file è troppo grande per essere caricato. Il limite è di $limitInMB MB.'''; String get deleteConversationLabel => 'Elimina conversazione'; @override - String get deleteConversationQuestion => - 'Sei sicuro di voler eliminare questa conversazione?'; + String get deleteConversationQuestion => 'Sei sicuro di voler eliminare questa conversazione?'; @override - String get streamChatLabel => 'Stream Chat'; + String get streamChatLabel => 'Conversazioni'; @override String get searchingForNetworkText => 'Cercando una connessione'; @@ -338,8 +327,7 @@ Il file è troppo grande per essere caricato. Il limite è di $limitInMB MB.'''; String get leaveConversationLabel => 'Esci dalla conversazione'; @override - String get leaveConversationQuestion => - 'Sei sicuro di voler lasciare questa conversazione?'; + String get leaveConversationQuestion => 'Sei sicuro di voler lasciare questa conversazione?'; @override String get showInChatLabel => 'Mostra nella chat'; @@ -375,8 +363,7 @@ Il file è troppo grande per essere caricato. Il limite è di $limitInMB MB.'''; String galleryPaginationText({ required int currentPage, required int totalPages, - }) => - '${currentPage + 1} di $totalPages'; + }) => '${currentPage + 1} di $totalPages'; @override String get fileText => 'file'; @@ -385,7 +372,8 @@ Il file è troppo grande per essere caricato. Il limite è di $limitInMB MB.'''; String get replyToMessageLabel => 'Rispondi al messaggio'; @override - String attachmentLimitExceedError(int limit) => ''' + String attachmentLimitExceedError(int limit) => + ''' Attenzione: il limite massimo di $limit file è stato superato. '''; @@ -444,8 +432,7 @@ Attenzione: il limite massimo di $limit file è stato superato. } @override - String get linkDisabledDetails => - 'Non è permesso condividere link in questa convesazione.'; + String get linkDisabledDetails => 'Non è permesso condividere link in questa convesazione.'; @override String get linkDisabledError => 'I links sono disattivati'; @@ -564,15 +551,13 @@ Attenzione: il limite massimo di $limit file è stato superato. String get enterYourCommentLabel => 'Inserisci il tuo commento'; @override - String get endVoteConfirmationText => - 'Sei sicuro di voler terminare il voto?'; + String get endVoteConfirmationText => 'Sei sicuro di voler terminare il voto?'; @override String get deletePollOptionLabel => "Elimina l'opzione"; @override - String get deletePollOptionQuestion => - 'Sei sicuro di voler eliminare questa opzione?'; + String get deletePollOptionQuestion => 'Sei sicuro di voler eliminare questa opzione?'; @override String get createLabel => 'Crea'; @@ -616,17 +601,16 @@ Attenzione: il limite massimo di $limit file è stato superato. @override String voteCountLabel({int? count}) => switch (count) { - null || < 1 => '0 voti', - 1 => '1 voto', - _ => '$count voti', - }; + null || < 1 => '0 voti', + 1 => '1 voto', + _ => '$count voti', + }; @override String get noPollVotesLabel => 'Attualmente non ci sono voti nel sondaggio'; @override - String get loadingPollVotesError => - 'Errore durante il caricamento dei voti del sondaggio'; + String get loadingPollVotesError => 'Errore durante il caricamento dei voti del sondaggio'; @override String get repliedToLabel => 'risposto a:'; @@ -641,15 +625,13 @@ Attenzione: il limite massimo di $limit file è stato superato. String get slideToCancelLabel => 'Scorri per annullare'; @override - String get holdToRecordLabel => - 'Tieni premuto per registrare, rilascia per inviare'; + String get holdToRecordLabel => 'Tieni premuto per registrare, rilascia per inviare'; @override String get sendAnywayLabel => 'Invia comunque'; @override - String get moderatedMessageBlockedText => - 'Messaggio bloccato dalle politiche di moderazione'; + String get moderatedMessageBlockedText => 'Messaggio bloccato dalle politiche di moderazione'; @override String get moderationReviewModalTitle => 'Sei sicuro?'; @@ -673,6 +655,18 @@ Attenzione: il limite massimo di $limit file è stato superato. @override String get videoAttachmentText => 'Video'; + @override + String get fileAttachmentText => 'File'; + + @override + String filesAttachmentCountText(int count) => count == 1 ? 'File' : '$count file'; + + @override + String photosAttachmentCountText(int count) => count == 1 ? 'Foto' : '$count foto'; + + @override + String videosAttachmentCountText(int count) => count == 1 ? 'Video' : '$count video'; + @override String get pollYouVotedText => 'Hai votato'; diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ja.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ja.dart index 25dc633132..2fa9abb65f 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ja.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ja.dart @@ -49,8 +49,7 @@ class StreamChatLocalizationsJa extends GlobalStreamChatLocalizations { String attachmentsUploadProgressText({ required int remaining, required int total, - }) => - '$remaining/${total}mbのアップロード中…'; + }) => '$remaining/${total}mbのアップロード中…'; @override String pinnedByUserText({ @@ -129,8 +128,7 @@ class StreamChatLocalizationsJa extends GlobalStreamChatLocalizations { '圧縮を試しましたがサイズをオーバーしました'; @override - String fileTooLargeError(double limitInMB) => - 'ファイルが大きすぎてアップロードできません。ファイルサイズの制限は${limitInMB}MBです。'; + String fileTooLargeError(double limitInMB) => 'ファイルが大きすぎてアップロードできません。ファイルサイズの制限は${limitInMB}MBです。'; @override String get couldNotReadBytesFromFileError => 'ファイルからバイトを読み取れませんでした'; @@ -160,11 +158,10 @@ class StreamChatLocalizationsJa extends GlobalStreamChatLocalizations { String get somethingWentWrongError => 'エラーが発生しました'; @override - String get addMoreFilesLabel => 'ファイルの追加'; + String get addMoreFilesLabel => 'さらに追加'; @override - String get enablePhotoAndVideoAccessMessage => - 'お友達と共有できるように、写真やビデオへのアクセスを有効にしてください。'; + String get enablePhotoAndVideoAccessMessage => 'お友達と共有できるように、写真やビデオへのアクセスを有効にしてください。'; @override String get allowGalleryAccessMessage => 'ギャラリーへのアクセスを許可する'; @@ -281,7 +278,7 @@ class StreamChatLocalizationsJa extends GlobalStreamChatLocalizations { String get deleteConversationQuestion => '本当に会話を削除しますか?'; @override - String get streamChatLabel => 'ストリームチャット'; + String get streamChatLabel => 'チャット'; @override String get searchingForNetworkText => 'ネットワークを検索中'; @@ -349,8 +346,7 @@ class StreamChatLocalizationsJa extends GlobalStreamChatLocalizations { String galleryPaginationText({ required int currentPage, required int totalPages, - }) => - '${currentPage + 1} / $totalPages'; + }) => '${currentPage + 1} / $totalPages'; @override String get fileText => 'ファイル'; @@ -365,7 +361,8 @@ class StreamChatLocalizationsJa extends GlobalStreamChatLocalizations { String get viewLibrary => 'ライブラリを表示'; @override - String attachmentLimitExceedError(int limit) => ''' + String attachmentLimitExceedError(int limit) => + ''' 添付ファイルの制限を超えました:$limit個のファイル以上を添付することはできません '''; @@ -441,8 +438,7 @@ class StreamChatLocalizationsJa extends GlobalStreamChatLocalizations { } @override - String get markUnreadError => - 'メッセージを未読にする際にエラーが発生しました。最新の100件のチャンネルメッセージより古い未読メッセージはマークできません。'; + String get markUnreadError => 'メッセージを未読にする際にエラーが発生しました。最新の100件のチャンネルメッセージより古い未読メッセージはマークできません。'; @override String createPollLabel({bool isNew = false}) { @@ -584,10 +580,10 @@ class StreamChatLocalizationsJa extends GlobalStreamChatLocalizations { @override String voteCountLabel({int? count}) => switch (count) { - null || < 1 => '0 票', - 1 => '1 票', - _ => '$count 票', - }; + null || < 1 => '0 票', + 1 => '1 票', + _ => '$count 票', + }; @override String get noPollVotesLabel => '現在投票はありません'; @@ -619,8 +615,7 @@ class StreamChatLocalizationsJa extends GlobalStreamChatLocalizations { String get moderationReviewModalTitle => 'よろしいですか?'; @override - String get moderationReviewModalDescription => - '''あなたのコメントが他の人にどのような影響を与えるかを考え、コミュニティガイドラインに従ってください。'''; + String get moderationReviewModalDescription => '''あなたのコメントが他の人にどのような影響を与えるかを考え、コミュニティガイドラインに従ってください。'''; @override String get emptyMessagePreviewText => ''; @@ -637,6 +632,18 @@ class StreamChatLocalizationsJa extends GlobalStreamChatLocalizations { @override String get videoAttachmentText => '動画'; + @override + String get fileAttachmentText => 'ファイル'; + + @override + String filesAttachmentCountText(int count) => count == 1 ? 'ファイル' : '$count件のファイル'; + + @override + String photosAttachmentCountText(int count) => count == 1 ? '写真' : '$count枚の写真'; + + @override + String videosAttachmentCountText(int count) => count == 1 ? '動画' : '$count本の動画'; + @override String get pollYouVotedText => '投票しました'; diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ko.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ko.dart index 471dcda48a..6269977a04 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ko.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ko.dart @@ -49,8 +49,7 @@ class StreamChatLocalizationsKo extends GlobalStreamChatLocalizations { String attachmentsUploadProgressText({ required int remaining, required int total, - }) => - '$remaining/${total}mb를 업로드중...'; + }) => '$remaining/${total}mb를 업로드중...'; @override String pinnedByUserText({ @@ -129,8 +128,7 @@ class StreamChatLocalizationsKo extends GlobalStreamChatLocalizations { '우리는 압축해 보았지만 충분하지 않았습니다.'; @override - String fileTooLargeError(double limitInMB) => - '파일이 너무 커서 업로드할 수 없습니다. 파일 크기 제한은 ${limitInMB}MB입니다.'; + String fileTooLargeError(double limitInMB) => '파일이 너무 커서 업로드할 수 없습니다. 파일 크기 제한은 ${limitInMB}MB입니다.'; @override String get couldNotReadBytesFromFileError => '파일에서 바이트를 읽을 수 없습니다.'; @@ -160,11 +158,10 @@ class StreamChatLocalizationsKo extends GlobalStreamChatLocalizations { String get somethingWentWrongError => '뭔가 잘못됐습느다'; @override - String get addMoreFilesLabel => '파일을 추가함'; + String get addMoreFilesLabel => '더 추가'; @override - String get enablePhotoAndVideoAccessMessage => - '친구와 공유할 수 있도록 사진과 동영상에 액세스할 수 있도록 설정하십시오.'; + String get enablePhotoAndVideoAccessMessage => '친구와 공유할 수 있도록 사진과 동영상에 액세스할 수 있도록 설정하십시오.'; @override String get allowGalleryAccessMessage => '갤러리에 대한 액세스를 허용합니다'; @@ -282,7 +279,7 @@ class StreamChatLocalizationsKo extends GlobalStreamChatLocalizations { String get deleteConversationQuestion => '대화를 삭제하시겠습니까?'; @override - String get streamChatLabel => '스트림 채팅'; + String get streamChatLabel => '채팅'; @override String get searchingForNetworkText => '네트워크를 검색하는 중입니다.'; @@ -350,8 +347,7 @@ class StreamChatLocalizationsKo extends GlobalStreamChatLocalizations { String galleryPaginationText({ required int currentPage, required int totalPages, - }) => - '${currentPage + 1} / $totalPages'; + }) => '${currentPage + 1} / $totalPages'; //3 / 11 @@ -369,8 +365,7 @@ class StreamChatLocalizationsKo extends GlobalStreamChatLocalizations { String get viewLibrary => '라이브러리 보기'; @override - String attachmentLimitExceedError(int limit) => - '첨부 파일 제한 초과: $limit 이상의 첨부 파일을 추가할 수 없습니다'; + String attachmentLimitExceedError(int limit) => '첨부 파일 제한 초과: $limit 이상의 첨부 파일을 추가할 수 없습니다'; @override String get downloadLabel => '다운로드'; @@ -588,10 +583,10 @@ class StreamChatLocalizationsKo extends GlobalStreamChatLocalizations { @override String voteCountLabel({int? count}) => switch (count) { - null || < 1 => '0 표', - 1 => '1 표', - _ => '$count 표', - }; + null || < 1 => '0 표', + 1 => '1 표', + _ => '$count 표', + }; @override String get noPollVotesLabel => '현재 투표가 없습니다'; @@ -623,8 +618,7 @@ class StreamChatLocalizationsKo extends GlobalStreamChatLocalizations { String get moderationReviewModalTitle => '확실합니까?'; @override - String get moderationReviewModalDescription => - '''귀하의 댓글이 다른 사람들에게 어떤 영향을 미칠 수 있는지 고려하고 커뮤니티 가이드라인을 준수하세요.'''; + String get moderationReviewModalDescription => '''귀하의 댓글이 다른 사람들에게 어떤 영향을 미칠 수 있는지 고려하고 커뮤니티 가이드라인을 준수하세요.'''; @override String get emptyMessagePreviewText => ''; @@ -641,6 +635,18 @@ class StreamChatLocalizationsKo extends GlobalStreamChatLocalizations { @override String get videoAttachmentText => '비디오'; + @override + String get fileAttachmentText => '파일'; + + @override + String filesAttachmentCountText(int count) => count == 1 ? '파일' : '파일 $count개'; + + @override + String photosAttachmentCountText(int count) => count == 1 ? '사진' : '사진 $count장'; + + @override + String videosAttachmentCountText(int count) => count == 1 ? '동영상' : '동영상 $count개'; + @override String get pollYouVotedText => '투표했습니다'; diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_no.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_no.dart index 7ea81dae20..a335042b0d 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_no.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_no.dart @@ -51,8 +51,7 @@ class StreamChatLocalizationsNo extends GlobalStreamChatLocalizations { String attachmentsUploadProgressText({ required int remaining, required int total, - }) => - 'Laster opp $remaining/$total ...'; + }) => 'Laster opp $remaining/$total ...'; @override String pinnedByUserText({ @@ -65,8 +64,7 @@ class StreamChatLocalizationsNo extends GlobalStreamChatLocalizations { } @override - String get sendMessagePermissionError => - 'Du har ikke tillatelse til å sende meldinger'; + String get sendMessagePermissionError => 'Du har ikke tillatelse til å sende meldinger'; @override String get emptyMessagesText => 'Det er ingen meldinger akkurat nå'; @@ -135,8 +133,7 @@ class StreamChatLocalizationsNo extends GlobalStreamChatLocalizations { 'Vi prøvde å komprimere den, men det hjalp ikke.'; @override - String fileTooLargeError(double limitInMB) => - 'Filen er for stor til å laste opp. Filgrense er $limitInMB MB.'; + String fileTooLargeError(double limitInMB) => 'Filen er for stor til å laste opp. Filgrense er $limitInMB MB.'; @override String get addAFileLabel => 'Legg til en fil'; @@ -163,7 +160,7 @@ class StreamChatLocalizationsNo extends GlobalStreamChatLocalizations { String get somethingWentWrongError => 'Noe gikk galt'; @override - String get addMoreFilesLabel => 'Legg til flere filer'; + String get addMoreFilesLabel => 'Legg til flere'; @override String get enablePhotoAndVideoAccessMessage => @@ -189,8 +186,7 @@ class StreamChatLocalizationsNo extends GlobalStreamChatLocalizations { String get flagMessageSuccessfulLabel => 'Melding rapportert'; @override - String get flagMessageSuccessfulText => - 'Meldingen har blitt rapportert til en moderator.'; + String get flagMessageSuccessfulText => 'Meldingen har blitt rapportert til en moderator.'; @override String get deleteLabel => 'SLETT'; @@ -199,12 +195,10 @@ class StreamChatLocalizationsNo extends GlobalStreamChatLocalizations { String get deleteMessageLabel => 'Slett melding'; @override - String get deleteMessageQuestion => - 'Er du sikker på at du ønsker å slette denne meldingen permanent?'; + String get deleteMessageQuestion => 'Er du sikker på at du ønsker å slette denne meldingen permanent?'; @override - String get operationCouldNotBeCompletedText => - 'Denne handlingen kunne ikke bli gjennomført.'; + String get operationCouldNotBeCompletedText => 'Denne handlingen kunne ikke bli gjennomført.'; @override String get replyLabel => 'Svar'; @@ -274,8 +268,7 @@ class StreamChatLocalizationsNo extends GlobalStreamChatLocalizations { String get letsStartChattingLabel => 'La oss starte å chatte!'; @override - String get sendingFirstMessageLabel => - 'Hva med å sende din første melding til en venn?'; + String get sendingFirstMessageLabel => 'Hva med å sende din første melding til en venn?'; @override String get startAChatLabel => 'Start en chat'; @@ -287,11 +280,10 @@ class StreamChatLocalizationsNo extends GlobalStreamChatLocalizations { String get deleteConversationLabel => 'Slett samtale'; @override - String get deleteConversationQuestion => - 'Er du sikker på at du ønsker å slette denne samtalen?'; + String get deleteConversationQuestion => 'Er du sikker på at du ønsker å slette denne samtalen?'; @override - String get streamChatLabel => 'Stream Chat'; + String get streamChatLabel => 'Samtaler'; @override String get searchingForNetworkText => 'Søker etter nettverk'; @@ -327,8 +319,7 @@ class StreamChatLocalizationsNo extends GlobalStreamChatLocalizations { String get leaveConversationLabel => 'Forlat samtale'; @override - String get leaveConversationQuestion => - 'Er du sikker på at du ønsker å forlate denne samtalen?'; + String get leaveConversationQuestion => 'Er du sikker på at du ønsker å forlate denne samtalen?'; @override String get showInChatLabel => 'Se i chat'; @@ -364,8 +355,7 @@ class StreamChatLocalizationsNo extends GlobalStreamChatLocalizations { String galleryPaginationText({ required int currentPage, required int totalPages, - }) => - '${currentPage + 1} of $totalPages'; + }) => '${currentPage + 1} of $totalPages'; @override String get fileText => 'Fil'; @@ -374,15 +364,13 @@ class StreamChatLocalizationsNo extends GlobalStreamChatLocalizations { String get replyToMessageLabel => 'Svar på melding'; @override - String attachmentLimitExceedError(int limit) => - 'Antall vedlegg oversteget, maks antall: $limit'; + String attachmentLimitExceedError(int limit) => 'Antall vedlegg oversteget, maks antall: $limit'; @override String get slowModeOnLabel => 'Sakte modus PÅ'; @override - String get linkDisabledDetails => - 'Sende lenker er ikke lov i denne samtalen.'; + String get linkDisabledDetails => 'Sende lenker er ikke lov i denne samtalen.'; @override String get linkDisabledError => 'Lenker er deaktivert'; @@ -394,8 +382,7 @@ class StreamChatLocalizationsNo extends GlobalStreamChatLocalizations { String unreadMessagesSeparatorText() => 'Nye meldinger.'; @override - String get couldNotReadBytesFromFileError => - 'Kunne ikke lese bytes fra filen.'; + String get couldNotReadBytesFromFileError => 'Kunne ikke lese bytes fra filen.'; @override String get downloadLabel => 'Nedlasting'; @@ -435,8 +422,7 @@ class StreamChatLocalizationsNo extends GlobalStreamChatLocalizations { } @override - String get enableFileAccessMessage => - 'Aktiver tilgang til filer slik at du kan dele dem med venner.'; + String get enableFileAccessMessage => 'Aktiver tilgang til filer slik at du kan dele dem med venner.'; @override String get allowFileAccessMessage => 'Gi tilgang til filer'; @@ -502,8 +488,7 @@ class StreamChatLocalizationsNo extends GlobalStreamChatLocalizations { String get multipleAnswersLabel => 'Flere svar'; @override - String get maximumVotesPerPersonLabel => - 'Maksimalt antall stemmer per person'; + String get maximumVotesPerPersonLabel => 'Maksimalt antall stemmer per person'; @override String? maxVotesPerPersonValidationError(int votes, Range range) { @@ -545,15 +530,13 @@ class StreamChatLocalizationsNo extends GlobalStreamChatLocalizations { String get enterYourCommentLabel => 'Skriv inn kommentaren din'; @override - String get endVoteConfirmationText => - 'Er du sikker på at du vil avslutte avstemningen?'; + String get endVoteConfirmationText => 'Er du sikker på at du vil avslutte avstemningen?'; @override String get deletePollOptionLabel => 'Slett alternativ'; @override - String get deletePollOptionQuestion => - 'Er du sikker på at du vil slette dette alternativet?'; + String get deletePollOptionQuestion => 'Er du sikker på at du vil slette dette alternativet?'; @override String get createLabel => 'Opprett'; @@ -597,10 +580,10 @@ class StreamChatLocalizationsNo extends GlobalStreamChatLocalizations { @override String voteCountLabel({int? count}) => switch (count) { - null || < 1 => '0 stemmer', - 1 => '1 stemme', - _ => '$count stemmer', - }; + null || < 1 => '0 stemmer', + 1 => '1 stemme', + _ => '$count stemmer', + }; @override String get noPollVotesLabel => 'Det er ingen stemmer for øyeblikket'; @@ -627,8 +610,7 @@ class StreamChatLocalizationsNo extends GlobalStreamChatLocalizations { String get sendAnywayLabel => 'Send likevel'; @override - String get moderatedMessageBlockedText => - 'Meldingen ble blokkert av modereringsregler'; + String get moderatedMessageBlockedText => 'Meldingen ble blokkert av modereringsregler'; @override String get moderationReviewModalTitle => 'Er du sikker?'; @@ -652,6 +634,18 @@ class StreamChatLocalizationsNo extends GlobalStreamChatLocalizations { @override String get videoAttachmentText => 'Video'; + @override + String get fileAttachmentText => 'Fil'; + + @override + String filesAttachmentCountText(int count) => count == 1 ? 'Fil' : '$count filer'; + + @override + String photosAttachmentCountText(int count) => count == 1 ? 'Bilde' : '$count bilder'; + + @override + String videosAttachmentCountText(int count) => count == 1 ? 'Video' : '$count videoer'; + @override String get pollYouVotedText => 'Du stemte'; diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_pt.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_pt.dart index 2b8a79956e..72530f80fe 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_pt.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_pt.dart @@ -51,8 +51,7 @@ class StreamChatLocalizationsPt extends GlobalStreamChatLocalizations { String attachmentsUploadProgressText({ required int remaining, required int total, - }) => - 'Tranferência em andamento $remaining/$total ...'; + }) => 'Tranferência em andamento $remaining/$total ...'; @override String pinnedByUserText({ @@ -71,8 +70,7 @@ class StreamChatLocalizationsPt extends GlobalStreamChatLocalizations { String get genericErrorText => 'Ocorreu um problema'; @override - String get loadingMessagesError => - 'Ocorreu um problema ao carregar a mensagem'; + String get loadingMessagesError => 'Ocorreu um problema ao carregar a mensagem'; @override String resultCountText(int count) => '$count resultados'; @@ -111,8 +109,7 @@ class StreamChatLocalizationsPt extends GlobalStreamChatLocalizations { String get reconnectingLabel => 'Reconectando...'; @override - String get alsoSendAsDirectMessageLabel => - 'Enviar também como mensagem direta'; + String get alsoSendAsDirectMessageLabel => 'Enviar também como mensagem direta'; @override String get addACommentOrSendLabel => 'Adicionar um comnetário ou enviar'; @@ -138,8 +135,7 @@ class StreamChatLocalizationsPt extends GlobalStreamChatLocalizations { 'O tamanho máximo dos arquivos é de $limitInMB MB.'; @override - String get couldNotReadBytesFromFileError => - 'Não foi possível ler os bytes do arquivo.'; + String get couldNotReadBytesFromFileError => 'Não foi possível ler os bytes do arquivo.'; @override String get addAFileLabel => 'Adicionar um arquivo'; @@ -166,7 +162,7 @@ class StreamChatLocalizationsPt extends GlobalStreamChatLocalizations { String get somethingWentWrongError => 'Algo deu errado'; @override - String get addMoreFilesLabel => 'Adicionar mais arquivos'; + String get addMoreFilesLabel => 'Adicionar mais'; @override String get enablePhotoAndVideoAccessMessage => @@ -179,8 +175,7 @@ class StreamChatLocalizationsPt extends GlobalStreamChatLocalizations { String get flagMessageLabel => 'Denunciar mensagem'; @override - String get flagMessageQuestion => - 'Gostaria de enviar esta mensagem ao moderador para maior investigação?'; + String get flagMessageQuestion => 'Gostaria de enviar esta mensagem ao moderador para maior investigação?'; @override String get flagLabel => 'DENUNCIAR'; @@ -192,8 +187,7 @@ class StreamChatLocalizationsPt extends GlobalStreamChatLocalizations { String get flagMessageSuccessfulLabel => 'Mensagem denunciada'; @override - String get flagMessageSuccessfulText => - 'Esta mensagem foi enviada a um moderador.'; + String get flagMessageSuccessfulText => 'Esta mensagem foi enviada a um moderador.'; @override String get deleteLabel => 'APAGAR'; @@ -202,12 +196,10 @@ class StreamChatLocalizationsPt extends GlobalStreamChatLocalizations { String get deleteMessageLabel => 'Apagar mensagem'; @override - String get deleteMessageQuestion => - 'Você tem certeza que deseja apagar essa mensagem permanentemente?'; + String get deleteMessageQuestion => 'Você tem certeza que deseja apagar essa mensagem permanentemente?'; @override - String get operationCouldNotBeCompletedText => - 'A operação não pode ser completada.'; + String get operationCouldNotBeCompletedText => 'A operação não pode ser completada.'; @override String get replyLabel => 'Resposta'; @@ -277,8 +269,7 @@ class StreamChatLocalizationsPt extends GlobalStreamChatLocalizations { String get letsStartChattingLabel => 'Vamos começar a conversar!'; @override - String get sendingFirstMessageLabel => - 'Que tal enviar sua primeira mensagem a um amigo?'; + String get sendingFirstMessageLabel => 'Que tal enviar sua primeira mensagem a um amigo?'; @override String get startAChatLabel => 'Iniciar uma conversa'; @@ -290,11 +281,10 @@ class StreamChatLocalizationsPt extends GlobalStreamChatLocalizations { String get deleteConversationLabel => 'Apagar a conversa'; @override - String get deleteConversationQuestion => - 'Tem certeza que deseja apagar essa conversa?'; + String get deleteConversationQuestion => 'Tem certeza que deseja apagar essa conversa?'; @override - String get streamChatLabel => 'Stream Chat'; + String get streamChatLabel => 'Conversas'; @override String get searchingForNetworkText => 'Pesquisando rede'; @@ -330,8 +320,7 @@ class StreamChatLocalizationsPt extends GlobalStreamChatLocalizations { String get leaveConversationLabel => 'Sair da conversa'; @override - String get leaveConversationQuestion => - 'Tem certeza que deseja sair dessa conversa?'; + String get leaveConversationQuestion => 'Tem certeza que deseja sair dessa conversa?'; @override String get showInChatLabel => 'Mostrar no chat'; @@ -367,8 +356,7 @@ class StreamChatLocalizationsPt extends GlobalStreamChatLocalizations { String galleryPaginationText({ required int currentPage, required int totalPages, - }) => - '${currentPage + 1} de $totalPages'; + }) => '${currentPage + 1} de $totalPages'; @override String get fileText => 'Arquivo'; @@ -377,7 +365,8 @@ class StreamChatLocalizationsPt extends GlobalStreamChatLocalizations { String get replyToMessageLabel => 'Responder à mensagem'; @override - String attachmentLimitExceedError(int limit) => ''' + String attachmentLimitExceedError(int limit) => + ''' Não é possível adicionar mais de $limit arquivos de uma vez '''; @@ -433,15 +422,13 @@ Não é possível adicionar mais de $limit arquivos de uma vez } @override - String get linkDisabledDetails => - 'O envio de links não é permitido nesta conversa.'; + String get linkDisabledDetails => 'O envio de links não é permitido nesta conversa.'; @override String get linkDisabledError => 'Os links estão desativados'; @override - String get sendMessagePermissionError => - 'Você não tem permissão para enviar mensagens'; + String get sendMessagePermissionError => 'Você não tem permissão para enviar mensagens'; @override String get viewLibrary => 'Ver biblioteca'; @@ -450,8 +437,7 @@ Não é possível adicionar mais de $limit arquivos de uma vez String unreadMessagesSeparatorText() => 'Novas mensagens'; @override - String get enableFileAccessMessage => - 'Ative o acesso aos arquivos para poder compartilhá-los com amigos.'; + String get enableFileAccessMessage => 'Ative o acesso aos arquivos para poder compartilhá-los com amigos.'; @override String get allowFileAccessMessage => 'Permitir acesso aos arquivos'; @@ -559,15 +545,13 @@ Não é possível adicionar mais de $limit arquivos de uma vez String get enterYourCommentLabel => 'Inserir seu comentário'; @override - String get endVoteConfirmationText => - 'Tem certeza de que deseja encerrar a votação?'; + String get endVoteConfirmationText => 'Tem certeza de que deseja encerrar a votação?'; @override String get deletePollOptionLabel => 'Excluir opção'; @override - String get deletePollOptionQuestion => - 'Tem certeza de que deseja excluir esta opção?'; + String get deletePollOptionQuestion => 'Tem certeza de que deseja excluir esta opção?'; @override String get createLabel => 'Criar'; @@ -611,10 +595,10 @@ Não é possível adicionar mais de $limit arquivos de uma vez @override String voteCountLabel({int? count}) => switch (count) { - null || < 1 => '0 votos', - 1 => '1 voto', - _ => '$count votos', - }; + null || < 1 => '0 votos', + 1 => '1 voto', + _ => '$count votos', + }; @override String get noPollVotesLabel => 'Não há votos no momento'; @@ -635,15 +619,13 @@ Não é possível adicionar mais de $limit arquivos de uma vez String get slideToCancelLabel => 'Deslize para cancelar'; @override - String get holdToRecordLabel => - 'Mantenha pressionado para gravar, solte para enviar'; + String get holdToRecordLabel => 'Mantenha pressionado para gravar, solte para enviar'; @override String get sendAnywayLabel => 'Enviar mesmo assim'; @override - String get moderatedMessageBlockedText => - 'Mensagem bloqueada pelas políticas de moderação'; + String get moderatedMessageBlockedText => 'Mensagem bloqueada pelas políticas de moderação'; @override String get moderationReviewModalTitle => 'Tem certeza?'; @@ -667,6 +649,18 @@ Não é possível adicionar mais de $limit arquivos de uma vez @override String get videoAttachmentText => 'Vídeo'; + @override + String get fileAttachmentText => 'Arquivo'; + + @override + String filesAttachmentCountText(int count) => count == 1 ? 'Arquivo' : '$count arquivos'; + + @override + String photosAttachmentCountText(int count) => count == 1 ? 'Foto' : '$count fotos'; + + @override + String videosAttachmentCountText(int count) => count == 1 ? 'Vídeo' : '$count vídeos'; + @override String get pollYouVotedText => 'Você votou'; diff --git a/packages/stream_chat_localizations/lib/stream_chat_localizations.dart b/packages/stream_chat_localizations/lib/stream_chat_localizations.dart index 27c018806a..852e8e2f54 100644 --- a/packages/stream_chat_localizations/lib/stream_chat_localizations.dart +++ b/packages/stream_chat_localizations/lib/stream_chat_localizations.dart @@ -2,8 +2,5 @@ library stream_chat_localizations; export 'package:flutter_localizations/flutter_localizations.dart' - show - GlobalCupertinoLocalizations, - GlobalMaterialLocalizations, - GlobalWidgetsLocalizations; + show GlobalCupertinoLocalizations, GlobalMaterialLocalizations, GlobalWidgetsLocalizations; export 'src/stream_chat_localizations.dart' hide getStreamChatTranslation; diff --git a/packages/stream_chat_localizations/pubspec.yaml b/packages/stream_chat_localizations/pubspec.yaml index 7be029acac..acdce87309 100644 --- a/packages/stream_chat_localizations/pubspec.yaml +++ b/packages/stream_chat_localizations/pubspec.yaml @@ -18,8 +18,8 @@ issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues # 2. Add it to the melos.yaml file for future updates. environment: - sdk: ^3.6.2 - flutter: ">=3.27.4" + sdk: ^3.10.0 + flutter: ">=3.38.1" dependencies: flutter: diff --git a/packages/stream_chat_localizations/test/basics_test.dart b/packages/stream_chat_localizations/test/basics_test.dart index a05877f43c..f973c08d45 100644 --- a/packages/stream_chat_localizations/test/basics_test.dart +++ b/packages/stream_chat_localizations/test/basics_test.dart @@ -6,28 +6,32 @@ import 'package:stream_chat_localizations/stream_chat_localizations.dart'; void main() { testWidgets('Nested Localizations', (WidgetTester tester) async { - await tester.pumpWidget(MaterialApp( - theme: ThemeData( - useMaterial3: false, + await tester.pumpWidget( + MaterialApp( + theme: ThemeData( + useMaterial3: false, + ), + // Creates the outer Localizations widget. + home: ListView( + children: [ + const LocalizationTracker(key: ValueKey('outer')), + Localizations( + locale: const Locale('hi'), + delegates: GlobalStreamChatLocalizations.delegates, + child: const LocalizationTracker(key: ValueKey('inner')), + ), + ], + ), ), - // Creates the outer Localizations widget. - home: ListView( - children: [ - const LocalizationTracker(key: ValueKey('outer')), - Localizations( - locale: const Locale('hi'), - delegates: GlobalStreamChatLocalizations.delegates, - child: const LocalizationTracker(key: ValueKey('inner')), - ), - ], - ), - )); + ); final LocalizationTrackerState outerTracker = tester.state( - find.byKey(const ValueKey('outer'), skipOffstage: false)); + find.byKey(const ValueKey('outer'), skipOffstage: false), + ); expect(outerTracker.captionFontSize, 12.0); final LocalizationTrackerState innerTracker = tester.state( - find.byKey(const ValueKey('inner'), skipOffstage: false)); + find.byKey(const ValueKey('inner'), skipOffstage: false), + ); expect(innerTracker.captionFontSize, 13.0); }); @@ -36,19 +40,21 @@ void main() { 'during didChangeDependencies', (WidgetTester tester) async { // PageView calls ScrollPosition.dispose() during didChangeDependencies. - await tester.pumpWidget(MaterialApp( - supportedLocales: const [ - Locale('en', 'US'), - Locale('hi', 'IN'), - ], - localizationsDelegates: const [ - DummyLocalizations.delegate, - GlobalStreamChatLocalizations.delegate, - GlobalMaterialLocalizations.delegate, - GlobalCupertinoLocalizations.delegate, - ], - home: PageView(), - )); + await tester.pumpWidget( + MaterialApp( + supportedLocales: const [ + Locale('en', 'US'), + Locale('hi', 'IN'), + ], + localizationsDelegates: const [ + DummyLocalizations.delegate, + GlobalStreamChatLocalizations.delegate, + GlobalMaterialLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + ], + home: PageView(), + ), + ); await tester.binding.setLocale('hi', 'IN'); await tester.pump(); @@ -58,14 +64,16 @@ void main() { testWidgets('Locale without countryCode', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/pull/16782 - await tester.pumpWidget(MaterialApp( - localizationsDelegates: GlobalStreamChatLocalizations.delegates, - supportedLocales: const [ - Locale('en', 'US'), - Locale('hi'), - ], - home: Container(), - )); + await tester.pumpWidget( + MaterialApp( + localizationsDelegates: GlobalStreamChatLocalizations.delegates, + supportedLocales: const [ + Locale('en', 'US'), + Locale('hi'), + ], + home: Container(), + ), + ); await tester.binding.setLocale('hi', ''); await tester.pump(); @@ -76,8 +84,7 @@ void main() { /// A localizations delegate that does not contain any useful data, and is only /// used to trigger didChangeDependencies upon locale change. -class _DummyLocalizationsDelegate - extends LocalizationsDelegate { +class _DummyLocalizationsDelegate extends LocalizationsDelegate { const _DummyLocalizationsDelegate(); @override diff --git a/packages/stream_chat_localizations/test/override_test.dart b/packages/stream_chat_localizations/test/override_test.dart index 6b775de167..7714fa5f5a 100644 --- a/packages/stream_chat_localizations/test/override_test.dart +++ b/packages/stream_chat_localizations/test/override_test.dart @@ -16,8 +16,7 @@ class FooStreamChatLocalizations extends StreamChatLocalizationsEn { final String launchUrlError; } -class FooStreamChatLocalizationsDelegate - extends LocalizationsDelegate { +class FooStreamChatLocalizationsDelegate extends LocalizationsDelegate { const FooStreamChatLocalizationsDelegate({ this.supportedLanguage = 'en', this.launchUrlError = 'foo', @@ -27,15 +26,12 @@ class FooStreamChatLocalizationsDelegate final String launchUrlError; @override - bool isSupported(Locale locale) => - supportedLanguage == 'allLanguages' || - locale.languageCode == supportedLanguage; + bool isSupported(Locale locale) => supportedLanguage == 'allLanguages' || locale.languageCode == supportedLanguage; @override - Future load(Locale locale) => - SynchronousFuture( - FooStreamChatLocalizations(locale, launchUrlError), - ); + Future load(Locale locale) => SynchronousFuture( + FooStreamChatLocalizations(locale, launchUrlError), + ); @override bool shouldReload(FooStreamChatLocalizationsDelegate old) => false; @@ -43,24 +39,22 @@ class FooStreamChatLocalizationsDelegate Widget buildFrame({ Locale? locale, - Iterable delegates = - GlobalStreamChatLocalizations.delegates, + Iterable delegates = GlobalStreamChatLocalizations.delegates, required WidgetBuilder buildContent, LocaleResolutionCallback? localeResolutionCallback, Iterable supportedLocales = const [ Locale('en', 'US'), Locale('hi', 'IN'), ], -}) => - MaterialApp( - color: const Color(0xFFFFFFFF), - locale: locale, - supportedLocales: supportedLocales, - localizationsDelegates: delegates, - localeResolutionCallback: localeResolutionCallback, - onGenerateRoute: (RouteSettings settings) => MaterialPageRoute( - builder: (BuildContext context) => buildContent(context)), - ); +}) => MaterialApp( + color: const Color(0xFFFFFFFF), + locale: locale, + supportedLocales: supportedLocales, + localizationsDelegates: delegates, + localeResolutionCallback: localeResolutionCallback, + onGenerateRoute: (RouteSettings settings) => + MaterialPageRoute(builder: (BuildContext context) => buildContent(context)), +); void main() { testWidgets( @@ -104,23 +98,23 @@ void main() { "Localizations.override widget tracks parent's locale", (WidgetTester tester) async { Widget buildLocaleFrame(Locale locale) => buildFrame( - locale: locale, - supportedLocales: [locale], - buildContent: (BuildContext context) => Localizations.override( - context: context, - child: Builder( - builder: (BuildContext context) { - // No StreamChatLocalizations are defined for the first - // Localizations ancestor, so we should get the values from - // the default one, i.e. the one created by WidgetsApp via - // the LocalizationsDelegate provided by MaterialApp. - return Text( - StreamChatLocalizations.of(context)!.launchUrlError, - ); - }, - ), - ), - ); + locale: locale, + supportedLocales: [locale], + buildContent: (BuildContext context) => Localizations.override( + context: context, + child: Builder( + builder: (BuildContext context) { + // No StreamChatLocalizations are defined for the first + // Localizations ancestor, so we should get the values from + // the default one, i.e. the one created by WidgetsApp via + // the LocalizationsDelegate provided by MaterialApp. + return Text( + StreamChatLocalizations.of(context)!.launchUrlError, + ); + }, + ), + ), + ); await tester.pumpWidget(buildLocaleFrame(const Locale('en', 'US'))); expect(find.text('Cannot launch the url'), findsOneWidget); @@ -130,28 +124,27 @@ void main() { }, ); - testWidgets('Localizations.override widget with hardwired locale', - (WidgetTester tester) async { + testWidgets('Localizations.override widget with hardwired locale', (WidgetTester tester) async { Widget buildLocaleFrame(Locale locale) => buildFrame( - locale: locale, - buildContent: (BuildContext context) { - return Localizations.override( - context: context, - locale: const Locale('en', 'US'), - child: Builder( - builder: (BuildContext context) { - // No StreamChatLocalizations are defined for the first - // Localizations ancestor, so we should get the values from - // the default one, i.e. the one created by WidgetsApp via - // the LocalizationsDelegate provided by MaterialApp. - return Text( - StreamChatLocalizations.of(context)!.launchUrlError, - ); - }, - ), - ); - }, + locale: locale, + buildContent: (BuildContext context) { + return Localizations.override( + context: context, + locale: const Locale('en', 'US'), + child: Builder( + builder: (BuildContext context) { + // No StreamChatLocalizations are defined for the first + // Localizations ancestor, so we should get the values from + // the default one, i.e. the one created by WidgetsApp via + // the LocalizationsDelegate provided by MaterialApp. + return Text( + StreamChatLocalizations.of(context)!.launchUrlError, + ); + }, + ), ); + }, + ); await tester.pumpWidget(buildLocaleFrame(const Locale('en', 'US'))); expect(find.text('Cannot launch the url'), findsOneWidget); @@ -165,30 +158,32 @@ void main() { (WidgetTester tester) async { final Key textKey = UniqueKey(); - await tester.pumpWidget(buildFrame( - delegates: [ - ...GlobalStreamChatLocalizations.delegates, - const FooStreamChatLocalizationsDelegate( - supportedLanguage: 'fr', - launchUrlError: "Impossible de lancer l'url", - ), - const FooStreamChatLocalizationsDelegate( - supportedLanguage: 'uz', - launchUrlError: 'test', + await tester.pumpWidget( + buildFrame( + delegates: [ + ...GlobalStreamChatLocalizations.delegates, + const FooStreamChatLocalizationsDelegate( + supportedLanguage: 'fr', + launchUrlError: "Impossible de lancer l'url", + ), + const FooStreamChatLocalizationsDelegate( + supportedLanguage: 'uz', + launchUrlError: 'test', + ), + ], + supportedLocales: const [ + Locale('en'), + Locale('hi'), + Locale('fr'), + Locale('de'), + Locale('uz'), + ], + buildContent: (BuildContext context) => Text( + StreamChatLocalizations.of(context)!.launchUrlError, + key: textKey, ), - ], - supportedLocales: const [ - Locale('en'), - Locale('hi'), - Locale('fr'), - Locale('de'), - Locale('uz'), - ], - buildContent: (BuildContext context) => Text( - StreamChatLocalizations.of(context)!.launchUrlError, - key: textKey, ), - )); + ); expect( tester.widget(find.byKey(textKey)).data, @@ -214,24 +209,25 @@ void main() { (WidgetTester tester) async { final Key textKey = UniqueKey(); - await tester.pumpWidget(buildFrame( - // Accept whatever locale we're given - localeResolutionCallback: - (Locale? locale, Iterable supportedLocales) => locale, - delegates: [ - const FooStreamChatLocalizationsDelegate( - supportedLanguage: 'allLanguages', - ), - ...GlobalStreamChatLocalizations.delegates, - ], - buildContent: (BuildContext context) { - // Should always be 'foo', no matter what the locale is - return Text( - StreamChatLocalizations.of(context)!.launchUrlError, - key: textKey, - ); - }, - )); + await tester.pumpWidget( + buildFrame( + // Accept whatever locale we're given + localeResolutionCallback: (Locale? locale, Iterable supportedLocales) => locale, + delegates: [ + const FooStreamChatLocalizationsDelegate( + supportedLanguage: 'allLanguages', + ), + ...GlobalStreamChatLocalizations.delegates, + ], + buildContent: (BuildContext context) { + // Should always be 'foo', no matter what the locale is + return Text( + StreamChatLocalizations.of(context)!.launchUrlError, + key: textKey, + ); + }, + ), + ); expect(tester.widget(find.byKey(textKey)).data, 'foo'); @@ -250,16 +246,18 @@ void main() { (WidgetTester tester) async { final Key textKey = UniqueKey(); - await tester.pumpWidget(buildFrame( - delegates: [ - const FooStreamChatLocalizationsDelegate(), - ], - // supportedLocales not specified, so all locales resolve to 'en' - buildContent: (BuildContext context) => Text( - StreamChatLocalizations.of(context)!.launchUrlError, - key: textKey, + await tester.pumpWidget( + buildFrame( + delegates: [ + const FooStreamChatLocalizationsDelegate(), + ], + // supportedLocales not specified, so all locales resolve to 'en' + buildContent: (BuildContext context) => Text( + StreamChatLocalizations.of(context)!.launchUrlError, + key: textKey, + ), ), - )); + ); // Unsupported locale '_' (the widget tester's default) resolves to 'en'. expect(tester.widget(find.byKey(textKey)).data, 'foo'); diff --git a/packages/stream_chat_localizations/test/translations_test.dart b/packages/stream_chat_localizations/test/translations_test.dart index 7abac606d3..8dde5986ad 100644 --- a/packages/stream_chat_localizations/test/translations_test.dart +++ b/packages/stream_chat_localizations/test/translations_test.dart @@ -7,10 +7,8 @@ void main() { for (final language in kStreamChatSupportedLanguages) { test('translations exist for $language', () async { final locale = Locale(language); - expect( - GlobalStreamChatLocalizations.delegate.isSupported(locale), isTrue); - final localizations = - await GlobalStreamChatLocalizations.delegate.load(locale); + expect(GlobalStreamChatLocalizations.delegate.isSupported(locale), isTrue); + final localizations = await GlobalStreamChatLocalizations.delegate.load(locale); expect(localizations.launchUrlError, isNotNull); expect(localizations.loadingUsersError, isNotNull); expect(localizations.noUsersLabel, isNotNull); @@ -194,18 +192,15 @@ void main() { expect(localizations.couldNotReadBytesFromFileError, isNotNull); expect(localizations.toggleMuteUnmuteAction(isMuted: false), isNotNull); expect(localizations.downloadLabel, isNotNull); - expect(localizations.toggleMuteUnmuteGroupQuestion(isMuted: true), - isNotNull); + expect(localizations.toggleMuteUnmuteGroupQuestion(isMuted: true), isNotNull); expect(localizations.toggleMuteUnmuteGroupText(isMuted: true), isNotNull); - expect( - localizations.toggleMuteUnmuteUserQuestion(isMuted: true), isNotNull); + expect(localizations.toggleMuteUnmuteUserQuestion(isMuted: true), isNotNull); expect(localizations.toggleMuteUnmuteUserText(isMuted: true), isNotNull); expect(localizations.viewLibrary, isNotNull); expect(localizations.unreadMessagesSeparatorText(), isNotNull); expect(localizations.enableFileAccessMessage, isNotNull); expect(localizations.allowFileAccessMessage, isNotNull); - expect( - localizations.unreadCountIndicatorLabel(unreadCount: 2), isNotNull); + expect(localizations.unreadCountIndicatorLabel(unreadCount: 2), isNotNull); expect(localizations.unreadMessagesSeparatorText(), isNotNull); expect(localizations.markUnreadError, isNotNull); expect(localizations.markAsUnreadLabel, isNotNull); @@ -306,6 +301,10 @@ void main() { expect(localizations.audioAttachmentText, isNotNull); expect(localizations.imageAttachmentText, isNotNull); expect(localizations.videoAttachmentText, isNotNull); + expect(localizations.fileAttachmentText, isNotNull); + expect(localizations.filesAttachmentCountText(3), isNotNull); + expect(localizations.photosAttachmentCountText(3), isNotNull); + expect(localizations.videosAttachmentCountText(3), isNotNull); expect(localizations.pollYouVotedText, isNotNull); expect(localizations.pollSomeoneVotedText('TestUser'), isNotNull); expect(localizations.pollYouCreatedText, isNotNull); diff --git a/packages/stream_chat_persistence/example/lib/main.dart b/packages/stream_chat_persistence/example/lib/main.dart index 6e71a555da..f82143fbbf 100644 --- a/packages/stream_chat_persistence/example/lib/main.dart +++ b/packages/stream_chat_persistence/example/lib/main.dart @@ -22,8 +22,7 @@ Future main() async { await client.connectUser( User( id: 'cool-shadow-7', - image: - 'https://getstream.io/random_png/?id=cool-shadow-7&name=Cool+shadow', + image: 'https://getstream.io/random_png/?id=cool-shadow-7&name=Cool+shadow', ), 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiY29vbC1zaGFkb3ctNyJ9.' 'gkOlCRb1qgy4joHPaxFwPOdXcGvSPvp6QY0S4mpRkVo', @@ -95,31 +94,32 @@ class HomeScreen extends StatelessWidget { body: SafeArea( child: StreamBuilder( stream: messages, - builder: ( - BuildContext context, - AsyncSnapshot snapshot, - ) { - if (snapshot.hasData && snapshot.data != null) { - final _messages = snapshot.data!.messages ?? []; - return MessageView( - messages: _messages.reversed.toList(), - channel: channel, - ); - } else if (snapshot.hasError) { - return const Center( - child: Text( - 'There was an error loading messages. Please see logs.', - ), - ); - } - return const Center( - child: SizedBox( - width: 100, - height: 100, - child: CircularProgressIndicator(), - ), - ); - }, + builder: + ( + BuildContext context, + AsyncSnapshot snapshot, + ) { + if (snapshot.hasData && snapshot.data != null) { + final _messages = snapshot.data!.messages ?? []; + return MessageView( + messages: _messages.reversed.toList(), + channel: channel, + ); + } else if (snapshot.hasError) { + return const Center( + child: Text( + 'There was an error loading messages. Please see logs.', + ), + ); + } + return const Center( + child: SizedBox( + width: 100, + height: 100, + child: CircularProgressIndicator(), + ), + ); + }, ), ), ); diff --git a/packages/stream_chat_persistence/example/pubspec.yaml b/packages/stream_chat_persistence/example/pubspec.yaml index 05bb0e7956..e9b5296d15 100644 --- a/packages/stream_chat_persistence/example/pubspec.yaml +++ b/packages/stream_chat_persistence/example/pubspec.yaml @@ -16,8 +16,8 @@ version: 1.0.0+1 # 2. Add it to the melos.yaml file for future updates. environment: - sdk: ^3.6.2 - flutter: ">=3.27.4" + sdk: ^3.10.0 + flutter: ">=3.38.1" dependencies: cupertino_icons: ^1.0.3 diff --git a/packages/stream_chat_persistence/lib/src/converter/voting_visibility_converter.dart b/packages/stream_chat_persistence/lib/src/converter/voting_visibility_converter.dart index 2d5786d558..047be71a46 100644 --- a/packages/stream_chat_persistence/lib/src/converter/voting_visibility_converter.dart +++ b/packages/stream_chat_persistence/lib/src/converter/voting_visibility_converter.dart @@ -2,8 +2,7 @@ import 'package:drift/drift.dart'; import 'package:stream_chat/stream_chat.dart'; /// A [TypeConverter] that serializes [VotingVisibility] to a [String] column. -class VotingVisibilityConverter - extends TypeConverter { +class VotingVisibilityConverter extends TypeConverter { /// Constant default constructor. const VotingVisibilityConverter(); diff --git a/packages/stream_chat_persistence/lib/src/dao/channel_dao.dart b/packages/stream_chat_persistence/lib/src/dao/channel_dao.dart index f3a3de415a..88754425c6 100644 --- a/packages/stream_chat_persistence/lib/src/dao/channel_dao.dart +++ b/packages/stream_chat_persistence/lib/src/dao/channel_dao.dart @@ -9,20 +9,21 @@ part 'channel_dao.g.dart'; /// The Data Access Object for operations in [Channels] table. @DriftAccessor(tables: [Channels, Users]) -class ChannelDao extends DatabaseAccessor - with _$ChannelDaoMixin { +class ChannelDao extends DatabaseAccessor with _$ChannelDaoMixin { /// Creates a new channel dao instance ChannelDao(super.db); /// Get channel by cid - Future getChannelByCid(String cid) async => - (select(channels)..where((c) => c.cid.equals(cid))).join([ + Future getChannelByCid(String cid) async => (select(channels)..where((c) => c.cid.equals(cid))) + .join([ leftOuterJoin(users, channels.createdById.equalsExp(users.id)), - ]).map((rows) { + ]) + .map((rows) { final channel = rows.readTable(channels); final createdBy = rows.readTableOrNull(users); return channel.toChannelModel(createdBy: createdBy?.toUser()); - }).getSingleOrNull(); + }) + .getSingleOrNull(); /// Delete all channels by matching cid in [cids] /// @@ -34,17 +35,18 @@ class ChannelDao extends DatabaseAccessor (delete(channels)..where((tbl) => tbl.cid.isIn(cids))).go(); /// Get the channel cids saved in the storage - Future> get cids => (select(channels) - ..orderBy([(c) => OrderingTerm.desc(c.lastMessageAt)]) - ..limit(250)) - .map((c) => c.cid) - .get(); + Future> get cids => + (select(channels) + ..orderBy([(c) => OrderingTerm.desc(c.lastMessageAt)]) + ..limit(250)) + .map((c) => c.cid) + .get(); /// Updates all the channels using the new [channelList] data Future updateChannels(List channelList) => batch( - (it) => it.insertAllOnConflictUpdate( - channels, - channelList.map((c) => c.toEntity()).toList(), - ), - ); + (it) => it.insertAllOnConflictUpdate( + channels, + channelList.map((c) => c.toEntity()).toList(), + ), + ); } diff --git a/packages/stream_chat_persistence/lib/src/dao/channel_query_dao.dart b/packages/stream_chat_persistence/lib/src/dao/channel_query_dao.dart index b0931ee3be..a8f0d922f8 100644 --- a/packages/stream_chat_persistence/lib/src/dao/channel_query_dao.dart +++ b/packages/stream_chat_persistence/lib/src/dao/channel_query_dao.dart @@ -12,8 +12,7 @@ part 'channel_query_dao.g.dart'; /// The Data Access Object for operations in [ChannelQueries] table. @DriftAccessor(tables: [ChannelQueries, Channels, Users]) -class ChannelQueryDao extends DatabaseAccessor - with _$ChannelQueryDaoMixin { +class ChannelQueryDao extends DatabaseAccessor with _$ChannelQueryDaoMixin { /// Creates a new channel query dao instance ChannelQueryDao(super.db); @@ -32,35 +31,29 @@ class ChannelQueryDao extends DatabaseAccessor Filter? filter, List cids, { bool clearQueryCache = false, - }) async => - transaction(() async { - final hash = _computeHash(filter); - if (clearQueryCache) { - await batch((it) { - it.deleteWhere( - channelQueries, - (c) => c.queryHash.equals(hash), - ); - }); - } - - await batch((it) { - it.insertAllOnConflictUpdate( - channelQueries, - cids - .map((cid) => - ChannelQueryEntity(queryHash: hash, channelCid: cid)) - .toList(), - ); - }); + }) async => transaction(() async { + final hash = _computeHash(filter); + if (clearQueryCache) { + await batch((it) { + it.deleteWhere( + channelQueries, + (c) => c.queryHash.equals(hash), + ); }); + } + + await batch((it) { + it.insertAllOnConflictUpdate( + channelQueries, + cids.map((cid) => ChannelQueryEntity(queryHash: hash, channelCid: cid)).toList(), + ); + }); + }); /// Future> getCachedChannelCids(Filter? filter) { final hash = _computeHash(filter); - return (select(channelQueries)..where((c) => c.queryHash.equals(hash))) - .map((c) => c.channelCid) - .get(); + return (select(channelQueries)..where((c) => c.queryHash.equals(hash))).map((c) => c.channelCid).get(); } /// Get list of channels by filter, sort and paginationParams @@ -68,13 +61,16 @@ class ChannelQueryDao extends DatabaseAccessor final cachedChannelCids = await getCachedChannelCids(filter); final query = select(channels)..where((c) => c.cid.isIn(cachedChannelCids)); - final cachedChannels = await query.join([ - leftOuterJoin(users, channels.createdById.equalsExp(users.id)), - ]).map((row) { - final createdByEntity = row.readTableOrNull(users); - final channelEntity = row.readTable(channels); - return channelEntity.toChannelModel(createdBy: createdByEntity?.toUser()); - }).get(); + final cachedChannels = await query + .join([ + leftOuterJoin(users, channels.createdById.equalsExp(users.id)), + ]) + .map((row) { + final createdByEntity = row.readTableOrNull(users); + final channelEntity = row.readTable(channels); + return channelEntity.toChannelModel(createdBy: createdByEntity?.toUser()); + }) + .get(); return cachedChannels; } diff --git a/packages/stream_chat_persistence/lib/src/dao/connection_event_dao.dart b/packages/stream_chat_persistence/lib/src/dao/connection_event_dao.dart index fa7ff9829d..976d5931f0 100644 --- a/packages/stream_chat_persistence/lib/src/dao/connection_event_dao.dart +++ b/packages/stream_chat_persistence/lib/src/dao/connection_event_dao.dart @@ -9,37 +9,32 @@ part 'connection_event_dao.g.dart'; /// The Data Access Object for operations in [ConnectionEvents] table. @DriftAccessor(tables: [ConnectionEvents]) -class ConnectionEventDao extends DatabaseAccessor - with _$ConnectionEventDaoMixin { +class ConnectionEventDao extends DatabaseAccessor with _$ConnectionEventDaoMixin { /// Creates a new connection event dao instance ConnectionEventDao(super.db); /// Get the latest stored connection event - Future get connectionEvent => select(connectionEvents) - .map((eventEntity) => eventEntity.toEvent()) - .getSingleOrNull(); + Future get connectionEvent => + select(connectionEvents).map((eventEntity) => eventEntity.toEvent()).getSingleOrNull(); /// Get the latest stored lastSyncAt - Future get lastSyncAt => - select(connectionEvents).getSingleOrNull().then((r) => r?.lastSyncAt); + Future get lastSyncAt => select(connectionEvents).getSingleOrNull().then((r) => r?.lastSyncAt); /// Update stored connection event with latest data Future updateConnectionEvent(Event event) => transaction(() async { - final connectionInfo = await select(connectionEvents).getSingleOrNull(); - return into(connectionEvents).insertOnConflictUpdate( - ConnectionEventEntity( - id: 1, - type: event.type, - lastSyncAt: connectionInfo?.lastSyncAt, - lastEventAt: event.createdAt, - totalUnreadCount: - event.totalUnreadCount ?? connectionInfo?.totalUnreadCount, - ownUser: event.me?.toJson() ?? connectionInfo?.ownUser, - unreadChannels: - event.unreadChannels ?? connectionInfo?.unreadChannels, - ), - ); - }); + final connectionInfo = await select(connectionEvents).getSingleOrNull(); + return into(connectionEvents).insertOnConflictUpdate( + ConnectionEventEntity( + id: 1, + type: event.type, + lastSyncAt: connectionInfo?.lastSyncAt, + lastEventAt: event.createdAt, + totalUnreadCount: event.totalUnreadCount ?? connectionInfo?.totalUnreadCount, + ownUser: event.me?.toJson() ?? connectionInfo?.ownUser, + unreadChannels: event.unreadChannels ?? connectionInfo?.unreadChannels, + ), + ); + }); /// Update stored lastSyncAt with latest data Future updateLastSyncAt(DateTime lastSyncAt) async => diff --git a/packages/stream_chat_persistence/lib/src/dao/connection_event_dao.g.dart b/packages/stream_chat_persistence/lib/src/dao/connection_event_dao.g.dart index 32541de5d5..10a23aa21a 100644 --- a/packages/stream_chat_persistence/lib/src/dao/connection_event_dao.g.dart +++ b/packages/stream_chat_persistence/lib/src/dao/connection_event_dao.g.dart @@ -4,6 +4,5 @@ part of 'connection_event_dao.dart'; // ignore_for_file: type=lint mixin _$ConnectionEventDaoMixin on DatabaseAccessor { - $ConnectionEventsTable get connectionEvents => - attachedDatabase.connectionEvents; + $ConnectionEventsTable get connectionEvents => attachedDatabase.connectionEvents; } diff --git a/packages/stream_chat_persistence/lib/src/dao/draft_message_dao.dart b/packages/stream_chat_persistence/lib/src/dao/draft_message_dao.dart index 5065b3bda4..a0602a0679 100644 --- a/packages/stream_chat_persistence/lib/src/dao/draft_message_dao.dart +++ b/packages/stream_chat_persistence/lib/src/dao/draft_message_dao.dart @@ -10,8 +10,7 @@ part 'draft_message_dao.g.dart'; /// The Data Access Object for operations in [DraftMessages] table. @DriftAccessor(tables: [DraftMessages, Messages]) -class DraftMessageDao extends DatabaseAccessor - with _$DraftMessageDaoMixin { +class DraftMessageDao extends DatabaseAccessor with _$DraftMessageDaoMixin { /// Creates a new draft message dao instance DraftMessageDao(this._db) : super(_db); @@ -26,19 +25,19 @@ class DraftMessageDao extends DatabaseAccessor final parentMessage = await switch (entity.parentId) { final id? => _db.messageDao.getMessageById( - id, - fetchDraft: fetchDraft, - fetchSharedLocation: fetchSharedLocation, - ), + id, + fetchDraft: fetchDraft, + fetchSharedLocation: fetchSharedLocation, + ), _ => null, }; final quotedMessage = await switch (entity.quotedMessageId) { final id? => _db.messageDao.getMessageById( - id, - fetchDraft: fetchDraft, - fetchSharedLocation: fetchSharedLocation, - ), + id, + fetchDraft: fetchDraft, + fetchSharedLocation: fetchSharedLocation, + ), _ => null, }; diff --git a/packages/stream_chat_persistence/lib/src/dao/location_dao.dart b/packages/stream_chat_persistence/lib/src/dao/location_dao.dart index 6e12421179..f6d6bb6cfa 100644 --- a/packages/stream_chat_persistence/lib/src/dao/location_dao.dart +++ b/packages/stream_chat_persistence/lib/src/dao/location_dao.dart @@ -10,8 +10,7 @@ part 'location_dao.g.dart'; /// The Data Access Object for operations in [Locations] table. @DriftAccessor(tables: [Locations]) -class LocationDao extends DatabaseAccessor - with _$LocationDaoMixin { +class LocationDao extends DatabaseAccessor with _$LocationDaoMixin { /// Creates a new location dao instance LocationDao(this._db) : super(_db); @@ -31,10 +30,10 @@ class LocationDao extends DatabaseAccessor final message = await switch (entity.messageId) { final id? => _db.messageDao.getMessageById( - id, - fetchDraft: fetchDraft, - fetchSharedLocation: fetchSharedLocation, - ), + id, + fetchDraft: fetchDraft, + fetchSharedLocation: fetchSharedLocation, + ), _ => null, }; @@ -54,8 +53,9 @@ class LocationDao extends DatabaseAccessor /// Get location by message ID Future getLocationByMessageId(String messageId) async { - final query = select(locations) // - ..where((tbl) => tbl.messageId.equals(messageId)); + final query = + select(locations) // + ..where((tbl) => tbl.messageId.equals(messageId)); final result = await query.getSingleOrNull(); if (result == null) return null; @@ -74,8 +74,7 @@ class LocationDao extends DatabaseAccessor } /// Delete locations by channel ID - Future deleteLocationsByCid(String cid) => - (delete(locations)..where((tbl) => tbl.channelCid.equals(cid))).go(); + Future deleteLocationsByCid(String cid) => (delete(locations)..where((tbl) => tbl.channelCid.equals(cid))).go(); /// Delete locations by message IDs Future deleteLocationsByMessageIds(List messageIds) => diff --git a/packages/stream_chat_persistence/lib/src/dao/member_dao.dart b/packages/stream_chat_persistence/lib/src/dao/member_dao.dart index fb345d3bff..7d61ecb4cc 100644 --- a/packages/stream_chat_persistence/lib/src/dao/member_dao.dart +++ b/packages/stream_chat_persistence/lib/src/dao/member_dao.dart @@ -9,38 +9,39 @@ part 'member_dao.g.dart'; /// The Data Access Object for operations in [Members] table. @DriftAccessor(tables: [Members, Users]) -class MemberDao extends DatabaseAccessor - with _$MemberDaoMixin { +class MemberDao extends DatabaseAccessor with _$MemberDaoMixin { /// Creates a new member dao instance MemberDao(super.db); /// Get all members where [Members.channelCid] matches [cid] Future> getMembersByCid(String cid) async => (select(members).join([ - leftOuterJoin(users, members.userId.equalsExp(users.id)), - ]) + leftOuterJoin(users, members.userId.equalsExp(users.id)), + ]) ..where(members.channelCid.equals(cid)) ..orderBy([OrderingTerm.asc(members.createdAt)])) .map((row) { - final userEntity = row.readTable(users); - final memberEntity = row.readTable(members); - return memberEntity.toMember(user: userEntity.toUser()); - }).get(); + final userEntity = row.readTable(users); + final memberEntity = row.readTable(members); + return memberEntity.toMember(user: userEntity.toUser()); + }) + .get(); /// Updates all the members using the new [memberList] data - Future updateMembers(String cid, List memberList) => - bulkUpdateMembers({cid: memberList}); + Future updateMembers(String cid, List memberList) => bulkUpdateMembers({cid: memberList}); /// Bulk updates the members data of multiple channels Future bulkUpdateMembers( Map?> channelWithMembers, ) { final entities = channelWithMembers.entries - .map((entry) => - entry.value?.map( - (member) => member.toEntity(cid: entry.key), - ) ?? - []) + .map( + (entry) => + entry.value?.map( + (member) => member.toEntity(cid: entry.key), + ) ?? + [], + ) .expand((it) => it) .toList(growable: false); return batch( @@ -50,9 +51,9 @@ class MemberDao extends DatabaseAccessor /// Deletes all the members whose [Members.channelCid] is present in [cids] Future deleteMemberByCids(List cids) async => batch((it) { - it.deleteWhere( - members, - (m) => m.channelCid.isIn(cids), - ); - }); + it.deleteWhere( + members, + (m) => m.channelCid.isIn(cids), + ); + }); } diff --git a/packages/stream_chat_persistence/lib/src/dao/message_dao.dart b/packages/stream_chat_persistence/lib/src/dao/message_dao.dart index 53e15c48f1..86b8f7275c 100644 --- a/packages/stream_chat_persistence/lib/src/dao/message_dao.dart +++ b/packages/stream_chat_persistence/lib/src/dao/message_dao.dart @@ -12,8 +12,7 @@ part 'message_dao.g.dart'; /// The Data Access Object for operations in [Messages] table. @DriftAccessor(tables: [Messages, Users]) -class MessageDao extends DatabaseAccessor - with _$MessageDaoMixin { +class MessageDao extends DatabaseAccessor with _$MessageDaoMixin { /// Creates a new message dao instance MessageDao(this._db) : super(_db); @@ -63,9 +62,9 @@ class MessageDao extends DatabaseAccessor final draft = await switch (fetchDraft) { true => _db.draftMessageDao.getDraftMessageByCid( - msgEntity.channelCid, - parentId: msgEntity.id, - ), + msgEntity.channelCid, + parentId: msgEntity.id, + ), _ => null, }; @@ -101,8 +100,7 @@ class MessageDao extends DatabaseAccessor _pinnedByUsers, messages.pinnedByUserId.equalsExp(_pinnedByUsers.id), ), - ]) - ..where(messages.id.equals(id)); + ])..where(messages.id.equals(id)); final result = await query.getSingleOrNull(); if (result == null) return null; @@ -116,19 +114,20 @@ class MessageDao extends DatabaseAccessor /// Returns all the messages of a particular thread by matching /// [Messages.channelCid] with [cid] - Future> getThreadMessages(String cid) async => - Future.wait(await (select(messages).join([ - leftOuterJoin(_users, messages.userId.equalsExp(_users.id)), - leftOuterJoin( - _pinnedByUsers, - messages.pinnedByUserId.equalsExp(_pinnedByUsers.id), - ), - ]) - ..where(messages.channelCid.equals(cid)) - ..where(messages.parentId.isNotNull()) - ..orderBy([OrderingTerm.asc(messages.createdAt)])) - .map(_messageFromJoinRow) - .get()); + Future> getThreadMessages(String cid) async => Future.wait( + await (select(messages).join([ + leftOuterJoin(_users, messages.userId.equalsExp(_users.id)), + leftOuterJoin( + _pinnedByUsers, + messages.pinnedByUserId.equalsExp(_pinnedByUsers.id), + ), + ]) + ..where(messages.channelCid.equals(cid)) + ..where(messages.parentId.isNotNull()) + ..orderBy([OrderingTerm.asc(messages.createdAt)])) + .map(_messageFromJoinRow) + .get(), + ); /// Returns all the messages of a particular thread by matching /// [Messages.parentId] with [parentId] @@ -136,18 +135,20 @@ class MessageDao extends DatabaseAccessor String parentId, { PaginationParams? options, }) async { - final msgList = await Future.wait(await (select(messages).join([ - leftOuterJoin(_users, messages.userId.equalsExp(_users.id)), - leftOuterJoin( - _pinnedByUsers, - messages.pinnedByUserId.equalsExp(_pinnedByUsers.id), - ), - ]) - ..where(messages.parentId.isNotNull()) - ..where(messages.parentId.equals(parentId)) - ..orderBy([OrderingTerm.asc(messages.createdAt)])) - .map(_messageFromJoinRow) - .get()); + final msgList = await Future.wait( + await (select(messages).join([ + leftOuterJoin(_users, messages.userId.equalsExp(_users.id)), + leftOuterJoin( + _pinnedByUsers, + messages.pinnedByUserId.equalsExp(_pinnedByUsers.id), + ), + ]) + ..where(messages.parentId.isNotNull()) + ..where(messages.parentId.equals(parentId)) + ..orderBy([OrderingTerm.asc(messages.createdAt)])) + .map(_messageFromJoinRow) + .get(), + ); if (msgList.isNotEmpty) { if (options?.lessThan != null) { @@ -182,16 +183,17 @@ class MessageDao extends DatabaseAccessor bool fetchSharedLocation = true, PaginationParams? messagePagination, }) async { - final query = select(messages).join([ - leftOuterJoin(_users, messages.userId.equalsExp(_users.id)), - leftOuterJoin( - _pinnedByUsers, - messages.pinnedByUserId.equalsExp(_pinnedByUsers.id), - ), - ]) - ..where(messages.channelCid.equals(cid)) - ..where(messages.parentId.isNull() | messages.showInChannel.equals(true)) - ..orderBy([OrderingTerm.asc(messages.createdAt)]); + final query = + select(messages).join([ + leftOuterJoin(_users, messages.userId.equalsExp(_users.id)), + leftOuterJoin( + _pinnedByUsers, + messages.pinnedByUserId.equalsExp(_pinnedByUsers.id), + ), + ]) + ..where(messages.channelCid.equals(cid)) + ..where(messages.parentId.isNull() | messages.showInChannel.equals(true)) + ..orderBy([OrderingTerm.asc(messages.createdAt)]); final result = await query.get(); if (result.isEmpty) return []; @@ -224,9 +226,7 @@ class MessageDao extends DatabaseAccessor } } if (messagePagination?.limit != null) { - return msgList - .skip(max(0, msgList.length - messagePagination!.limit)) - .toList(); + return msgList.skip(max(0, msgList.length - messagePagination!.limit)).toList(); } } return msgList; @@ -253,8 +253,7 @@ class MessageDao extends DatabaseAccessor }) async { if (hardDelete) { // Hard delete: remove from database - final deleteQuery = delete(messages) - ..where((tbl) => tbl.userId.equals(userId)); + final deleteQuery = delete(messages)..where((tbl) => tbl.userId.equals(userId)); if (cid != null) { deleteQuery.where((tbl) => tbl.channelCid.equals(cid)); @@ -264,8 +263,7 @@ class MessageDao extends DatabaseAccessor } // Soft delete: update messages to mark as deleted - final updateQuery = update(messages) - ..where((tbl) => tbl.userId.equals(userId)); + final updateQuery = update(messages)..where((tbl) => tbl.userId.equals(userId)); if (cid != null) { updateQuery.where((tbl) => tbl.channelCid.equals(cid)); @@ -282,19 +280,20 @@ class MessageDao extends DatabaseAccessor /// Updates the message data of a particular channel with /// the new [messageList] data - Future updateMessages(String cid, List messageList) => - bulkUpdateMessages({cid: messageList}); + Future updateMessages(String cid, List messageList) => bulkUpdateMessages({cid: messageList}); /// Bulk updates the message data of multiple channels Future bulkUpdateMessages( Map?> channelWithMessages, ) { final entities = channelWithMessages.entries - .map((entry) => - entry.value?.map( - (message) => message.toEntity(cid: entry.key), - ) ?? - []) + .map( + (entry) => + entry.value?.map( + (message) => message.toEntity(cid: entry.key), + ) ?? + [], + ) .expand((it) => it) .toList(growable: false); return batch( diff --git a/packages/stream_chat_persistence/lib/src/dao/pinned_message_dao.dart b/packages/stream_chat_persistence/lib/src/dao/pinned_message_dao.dart index afd0c18c33..413c7164f4 100644 --- a/packages/stream_chat_persistence/lib/src/dao/pinned_message_dao.dart +++ b/packages/stream_chat_persistence/lib/src/dao/pinned_message_dao.dart @@ -12,8 +12,7 @@ part 'pinned_message_dao.g.dart'; /// The Data Access Object for operations in [Messages] table. @DriftAccessor(tables: [PinnedMessages, Users]) -class PinnedMessageDao extends DatabaseAccessor - with _$PinnedMessageDaoMixin { +class PinnedMessageDao extends DatabaseAccessor with _$PinnedMessageDaoMixin { /// Creates a new message dao instance PinnedMessageDao(this._db) : super(_db); @@ -45,10 +44,8 @@ class PinnedMessageDao extends DatabaseAccessor final userEntity = rows.readTableOrNull(_users); final pinnedByEntity = rows.readTableOrNull(_pinnedByUsers); final msgEntity = rows.readTable(pinnedMessages); - final latestReactions = - await _db.pinnedMessageReactionDao.getReactions(msgEntity.id); - final ownReactions = - await _db.pinnedMessageReactionDao.getReactionsByUserId( + final latestReactions = await _db.pinnedMessageReactionDao.getReactions(msgEntity.id); + final ownReactions = await _db.pinnedMessageReactionDao.getReactionsByUserId( msgEntity.id, _db.userId, ); @@ -65,9 +62,9 @@ class PinnedMessageDao extends DatabaseAccessor final draft = await switch (fetchDraft) { true => _db.draftMessageDao.getDraftMessageByCid( - msgEntity.channelCid, - parentId: msgEntity.id, - ), + msgEntity.channelCid, + parentId: msgEntity.id, + ), _ => null, }; @@ -100,8 +97,7 @@ class PinnedMessageDao extends DatabaseAccessor _pinnedByUsers, pinnedMessages.pinnedByUserId.equalsExp(_pinnedByUsers.id), ), - ]) - ..where(pinnedMessages.id.equals(id)); + ])..where(pinnedMessages.id.equals(id)); final result = await query.getSingleOrNull(); if (result == null) return null; @@ -115,19 +111,20 @@ class PinnedMessageDao extends DatabaseAccessor /// Returns all the messages of a particular thread by matching /// [PinnedMessages.channelCid] with [cid] - Future> getThreadMessages(String cid) async => - Future.wait(await (select(pinnedMessages).join([ - leftOuterJoin(_users, pinnedMessages.userId.equalsExp(_users.id)), - leftOuterJoin( - _pinnedByUsers, - pinnedMessages.pinnedByUserId.equalsExp(_pinnedByUsers.id), - ), - ]) - ..where(pinnedMessages.channelCid.equals(cid)) - ..where(pinnedMessages.parentId.isNotNull()) - ..orderBy([OrderingTerm.asc(pinnedMessages.createdAt)])) - .map(_messageFromJoinRow) - .get()); + Future> getThreadMessages(String cid) async => Future.wait( + await (select(pinnedMessages).join([ + leftOuterJoin(_users, pinnedMessages.userId.equalsExp(_users.id)), + leftOuterJoin( + _pinnedByUsers, + pinnedMessages.pinnedByUserId.equalsExp(_pinnedByUsers.id), + ), + ]) + ..where(pinnedMessages.channelCid.equals(cid)) + ..where(pinnedMessages.parentId.isNotNull()) + ..orderBy([OrderingTerm.asc(pinnedMessages.createdAt)])) + .map(_messageFromJoinRow) + .get(), + ); /// Returns all the messages of a particular thread by matching /// [PinnedMessages.parentId] with [parentId] @@ -135,18 +132,20 @@ class PinnedMessageDao extends DatabaseAccessor String parentId, { PaginationParams? options, }) async { - final msgList = await Future.wait(await (select(pinnedMessages).join([ - leftOuterJoin(_users, pinnedMessages.userId.equalsExp(_users.id)), - leftOuterJoin( - _pinnedByUsers, - pinnedMessages.pinnedByUserId.equalsExp(_pinnedByUsers.id), - ), - ]) - ..where(pinnedMessages.parentId.isNotNull()) - ..where(pinnedMessages.parentId.equals(parentId)) - ..orderBy([OrderingTerm.asc(pinnedMessages.createdAt)])) - .map(_messageFromJoinRow) - .get()); + final msgList = await Future.wait( + await (select(pinnedMessages).join([ + leftOuterJoin(_users, pinnedMessages.userId.equalsExp(_users.id)), + leftOuterJoin( + _pinnedByUsers, + pinnedMessages.pinnedByUserId.equalsExp(_pinnedByUsers.id), + ), + ]) + ..where(pinnedMessages.parentId.isNotNull()) + ..where(pinnedMessages.parentId.equals(parentId)) + ..orderBy([OrderingTerm.asc(pinnedMessages.createdAt)])) + .map(_messageFromJoinRow) + .get(), + ); if (msgList.isNotEmpty) { if (options?.lessThan != null) { @@ -180,17 +179,17 @@ class PinnedMessageDao extends DatabaseAccessor bool fetchSharedLocation = true, PaginationParams? messagePagination, }) async { - final query = select(pinnedMessages).join([ - leftOuterJoin(_users, pinnedMessages.userId.equalsExp(_users.id)), - leftOuterJoin( - _pinnedByUsers, - pinnedMessages.pinnedByUserId.equalsExp(_pinnedByUsers.id), - ), - ]) - ..where(pinnedMessages.channelCid.equals(cid)) - ..where(pinnedMessages.parentId.isNull() | - pinnedMessages.showInChannel.equals(true)) - ..orderBy([OrderingTerm.asc(pinnedMessages.createdAt)]); + final query = + select(pinnedMessages).join([ + leftOuterJoin(_users, pinnedMessages.userId.equalsExp(_users.id)), + leftOuterJoin( + _pinnedByUsers, + pinnedMessages.pinnedByUserId.equalsExp(_pinnedByUsers.id), + ), + ]) + ..where(pinnedMessages.channelCid.equals(cid)) + ..where(pinnedMessages.parentId.isNull() | pinnedMessages.showInChannel.equals(true)) + ..orderBy([OrderingTerm.asc(pinnedMessages.createdAt)]); final result = await query.get(); if (result.isEmpty) return []; @@ -250,8 +249,7 @@ class PinnedMessageDao extends DatabaseAccessor }) async { if (hardDelete) { // Hard delete: remove from database - final deleteQuery = delete(pinnedMessages) - ..where((tbl) => tbl.userId.equals(userId)); + final deleteQuery = delete(pinnedMessages)..where((tbl) => tbl.userId.equals(userId)); if (cid != null) { deleteQuery.where((tbl) => tbl.channelCid.equals(cid)); @@ -261,8 +259,7 @@ class PinnedMessageDao extends DatabaseAccessor } // Soft delete: update messages to mark as deleted - final updateQuery = update(pinnedMessages) - ..where((tbl) => tbl.userId.equals(userId)); + final updateQuery = update(pinnedMessages)..where((tbl) => tbl.userId.equals(userId)); if (cid != null) { updateQuery.where((tbl) => tbl.channelCid.equals(cid)); @@ -279,19 +276,20 @@ class PinnedMessageDao extends DatabaseAccessor /// Updates the message data of a particular channel with /// the new [messageList] data - Future updateMessages(String cid, List messageList) => - bulkUpdateMessages({cid: messageList}); + Future updateMessages(String cid, List messageList) => bulkUpdateMessages({cid: messageList}); /// Bulk updates the message data of multiple channels Future bulkUpdateMessages( Map?> channelWithMessages, ) { final entities = channelWithMessages.entries - .map((entry) => - entry.value?.map( - (message) => message.toPinnedEntity(cid: entry.key), - ) ?? - []) + .map( + (entry) => + entry.value?.map( + (message) => message.toPinnedEntity(cid: entry.key), + ) ?? + [], + ) .expand((it) => it) .toList(growable: false); return batch( diff --git a/packages/stream_chat_persistence/lib/src/dao/pinned_message_reaction_dao.dart b/packages/stream_chat_persistence/lib/src/dao/pinned_message_reaction_dao.dart index c5c5a6d456..9a81c6e46e 100644 --- a/packages/stream_chat_persistence/lib/src/dao/pinned_message_reaction_dao.dart +++ b/packages/stream_chat_persistence/lib/src/dao/pinned_message_reaction_dao.dart @@ -9,8 +9,7 @@ part 'pinned_message_reaction_dao.g.dart'; /// The Data Access Object for operations in [PinnedMessageReactions] table. @DriftAccessor(tables: [PinnedMessageReactions, Users]) -class PinnedMessageReactionDao extends DatabaseAccessor - with _$PinnedMessageReactionDaoMixin { +class PinnedMessageReactionDao extends DatabaseAccessor with _$PinnedMessageReactionDaoMixin { /// Creates a new reaction dao instance PinnedMessageReactionDao(super.db); @@ -18,15 +17,16 @@ class PinnedMessageReactionDao extends DatabaseAccessor /// [Reactions.messageId] with [messageId] Future> getReactions(String messageId) => (select(pinnedMessageReactions).join([ - leftOuterJoin(users, pinnedMessageReactions.userId.equalsExp(users.id)), - ]) + leftOuterJoin(users, pinnedMessageReactions.userId.equalsExp(users.id)), + ]) ..where(pinnedMessageReactions.messageId.equals(messageId)) ..orderBy([OrderingTerm.asc(pinnedMessageReactions.createdAt)])) .map((rows) { - final userEntity = rows.readTableOrNull(users); - final reactionEntity = rows.readTable(pinnedMessageReactions); - return reactionEntity.toReaction(user: userEntity?.toUser()); - }).get(); + final userEntity = rows.readTableOrNull(users); + final reactionEntity = rows.readTable(pinnedMessageReactions); + return reactionEntity.toReaction(user: userEntity?.toUser()); + }) + .get(); /// Returns all the reactions of a particular message /// added by a particular user by matching @@ -42,19 +42,18 @@ class PinnedMessageReactionDao extends DatabaseAccessor /// Updates the reactions data with the new [reactionList] data Future updateReactions(List reactionList) => batch((it) { - it.insertAllOnConflictUpdate( - pinnedMessageReactions, - reactionList.map((r) => r.toPinnedEntity()).toList(), - ); - }); + it.insertAllOnConflictUpdate( + pinnedMessageReactions, + reactionList.map((r) => r.toPinnedEntity()).toList(), + ); + }); /// Deletes all the reactions whose [Reactions.messageId] is /// present in [messageIds] - Future deleteReactionsByMessageIds(List messageIds) => - batch((it) { - it.deleteWhere( - pinnedMessageReactions, - (r) => r.messageId.isIn(messageIds), - ); - }); + Future deleteReactionsByMessageIds(List messageIds) => batch((it) { + it.deleteWhere( + pinnedMessageReactions, + (r) => r.messageId.isIn(messageIds), + ); + }); } diff --git a/packages/stream_chat_persistence/lib/src/dao/pinned_message_reaction_dao.g.dart b/packages/stream_chat_persistence/lib/src/dao/pinned_message_reaction_dao.g.dart index d25c2f4512..0b7393b68b 100644 --- a/packages/stream_chat_persistence/lib/src/dao/pinned_message_reaction_dao.g.dart +++ b/packages/stream_chat_persistence/lib/src/dao/pinned_message_reaction_dao.g.dart @@ -5,7 +5,6 @@ part of 'pinned_message_reaction_dao.dart'; // ignore_for_file: type=lint mixin _$PinnedMessageReactionDaoMixin on DatabaseAccessor { $PinnedMessagesTable get pinnedMessages => attachedDatabase.pinnedMessages; - $PinnedMessageReactionsTable get pinnedMessageReactions => - attachedDatabase.pinnedMessageReactions; + $PinnedMessageReactionsTable get pinnedMessageReactions => attachedDatabase.pinnedMessageReactions; $UsersTable get users => attachedDatabase.users; } diff --git a/packages/stream_chat_persistence/lib/src/dao/poll_dao.dart b/packages/stream_chat_persistence/lib/src/dao/poll_dao.dart index 10a10a024d..43924285e7 100644 --- a/packages/stream_chat_persistence/lib/src/dao/poll_dao.dart +++ b/packages/stream_chat_persistence/lib/src/dao/poll_dao.dart @@ -45,28 +45,27 @@ class PollDao extends DatabaseAccessor with _$PollDaoMixin { } /// Returns the poll by matching [Polls.id] with [pollId] - Future getPollById(String pollId) async => - await (select(polls)..where((it) => it.id.equals(pollId))) - .join([leftOuterJoin(users, polls.createdById.equalsExp(users.id))]) - .map(_pollFromJoinRow) - .getSingleOrNull(); + Future getPollById(String pollId) async => await (select(polls)..where((it) => it.id.equals(pollId))) + .join([leftOuterJoin(users, polls.createdById.equalsExp(users.id))]) + .map(_pollFromJoinRow) + .getSingleOrNull(); /// Updates all the polls using the new [pollList] data Future updatePolls(List pollList) => batch( - (it) => it.insertAllOnConflictUpdate( - polls, - pollList.map((it) => it.toEntity()), - ), - ); + (it) => it.insertAllOnConflictUpdate( + polls, + pollList.map((it) => it.toEntity()), + ), + ); /// Returns the list of all the polls stored in db - Future> getPolls() async => Future.wait(await (select(polls) - ..orderBy([(it) => OrderingTerm.desc(it.createdAt)])) - .join([leftOuterJoin(users, polls.createdById.equalsExp(users.id))]) - .map(_pollFromJoinRow) - .get()); + Future> getPolls() async => Future.wait( + await (select(polls)..orderBy([(it) => OrderingTerm.desc(it.createdAt)])) + .join([leftOuterJoin(users, polls.createdById.equalsExp(users.id))]) + .map(_pollFromJoinRow) + .get(), + ); /// Deletes all the polls whose [Polls.id] is present in [pollIds] - Future deletePollsByIds(List pollIds) => - (delete(polls)..where((tbl) => tbl.id.isIn(pollIds))).go(); + Future deletePollsByIds(List pollIds) => (delete(polls)..where((tbl) => tbl.id.isIn(pollIds))).go(); } diff --git a/packages/stream_chat_persistence/lib/src/dao/poll_vote_dao.dart b/packages/stream_chat_persistence/lib/src/dao/poll_vote_dao.dart index 55884f4a70..6fb765d8e3 100644 --- a/packages/stream_chat_persistence/lib/src/dao/poll_vote_dao.dart +++ b/packages/stream_chat_persistence/lib/src/dao/poll_vote_dao.dart @@ -11,8 +11,7 @@ part 'poll_vote_dao.g.dart'; /// The Data Access Object for operations in [Polls] table. @DriftAccessor(tables: [PollVotes, Users]) -class PollVoteDao extends DatabaseAccessor - with _$PollVoteDaoMixin { +class PollVoteDao extends DatabaseAccessor with _$PollVoteDaoMixin { /// Creates a new poll vote dao instance PollVoteDao(super.db); @@ -20,23 +19,24 @@ class PollVoteDao extends DatabaseAccessor /// [Reactions.messageId] with [messageId] Future> getPollVotes(String pollId) => (select(pollVotes).join([ - leftOuterJoin(users, pollVotes.userId.equalsExp(users.id)), - ]) + leftOuterJoin(users, pollVotes.userId.equalsExp(users.id)), + ]) ..where(pollVotes.pollId.equals(pollId)) ..orderBy([OrderingTerm.asc(pollVotes.createdAt)])) .map((rows) { - final userEntity = rows.readTableOrNull(users); - final pollVoteEntity = rows.readTable(pollVotes); - return pollVoteEntity.toPollVote(user: userEntity?.toUser()); - }).get(); + final userEntity = rows.readTableOrNull(users); + final pollVoteEntity = rows.readTable(pollVotes); + return pollVoteEntity.toPollVote(user: userEntity?.toUser()); + }) + .get(); /// Updates the poll votes data with the new [pollVoteList] data Future updatePollVotes(List pollVoteList) => batch( - (it) => it.insertAllOnConflictUpdate( - pollVotes, - pollVoteList.map((it) => it.toEntity()), - ), - ); + (it) => it.insertAllOnConflictUpdate( + pollVotes, + pollVoteList.map((it) => it.toEntity()), + ), + ); /// Deletes all the poll votes whose [PollVote.pollId] is /// present in [pollIds] diff --git a/packages/stream_chat_persistence/lib/src/dao/reaction_dao.dart b/packages/stream_chat_persistence/lib/src/dao/reaction_dao.dart index d6dae9bd99..2a68626159 100644 --- a/packages/stream_chat_persistence/lib/src/dao/reaction_dao.dart +++ b/packages/stream_chat_persistence/lib/src/dao/reaction_dao.dart @@ -9,8 +9,7 @@ part 'reaction_dao.g.dart'; /// The Data Access Object for operations in [Reactions] table. @DriftAccessor(tables: [Reactions, Users]) -class ReactionDao extends DatabaseAccessor - with _$ReactionDaoMixin { +class ReactionDao extends DatabaseAccessor with _$ReactionDaoMixin { /// Creates a new reaction dao instance ReactionDao(super.db); @@ -18,15 +17,16 @@ class ReactionDao extends DatabaseAccessor /// [Reactions.messageId] with [messageId] Future> getReactions(String messageId) => (select(reactions).join([ - leftOuterJoin(users, reactions.userId.equalsExp(users.id)), - ]) + leftOuterJoin(users, reactions.userId.equalsExp(users.id)), + ]) ..where(reactions.messageId.equals(messageId)) ..orderBy([OrderingTerm.asc(reactions.createdAt)])) .map((rows) { - final userEntity = rows.readTableOrNull(users); - final reactionEntity = rows.readTable(reactions); - return reactionEntity.toReaction(user: userEntity?.toUser()); - }).get(); + final userEntity = rows.readTableOrNull(users); + final reactionEntity = rows.readTable(reactions); + return reactionEntity.toReaction(user: userEntity?.toUser()); + }) + .get(); /// Returns all the reactions of a particular message /// added by a particular user by matching @@ -42,19 +42,18 @@ class ReactionDao extends DatabaseAccessor /// Updates the reactions data with the new [reactionList] data Future updateReactions(List reactionList) => batch((it) { - it.insertAllOnConflictUpdate( - reactions, - reactionList.map((r) => r.toEntity()).toList(), - ); - }); + it.insertAllOnConflictUpdate( + reactions, + reactionList.map((r) => r.toEntity()).toList(), + ); + }); /// Deletes all the reactions whose [Reactions.messageId] is /// present in [messageIds] - Future deleteReactionsByMessageIds(List messageIds) => - batch((it) { - it.deleteWhere( - reactions, - (r) => r.messageId.isIn(messageIds), - ); - }); + Future deleteReactionsByMessageIds(List messageIds) => batch((it) { + it.deleteWhere( + reactions, + (r) => r.messageId.isIn(messageIds), + ); + }); } diff --git a/packages/stream_chat_persistence/lib/src/dao/read_dao.dart b/packages/stream_chat_persistence/lib/src/dao/read_dao.dart index 4e2024f1ff..c6beec0f9f 100644 --- a/packages/stream_chat_persistence/lib/src/dao/read_dao.dart +++ b/packages/stream_chat_persistence/lib/src/dao/read_dao.dart @@ -14,32 +14,35 @@ class ReadDao extends DatabaseAccessor with _$ReadDaoMixin { ReadDao(super.db); /// Get all reads where [Reads.channelCid] matches [cid] - Future> getReadsByCid(String cid) async => (select(reads).join([ - leftOuterJoin(users, reads.userId.equalsExp(users.id)), - ]) + Future> getReadsByCid(String cid) async => + (select(reads).join([ + leftOuterJoin(users, reads.userId.equalsExp(users.id)), + ]) ..where(reads.channelCid.equals(cid)) ..orderBy([ OrderingTerm.asc(reads.lastRead), ])) .map((row) { - final userEntity = row.readTable(users); - final readEntity = row.readTable(reads); - return readEntity.toRead(user: userEntity.toUser()); - }).get(); + final userEntity = row.readTable(users); + final readEntity = row.readTable(reads); + return readEntity.toRead(user: userEntity.toUser()); + }) + .get(); /// Updates the read data of a particular channel with /// the new [readList] data - Future updateReads(String cid, List readList) => - bulkUpdateReads({cid: readList}); + Future updateReads(String cid, List readList) => bulkUpdateReads({cid: readList}); /// Bulk updates the reads data of multiple channels Future bulkUpdateReads(Map?> channelWithReads) { final entities = channelWithReads.entries - .map((entry) => - entry.value?.map( - (read) => read.toEntity(cid: entry.key), - ) ?? - []) + .map( + (entry) => + entry.value?.map( + (read) => read.toEntity(cid: entry.key), + ) ?? + [], + ) .expand((it) => it) .toList(growable: false); return batch((batch) => batch.insertAllOnConflictUpdate(reads, entities)); diff --git a/packages/stream_chat_persistence/lib/src/dao/user_dao.dart b/packages/stream_chat_persistence/lib/src/dao/user_dao.dart index 1366dd2541..214425d882 100644 --- a/packages/stream_chat_persistence/lib/src/dao/user_dao.dart +++ b/packages/stream_chat_persistence/lib/src/dao/user_dao.dart @@ -14,15 +14,13 @@ class UserDao extends DatabaseAccessor with _$UserDaoMixin { /// Updates the users data with the new [userList] data Future updateUsers(List userList) => batch( - (it) => it.insertAllOnConflictUpdate( - users, - userList.map((u) => u.toEntity()).toList(), - ), - ); + (it) => it.insertAllOnConflictUpdate( + users, + userList.map((u) => u.toEntity()).toList(), + ), + ); /// Returns the list of all the users stored in db Future> getUsers() => - (select(users)..orderBy([(u) => OrderingTerm.desc(u.createdAt)])) - .map((it) => it.toUser()) - .get(); + (select(users)..orderBy([(u) => OrderingTerm.desc(u.createdAt)])).map((it) => it.toUser()).get(); } diff --git a/packages/stream_chat_persistence/lib/src/db/drift_chat_database.dart b/packages/stream_chat_persistence/lib/src/db/drift_chat_database.dart index ab0a5c3ff5..3fc6eb552d 100644 --- a/packages/stream_chat_persistence/lib/src/db/drift_chat_database.dart +++ b/packages/stream_chat_persistence/lib/src/db/drift_chat_database.dart @@ -61,18 +61,18 @@ class DriftChatDatabase extends _$DriftChatDatabase { @override MigrationStrategy get migration => MigrationStrategy( - beforeOpen: (details) async { - await customStatement('PRAGMA foreign_keys = ON'); - }, - onUpgrade: (migrator, from, to) async { - if (from != to) { - for (final table in allTables) { - await migrator.deleteTable(table.actualTableName); - } - await migrator.createAll(); - } - }, - ); + beforeOpen: (details) async { + await customStatement('PRAGMA foreign_keys = ON'); + }, + onUpgrade: (migrator, from, to) async { + if (from != to) { + for (final table in allTables) { + await migrator.deleteTable(table.actualTableName); + } + await migrator.createAll(); + } + }, + ); /// Deletes all the tables Future flush() async { diff --git a/packages/stream_chat_persistence/lib/src/db/drift_chat_database.g.dart b/packages/stream_chat_persistence/lib/src/db/drift_chat_database.g.dart index 59e1bd5ef9..899609385e 100644 --- a/packages/stream_chat_persistence/lib/src/db/drift_chat_database.g.dart +++ b/packages/stream_chat_persistence/lib/src/db/drift_chat_database.g.dart @@ -3,8 +3,7 @@ part of 'drift_chat_database.dart'; // ignore_for_file: type=lint -class $ChannelsTable extends Channels - with TableInfo<$ChannelsTable, ChannelEntity> { +class $ChannelsTable extends Channels with TableInfo<$ChannelsTable, ChannelEntity> { @override final GeneratedDatabase attachedDatabase; final String? _alias; @@ -12,124 +11,164 @@ class $ChannelsTable extends Channels static const VerificationMeta _idMeta = const VerificationMeta('id'); @override late final GeneratedColumn id = GeneratedColumn( - 'id', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true); + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); static const VerificationMeta _typeMeta = const VerificationMeta('type'); @override late final GeneratedColumn type = GeneratedColumn( - 'type', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true); + 'type', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); static const VerificationMeta _cidMeta = const VerificationMeta('cid'); @override late final GeneratedColumn cid = GeneratedColumn( - 'cid', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true); - @override - late final GeneratedColumnWithTypeConverter?, String> - ownCapabilities = GeneratedColumn( - 'own_capabilities', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false) - .withConverter?>( - $ChannelsTable.$converterownCapabilitiesn); - @override - late final GeneratedColumnWithTypeConverter, String> - config = GeneratedColumn('config', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true) - .withConverter>($ChannelsTable.$converterconfig); + 'cid', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + late final GeneratedColumnWithTypeConverter?, String> ownCapabilities = GeneratedColumn( + 'own_capabilities', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ).withConverter?>($ChannelsTable.$converterownCapabilitiesn); + @override + late final GeneratedColumnWithTypeConverter, String> config = GeneratedColumn( + 'config', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ).withConverter>($ChannelsTable.$converterconfig); static const VerificationMeta _frozenMeta = const VerificationMeta('frozen'); @override late final GeneratedColumn frozen = GeneratedColumn( - 'frozen', aliasedName, false, - type: DriftSqlType.bool, - requiredDuringInsert: false, - defaultConstraints: - GeneratedColumn.constraintIsAlways('CHECK ("frozen" IN (0, 1))'), - defaultValue: const Constant(false)); - static const VerificationMeta _lastMessageAtMeta = - const VerificationMeta('lastMessageAt'); - @override - late final GeneratedColumn lastMessageAt = - GeneratedColumn('last_message_at', aliasedName, true, - type: DriftSqlType.dateTime, requiredDuringInsert: false); - static const VerificationMeta _createdAtMeta = - const VerificationMeta('createdAt'); + 'frozen', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways('CHECK ("frozen" IN (0, 1))'), + defaultValue: const Constant(false), + ); + static const VerificationMeta _lastMessageAtMeta = const VerificationMeta('lastMessageAt'); + @override + late final GeneratedColumn lastMessageAt = GeneratedColumn( + 'last_message_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + static const VerificationMeta _createdAtMeta = const VerificationMeta('createdAt'); @override late final GeneratedColumn createdAt = GeneratedColumn( - 'created_at', aliasedName, false, - type: DriftSqlType.dateTime, - requiredDuringInsert: false, - defaultValue: currentDateAndTime); - static const VerificationMeta _updatedAtMeta = - const VerificationMeta('updatedAt'); + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime, + ); + static const VerificationMeta _updatedAtMeta = const VerificationMeta('updatedAt'); @override late final GeneratedColumn updatedAt = GeneratedColumn( - 'updated_at', aliasedName, false, - type: DriftSqlType.dateTime, - requiredDuringInsert: false, - defaultValue: currentDateAndTime); - static const VerificationMeta _deletedAtMeta = - const VerificationMeta('deletedAt'); + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime, + ); + static const VerificationMeta _deletedAtMeta = const VerificationMeta('deletedAt'); @override late final GeneratedColumn deletedAt = GeneratedColumn( - 'deleted_at', aliasedName, true, - type: DriftSqlType.dateTime, requiredDuringInsert: false); - static const VerificationMeta _memberCountMeta = - const VerificationMeta('memberCount'); + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + static const VerificationMeta _memberCountMeta = const VerificationMeta('memberCount'); @override late final GeneratedColumn memberCount = GeneratedColumn( - 'member_count', aliasedName, false, - type: DriftSqlType.int, - requiredDuringInsert: false, - defaultValue: const Constant(0)); - static const VerificationMeta _messageCountMeta = - const VerificationMeta('messageCount'); + 'member_count', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const Constant(0), + ); + static const VerificationMeta _messageCountMeta = const VerificationMeta('messageCount'); @override late final GeneratedColumn messageCount = GeneratedColumn( - 'message_count', aliasedName, true, - type: DriftSqlType.int, requiredDuringInsert: false); - static const VerificationMeta _createdByIdMeta = - const VerificationMeta('createdById'); + 'message_count', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + static const VerificationMeta _createdByIdMeta = const VerificationMeta('createdById'); @override late final GeneratedColumn createdById = GeneratedColumn( - 'created_by_id', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); - @override - late final GeneratedColumnWithTypeConverter?, String> - filterTags = GeneratedColumn('filter_tags', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false) - .withConverter?>($ChannelsTable.$converterfilterTagsn); - @override - late final GeneratedColumnWithTypeConverter?, String> - extraData = GeneratedColumn('extra_data', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false) - .withConverter?>( - $ChannelsTable.$converterextraDatan); + 'created_by_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + late final GeneratedColumnWithTypeConverter?, String> filterTags = GeneratedColumn( + 'filter_tags', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ).withConverter?>($ChannelsTable.$converterfilterTagsn); + @override + late final GeneratedColumnWithTypeConverter?, String> extraData = GeneratedColumn( + 'extra_data', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ).withConverter?>($ChannelsTable.$converterextraDatan); @override List get $columns => [ - id, - type, - cid, - ownCapabilities, - config, - frozen, - lastMessageAt, - createdAt, - updatedAt, - deletedAt, - memberCount, - messageCount, - createdById, - filterTags, - extraData - ]; + id, + type, + cid, + ownCapabilities, + config, + frozen, + lastMessageAt, + createdAt, + updatedAt, + deletedAt, + memberCount, + messageCount, + createdById, + filterTags, + extraData, + ]; @override String get aliasedName => _alias ?? actualTableName; @override String get actualTableName => $name; static const String $name = 'channels'; @override - VerificationContext validateIntegrity(Insertable instance, - {bool isInserting = false}) { + VerificationContext validateIntegrity(Insertable instance, {bool isInserting = false}) { final context = VerificationContext(); final data = instance.toColumns(true); if (data.containsKey('id')) { @@ -138,56 +177,41 @@ class $ChannelsTable extends Channels context.missing(_idMeta); } if (data.containsKey('type')) { - context.handle( - _typeMeta, type.isAcceptableOrUnknown(data['type']!, _typeMeta)); + context.handle(_typeMeta, type.isAcceptableOrUnknown(data['type']!, _typeMeta)); } else if (isInserting) { context.missing(_typeMeta); } if (data.containsKey('cid')) { - context.handle( - _cidMeta, cid.isAcceptableOrUnknown(data['cid']!, _cidMeta)); + context.handle(_cidMeta, cid.isAcceptableOrUnknown(data['cid']!, _cidMeta)); } else if (isInserting) { context.missing(_cidMeta); } if (data.containsKey('frozen')) { - context.handle(_frozenMeta, - frozen.isAcceptableOrUnknown(data['frozen']!, _frozenMeta)); + context.handle(_frozenMeta, frozen.isAcceptableOrUnknown(data['frozen']!, _frozenMeta)); } if (data.containsKey('last_message_at')) { context.handle( - _lastMessageAtMeta, - lastMessageAt.isAcceptableOrUnknown( - data['last_message_at']!, _lastMessageAtMeta)); + _lastMessageAtMeta, + lastMessageAt.isAcceptableOrUnknown(data['last_message_at']!, _lastMessageAtMeta), + ); } if (data.containsKey('created_at')) { - context.handle(_createdAtMeta, - createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); + context.handle(_createdAtMeta, createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); } if (data.containsKey('updated_at')) { - context.handle(_updatedAtMeta, - updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta)); + context.handle(_updatedAtMeta, updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta)); } if (data.containsKey('deleted_at')) { - context.handle(_deletedAtMeta, - deletedAt.isAcceptableOrUnknown(data['deleted_at']!, _deletedAtMeta)); + context.handle(_deletedAtMeta, deletedAt.isAcceptableOrUnknown(data['deleted_at']!, _deletedAtMeta)); } if (data.containsKey('member_count')) { - context.handle( - _memberCountMeta, - memberCount.isAcceptableOrUnknown( - data['member_count']!, _memberCountMeta)); + context.handle(_memberCountMeta, memberCount.isAcceptableOrUnknown(data['member_count']!, _memberCountMeta)); } if (data.containsKey('message_count')) { - context.handle( - _messageCountMeta, - messageCount.isAcceptableOrUnknown( - data['message_count']!, _messageCountMeta)); + context.handle(_messageCountMeta, messageCount.isAcceptableOrUnknown(data['message_count']!, _messageCountMeta)); } if (data.containsKey('created_by_id')) { - context.handle( - _createdByIdMeta, - createdById.isAcceptableOrUnknown( - data['created_by_id']!, _createdByIdMeta)); + context.handle(_createdByIdMeta, createdById.isAcceptableOrUnknown(data['created_by_id']!, _createdByIdMeta)); } return context; } @@ -198,40 +222,32 @@ class $ChannelsTable extends Channels ChannelEntity map(Map data, {String? tablePrefix}) { final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; return ChannelEntity( - id: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}id'])!, - type: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}type'])!, - cid: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}cid'])!, + id: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}id'])!, + type: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}type'])!, + cid: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}cid'])!, ownCapabilities: $ChannelsTable.$converterownCapabilitiesn.fromSql( - attachedDatabase.typeMapping.read( - DriftSqlType.string, data['${effectivePrefix}own_capabilities'])), - config: $ChannelsTable.$converterconfig.fromSql(attachedDatabase - .typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}config'])!), - frozen: attachedDatabase.typeMapping - .read(DriftSqlType.bool, data['${effectivePrefix}frozen'])!, + attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}own_capabilities']), + ), + config: $ChannelsTable.$converterconfig.fromSql( + attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}config'])!, + ), + frozen: attachedDatabase.typeMapping.read(DriftSqlType.bool, data['${effectivePrefix}frozen'])!, lastMessageAt: attachedDatabase.typeMapping.read( - DriftSqlType.dateTime, data['${effectivePrefix}last_message_at']), - createdAt: attachedDatabase.typeMapping - .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, - updatedAt: attachedDatabase.typeMapping - .read(DriftSqlType.dateTime, data['${effectivePrefix}updated_at'])!, - deletedAt: attachedDatabase.typeMapping - .read(DriftSqlType.dateTime, data['${effectivePrefix}deleted_at']), - memberCount: attachedDatabase.typeMapping - .read(DriftSqlType.int, data['${effectivePrefix}member_count'])!, - messageCount: attachedDatabase.typeMapping - .read(DriftSqlType.int, data['${effectivePrefix}message_count']), - createdById: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}created_by_id']), - filterTags: $ChannelsTable.$converterfilterTagsn.fromSql(attachedDatabase - .typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}filter_tags'])), - extraData: $ChannelsTable.$converterextraDatan.fromSql(attachedDatabase - .typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}extra_data'])), + DriftSqlType.dateTime, + data['${effectivePrefix}last_message_at'], + ), + createdAt: attachedDatabase.typeMapping.read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + updatedAt: attachedDatabase.typeMapping.read(DriftSqlType.dateTime, data['${effectivePrefix}updated_at'])!, + deletedAt: attachedDatabase.typeMapping.read(DriftSqlType.dateTime, data['${effectivePrefix}deleted_at']), + memberCount: attachedDatabase.typeMapping.read(DriftSqlType.int, data['${effectivePrefix}member_count'])!, + messageCount: attachedDatabase.typeMapping.read(DriftSqlType.int, data['${effectivePrefix}message_count']), + createdById: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}created_by_id']), + filterTags: $ChannelsTable.$converterfilterTagsn.fromSql( + attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}filter_tags']), + ), + extraData: $ChannelsTable.$converterextraDatan.fromSql( + attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}extra_data']), + ), ); } @@ -240,20 +256,19 @@ class $ChannelsTable extends Channels return $ChannelsTable(attachedDatabase, alias); } - static TypeConverter, String> $converterownCapabilities = - ListConverter(); - static TypeConverter?, String?> $converterownCapabilitiesn = - NullAwareTypeConverter.wrap($converterownCapabilities); - static TypeConverter, String> $converterconfig = - MapConverter(); - static TypeConverter, String> $converterfilterTags = - ListConverter(); - static TypeConverter?, String?> $converterfilterTagsn = - NullAwareTypeConverter.wrap($converterfilterTags); - static TypeConverter, String> $converterextraData = - MapConverter(); - static TypeConverter?, String?> $converterextraDatan = - NullAwareTypeConverter.wrap($converterextraData); + static TypeConverter, String> $converterownCapabilities = ListConverter(); + static TypeConverter?, String?> $converterownCapabilitiesn = NullAwareTypeConverter.wrap( + $converterownCapabilities, + ); + static TypeConverter, String> $converterconfig = MapConverter(); + static TypeConverter, String> $converterfilterTags = ListConverter(); + static TypeConverter?, String?> $converterfilterTagsn = NullAwareTypeConverter.wrap( + $converterfilterTags, + ); + static TypeConverter, String> $converterextraData = MapConverter(); + static TypeConverter?, String?> $converterextraDatan = NullAwareTypeConverter.wrap( + $converterextraData, + ); } class ChannelEntity extends DataClass implements Insertable { @@ -301,22 +316,23 @@ class ChannelEntity extends DataClass implements Insertable { /// Map of custom channel extraData final Map? extraData; - const ChannelEntity( - {required this.id, - required this.type, - required this.cid, - this.ownCapabilities, - required this.config, - required this.frozen, - this.lastMessageAt, - required this.createdAt, - required this.updatedAt, - this.deletedAt, - required this.memberCount, - this.messageCount, - this.createdById, - this.filterTags, - this.extraData}); + const ChannelEntity({ + required this.id, + required this.type, + required this.cid, + this.ownCapabilities, + required this.config, + required this.frozen, + this.lastMessageAt, + required this.createdAt, + required this.updatedAt, + this.deletedAt, + required this.memberCount, + this.messageCount, + this.createdById, + this.filterTags, + this.extraData, + }); @override Map toColumns(bool nullToAbsent) { final map = {}; @@ -324,12 +340,10 @@ class ChannelEntity extends DataClass implements Insertable { map['type'] = Variable(type); map['cid'] = Variable(cid); if (!nullToAbsent || ownCapabilities != null) { - map['own_capabilities'] = Variable( - $ChannelsTable.$converterownCapabilitiesn.toSql(ownCapabilities)); + map['own_capabilities'] = Variable($ChannelsTable.$converterownCapabilitiesn.toSql(ownCapabilities)); } { - map['config'] = - Variable($ChannelsTable.$converterconfig.toSql(config)); + map['config'] = Variable($ChannelsTable.$converterconfig.toSql(config)); } map['frozen'] = Variable(frozen); if (!nullToAbsent || lastMessageAt != null) { @@ -348,25 +362,21 @@ class ChannelEntity extends DataClass implements Insertable { map['created_by_id'] = Variable(createdById); } if (!nullToAbsent || filterTags != null) { - map['filter_tags'] = Variable( - $ChannelsTable.$converterfilterTagsn.toSql(filterTags)); + map['filter_tags'] = Variable($ChannelsTable.$converterfilterTagsn.toSql(filterTags)); } if (!nullToAbsent || extraData != null) { - map['extra_data'] = Variable( - $ChannelsTable.$converterextraDatan.toSql(extraData)); + map['extra_data'] = Variable($ChannelsTable.$converterextraDatan.toSql(extraData)); } return map; } - factory ChannelEntity.fromJson(Map json, - {ValueSerializer? serializer}) { + factory ChannelEntity.fromJson(Map json, {ValueSerializer? serializer}) { serializer ??= driftRuntimeOptions.defaultSerializer; return ChannelEntity( id: serializer.fromJson(json['id']), type: serializer.fromJson(json['type']), cid: serializer.fromJson(json['cid']), - ownCapabilities: - serializer.fromJson?>(json['ownCapabilities']), + ownCapabilities: serializer.fromJson?>(json['ownCapabilities']), config: serializer.fromJson>(json['config']), frozen: serializer.fromJson(json['frozen']), lastMessageAt: serializer.fromJson(json['lastMessageAt']), @@ -402,68 +412,55 @@ class ChannelEntity extends DataClass implements Insertable { }; } - ChannelEntity copyWith( - {String? id, - String? type, - String? cid, - Value?> ownCapabilities = const Value.absent(), - Map? config, - bool? frozen, - Value lastMessageAt = const Value.absent(), - DateTime? createdAt, - DateTime? updatedAt, - Value deletedAt = const Value.absent(), - int? memberCount, - Value messageCount = const Value.absent(), - Value createdById = const Value.absent(), - Value?> filterTags = const Value.absent(), - Value?> extraData = const Value.absent()}) => - ChannelEntity( - id: id ?? this.id, - type: type ?? this.type, - cid: cid ?? this.cid, - ownCapabilities: ownCapabilities.present - ? ownCapabilities.value - : this.ownCapabilities, - config: config ?? this.config, - frozen: frozen ?? this.frozen, - lastMessageAt: - lastMessageAt.present ? lastMessageAt.value : this.lastMessageAt, - createdAt: createdAt ?? this.createdAt, - updatedAt: updatedAt ?? this.updatedAt, - deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, - memberCount: memberCount ?? this.memberCount, - messageCount: - messageCount.present ? messageCount.value : this.messageCount, - createdById: createdById.present ? createdById.value : this.createdById, - filterTags: filterTags.present ? filterTags.value : this.filterTags, - extraData: extraData.present ? extraData.value : this.extraData, - ); + ChannelEntity copyWith({ + String? id, + String? type, + String? cid, + Value?> ownCapabilities = const Value.absent(), + Map? config, + bool? frozen, + Value lastMessageAt = const Value.absent(), + DateTime? createdAt, + DateTime? updatedAt, + Value deletedAt = const Value.absent(), + int? memberCount, + Value messageCount = const Value.absent(), + Value createdById = const Value.absent(), + Value?> filterTags = const Value.absent(), + Value?> extraData = const Value.absent(), + }) => ChannelEntity( + id: id ?? this.id, + type: type ?? this.type, + cid: cid ?? this.cid, + ownCapabilities: ownCapabilities.present ? ownCapabilities.value : this.ownCapabilities, + config: config ?? this.config, + frozen: frozen ?? this.frozen, + lastMessageAt: lastMessageAt.present ? lastMessageAt.value : this.lastMessageAt, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + memberCount: memberCount ?? this.memberCount, + messageCount: messageCount.present ? messageCount.value : this.messageCount, + createdById: createdById.present ? createdById.value : this.createdById, + filterTags: filterTags.present ? filterTags.value : this.filterTags, + extraData: extraData.present ? extraData.value : this.extraData, + ); ChannelEntity copyWithCompanion(ChannelsCompanion data) { return ChannelEntity( id: data.id.present ? data.id.value : this.id, type: data.type.present ? data.type.value : this.type, cid: data.cid.present ? data.cid.value : this.cid, - ownCapabilities: data.ownCapabilities.present - ? data.ownCapabilities.value - : this.ownCapabilities, + ownCapabilities: data.ownCapabilities.present ? data.ownCapabilities.value : this.ownCapabilities, config: data.config.present ? data.config.value : this.config, frozen: data.frozen.present ? data.frozen.value : this.frozen, - lastMessageAt: data.lastMessageAt.present - ? data.lastMessageAt.value - : this.lastMessageAt, + lastMessageAt: data.lastMessageAt.present ? data.lastMessageAt.value : this.lastMessageAt, createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, - memberCount: - data.memberCount.present ? data.memberCount.value : this.memberCount, - messageCount: data.messageCount.present - ? data.messageCount.value - : this.messageCount, - createdById: - data.createdById.present ? data.createdById.value : this.createdById, - filterTags: - data.filterTags.present ? data.filterTags.value : this.filterTags, + memberCount: data.memberCount.present ? data.memberCount.value : this.memberCount, + messageCount: data.messageCount.present ? data.messageCount.value : this.messageCount, + createdById: data.createdById.present ? data.createdById.value : this.createdById, + filterTags: data.filterTags.present ? data.filterTags.value : this.filterTags, extraData: data.extraData.present ? data.extraData.value : this.extraData, ); } @@ -492,21 +489,22 @@ class ChannelEntity extends DataClass implements Insertable { @override int get hashCode => Object.hash( - id, - type, - cid, - ownCapabilities, - config, - frozen, - lastMessageAt, - createdAt, - updatedAt, - deletedAt, - memberCount, - messageCount, - createdById, - filterTags, - extraData); + id, + type, + cid, + ownCapabilities, + config, + frozen, + lastMessageAt, + createdAt, + updatedAt, + deletedAt, + memberCount, + messageCount, + createdById, + filterTags, + extraData, + ); @override bool operator ==(Object other) => identical(this, other) || @@ -580,10 +578,10 @@ class ChannelsCompanion extends UpdateCompanion { this.filterTags = const Value.absent(), this.extraData = const Value.absent(), this.rowid = const Value.absent(), - }) : id = Value(id), - type = Value(type), - cid = Value(cid), - config = Value(config); + }) : id = Value(id), + type = Value(type), + cid = Value(cid), + config = Value(config); static Insertable custom({ Expression? id, Expression? type, @@ -622,23 +620,24 @@ class ChannelsCompanion extends UpdateCompanion { }); } - ChannelsCompanion copyWith( - {Value? id, - Value? type, - Value? cid, - Value?>? ownCapabilities, - Value>? config, - Value? frozen, - Value? lastMessageAt, - Value? createdAt, - Value? updatedAt, - Value? deletedAt, - Value? memberCount, - Value? messageCount, - Value? createdById, - Value?>? filterTags, - Value?>? extraData, - Value? rowid}) { + ChannelsCompanion copyWith({ + Value? id, + Value? type, + Value? cid, + Value?>? ownCapabilities, + Value>? config, + Value? frozen, + Value? lastMessageAt, + Value? createdAt, + Value? updatedAt, + Value? deletedAt, + Value? memberCount, + Value? messageCount, + Value? createdById, + Value?>? filterTags, + Value?>? extraData, + Value? rowid, + }) { return ChannelsCompanion( id: id ?? this.id, type: type ?? this.type, @@ -672,13 +671,12 @@ class ChannelsCompanion extends UpdateCompanion { map['cid'] = Variable(cid.value); } if (ownCapabilities.present) { - map['own_capabilities'] = Variable($ChannelsTable - .$converterownCapabilitiesn - .toSql(ownCapabilities.value)); + map['own_capabilities'] = Variable( + $ChannelsTable.$converterownCapabilitiesn.toSql(ownCapabilities.value), + ); } if (config.present) { - map['config'] = - Variable($ChannelsTable.$converterconfig.toSql(config.value)); + map['config'] = Variable($ChannelsTable.$converterconfig.toSql(config.value)); } if (frozen.present) { map['frozen'] = Variable(frozen.value); @@ -705,12 +703,10 @@ class ChannelsCompanion extends UpdateCompanion { map['created_by_id'] = Variable(createdById.value); } if (filterTags.present) { - map['filter_tags'] = Variable( - $ChannelsTable.$converterfilterTagsn.toSql(filterTags.value)); + map['filter_tags'] = Variable($ChannelsTable.$converterfilterTagsn.toSql(filterTags.value)); } if (extraData.present) { - map['extra_data'] = Variable( - $ChannelsTable.$converterextraDatan.toSql(extraData.value)); + map['extra_data'] = Variable($ChannelsTable.$converterextraDatan.toSql(extraData.value)); } if (rowid.present) { map['rowid'] = Variable(rowid.value); @@ -742,8 +738,7 @@ class ChannelsCompanion extends UpdateCompanion { } } -class $MessagesTable extends Messages - with TableInfo<$MessagesTable, MessageEntity> { +class $MessagesTable extends Messages with TableInfo<$MessagesTable, MessageEntity> { @override final GeneratedDatabase attachedDatabase; final String? _alias; @@ -751,251 +746,336 @@ class $MessagesTable extends Messages static const VerificationMeta _idMeta = const VerificationMeta('id'); @override late final GeneratedColumn id = GeneratedColumn( - 'id', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true); - static const VerificationMeta _messageTextMeta = - const VerificationMeta('messageText'); + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _messageTextMeta = const VerificationMeta('messageText'); @override late final GeneratedColumn messageText = GeneratedColumn( - 'message_text', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); - @override - late final GeneratedColumnWithTypeConverter, String> - attachments = GeneratedColumn('attachments', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true) - .withConverter>($MessagesTable.$converterattachments); + 'message_text', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + late final GeneratedColumnWithTypeConverter, String> attachments = GeneratedColumn( + 'attachments', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ).withConverter>($MessagesTable.$converterattachments); static const VerificationMeta _stateMeta = const VerificationMeta('state'); @override late final GeneratedColumn state = GeneratedColumn( - 'state', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true); + 'state', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); static const VerificationMeta _typeMeta = const VerificationMeta('type'); @override late final GeneratedColumn type = GeneratedColumn( - 'type', aliasedName, false, - type: DriftSqlType.string, - requiredDuringInsert: false, - defaultValue: const Constant('regular')); - @override - late final GeneratedColumnWithTypeConverter, String> - mentionedUsers = GeneratedColumn( - 'mentioned_users', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true) - .withConverter>($MessagesTable.$convertermentionedUsers); - @override - late final GeneratedColumnWithTypeConverter?, - String> reactionGroups = GeneratedColumn( - 'reaction_groups', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false) - .withConverter?>( - $MessagesTable.$converterreactionGroupsn); - static const VerificationMeta _parentIdMeta = - const VerificationMeta('parentId'); + 'type', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const Constant('regular'), + ); + @override + late final GeneratedColumnWithTypeConverter, String> mentionedUsers = GeneratedColumn( + 'mentioned_users', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ).withConverter>($MessagesTable.$convertermentionedUsers); + @override + late final GeneratedColumnWithTypeConverter?, String> reactionGroups = + GeneratedColumn( + 'reaction_groups', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ).withConverter?>($MessagesTable.$converterreactionGroupsn); + static const VerificationMeta _parentIdMeta = const VerificationMeta('parentId'); @override late final GeneratedColumn parentId = GeneratedColumn( - 'parent_id', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); - static const VerificationMeta _quotedMessageIdMeta = - const VerificationMeta('quotedMessageId'); + 'parent_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _quotedMessageIdMeta = const VerificationMeta('quotedMessageId'); @override late final GeneratedColumn quotedMessageId = GeneratedColumn( - 'quoted_message_id', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); + 'quoted_message_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); static const VerificationMeta _pollIdMeta = const VerificationMeta('pollId'); @override late final GeneratedColumn pollId = GeneratedColumn( - 'poll_id', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); - static const VerificationMeta _replyCountMeta = - const VerificationMeta('replyCount'); + 'poll_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _replyCountMeta = const VerificationMeta('replyCount'); @override late final GeneratedColumn replyCount = GeneratedColumn( - 'reply_count', aliasedName, true, - type: DriftSqlType.int, requiredDuringInsert: false); - static const VerificationMeta _showInChannelMeta = - const VerificationMeta('showInChannel'); + 'reply_count', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + static const VerificationMeta _showInChannelMeta = const VerificationMeta('showInChannel'); @override late final GeneratedColumn showInChannel = GeneratedColumn( - 'show_in_channel', aliasedName, true, - type: DriftSqlType.bool, - requiredDuringInsert: false, - defaultConstraints: GeneratedColumn.constraintIsAlways( - 'CHECK ("show_in_channel" IN (0, 1))')); - static const VerificationMeta _shadowedMeta = - const VerificationMeta('shadowed'); + 'show_in_channel', + aliasedName, + true, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways('CHECK ("show_in_channel" IN (0, 1))'), + ); + static const VerificationMeta _shadowedMeta = const VerificationMeta('shadowed'); @override late final GeneratedColumn shadowed = GeneratedColumn( - 'shadowed', aliasedName, false, - type: DriftSqlType.bool, - requiredDuringInsert: false, - defaultConstraints: - GeneratedColumn.constraintIsAlways('CHECK ("shadowed" IN (0, 1))'), - defaultValue: const Constant(false)); - static const VerificationMeta _commandMeta = - const VerificationMeta('command'); + 'shadowed', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways('CHECK ("shadowed" IN (0, 1))'), + defaultValue: const Constant(false), + ); + static const VerificationMeta _commandMeta = const VerificationMeta('command'); @override late final GeneratedColumn command = GeneratedColumn( - 'command', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); - static const VerificationMeta _localCreatedAtMeta = - const VerificationMeta('localCreatedAt'); - @override - late final GeneratedColumn localCreatedAt = - GeneratedColumn('local_created_at', aliasedName, true, - type: DriftSqlType.dateTime, requiredDuringInsert: false); - static const VerificationMeta _remoteCreatedAtMeta = - const VerificationMeta('remoteCreatedAt'); - @override - late final GeneratedColumn remoteCreatedAt = - GeneratedColumn('remote_created_at', aliasedName, true, - type: DriftSqlType.dateTime, requiredDuringInsert: false); - static const VerificationMeta _localUpdatedAtMeta = - const VerificationMeta('localUpdatedAt'); - @override - late final GeneratedColumn localUpdatedAt = - GeneratedColumn('local_updated_at', aliasedName, true, - type: DriftSqlType.dateTime, requiredDuringInsert: false); - static const VerificationMeta _remoteUpdatedAtMeta = - const VerificationMeta('remoteUpdatedAt'); - @override - late final GeneratedColumn remoteUpdatedAt = - GeneratedColumn('remote_updated_at', aliasedName, true, - type: DriftSqlType.dateTime, requiredDuringInsert: false); - static const VerificationMeta _localDeletedAtMeta = - const VerificationMeta('localDeletedAt'); - @override - late final GeneratedColumn localDeletedAt = - GeneratedColumn('local_deleted_at', aliasedName, true, - type: DriftSqlType.dateTime, requiredDuringInsert: false); - static const VerificationMeta _remoteDeletedAtMeta = - const VerificationMeta('remoteDeletedAt'); - @override - late final GeneratedColumn remoteDeletedAt = - GeneratedColumn('remote_deleted_at', aliasedName, true, - type: DriftSqlType.dateTime, requiredDuringInsert: false); - static const VerificationMeta _deletedForMeMeta = - const VerificationMeta('deletedForMe'); + 'command', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _localCreatedAtMeta = const VerificationMeta('localCreatedAt'); + @override + late final GeneratedColumn localCreatedAt = GeneratedColumn( + 'local_created_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + static const VerificationMeta _remoteCreatedAtMeta = const VerificationMeta('remoteCreatedAt'); + @override + late final GeneratedColumn remoteCreatedAt = GeneratedColumn( + 'remote_created_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + static const VerificationMeta _localUpdatedAtMeta = const VerificationMeta('localUpdatedAt'); + @override + late final GeneratedColumn localUpdatedAt = GeneratedColumn( + 'local_updated_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + static const VerificationMeta _remoteUpdatedAtMeta = const VerificationMeta('remoteUpdatedAt'); + @override + late final GeneratedColumn remoteUpdatedAt = GeneratedColumn( + 'remote_updated_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + static const VerificationMeta _localDeletedAtMeta = const VerificationMeta('localDeletedAt'); + @override + late final GeneratedColumn localDeletedAt = GeneratedColumn( + 'local_deleted_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + static const VerificationMeta _remoteDeletedAtMeta = const VerificationMeta('remoteDeletedAt'); + @override + late final GeneratedColumn remoteDeletedAt = GeneratedColumn( + 'remote_deleted_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + static const VerificationMeta _deletedForMeMeta = const VerificationMeta('deletedForMe'); @override late final GeneratedColumn deletedForMe = GeneratedColumn( - 'deleted_for_me', aliasedName, true, - type: DriftSqlType.bool, - requiredDuringInsert: false, - defaultConstraints: GeneratedColumn.constraintIsAlways( - 'CHECK ("deleted_for_me" IN (0, 1))')); - static const VerificationMeta _messageTextUpdatedAtMeta = - const VerificationMeta('messageTextUpdatedAt'); - @override - late final GeneratedColumn messageTextUpdatedAt = - GeneratedColumn('message_text_updated_at', aliasedName, true, - type: DriftSqlType.dateTime, requiredDuringInsert: false); + 'deleted_for_me', + aliasedName, + true, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways('CHECK ("deleted_for_me" IN (0, 1))'), + ); + static const VerificationMeta _messageTextUpdatedAtMeta = const VerificationMeta('messageTextUpdatedAt'); + @override + late final GeneratedColumn messageTextUpdatedAt = GeneratedColumn( + 'message_text_updated_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); static const VerificationMeta _userIdMeta = const VerificationMeta('userId'); @override late final GeneratedColumn userId = GeneratedColumn( - 'user_id', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); - static const VerificationMeta _channelRoleMeta = - const VerificationMeta('channelRole'); + 'user_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _channelRoleMeta = const VerificationMeta('channelRole'); @override late final GeneratedColumn channelRole = GeneratedColumn( - 'channel_role', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); + 'channel_role', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); static const VerificationMeta _pinnedMeta = const VerificationMeta('pinned'); @override late final GeneratedColumn pinned = GeneratedColumn( - 'pinned', aliasedName, false, - type: DriftSqlType.bool, - requiredDuringInsert: false, - defaultConstraints: - GeneratedColumn.constraintIsAlways('CHECK ("pinned" IN (0, 1))'), - defaultValue: const Constant(false)); - static const VerificationMeta _pinnedAtMeta = - const VerificationMeta('pinnedAt'); + 'pinned', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways('CHECK ("pinned" IN (0, 1))'), + defaultValue: const Constant(false), + ); + static const VerificationMeta _pinnedAtMeta = const VerificationMeta('pinnedAt'); @override late final GeneratedColumn pinnedAt = GeneratedColumn( - 'pinned_at', aliasedName, true, - type: DriftSqlType.dateTime, requiredDuringInsert: false); - static const VerificationMeta _pinExpiresMeta = - const VerificationMeta('pinExpires'); + 'pinned_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + static const VerificationMeta _pinExpiresMeta = const VerificationMeta('pinExpires'); @override late final GeneratedColumn pinExpires = GeneratedColumn( - 'pin_expires', aliasedName, true, - type: DriftSqlType.dateTime, requiredDuringInsert: false); - static const VerificationMeta _pinnedByUserIdMeta = - const VerificationMeta('pinnedByUserId'); + 'pin_expires', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + static const VerificationMeta _pinnedByUserIdMeta = const VerificationMeta('pinnedByUserId'); @override late final GeneratedColumn pinnedByUserId = GeneratedColumn( - 'pinned_by_user_id', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); - static const VerificationMeta _channelCidMeta = - const VerificationMeta('channelCid'); + 'pinned_by_user_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _channelCidMeta = const VerificationMeta('channelCid'); @override late final GeneratedColumn channelCid = GeneratedColumn( - 'channel_cid', aliasedName, false, - type: DriftSqlType.string, - requiredDuringInsert: true, - defaultConstraints: GeneratedColumn.constraintIsAlways( - 'REFERENCES channels (cid) ON DELETE CASCADE')); - @override - late final GeneratedColumnWithTypeConverter?, String> - i18n = GeneratedColumn('i18n', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false) - .withConverter?>($MessagesTable.$converteri18n); - @override - late final GeneratedColumnWithTypeConverter?, String> - restrictedVisibility = GeneratedColumn( - 'restricted_visibility', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false) - .withConverter?>( - $MessagesTable.$converterrestrictedVisibilityn); - @override - late final GeneratedColumnWithTypeConverter?, String> - extraData = GeneratedColumn('extra_data', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false) - .withConverter?>( - $MessagesTable.$converterextraDatan); + 'channel_cid', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways('REFERENCES channels (cid) ON DELETE CASCADE'), + ); + @override + late final GeneratedColumnWithTypeConverter?, String> i18n = GeneratedColumn( + 'i18n', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ).withConverter?>($MessagesTable.$converteri18n); + @override + late final GeneratedColumnWithTypeConverter?, String> restrictedVisibility = GeneratedColumn( + 'restricted_visibility', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ).withConverter?>($MessagesTable.$converterrestrictedVisibilityn); + @override + late final GeneratedColumnWithTypeConverter?, String> extraData = GeneratedColumn( + 'extra_data', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ).withConverter?>($MessagesTable.$converterextraDatan); @override List get $columns => [ - id, - messageText, - attachments, - state, - type, - mentionedUsers, - reactionGroups, - parentId, - quotedMessageId, - pollId, - replyCount, - showInChannel, - shadowed, - command, - localCreatedAt, - remoteCreatedAt, - localUpdatedAt, - remoteUpdatedAt, - localDeletedAt, - remoteDeletedAt, - deletedForMe, - messageTextUpdatedAt, - userId, - channelRole, - pinned, - pinnedAt, - pinExpires, - pinnedByUserId, - channelCid, - i18n, - restrictedVisibility, - extraData - ]; + id, + messageText, + attachments, + state, + type, + mentionedUsers, + reactionGroups, + parentId, + quotedMessageId, + pollId, + replyCount, + showInChannel, + shadowed, + command, + localCreatedAt, + remoteCreatedAt, + localUpdatedAt, + remoteUpdatedAt, + localDeletedAt, + remoteDeletedAt, + deletedForMe, + messageTextUpdatedAt, + userId, + channelRole, + pinned, + pinnedAt, + pinExpires, + pinnedByUserId, + channelCid, + i18n, + restrictedVisibility, + extraData, + ]; @override String get aliasedName => _alias ?? actualTableName; @override String get actualTableName => $name; static const String $name = 'messages'; @override - VerificationContext validateIntegrity(Insertable instance, - {bool isInserting = false}) { + VerificationContext validateIntegrity(Insertable instance, {bool isInserting = false}) { final context = VerificationContext(); final data = instance.toColumns(true); if (data.containsKey('id')) { @@ -1004,138 +1084,111 @@ class $MessagesTable extends Messages context.missing(_idMeta); } if (data.containsKey('message_text')) { - context.handle( - _messageTextMeta, - messageText.isAcceptableOrUnknown( - data['message_text']!, _messageTextMeta)); + context.handle(_messageTextMeta, messageText.isAcceptableOrUnknown(data['message_text']!, _messageTextMeta)); } if (data.containsKey('state')) { - context.handle( - _stateMeta, state.isAcceptableOrUnknown(data['state']!, _stateMeta)); + context.handle(_stateMeta, state.isAcceptableOrUnknown(data['state']!, _stateMeta)); } else if (isInserting) { context.missing(_stateMeta); } if (data.containsKey('type')) { - context.handle( - _typeMeta, type.isAcceptableOrUnknown(data['type']!, _typeMeta)); + context.handle(_typeMeta, type.isAcceptableOrUnknown(data['type']!, _typeMeta)); } if (data.containsKey('parent_id')) { - context.handle(_parentIdMeta, - parentId.isAcceptableOrUnknown(data['parent_id']!, _parentIdMeta)); + context.handle(_parentIdMeta, parentId.isAcceptableOrUnknown(data['parent_id']!, _parentIdMeta)); } if (data.containsKey('quoted_message_id')) { context.handle( - _quotedMessageIdMeta, - quotedMessageId.isAcceptableOrUnknown( - data['quoted_message_id']!, _quotedMessageIdMeta)); + _quotedMessageIdMeta, + quotedMessageId.isAcceptableOrUnknown(data['quoted_message_id']!, _quotedMessageIdMeta), + ); } if (data.containsKey('poll_id')) { - context.handle(_pollIdMeta, - pollId.isAcceptableOrUnknown(data['poll_id']!, _pollIdMeta)); + context.handle(_pollIdMeta, pollId.isAcceptableOrUnknown(data['poll_id']!, _pollIdMeta)); } if (data.containsKey('reply_count')) { - context.handle( - _replyCountMeta, - replyCount.isAcceptableOrUnknown( - data['reply_count']!, _replyCountMeta)); + context.handle(_replyCountMeta, replyCount.isAcceptableOrUnknown(data['reply_count']!, _replyCountMeta)); } if (data.containsKey('show_in_channel')) { context.handle( - _showInChannelMeta, - showInChannel.isAcceptableOrUnknown( - data['show_in_channel']!, _showInChannelMeta)); + _showInChannelMeta, + showInChannel.isAcceptableOrUnknown(data['show_in_channel']!, _showInChannelMeta), + ); } if (data.containsKey('shadowed')) { - context.handle(_shadowedMeta, - shadowed.isAcceptableOrUnknown(data['shadowed']!, _shadowedMeta)); + context.handle(_shadowedMeta, shadowed.isAcceptableOrUnknown(data['shadowed']!, _shadowedMeta)); } if (data.containsKey('command')) { - context.handle(_commandMeta, - command.isAcceptableOrUnknown(data['command']!, _commandMeta)); + context.handle(_commandMeta, command.isAcceptableOrUnknown(data['command']!, _commandMeta)); } if (data.containsKey('local_created_at')) { context.handle( - _localCreatedAtMeta, - localCreatedAt.isAcceptableOrUnknown( - data['local_created_at']!, _localCreatedAtMeta)); + _localCreatedAtMeta, + localCreatedAt.isAcceptableOrUnknown(data['local_created_at']!, _localCreatedAtMeta), + ); } if (data.containsKey('remote_created_at')) { context.handle( - _remoteCreatedAtMeta, - remoteCreatedAt.isAcceptableOrUnknown( - data['remote_created_at']!, _remoteCreatedAtMeta)); + _remoteCreatedAtMeta, + remoteCreatedAt.isAcceptableOrUnknown(data['remote_created_at']!, _remoteCreatedAtMeta), + ); } if (data.containsKey('local_updated_at')) { context.handle( - _localUpdatedAtMeta, - localUpdatedAt.isAcceptableOrUnknown( - data['local_updated_at']!, _localUpdatedAtMeta)); + _localUpdatedAtMeta, + localUpdatedAt.isAcceptableOrUnknown(data['local_updated_at']!, _localUpdatedAtMeta), + ); } if (data.containsKey('remote_updated_at')) { context.handle( - _remoteUpdatedAtMeta, - remoteUpdatedAt.isAcceptableOrUnknown( - data['remote_updated_at']!, _remoteUpdatedAtMeta)); + _remoteUpdatedAtMeta, + remoteUpdatedAt.isAcceptableOrUnknown(data['remote_updated_at']!, _remoteUpdatedAtMeta), + ); } if (data.containsKey('local_deleted_at')) { context.handle( - _localDeletedAtMeta, - localDeletedAt.isAcceptableOrUnknown( - data['local_deleted_at']!, _localDeletedAtMeta)); + _localDeletedAtMeta, + localDeletedAt.isAcceptableOrUnknown(data['local_deleted_at']!, _localDeletedAtMeta), + ); } if (data.containsKey('remote_deleted_at')) { context.handle( - _remoteDeletedAtMeta, - remoteDeletedAt.isAcceptableOrUnknown( - data['remote_deleted_at']!, _remoteDeletedAtMeta)); + _remoteDeletedAtMeta, + remoteDeletedAt.isAcceptableOrUnknown(data['remote_deleted_at']!, _remoteDeletedAtMeta), + ); } if (data.containsKey('deleted_for_me')) { - context.handle( - _deletedForMeMeta, - deletedForMe.isAcceptableOrUnknown( - data['deleted_for_me']!, _deletedForMeMeta)); + context.handle(_deletedForMeMeta, deletedForMe.isAcceptableOrUnknown(data['deleted_for_me']!, _deletedForMeMeta)); } if (data.containsKey('message_text_updated_at')) { context.handle( - _messageTextUpdatedAtMeta, - messageTextUpdatedAt.isAcceptableOrUnknown( - data['message_text_updated_at']!, _messageTextUpdatedAtMeta)); + _messageTextUpdatedAtMeta, + messageTextUpdatedAt.isAcceptableOrUnknown(data['message_text_updated_at']!, _messageTextUpdatedAtMeta), + ); } if (data.containsKey('user_id')) { - context.handle(_userIdMeta, - userId.isAcceptableOrUnknown(data['user_id']!, _userIdMeta)); + context.handle(_userIdMeta, userId.isAcceptableOrUnknown(data['user_id']!, _userIdMeta)); } if (data.containsKey('channel_role')) { - context.handle( - _channelRoleMeta, - channelRole.isAcceptableOrUnknown( - data['channel_role']!, _channelRoleMeta)); + context.handle(_channelRoleMeta, channelRole.isAcceptableOrUnknown(data['channel_role']!, _channelRoleMeta)); } if (data.containsKey('pinned')) { - context.handle(_pinnedMeta, - pinned.isAcceptableOrUnknown(data['pinned']!, _pinnedMeta)); + context.handle(_pinnedMeta, pinned.isAcceptableOrUnknown(data['pinned']!, _pinnedMeta)); } if (data.containsKey('pinned_at')) { - context.handle(_pinnedAtMeta, - pinnedAt.isAcceptableOrUnknown(data['pinned_at']!, _pinnedAtMeta)); + context.handle(_pinnedAtMeta, pinnedAt.isAcceptableOrUnknown(data['pinned_at']!, _pinnedAtMeta)); } if (data.containsKey('pin_expires')) { - context.handle( - _pinExpiresMeta, - pinExpires.isAcceptableOrUnknown( - data['pin_expires']!, _pinExpiresMeta)); + context.handle(_pinExpiresMeta, pinExpires.isAcceptableOrUnknown(data['pin_expires']!, _pinExpiresMeta)); } if (data.containsKey('pinned_by_user_id')) { context.handle( - _pinnedByUserIdMeta, - pinnedByUserId.isAcceptableOrUnknown( - data['pinned_by_user_id']!, _pinnedByUserIdMeta)); + _pinnedByUserIdMeta, + pinnedByUserId.isAcceptableOrUnknown(data['pinned_by_user_id']!, _pinnedByUserIdMeta), + ); } if (data.containsKey('channel_cid')) { - context.handle( - _channelCidMeta, - channelCid.isAcceptableOrUnknown( - data['channel_cid']!, _channelCidMeta)); + context.handle(_channelCidMeta, channelCid.isAcceptableOrUnknown(data['channel_cid']!, _channelCidMeta)); } else if (isInserting) { context.missing(_channelCidMeta); } @@ -1148,76 +1201,77 @@ class $MessagesTable extends Messages MessageEntity map(Map data, {String? tablePrefix}) { final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; return MessageEntity( - id: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}id'])!, - messageText: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}message_text']), - attachments: $MessagesTable.$converterattachments.fromSql(attachedDatabase - .typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}attachments'])!), - state: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}state'])!, - type: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}type'])!, + id: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}id'])!, + messageText: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}message_text']), + attachments: $MessagesTable.$converterattachments.fromSql( + attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}attachments'])!, + ), + state: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}state'])!, + type: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}type'])!, mentionedUsers: $MessagesTable.$convertermentionedUsers.fromSql( - attachedDatabase.typeMapping.read( - DriftSqlType.string, data['${effectivePrefix}mentioned_users'])!), + attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}mentioned_users'])!, + ), reactionGroups: $MessagesTable.$converterreactionGroupsn.fromSql( - attachedDatabase.typeMapping.read( - DriftSqlType.string, data['${effectivePrefix}reaction_groups'])), - parentId: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}parent_id']), + attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}reaction_groups']), + ), + parentId: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}parent_id']), quotedMessageId: attachedDatabase.typeMapping.read( - DriftSqlType.string, data['${effectivePrefix}quoted_message_id']), - pollId: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}poll_id']), - replyCount: attachedDatabase.typeMapping - .read(DriftSqlType.int, data['${effectivePrefix}reply_count']), - showInChannel: attachedDatabase.typeMapping - .read(DriftSqlType.bool, data['${effectivePrefix}show_in_channel']), - shadowed: attachedDatabase.typeMapping - .read(DriftSqlType.bool, data['${effectivePrefix}shadowed'])!, - command: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}command']), + DriftSqlType.string, + data['${effectivePrefix}quoted_message_id'], + ), + pollId: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}poll_id']), + replyCount: attachedDatabase.typeMapping.read(DriftSqlType.int, data['${effectivePrefix}reply_count']), + showInChannel: attachedDatabase.typeMapping.read(DriftSqlType.bool, data['${effectivePrefix}show_in_channel']), + shadowed: attachedDatabase.typeMapping.read(DriftSqlType.bool, data['${effectivePrefix}shadowed'])!, + command: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}command']), localCreatedAt: attachedDatabase.typeMapping.read( - DriftSqlType.dateTime, data['${effectivePrefix}local_created_at']), + DriftSqlType.dateTime, + data['${effectivePrefix}local_created_at'], + ), remoteCreatedAt: attachedDatabase.typeMapping.read( - DriftSqlType.dateTime, data['${effectivePrefix}remote_created_at']), + DriftSqlType.dateTime, + data['${effectivePrefix}remote_created_at'], + ), localUpdatedAt: attachedDatabase.typeMapping.read( - DriftSqlType.dateTime, data['${effectivePrefix}local_updated_at']), + DriftSqlType.dateTime, + data['${effectivePrefix}local_updated_at'], + ), remoteUpdatedAt: attachedDatabase.typeMapping.read( - DriftSqlType.dateTime, data['${effectivePrefix}remote_updated_at']), + DriftSqlType.dateTime, + data['${effectivePrefix}remote_updated_at'], + ), localDeletedAt: attachedDatabase.typeMapping.read( - DriftSqlType.dateTime, data['${effectivePrefix}local_deleted_at']), + DriftSqlType.dateTime, + data['${effectivePrefix}local_deleted_at'], + ), remoteDeletedAt: attachedDatabase.typeMapping.read( - DriftSqlType.dateTime, data['${effectivePrefix}remote_deleted_at']), - deletedForMe: attachedDatabase.typeMapping - .read(DriftSqlType.bool, data['${effectivePrefix}deleted_for_me']), + DriftSqlType.dateTime, + data['${effectivePrefix}remote_deleted_at'], + ), + deletedForMe: attachedDatabase.typeMapping.read(DriftSqlType.bool, data['${effectivePrefix}deleted_for_me']), messageTextUpdatedAt: attachedDatabase.typeMapping.read( - DriftSqlType.dateTime, - data['${effectivePrefix}message_text_updated_at']), - userId: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}user_id']), - channelRole: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}channel_role']), - pinned: attachedDatabase.typeMapping - .read(DriftSqlType.bool, data['${effectivePrefix}pinned'])!, - pinnedAt: attachedDatabase.typeMapping - .read(DriftSqlType.dateTime, data['${effectivePrefix}pinned_at']), - pinExpires: attachedDatabase.typeMapping - .read(DriftSqlType.dateTime, data['${effectivePrefix}pin_expires']), + DriftSqlType.dateTime, + data['${effectivePrefix}message_text_updated_at'], + ), + userId: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}user_id']), + channelRole: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}channel_role']), + pinned: attachedDatabase.typeMapping.read(DriftSqlType.bool, data['${effectivePrefix}pinned'])!, + pinnedAt: attachedDatabase.typeMapping.read(DriftSqlType.dateTime, data['${effectivePrefix}pinned_at']), + pinExpires: attachedDatabase.typeMapping.read(DriftSqlType.dateTime, data['${effectivePrefix}pin_expires']), pinnedByUserId: attachedDatabase.typeMapping.read( - DriftSqlType.string, data['${effectivePrefix}pinned_by_user_id']), - channelCid: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}channel_cid'])!, - i18n: $MessagesTable.$converteri18n.fromSql(attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}i18n'])), - restrictedVisibility: $MessagesTable.$converterrestrictedVisibilityn - .fromSql(attachedDatabase.typeMapping.read(DriftSqlType.string, - data['${effectivePrefix}restricted_visibility'])), - extraData: $MessagesTable.$converterextraDatan.fromSql(attachedDatabase - .typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}extra_data'])), + DriftSqlType.string, + data['${effectivePrefix}pinned_by_user_id'], + ), + channelCid: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}channel_cid'])!, + i18n: $MessagesTable.$converteri18n.fromSql( + attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}i18n']), + ), + restrictedVisibility: $MessagesTable.$converterrestrictedVisibilityn.fromSql( + attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}restricted_visibility']), + ), + extraData: $MessagesTable.$converterextraDatan.fromSql( + attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}extra_data']), + ), ); } @@ -1226,25 +1280,21 @@ class $MessagesTable extends Messages return $MessagesTable(attachedDatabase, alias); } - static TypeConverter, String> $converterattachments = - ListConverter(); - static TypeConverter, String> $convertermentionedUsers = - ListConverter(); - static TypeConverter, String> - $converterreactionGroups = ReactionGroupsConverter(); - static TypeConverter?, String?> - $converterreactionGroupsn = - NullAwareTypeConverter.wrap($converterreactionGroups); - static TypeConverter?, String?> $converteri18n = - NullableMapConverter(); - static TypeConverter, String> $converterrestrictedVisibility = - ListConverter(); - static TypeConverter?, String?> $converterrestrictedVisibilityn = - NullAwareTypeConverter.wrap($converterrestrictedVisibility); - static TypeConverter, String> $converterextraData = - MapConverter(); - static TypeConverter?, String?> $converterextraDatan = - NullAwareTypeConverter.wrap($converterextraData); + static TypeConverter, String> $converterattachments = ListConverter(); + static TypeConverter, String> $convertermentionedUsers = ListConverter(); + static TypeConverter, String> $converterreactionGroups = ReactionGroupsConverter(); + static TypeConverter?, String?> $converterreactionGroupsn = NullAwareTypeConverter.wrap( + $converterreactionGroups, + ); + static TypeConverter?, String?> $converteri18n = NullableMapConverter(); + static TypeConverter, String> $converterrestrictedVisibility = ListConverter(); + static TypeConverter?, String?> $converterrestrictedVisibilityn = NullAwareTypeConverter.wrap( + $converterrestrictedVisibility, + ); + static TypeConverter, String> $converterextraData = MapConverter(); + static TypeConverter?, String?> $converterextraDatan = NullAwareTypeConverter.wrap( + $converterextraData, + ); } class MessageEntity extends DataClass implements Insertable { @@ -1344,39 +1394,40 @@ class MessageEntity extends DataClass implements Insertable { /// Message custom extraData final Map? extraData; - const MessageEntity( - {required this.id, - this.messageText, - required this.attachments, - required this.state, - required this.type, - required this.mentionedUsers, - this.reactionGroups, - this.parentId, - this.quotedMessageId, - this.pollId, - this.replyCount, - this.showInChannel, - required this.shadowed, - this.command, - this.localCreatedAt, - this.remoteCreatedAt, - this.localUpdatedAt, - this.remoteUpdatedAt, - this.localDeletedAt, - this.remoteDeletedAt, - this.deletedForMe, - this.messageTextUpdatedAt, - this.userId, - this.channelRole, - required this.pinned, - this.pinnedAt, - this.pinExpires, - this.pinnedByUserId, - required this.channelCid, - this.i18n, - this.restrictedVisibility, - this.extraData}); + const MessageEntity({ + required this.id, + this.messageText, + required this.attachments, + required this.state, + required this.type, + required this.mentionedUsers, + this.reactionGroups, + this.parentId, + this.quotedMessageId, + this.pollId, + this.replyCount, + this.showInChannel, + required this.shadowed, + this.command, + this.localCreatedAt, + this.remoteCreatedAt, + this.localUpdatedAt, + this.remoteUpdatedAt, + this.localDeletedAt, + this.remoteDeletedAt, + this.deletedForMe, + this.messageTextUpdatedAt, + this.userId, + this.channelRole, + required this.pinned, + this.pinnedAt, + this.pinExpires, + this.pinnedByUserId, + required this.channelCid, + this.i18n, + this.restrictedVisibility, + this.extraData, + }); @override Map toColumns(bool nullToAbsent) { final map = {}; @@ -1385,18 +1436,15 @@ class MessageEntity extends DataClass implements Insertable { map['message_text'] = Variable(messageText); } { - map['attachments'] = Variable( - $MessagesTable.$converterattachments.toSql(attachments)); + map['attachments'] = Variable($MessagesTable.$converterattachments.toSql(attachments)); } map['state'] = Variable(state); map['type'] = Variable(type); { - map['mentioned_users'] = Variable( - $MessagesTable.$convertermentionedUsers.toSql(mentionedUsers)); + map['mentioned_users'] = Variable($MessagesTable.$convertermentionedUsers.toSql(mentionedUsers)); } if (!nullToAbsent || reactionGroups != null) { - map['reaction_groups'] = Variable( - $MessagesTable.$converterreactionGroupsn.toSql(reactionGroups)); + map['reaction_groups'] = Variable($MessagesTable.$converterreactionGroupsn.toSql(reactionGroups)); } if (!nullToAbsent || parentId != null) { map['parent_id'] = Variable(parentId); @@ -1462,19 +1510,17 @@ class MessageEntity extends DataClass implements Insertable { map['i18n'] = Variable($MessagesTable.$converteri18n.toSql(i18n)); } if (!nullToAbsent || restrictedVisibility != null) { - map['restricted_visibility'] = Variable($MessagesTable - .$converterrestrictedVisibilityn - .toSql(restrictedVisibility)); + map['restricted_visibility'] = Variable( + $MessagesTable.$converterrestrictedVisibilityn.toSql(restrictedVisibility), + ); } if (!nullToAbsent || extraData != null) { - map['extra_data'] = Variable( - $MessagesTable.$converterextraDatan.toSql(extraData)); + map['extra_data'] = Variable($MessagesTable.$converterextraDatan.toSql(extraData)); } return map; } - factory MessageEntity.fromJson(Map json, - {ValueSerializer? serializer}) { + factory MessageEntity.fromJson(Map json, {ValueSerializer? serializer}) { serializer ??= driftRuntimeOptions.defaultSerializer; return MessageEntity( id: serializer.fromJson(json['id']), @@ -1483,8 +1529,7 @@ class MessageEntity extends DataClass implements Insertable { state: serializer.fromJson(json['state']), type: serializer.fromJson(json['type']), mentionedUsers: serializer.fromJson>(json['mentionedUsers']), - reactionGroups: serializer - .fromJson?>(json['reactionGroups']), + reactionGroups: serializer.fromJson?>(json['reactionGroups']), parentId: serializer.fromJson(json['parentId']), quotedMessageId: serializer.fromJson(json['quotedMessageId']), pollId: serializer.fromJson(json['pollId']), @@ -1499,8 +1544,7 @@ class MessageEntity extends DataClass implements Insertable { localDeletedAt: serializer.fromJson(json['localDeletedAt']), remoteDeletedAt: serializer.fromJson(json['remoteDeletedAt']), deletedForMe: serializer.fromJson(json['deletedForMe']), - messageTextUpdatedAt: - serializer.fromJson(json['messageTextUpdatedAt']), + messageTextUpdatedAt: serializer.fromJson(json['messageTextUpdatedAt']), userId: serializer.fromJson(json['userId']), channelRole: serializer.fromJson(json['channelRole']), pinned: serializer.fromJson(json['pinned']), @@ -1509,8 +1553,7 @@ class MessageEntity extends DataClass implements Insertable { pinnedByUserId: serializer.fromJson(json['pinnedByUserId']), channelCid: serializer.fromJson(json['channelCid']), i18n: serializer.fromJson?>(json['i18n']), - restrictedVisibility: - serializer.fromJson?>(json['restrictedVisibility']), + restrictedVisibility: serializer.fromJson?>(json['restrictedVisibility']), extraData: serializer.fromJson?>(json['extraData']), ); } @@ -1524,8 +1567,7 @@ class MessageEntity extends DataClass implements Insertable { 'state': serializer.toJson(state), 'type': serializer.toJson(type), 'mentionedUsers': serializer.toJson>(mentionedUsers), - 'reactionGroups': - serializer.toJson?>(reactionGroups), + 'reactionGroups': serializer.toJson?>(reactionGroups), 'parentId': serializer.toJson(parentId), 'quotedMessageId': serializer.toJson(quotedMessageId), 'pollId': serializer.toJson(pollId), @@ -1540,8 +1582,7 @@ class MessageEntity extends DataClass implements Insertable { 'localDeletedAt': serializer.toJson(localDeletedAt), 'remoteDeletedAt': serializer.toJson(remoteDeletedAt), 'deletedForMe': serializer.toJson(deletedForMe), - 'messageTextUpdatedAt': - serializer.toJson(messageTextUpdatedAt), + 'messageTextUpdatedAt': serializer.toJson(messageTextUpdatedAt), 'userId': serializer.toJson(userId), 'channelRole': serializer.toJson(channelRole), 'pinned': serializer.toJson(pinned), @@ -1550,162 +1591,111 @@ class MessageEntity extends DataClass implements Insertable { 'pinnedByUserId': serializer.toJson(pinnedByUserId), 'channelCid': serializer.toJson(channelCid), 'i18n': serializer.toJson?>(i18n), - 'restrictedVisibility': - serializer.toJson?>(restrictedVisibility), + 'restrictedVisibility': serializer.toJson?>(restrictedVisibility), 'extraData': serializer.toJson?>(extraData), }; } - MessageEntity copyWith( - {String? id, - Value messageText = const Value.absent(), - List? attachments, - String? state, - String? type, - List? mentionedUsers, - Value?> reactionGroups = - const Value.absent(), - Value parentId = const Value.absent(), - Value quotedMessageId = const Value.absent(), - Value pollId = const Value.absent(), - Value replyCount = const Value.absent(), - Value showInChannel = const Value.absent(), - bool? shadowed, - Value command = const Value.absent(), - Value localCreatedAt = const Value.absent(), - Value remoteCreatedAt = const Value.absent(), - Value localUpdatedAt = const Value.absent(), - Value remoteUpdatedAt = const Value.absent(), - Value localDeletedAt = const Value.absent(), - Value remoteDeletedAt = const Value.absent(), - Value deletedForMe = const Value.absent(), - Value messageTextUpdatedAt = const Value.absent(), - Value userId = const Value.absent(), - Value channelRole = const Value.absent(), - bool? pinned, - Value pinnedAt = const Value.absent(), - Value pinExpires = const Value.absent(), - Value pinnedByUserId = const Value.absent(), - String? channelCid, - Value?> i18n = const Value.absent(), - Value?> restrictedVisibility = const Value.absent(), - Value?> extraData = const Value.absent()}) => - MessageEntity( - id: id ?? this.id, - messageText: messageText.present ? messageText.value : this.messageText, - attachments: attachments ?? this.attachments, - state: state ?? this.state, - type: type ?? this.type, - mentionedUsers: mentionedUsers ?? this.mentionedUsers, - reactionGroups: - reactionGroups.present ? reactionGroups.value : this.reactionGroups, - parentId: parentId.present ? parentId.value : this.parentId, - quotedMessageId: quotedMessageId.present - ? quotedMessageId.value - : this.quotedMessageId, - pollId: pollId.present ? pollId.value : this.pollId, - replyCount: replyCount.present ? replyCount.value : this.replyCount, - showInChannel: - showInChannel.present ? showInChannel.value : this.showInChannel, - shadowed: shadowed ?? this.shadowed, - command: command.present ? command.value : this.command, - localCreatedAt: - localCreatedAt.present ? localCreatedAt.value : this.localCreatedAt, - remoteCreatedAt: remoteCreatedAt.present - ? remoteCreatedAt.value - : this.remoteCreatedAt, - localUpdatedAt: - localUpdatedAt.present ? localUpdatedAt.value : this.localUpdatedAt, - remoteUpdatedAt: remoteUpdatedAt.present - ? remoteUpdatedAt.value - : this.remoteUpdatedAt, - localDeletedAt: - localDeletedAt.present ? localDeletedAt.value : this.localDeletedAt, - remoteDeletedAt: remoteDeletedAt.present - ? remoteDeletedAt.value - : this.remoteDeletedAt, - deletedForMe: - deletedForMe.present ? deletedForMe.value : this.deletedForMe, - messageTextUpdatedAt: messageTextUpdatedAt.present - ? messageTextUpdatedAt.value - : this.messageTextUpdatedAt, - userId: userId.present ? userId.value : this.userId, - channelRole: channelRole.present ? channelRole.value : this.channelRole, - pinned: pinned ?? this.pinned, - pinnedAt: pinnedAt.present ? pinnedAt.value : this.pinnedAt, - pinExpires: pinExpires.present ? pinExpires.value : this.pinExpires, - pinnedByUserId: - pinnedByUserId.present ? pinnedByUserId.value : this.pinnedByUserId, - channelCid: channelCid ?? this.channelCid, - i18n: i18n.present ? i18n.value : this.i18n, - restrictedVisibility: restrictedVisibility.present - ? restrictedVisibility.value - : this.restrictedVisibility, - extraData: extraData.present ? extraData.value : this.extraData, - ); + MessageEntity copyWith({ + String? id, + Value messageText = const Value.absent(), + List? attachments, + String? state, + String? type, + List? mentionedUsers, + Value?> reactionGroups = const Value.absent(), + Value parentId = const Value.absent(), + Value quotedMessageId = const Value.absent(), + Value pollId = const Value.absent(), + Value replyCount = const Value.absent(), + Value showInChannel = const Value.absent(), + bool? shadowed, + Value command = const Value.absent(), + Value localCreatedAt = const Value.absent(), + Value remoteCreatedAt = const Value.absent(), + Value localUpdatedAt = const Value.absent(), + Value remoteUpdatedAt = const Value.absent(), + Value localDeletedAt = const Value.absent(), + Value remoteDeletedAt = const Value.absent(), + Value deletedForMe = const Value.absent(), + Value messageTextUpdatedAt = const Value.absent(), + Value userId = const Value.absent(), + Value channelRole = const Value.absent(), + bool? pinned, + Value pinnedAt = const Value.absent(), + Value pinExpires = const Value.absent(), + Value pinnedByUserId = const Value.absent(), + String? channelCid, + Value?> i18n = const Value.absent(), + Value?> restrictedVisibility = const Value.absent(), + Value?> extraData = const Value.absent(), + }) => MessageEntity( + id: id ?? this.id, + messageText: messageText.present ? messageText.value : this.messageText, + attachments: attachments ?? this.attachments, + state: state ?? this.state, + type: type ?? this.type, + mentionedUsers: mentionedUsers ?? this.mentionedUsers, + reactionGroups: reactionGroups.present ? reactionGroups.value : this.reactionGroups, + parentId: parentId.present ? parentId.value : this.parentId, + quotedMessageId: quotedMessageId.present ? quotedMessageId.value : this.quotedMessageId, + pollId: pollId.present ? pollId.value : this.pollId, + replyCount: replyCount.present ? replyCount.value : this.replyCount, + showInChannel: showInChannel.present ? showInChannel.value : this.showInChannel, + shadowed: shadowed ?? this.shadowed, + command: command.present ? command.value : this.command, + localCreatedAt: localCreatedAt.present ? localCreatedAt.value : this.localCreatedAt, + remoteCreatedAt: remoteCreatedAt.present ? remoteCreatedAt.value : this.remoteCreatedAt, + localUpdatedAt: localUpdatedAt.present ? localUpdatedAt.value : this.localUpdatedAt, + remoteUpdatedAt: remoteUpdatedAt.present ? remoteUpdatedAt.value : this.remoteUpdatedAt, + localDeletedAt: localDeletedAt.present ? localDeletedAt.value : this.localDeletedAt, + remoteDeletedAt: remoteDeletedAt.present ? remoteDeletedAt.value : this.remoteDeletedAt, + deletedForMe: deletedForMe.present ? deletedForMe.value : this.deletedForMe, + messageTextUpdatedAt: messageTextUpdatedAt.present ? messageTextUpdatedAt.value : this.messageTextUpdatedAt, + userId: userId.present ? userId.value : this.userId, + channelRole: channelRole.present ? channelRole.value : this.channelRole, + pinned: pinned ?? this.pinned, + pinnedAt: pinnedAt.present ? pinnedAt.value : this.pinnedAt, + pinExpires: pinExpires.present ? pinExpires.value : this.pinExpires, + pinnedByUserId: pinnedByUserId.present ? pinnedByUserId.value : this.pinnedByUserId, + channelCid: channelCid ?? this.channelCid, + i18n: i18n.present ? i18n.value : this.i18n, + restrictedVisibility: restrictedVisibility.present ? restrictedVisibility.value : this.restrictedVisibility, + extraData: extraData.present ? extraData.value : this.extraData, + ); MessageEntity copyWithCompanion(MessagesCompanion data) { return MessageEntity( id: data.id.present ? data.id.value : this.id, - messageText: - data.messageText.present ? data.messageText.value : this.messageText, - attachments: - data.attachments.present ? data.attachments.value : this.attachments, + messageText: data.messageText.present ? data.messageText.value : this.messageText, + attachments: data.attachments.present ? data.attachments.value : this.attachments, state: data.state.present ? data.state.value : this.state, type: data.type.present ? data.type.value : this.type, - mentionedUsers: data.mentionedUsers.present - ? data.mentionedUsers.value - : this.mentionedUsers, - reactionGroups: data.reactionGroups.present - ? data.reactionGroups.value - : this.reactionGroups, + mentionedUsers: data.mentionedUsers.present ? data.mentionedUsers.value : this.mentionedUsers, + reactionGroups: data.reactionGroups.present ? data.reactionGroups.value : this.reactionGroups, parentId: data.parentId.present ? data.parentId.value : this.parentId, - quotedMessageId: data.quotedMessageId.present - ? data.quotedMessageId.value - : this.quotedMessageId, + quotedMessageId: data.quotedMessageId.present ? data.quotedMessageId.value : this.quotedMessageId, pollId: data.pollId.present ? data.pollId.value : this.pollId, - replyCount: - data.replyCount.present ? data.replyCount.value : this.replyCount, - showInChannel: data.showInChannel.present - ? data.showInChannel.value - : this.showInChannel, + replyCount: data.replyCount.present ? data.replyCount.value : this.replyCount, + showInChannel: data.showInChannel.present ? data.showInChannel.value : this.showInChannel, shadowed: data.shadowed.present ? data.shadowed.value : this.shadowed, command: data.command.present ? data.command.value : this.command, - localCreatedAt: data.localCreatedAt.present - ? data.localCreatedAt.value - : this.localCreatedAt, - remoteCreatedAt: data.remoteCreatedAt.present - ? data.remoteCreatedAt.value - : this.remoteCreatedAt, - localUpdatedAt: data.localUpdatedAt.present - ? data.localUpdatedAt.value - : this.localUpdatedAt, - remoteUpdatedAt: data.remoteUpdatedAt.present - ? data.remoteUpdatedAt.value - : this.remoteUpdatedAt, - localDeletedAt: data.localDeletedAt.present - ? data.localDeletedAt.value - : this.localDeletedAt, - remoteDeletedAt: data.remoteDeletedAt.present - ? data.remoteDeletedAt.value - : this.remoteDeletedAt, - deletedForMe: data.deletedForMe.present - ? data.deletedForMe.value - : this.deletedForMe, + localCreatedAt: data.localCreatedAt.present ? data.localCreatedAt.value : this.localCreatedAt, + remoteCreatedAt: data.remoteCreatedAt.present ? data.remoteCreatedAt.value : this.remoteCreatedAt, + localUpdatedAt: data.localUpdatedAt.present ? data.localUpdatedAt.value : this.localUpdatedAt, + remoteUpdatedAt: data.remoteUpdatedAt.present ? data.remoteUpdatedAt.value : this.remoteUpdatedAt, + localDeletedAt: data.localDeletedAt.present ? data.localDeletedAt.value : this.localDeletedAt, + remoteDeletedAt: data.remoteDeletedAt.present ? data.remoteDeletedAt.value : this.remoteDeletedAt, + deletedForMe: data.deletedForMe.present ? data.deletedForMe.value : this.deletedForMe, messageTextUpdatedAt: data.messageTextUpdatedAt.present ? data.messageTextUpdatedAt.value : this.messageTextUpdatedAt, userId: data.userId.present ? data.userId.value : this.userId, - channelRole: - data.channelRole.present ? data.channelRole.value : this.channelRole, + channelRole: data.channelRole.present ? data.channelRole.value : this.channelRole, pinned: data.pinned.present ? data.pinned.value : this.pinned, pinnedAt: data.pinnedAt.present ? data.pinnedAt.value : this.pinnedAt, - pinExpires: - data.pinExpires.present ? data.pinExpires.value : this.pinExpires, - pinnedByUserId: data.pinnedByUserId.present - ? data.pinnedByUserId.value - : this.pinnedByUserId, - channelCid: - data.channelCid.present ? data.channelCid.value : this.channelCid, + pinExpires: data.pinExpires.present ? data.pinExpires.value : this.pinExpires, + pinnedByUserId: data.pinnedByUserId.present ? data.pinnedByUserId.value : this.pinnedByUserId, + channelCid: data.channelCid.present ? data.channelCid.value : this.channelCid, i18n: data.i18n.present ? data.i18n.value : this.i18n, restrictedVisibility: data.restrictedVisibility.present ? data.restrictedVisibility.value @@ -1755,39 +1745,39 @@ class MessageEntity extends DataClass implements Insertable { @override int get hashCode => Object.hashAll([ - id, - messageText, - attachments, - state, - type, - mentionedUsers, - reactionGroups, - parentId, - quotedMessageId, - pollId, - replyCount, - showInChannel, - shadowed, - command, - localCreatedAt, - remoteCreatedAt, - localUpdatedAt, - remoteUpdatedAt, - localDeletedAt, - remoteDeletedAt, - deletedForMe, - messageTextUpdatedAt, - userId, - channelRole, - pinned, - pinnedAt, - pinExpires, - pinnedByUserId, - channelCid, - i18n, - restrictedVisibility, - extraData - ]); + id, + messageText, + attachments, + state, + type, + mentionedUsers, + reactionGroups, + parentId, + quotedMessageId, + pollId, + replyCount, + showInChannel, + shadowed, + command, + localCreatedAt, + remoteCreatedAt, + localUpdatedAt, + remoteUpdatedAt, + localDeletedAt, + remoteDeletedAt, + deletedForMe, + messageTextUpdatedAt, + userId, + channelRole, + pinned, + pinnedAt, + pinExpires, + pinnedByUserId, + channelCid, + i18n, + restrictedVisibility, + extraData, + ]); @override bool operator ==(Object other) => identical(this, other) || @@ -1929,11 +1919,11 @@ class MessagesCompanion extends UpdateCompanion { this.restrictedVisibility = const Value.absent(), this.extraData = const Value.absent(), this.rowid = const Value.absent(), - }) : id = Value(id), - attachments = Value(attachments), - state = Value(state), - mentionedUsers = Value(mentionedUsers), - channelCid = Value(channelCid); + }) : id = Value(id), + attachments = Value(attachments), + state = Value(state), + mentionedUsers = Value(mentionedUsers), + channelCid = Value(channelCid); static Insertable custom({ Expression? id, Expression? messageText, @@ -1991,8 +1981,7 @@ class MessagesCompanion extends UpdateCompanion { if (localDeletedAt != null) 'local_deleted_at': localDeletedAt, if (remoteDeletedAt != null) 'remote_deleted_at': remoteDeletedAt, if (deletedForMe != null) 'deleted_for_me': deletedForMe, - if (messageTextUpdatedAt != null) - 'message_text_updated_at': messageTextUpdatedAt, + if (messageTextUpdatedAt != null) 'message_text_updated_at': messageTextUpdatedAt, if (userId != null) 'user_id': userId, if (channelRole != null) 'channel_role': channelRole, if (pinned != null) 'pinned': pinned, @@ -2001,47 +1990,47 @@ class MessagesCompanion extends UpdateCompanion { if (pinnedByUserId != null) 'pinned_by_user_id': pinnedByUserId, if (channelCid != null) 'channel_cid': channelCid, if (i18n != null) 'i18n': i18n, - if (restrictedVisibility != null) - 'restricted_visibility': restrictedVisibility, + if (restrictedVisibility != null) 'restricted_visibility': restrictedVisibility, if (extraData != null) 'extra_data': extraData, if (rowid != null) 'rowid': rowid, }); } - MessagesCompanion copyWith( - {Value? id, - Value? messageText, - Value>? attachments, - Value? state, - Value? type, - Value>? mentionedUsers, - Value?>? reactionGroups, - Value? parentId, - Value? quotedMessageId, - Value? pollId, - Value? replyCount, - Value? showInChannel, - Value? shadowed, - Value? command, - Value? localCreatedAt, - Value? remoteCreatedAt, - Value? localUpdatedAt, - Value? remoteUpdatedAt, - Value? localDeletedAt, - Value? remoteDeletedAt, - Value? deletedForMe, - Value? messageTextUpdatedAt, - Value? userId, - Value? channelRole, - Value? pinned, - Value? pinnedAt, - Value? pinExpires, - Value? pinnedByUserId, - Value? channelCid, - Value?>? i18n, - Value?>? restrictedVisibility, - Value?>? extraData, - Value? rowid}) { + MessagesCompanion copyWith({ + Value? id, + Value? messageText, + Value>? attachments, + Value? state, + Value? type, + Value>? mentionedUsers, + Value?>? reactionGroups, + Value? parentId, + Value? quotedMessageId, + Value? pollId, + Value? replyCount, + Value? showInChannel, + Value? shadowed, + Value? command, + Value? localCreatedAt, + Value? remoteCreatedAt, + Value? localUpdatedAt, + Value? remoteUpdatedAt, + Value? localDeletedAt, + Value? remoteDeletedAt, + Value? deletedForMe, + Value? messageTextUpdatedAt, + Value? userId, + Value? channelRole, + Value? pinned, + Value? pinnedAt, + Value? pinExpires, + Value? pinnedByUserId, + Value? channelCid, + Value?>? i18n, + Value?>? restrictedVisibility, + Value?>? extraData, + Value? rowid, + }) { return MessagesCompanion( id: id ?? this.id, messageText: messageText ?? this.messageText, @@ -2089,8 +2078,7 @@ class MessagesCompanion extends UpdateCompanion { map['message_text'] = Variable(messageText.value); } if (attachments.present) { - map['attachments'] = Variable( - $MessagesTable.$converterattachments.toSql(attachments.value)); + map['attachments'] = Variable($MessagesTable.$converterattachments.toSql(attachments.value)); } if (state.present) { map['state'] = Variable(state.value); @@ -2099,12 +2087,10 @@ class MessagesCompanion extends UpdateCompanion { map['type'] = Variable(type.value); } if (mentionedUsers.present) { - map['mentioned_users'] = Variable( - $MessagesTable.$convertermentionedUsers.toSql(mentionedUsers.value)); + map['mentioned_users'] = Variable($MessagesTable.$convertermentionedUsers.toSql(mentionedUsers.value)); } if (reactionGroups.present) { - map['reaction_groups'] = Variable( - $MessagesTable.$converterreactionGroupsn.toSql(reactionGroups.value)); + map['reaction_groups'] = Variable($MessagesTable.$converterreactionGroupsn.toSql(reactionGroups.value)); } if (parentId.present) { map['parent_id'] = Variable(parentId.value); @@ -2149,8 +2135,7 @@ class MessagesCompanion extends UpdateCompanion { map['deleted_for_me'] = Variable(deletedForMe.value); } if (messageTextUpdatedAt.present) { - map['message_text_updated_at'] = - Variable(messageTextUpdatedAt.value); + map['message_text_updated_at'] = Variable(messageTextUpdatedAt.value); } if (userId.present) { map['user_id'] = Variable(userId.value); @@ -2174,17 +2159,15 @@ class MessagesCompanion extends UpdateCompanion { map['channel_cid'] = Variable(channelCid.value); } if (i18n.present) { - map['i18n'] = - Variable($MessagesTable.$converteri18n.toSql(i18n.value)); + map['i18n'] = Variable($MessagesTable.$converteri18n.toSql(i18n.value)); } if (restrictedVisibility.present) { - map['restricted_visibility'] = Variable($MessagesTable - .$converterrestrictedVisibilityn - .toSql(restrictedVisibility.value)); + map['restricted_visibility'] = Variable( + $MessagesTable.$converterrestrictedVisibilityn.toSql(restrictedVisibility.value), + ); } if (extraData.present) { - map['extra_data'] = Variable( - $MessagesTable.$converterextraDatan.toSql(extraData.value)); + map['extra_data'] = Variable($MessagesTable.$converterextraDatan.toSql(extraData.value)); } if (rowid.present) { map['rowid'] = Variable(rowid.value); @@ -2233,8 +2216,7 @@ class MessagesCompanion extends UpdateCompanion { } } -class $DraftMessagesTable extends DraftMessages - with TableInfo<$DraftMessagesTable, DraftMessageEntity> { +class $DraftMessagesTable extends DraftMessages with TableInfo<$DraftMessagesTable, DraftMessageEntity> { @override final GeneratedDatabase attachedDatabase; final String? _alias; @@ -2242,126 +2224,157 @@ class $DraftMessagesTable extends DraftMessages static const VerificationMeta _idMeta = const VerificationMeta('id'); @override late final GeneratedColumn id = GeneratedColumn( - 'id', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true); - static const VerificationMeta _messageTextMeta = - const VerificationMeta('messageText'); + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _messageTextMeta = const VerificationMeta('messageText'); @override late final GeneratedColumn messageText = GeneratedColumn( - 'message_text', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); - @override - late final GeneratedColumnWithTypeConverter, String> - attachments = GeneratedColumn('attachments', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true) - .withConverter>( - $DraftMessagesTable.$converterattachments); + 'message_text', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + late final GeneratedColumnWithTypeConverter, String> attachments = GeneratedColumn( + 'attachments', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ).withConverter>($DraftMessagesTable.$converterattachments); static const VerificationMeta _typeMeta = const VerificationMeta('type'); @override late final GeneratedColumn type = GeneratedColumn( - 'type', aliasedName, false, - type: DriftSqlType.string, - requiredDuringInsert: false, - defaultValue: const Constant('regular')); - @override - late final GeneratedColumnWithTypeConverter, String> - mentionedUsers = GeneratedColumn( - 'mentioned_users', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true) - .withConverter>( - $DraftMessagesTable.$convertermentionedUsers); - static const VerificationMeta _parentIdMeta = - const VerificationMeta('parentId'); + 'type', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const Constant('regular'), + ); + @override + late final GeneratedColumnWithTypeConverter, String> mentionedUsers = GeneratedColumn( + 'mentioned_users', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ).withConverter>($DraftMessagesTable.$convertermentionedUsers); + static const VerificationMeta _parentIdMeta = const VerificationMeta('parentId'); @override late final GeneratedColumn parentId = GeneratedColumn( - 'parent_id', aliasedName, true, - type: DriftSqlType.string, - requiredDuringInsert: false, - defaultConstraints: GeneratedColumn.constraintIsAlways( - 'REFERENCES messages (id) ON DELETE CASCADE')); - static const VerificationMeta _quotedMessageIdMeta = - const VerificationMeta('quotedMessageId'); + 'parent_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways('REFERENCES messages (id) ON DELETE CASCADE'), + ); + static const VerificationMeta _quotedMessageIdMeta = const VerificationMeta('quotedMessageId'); @override late final GeneratedColumn quotedMessageId = GeneratedColumn( - 'quoted_message_id', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); + 'quoted_message_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); static const VerificationMeta _pollIdMeta = const VerificationMeta('pollId'); @override late final GeneratedColumn pollId = GeneratedColumn( - 'poll_id', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); - static const VerificationMeta _showInChannelMeta = - const VerificationMeta('showInChannel'); + 'poll_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _showInChannelMeta = const VerificationMeta('showInChannel'); @override late final GeneratedColumn showInChannel = GeneratedColumn( - 'show_in_channel', aliasedName, true, - type: DriftSqlType.bool, - requiredDuringInsert: false, - defaultConstraints: GeneratedColumn.constraintIsAlways( - 'CHECK ("show_in_channel" IN (0, 1))')); - static const VerificationMeta _commandMeta = - const VerificationMeta('command'); + 'show_in_channel', + aliasedName, + true, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways('CHECK ("show_in_channel" IN (0, 1))'), + ); + static const VerificationMeta _commandMeta = const VerificationMeta('command'); @override late final GeneratedColumn command = GeneratedColumn( - 'command', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); + 'command', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); static const VerificationMeta _silentMeta = const VerificationMeta('silent'); @override late final GeneratedColumn silent = GeneratedColumn( - 'silent', aliasedName, false, - type: DriftSqlType.bool, - requiredDuringInsert: false, - defaultConstraints: - GeneratedColumn.constraintIsAlways('CHECK ("silent" IN (0, 1))'), - defaultValue: const Constant(false)); - static const VerificationMeta _createdAtMeta = - const VerificationMeta('createdAt'); + 'silent', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways('CHECK ("silent" IN (0, 1))'), + defaultValue: const Constant(false), + ); + static const VerificationMeta _createdAtMeta = const VerificationMeta('createdAt'); @override late final GeneratedColumn createdAt = GeneratedColumn( - 'created_at', aliasedName, false, - type: DriftSqlType.dateTime, - requiredDuringInsert: false, - defaultValue: currentDateAndTime); - static const VerificationMeta _channelCidMeta = - const VerificationMeta('channelCid'); + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime, + ); + static const VerificationMeta _channelCidMeta = const VerificationMeta('channelCid'); @override late final GeneratedColumn channelCid = GeneratedColumn( - 'channel_cid', aliasedName, false, - type: DriftSqlType.string, - requiredDuringInsert: true, - defaultConstraints: GeneratedColumn.constraintIsAlways( - 'REFERENCES channels (cid) ON DELETE CASCADE')); - @override - late final GeneratedColumnWithTypeConverter?, String> - extraData = GeneratedColumn('extra_data', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false) - .withConverter?>( - $DraftMessagesTable.$converterextraDatan); + 'channel_cid', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways('REFERENCES channels (cid) ON DELETE CASCADE'), + ); + @override + late final GeneratedColumnWithTypeConverter?, String> extraData = GeneratedColumn( + 'extra_data', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ).withConverter?>($DraftMessagesTable.$converterextraDatan); @override List get $columns => [ - id, - messageText, - attachments, - type, - mentionedUsers, - parentId, - quotedMessageId, - pollId, - showInChannel, - command, - silent, - createdAt, - channelCid, - extraData - ]; + id, + messageText, + attachments, + type, + mentionedUsers, + parentId, + quotedMessageId, + pollId, + showInChannel, + command, + silent, + createdAt, + channelCid, + extraData, + ]; @override String get aliasedName => _alias ?? actualTableName; @override String get actualTableName => $name; static const String $name = 'draft_messages'; @override - VerificationContext validateIntegrity(Insertable instance, - {bool isInserting = false}) { + VerificationContext validateIntegrity(Insertable instance, {bool isInserting = false}) { final context = VerificationContext(); final data = instance.toColumns(true); if (data.containsKey('id')) { @@ -2370,52 +2383,40 @@ class $DraftMessagesTable extends DraftMessages context.missing(_idMeta); } if (data.containsKey('message_text')) { - context.handle( - _messageTextMeta, - messageText.isAcceptableOrUnknown( - data['message_text']!, _messageTextMeta)); + context.handle(_messageTextMeta, messageText.isAcceptableOrUnknown(data['message_text']!, _messageTextMeta)); } if (data.containsKey('type')) { - context.handle( - _typeMeta, type.isAcceptableOrUnknown(data['type']!, _typeMeta)); + context.handle(_typeMeta, type.isAcceptableOrUnknown(data['type']!, _typeMeta)); } if (data.containsKey('parent_id')) { - context.handle(_parentIdMeta, - parentId.isAcceptableOrUnknown(data['parent_id']!, _parentIdMeta)); + context.handle(_parentIdMeta, parentId.isAcceptableOrUnknown(data['parent_id']!, _parentIdMeta)); } if (data.containsKey('quoted_message_id')) { context.handle( - _quotedMessageIdMeta, - quotedMessageId.isAcceptableOrUnknown( - data['quoted_message_id']!, _quotedMessageIdMeta)); + _quotedMessageIdMeta, + quotedMessageId.isAcceptableOrUnknown(data['quoted_message_id']!, _quotedMessageIdMeta), + ); } if (data.containsKey('poll_id')) { - context.handle(_pollIdMeta, - pollId.isAcceptableOrUnknown(data['poll_id']!, _pollIdMeta)); + context.handle(_pollIdMeta, pollId.isAcceptableOrUnknown(data['poll_id']!, _pollIdMeta)); } if (data.containsKey('show_in_channel')) { context.handle( - _showInChannelMeta, - showInChannel.isAcceptableOrUnknown( - data['show_in_channel']!, _showInChannelMeta)); + _showInChannelMeta, + showInChannel.isAcceptableOrUnknown(data['show_in_channel']!, _showInChannelMeta), + ); } if (data.containsKey('command')) { - context.handle(_commandMeta, - command.isAcceptableOrUnknown(data['command']!, _commandMeta)); + context.handle(_commandMeta, command.isAcceptableOrUnknown(data['command']!, _commandMeta)); } if (data.containsKey('silent')) { - context.handle(_silentMeta, - silent.isAcceptableOrUnknown(data['silent']!, _silentMeta)); + context.handle(_silentMeta, silent.isAcceptableOrUnknown(data['silent']!, _silentMeta)); } if (data.containsKey('created_at')) { - context.handle(_createdAtMeta, - createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); + context.handle(_createdAtMeta, createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); } if (data.containsKey('channel_cid')) { - context.handle( - _channelCidMeta, - channelCid.isAcceptableOrUnknown( - data['channel_cid']!, _channelCidMeta)); + context.handle(_channelCidMeta, channelCid.isAcceptableOrUnknown(data['channel_cid']!, _channelCidMeta)); } else if (isInserting) { context.missing(_channelCidMeta); } @@ -2428,37 +2429,29 @@ class $DraftMessagesTable extends DraftMessages DraftMessageEntity map(Map data, {String? tablePrefix}) { final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; return DraftMessageEntity( - id: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}id'])!, - messageText: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}message_text']), + id: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}id'])!, + messageText: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}message_text']), attachments: $DraftMessagesTable.$converterattachments.fromSql( - attachedDatabase.typeMapping.read( - DriftSqlType.string, data['${effectivePrefix}attachments'])!), - type: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}type'])!, + attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}attachments'])!, + ), + type: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}type'])!, mentionedUsers: $DraftMessagesTable.$convertermentionedUsers.fromSql( - attachedDatabase.typeMapping.read( - DriftSqlType.string, data['${effectivePrefix}mentioned_users'])!), - parentId: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}parent_id']), + attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}mentioned_users'])!, + ), + parentId: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}parent_id']), quotedMessageId: attachedDatabase.typeMapping.read( - DriftSqlType.string, data['${effectivePrefix}quoted_message_id']), - pollId: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}poll_id']), - showInChannel: attachedDatabase.typeMapping - .read(DriftSqlType.bool, data['${effectivePrefix}show_in_channel']), - command: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}command']), - silent: attachedDatabase.typeMapping - .read(DriftSqlType.bool, data['${effectivePrefix}silent'])!, - createdAt: attachedDatabase.typeMapping - .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, - channelCid: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}channel_cid'])!, + DriftSqlType.string, + data['${effectivePrefix}quoted_message_id'], + ), + pollId: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}poll_id']), + showInChannel: attachedDatabase.typeMapping.read(DriftSqlType.bool, data['${effectivePrefix}show_in_channel']), + command: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}command']), + silent: attachedDatabase.typeMapping.read(DriftSqlType.bool, data['${effectivePrefix}silent'])!, + createdAt: attachedDatabase.typeMapping.read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + channelCid: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}channel_cid'])!, extraData: $DraftMessagesTable.$converterextraDatan.fromSql( - attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}extra_data'])), + attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}extra_data']), + ), ); } @@ -2467,18 +2460,15 @@ class $DraftMessagesTable extends DraftMessages return $DraftMessagesTable(attachedDatabase, alias); } - static TypeConverter, String> $converterattachments = - ListConverter(); - static TypeConverter, String> $convertermentionedUsers = - ListConverter(); - static TypeConverter, String> $converterextraData = - MapConverter(); - static TypeConverter?, String?> $converterextraDatan = - NullAwareTypeConverter.wrap($converterextraData); + static TypeConverter, String> $converterattachments = ListConverter(); + static TypeConverter, String> $convertermentionedUsers = ListConverter(); + static TypeConverter, String> $converterextraData = MapConverter(); + static TypeConverter?, String?> $converterextraDatan = NullAwareTypeConverter.wrap( + $converterextraData, + ); } -class DraftMessageEntity extends DataClass - implements Insertable { +class DraftMessageEntity extends DataClass implements Insertable { /// The message id final String id; @@ -2521,21 +2511,22 @@ class DraftMessageEntity extends DataClass /// Message custom extraData final Map? extraData; - const DraftMessageEntity( - {required this.id, - this.messageText, - required this.attachments, - required this.type, - required this.mentionedUsers, - this.parentId, - this.quotedMessageId, - this.pollId, - this.showInChannel, - this.command, - required this.silent, - required this.createdAt, - required this.channelCid, - this.extraData}); + const DraftMessageEntity({ + required this.id, + this.messageText, + required this.attachments, + required this.type, + required this.mentionedUsers, + this.parentId, + this.quotedMessageId, + this.pollId, + this.showInChannel, + this.command, + required this.silent, + required this.createdAt, + required this.channelCid, + this.extraData, + }); @override Map toColumns(bool nullToAbsent) { final map = {}; @@ -2544,13 +2535,11 @@ class DraftMessageEntity extends DataClass map['message_text'] = Variable(messageText); } { - map['attachments'] = Variable( - $DraftMessagesTable.$converterattachments.toSql(attachments)); + map['attachments'] = Variable($DraftMessagesTable.$converterattachments.toSql(attachments)); } map['type'] = Variable(type); { - map['mentioned_users'] = Variable( - $DraftMessagesTable.$convertermentionedUsers.toSql(mentionedUsers)); + map['mentioned_users'] = Variable($DraftMessagesTable.$convertermentionedUsers.toSql(mentionedUsers)); } if (!nullToAbsent || parentId != null) { map['parent_id'] = Variable(parentId); @@ -2571,14 +2560,12 @@ class DraftMessageEntity extends DataClass map['created_at'] = Variable(createdAt); map['channel_cid'] = Variable(channelCid); if (!nullToAbsent || extraData != null) { - map['extra_data'] = Variable( - $DraftMessagesTable.$converterextraDatan.toSql(extraData)); + map['extra_data'] = Variable($DraftMessagesTable.$converterextraDatan.toSql(extraData)); } return map; } - factory DraftMessageEntity.fromJson(Map json, - {ValueSerializer? serializer}) { + factory DraftMessageEntity.fromJson(Map json, {ValueSerializer? serializer}) { serializer ??= driftRuntimeOptions.defaultSerializer; return DraftMessageEntity( id: serializer.fromJson(json['id']), @@ -2618,64 +2605,52 @@ class DraftMessageEntity extends DataClass }; } - DraftMessageEntity copyWith( - {String? id, - Value messageText = const Value.absent(), - List? attachments, - String? type, - List? mentionedUsers, - Value parentId = const Value.absent(), - Value quotedMessageId = const Value.absent(), - Value pollId = const Value.absent(), - Value showInChannel = const Value.absent(), - Value command = const Value.absent(), - bool? silent, - DateTime? createdAt, - String? channelCid, - Value?> extraData = const Value.absent()}) => - DraftMessageEntity( - id: id ?? this.id, - messageText: messageText.present ? messageText.value : this.messageText, - attachments: attachments ?? this.attachments, - type: type ?? this.type, - mentionedUsers: mentionedUsers ?? this.mentionedUsers, - parentId: parentId.present ? parentId.value : this.parentId, - quotedMessageId: quotedMessageId.present - ? quotedMessageId.value - : this.quotedMessageId, - pollId: pollId.present ? pollId.value : this.pollId, - showInChannel: - showInChannel.present ? showInChannel.value : this.showInChannel, - command: command.present ? command.value : this.command, - silent: silent ?? this.silent, - createdAt: createdAt ?? this.createdAt, - channelCid: channelCid ?? this.channelCid, - extraData: extraData.present ? extraData.value : this.extraData, - ); + DraftMessageEntity copyWith({ + String? id, + Value messageText = const Value.absent(), + List? attachments, + String? type, + List? mentionedUsers, + Value parentId = const Value.absent(), + Value quotedMessageId = const Value.absent(), + Value pollId = const Value.absent(), + Value showInChannel = const Value.absent(), + Value command = const Value.absent(), + bool? silent, + DateTime? createdAt, + String? channelCid, + Value?> extraData = const Value.absent(), + }) => DraftMessageEntity( + id: id ?? this.id, + messageText: messageText.present ? messageText.value : this.messageText, + attachments: attachments ?? this.attachments, + type: type ?? this.type, + mentionedUsers: mentionedUsers ?? this.mentionedUsers, + parentId: parentId.present ? parentId.value : this.parentId, + quotedMessageId: quotedMessageId.present ? quotedMessageId.value : this.quotedMessageId, + pollId: pollId.present ? pollId.value : this.pollId, + showInChannel: showInChannel.present ? showInChannel.value : this.showInChannel, + command: command.present ? command.value : this.command, + silent: silent ?? this.silent, + createdAt: createdAt ?? this.createdAt, + channelCid: channelCid ?? this.channelCid, + extraData: extraData.present ? extraData.value : this.extraData, + ); DraftMessageEntity copyWithCompanion(DraftMessagesCompanion data) { return DraftMessageEntity( id: data.id.present ? data.id.value : this.id, - messageText: - data.messageText.present ? data.messageText.value : this.messageText, - attachments: - data.attachments.present ? data.attachments.value : this.attachments, + messageText: data.messageText.present ? data.messageText.value : this.messageText, + attachments: data.attachments.present ? data.attachments.value : this.attachments, type: data.type.present ? data.type.value : this.type, - mentionedUsers: data.mentionedUsers.present - ? data.mentionedUsers.value - : this.mentionedUsers, + mentionedUsers: data.mentionedUsers.present ? data.mentionedUsers.value : this.mentionedUsers, parentId: data.parentId.present ? data.parentId.value : this.parentId, - quotedMessageId: data.quotedMessageId.present - ? data.quotedMessageId.value - : this.quotedMessageId, + quotedMessageId: data.quotedMessageId.present ? data.quotedMessageId.value : this.quotedMessageId, pollId: data.pollId.present ? data.pollId.value : this.pollId, - showInChannel: data.showInChannel.present - ? data.showInChannel.value - : this.showInChannel, + showInChannel: data.showInChannel.present ? data.showInChannel.value : this.showInChannel, command: data.command.present ? data.command.value : this.command, silent: data.silent.present ? data.silent.value : this.silent, createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, - channelCid: - data.channelCid.present ? data.channelCid.value : this.channelCid, + channelCid: data.channelCid.present ? data.channelCid.value : this.channelCid, extraData: data.extraData.present ? data.extraData.value : this.extraData, ); } @@ -2703,20 +2678,21 @@ class DraftMessageEntity extends DataClass @override int get hashCode => Object.hash( - id, - messageText, - attachments, - type, - mentionedUsers, - parentId, - quotedMessageId, - pollId, - showInChannel, - command, - silent, - createdAt, - channelCid, - extraData); + id, + messageText, + attachments, + type, + mentionedUsers, + parentId, + quotedMessageId, + pollId, + showInChannel, + command, + silent, + createdAt, + channelCid, + extraData, + ); @override bool operator ==(Object other) => identical(this, other) || @@ -2786,10 +2762,10 @@ class DraftMessagesCompanion extends UpdateCompanion { required String channelCid, this.extraData = const Value.absent(), this.rowid = const Value.absent(), - }) : id = Value(id), - attachments = Value(attachments), - mentionedUsers = Value(mentionedUsers), - channelCid = Value(channelCid); + }) : id = Value(id), + attachments = Value(attachments), + mentionedUsers = Value(mentionedUsers), + channelCid = Value(channelCid); static Insertable custom({ Expression? id, Expression? messageText, @@ -2826,22 +2802,23 @@ class DraftMessagesCompanion extends UpdateCompanion { }); } - DraftMessagesCompanion copyWith( - {Value? id, - Value? messageText, - Value>? attachments, - Value? type, - Value>? mentionedUsers, - Value? parentId, - Value? quotedMessageId, - Value? pollId, - Value? showInChannel, - Value? command, - Value? silent, - Value? createdAt, - Value? channelCid, - Value?>? extraData, - Value? rowid}) { + DraftMessagesCompanion copyWith({ + Value? id, + Value? messageText, + Value>? attachments, + Value? type, + Value>? mentionedUsers, + Value? parentId, + Value? quotedMessageId, + Value? pollId, + Value? showInChannel, + Value? command, + Value? silent, + Value? createdAt, + Value? channelCid, + Value?>? extraData, + Value? rowid, + }) { return DraftMessagesCompanion( id: id ?? this.id, messageText: messageText ?? this.messageText, @@ -2871,16 +2848,15 @@ class DraftMessagesCompanion extends UpdateCompanion { map['message_text'] = Variable(messageText.value); } if (attachments.present) { - map['attachments'] = Variable( - $DraftMessagesTable.$converterattachments.toSql(attachments.value)); + map['attachments'] = Variable($DraftMessagesTable.$converterattachments.toSql(attachments.value)); } if (type.present) { map['type'] = Variable(type.value); } if (mentionedUsers.present) { - map['mentioned_users'] = Variable($DraftMessagesTable - .$convertermentionedUsers - .toSql(mentionedUsers.value)); + map['mentioned_users'] = Variable( + $DraftMessagesTable.$convertermentionedUsers.toSql(mentionedUsers.value), + ); } if (parentId.present) { map['parent_id'] = Variable(parentId.value); @@ -2907,8 +2883,7 @@ class DraftMessagesCompanion extends UpdateCompanion { map['channel_cid'] = Variable(channelCid.value); } if (extraData.present) { - map['extra_data'] = Variable( - $DraftMessagesTable.$converterextraDatan.toSql(extraData.value)); + map['extra_data'] = Variable($DraftMessagesTable.$converterextraDatan.toSql(extraData.value)); } if (rowid.present) { map['rowid'] = Variable(rowid.value); @@ -2939,139 +2914,150 @@ class DraftMessagesCompanion extends UpdateCompanion { } } -class $LocationsTable extends Locations - with TableInfo<$LocationsTable, LocationEntity> { +class $LocationsTable extends Locations with TableInfo<$LocationsTable, LocationEntity> { @override final GeneratedDatabase attachedDatabase; final String? _alias; $LocationsTable(this.attachedDatabase, [this._alias]); - static const VerificationMeta _channelCidMeta = - const VerificationMeta('channelCid'); + static const VerificationMeta _channelCidMeta = const VerificationMeta('channelCid'); @override late final GeneratedColumn channelCid = GeneratedColumn( - 'channel_cid', aliasedName, true, - type: DriftSqlType.string, - requiredDuringInsert: false, - defaultConstraints: GeneratedColumn.constraintIsAlways( - 'REFERENCES channels (cid) ON DELETE CASCADE')); - static const VerificationMeta _messageIdMeta = - const VerificationMeta('messageId'); + 'channel_cid', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways('REFERENCES channels (cid) ON DELETE CASCADE'), + ); + static const VerificationMeta _messageIdMeta = const VerificationMeta('messageId'); @override late final GeneratedColumn messageId = GeneratedColumn( - 'message_id', aliasedName, true, - type: DriftSqlType.string, - requiredDuringInsert: false, - defaultConstraints: GeneratedColumn.constraintIsAlways( - 'REFERENCES messages (id) ON DELETE CASCADE')); + 'message_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways('REFERENCES messages (id) ON DELETE CASCADE'), + ); static const VerificationMeta _userIdMeta = const VerificationMeta('userId'); @override late final GeneratedColumn userId = GeneratedColumn( - 'user_id', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); - static const VerificationMeta _latitudeMeta = - const VerificationMeta('latitude'); + 'user_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _latitudeMeta = const VerificationMeta('latitude'); @override late final GeneratedColumn latitude = GeneratedColumn( - 'latitude', aliasedName, false, - type: DriftSqlType.double, requiredDuringInsert: true); - static const VerificationMeta _longitudeMeta = - const VerificationMeta('longitude'); + 'latitude', + aliasedName, + false, + type: DriftSqlType.double, + requiredDuringInsert: true, + ); + static const VerificationMeta _longitudeMeta = const VerificationMeta('longitude'); @override late final GeneratedColumn longitude = GeneratedColumn( - 'longitude', aliasedName, false, - type: DriftSqlType.double, requiredDuringInsert: true); - static const VerificationMeta _createdByDeviceIdMeta = - const VerificationMeta('createdByDeviceId'); - @override - late final GeneratedColumn createdByDeviceId = - GeneratedColumn('created_by_device_id', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); + 'longitude', + aliasedName, + false, + type: DriftSqlType.double, + requiredDuringInsert: true, + ); + static const VerificationMeta _createdByDeviceIdMeta = const VerificationMeta('createdByDeviceId'); + @override + late final GeneratedColumn createdByDeviceId = GeneratedColumn( + 'created_by_device_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); static const VerificationMeta _endAtMeta = const VerificationMeta('endAt'); @override late final GeneratedColumn endAt = GeneratedColumn( - 'end_at', aliasedName, true, - type: DriftSqlType.dateTime, requiredDuringInsert: false); - static const VerificationMeta _createdAtMeta = - const VerificationMeta('createdAt'); + 'end_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + static const VerificationMeta _createdAtMeta = const VerificationMeta('createdAt'); @override late final GeneratedColumn createdAt = GeneratedColumn( - 'created_at', aliasedName, false, - type: DriftSqlType.dateTime, - requiredDuringInsert: false, - defaultValue: currentDateAndTime); - static const VerificationMeta _updatedAtMeta = - const VerificationMeta('updatedAt'); + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime, + ); + static const VerificationMeta _updatedAtMeta = const VerificationMeta('updatedAt'); @override late final GeneratedColumn updatedAt = GeneratedColumn( - 'updated_at', aliasedName, false, - type: DriftSqlType.dateTime, - requiredDuringInsert: false, - defaultValue: currentDateAndTime); + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime, + ); @override List get $columns => [ - channelCid, - messageId, - userId, - latitude, - longitude, - createdByDeviceId, - endAt, - createdAt, - updatedAt - ]; + channelCid, + messageId, + userId, + latitude, + longitude, + createdByDeviceId, + endAt, + createdAt, + updatedAt, + ]; @override String get aliasedName => _alias ?? actualTableName; @override String get actualTableName => $name; static const String $name = 'locations'; @override - VerificationContext validateIntegrity(Insertable instance, - {bool isInserting = false}) { + VerificationContext validateIntegrity(Insertable instance, {bool isInserting = false}) { final context = VerificationContext(); final data = instance.toColumns(true); if (data.containsKey('channel_cid')) { - context.handle( - _channelCidMeta, - channelCid.isAcceptableOrUnknown( - data['channel_cid']!, _channelCidMeta)); + context.handle(_channelCidMeta, channelCid.isAcceptableOrUnknown(data['channel_cid']!, _channelCidMeta)); } if (data.containsKey('message_id')) { - context.handle(_messageIdMeta, - messageId.isAcceptableOrUnknown(data['message_id']!, _messageIdMeta)); + context.handle(_messageIdMeta, messageId.isAcceptableOrUnknown(data['message_id']!, _messageIdMeta)); } if (data.containsKey('user_id')) { - context.handle(_userIdMeta, - userId.isAcceptableOrUnknown(data['user_id']!, _userIdMeta)); + context.handle(_userIdMeta, userId.isAcceptableOrUnknown(data['user_id']!, _userIdMeta)); } if (data.containsKey('latitude')) { - context.handle(_latitudeMeta, - latitude.isAcceptableOrUnknown(data['latitude']!, _latitudeMeta)); + context.handle(_latitudeMeta, latitude.isAcceptableOrUnknown(data['latitude']!, _latitudeMeta)); } else if (isInserting) { context.missing(_latitudeMeta); } if (data.containsKey('longitude')) { - context.handle(_longitudeMeta, - longitude.isAcceptableOrUnknown(data['longitude']!, _longitudeMeta)); + context.handle(_longitudeMeta, longitude.isAcceptableOrUnknown(data['longitude']!, _longitudeMeta)); } else if (isInserting) { context.missing(_longitudeMeta); } if (data.containsKey('created_by_device_id')) { context.handle( - _createdByDeviceIdMeta, - createdByDeviceId.isAcceptableOrUnknown( - data['created_by_device_id']!, _createdByDeviceIdMeta)); + _createdByDeviceIdMeta, + createdByDeviceId.isAcceptableOrUnknown(data['created_by_device_id']!, _createdByDeviceIdMeta), + ); } if (data.containsKey('end_at')) { - context.handle( - _endAtMeta, endAt.isAcceptableOrUnknown(data['end_at']!, _endAtMeta)); + context.handle(_endAtMeta, endAt.isAcceptableOrUnknown(data['end_at']!, _endAtMeta)); } if (data.containsKey('created_at')) { - context.handle(_createdAtMeta, - createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); + context.handle(_createdAtMeta, createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); } if (data.containsKey('updated_at')) { - context.handle(_updatedAtMeta, - updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta)); + context.handle(_updatedAtMeta, updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta)); } return context; } @@ -3082,24 +3068,18 @@ class $LocationsTable extends Locations LocationEntity map(Map data, {String? tablePrefix}) { final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; return LocationEntity( - channelCid: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}channel_cid']), - messageId: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}message_id']), - userId: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}user_id']), - latitude: attachedDatabase.typeMapping - .read(DriftSqlType.double, data['${effectivePrefix}latitude'])!, - longitude: attachedDatabase.typeMapping - .read(DriftSqlType.double, data['${effectivePrefix}longitude'])!, + channelCid: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}channel_cid']), + messageId: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}message_id']), + userId: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}user_id']), + latitude: attachedDatabase.typeMapping.read(DriftSqlType.double, data['${effectivePrefix}latitude'])!, + longitude: attachedDatabase.typeMapping.read(DriftSqlType.double, data['${effectivePrefix}longitude'])!, createdByDeviceId: attachedDatabase.typeMapping.read( - DriftSqlType.string, data['${effectivePrefix}created_by_device_id']), - endAt: attachedDatabase.typeMapping - .read(DriftSqlType.dateTime, data['${effectivePrefix}end_at']), - createdAt: attachedDatabase.typeMapping - .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, - updatedAt: attachedDatabase.typeMapping - .read(DriftSqlType.dateTime, data['${effectivePrefix}updated_at'])!, + DriftSqlType.string, + data['${effectivePrefix}created_by_device_id'], + ), + endAt: attachedDatabase.typeMapping.read(DriftSqlType.dateTime, data['${effectivePrefix}end_at']), + createdAt: attachedDatabase.typeMapping.read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + updatedAt: attachedDatabase.typeMapping.read(DriftSqlType.dateTime, data['${effectivePrefix}updated_at'])!, ); } @@ -3137,16 +3117,17 @@ class LocationEntity extends DataClass implements Insertable { /// The date at which the location was last updated final DateTime updatedAt; - const LocationEntity( - {this.channelCid, - this.messageId, - this.userId, - required this.latitude, - required this.longitude, - this.createdByDeviceId, - this.endAt, - required this.createdAt, - required this.updatedAt}); + const LocationEntity({ + this.channelCid, + this.messageId, + this.userId, + required this.latitude, + required this.longitude, + this.createdByDeviceId, + this.endAt, + required this.createdAt, + required this.updatedAt, + }); @override Map toColumns(bool nullToAbsent) { final map = {}; @@ -3172,8 +3153,7 @@ class LocationEntity extends DataClass implements Insertable { return map; } - factory LocationEntity.fromJson(Map json, - {ValueSerializer? serializer}) { + factory LocationEntity.fromJson(Map json, {ValueSerializer? serializer}) { serializer ??= driftRuntimeOptions.defaultSerializer; return LocationEntity( channelCid: serializer.fromJson(json['channelCid']), @@ -3181,8 +3161,7 @@ class LocationEntity extends DataClass implements Insertable { userId: serializer.fromJson(json['userId']), latitude: serializer.fromJson(json['latitude']), longitude: serializer.fromJson(json['longitude']), - createdByDeviceId: - serializer.fromJson(json['createdByDeviceId']), + createdByDeviceId: serializer.fromJson(json['createdByDeviceId']), endAt: serializer.fromJson(json['endAt']), createdAt: serializer.fromJson(json['createdAt']), updatedAt: serializer.fromJson(json['updatedAt']), @@ -3204,40 +3183,35 @@ class LocationEntity extends DataClass implements Insertable { }; } - LocationEntity copyWith( - {Value channelCid = const Value.absent(), - Value messageId = const Value.absent(), - Value userId = const Value.absent(), - double? latitude, - double? longitude, - Value createdByDeviceId = const Value.absent(), - Value endAt = const Value.absent(), - DateTime? createdAt, - DateTime? updatedAt}) => - LocationEntity( - channelCid: channelCid.present ? channelCid.value : this.channelCid, - messageId: messageId.present ? messageId.value : this.messageId, - userId: userId.present ? userId.value : this.userId, - latitude: latitude ?? this.latitude, - longitude: longitude ?? this.longitude, - createdByDeviceId: createdByDeviceId.present - ? createdByDeviceId.value - : this.createdByDeviceId, - endAt: endAt.present ? endAt.value : this.endAt, - createdAt: createdAt ?? this.createdAt, - updatedAt: updatedAt ?? this.updatedAt, - ); + LocationEntity copyWith({ + Value channelCid = const Value.absent(), + Value messageId = const Value.absent(), + Value userId = const Value.absent(), + double? latitude, + double? longitude, + Value createdByDeviceId = const Value.absent(), + Value endAt = const Value.absent(), + DateTime? createdAt, + DateTime? updatedAt, + }) => LocationEntity( + channelCid: channelCid.present ? channelCid.value : this.channelCid, + messageId: messageId.present ? messageId.value : this.messageId, + userId: userId.present ? userId.value : this.userId, + latitude: latitude ?? this.latitude, + longitude: longitude ?? this.longitude, + createdByDeviceId: createdByDeviceId.present ? createdByDeviceId.value : this.createdByDeviceId, + endAt: endAt.present ? endAt.value : this.endAt, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ); LocationEntity copyWithCompanion(LocationsCompanion data) { return LocationEntity( - channelCid: - data.channelCid.present ? data.channelCid.value : this.channelCid, + channelCid: data.channelCid.present ? data.channelCid.value : this.channelCid, messageId: data.messageId.present ? data.messageId.value : this.messageId, userId: data.userId.present ? data.userId.value : this.userId, latitude: data.latitude.present ? data.latitude.value : this.latitude, longitude: data.longitude.present ? data.longitude.value : this.longitude, - createdByDeviceId: data.createdByDeviceId.present - ? data.createdByDeviceId.value - : this.createdByDeviceId, + createdByDeviceId: data.createdByDeviceId.present ? data.createdByDeviceId.value : this.createdByDeviceId, endAt: data.endAt.present ? data.endAt.value : this.endAt, createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, @@ -3261,8 +3235,8 @@ class LocationEntity extends DataClass implements Insertable { } @override - int get hashCode => Object.hash(channelCid, messageId, userId, latitude, - longitude, createdByDeviceId, endAt, createdAt, updatedAt); + int get hashCode => + Object.hash(channelCid, messageId, userId, latitude, longitude, createdByDeviceId, endAt, createdAt, updatedAt); @override bool operator ==(Object other) => identical(this, other) || @@ -3312,8 +3286,8 @@ class LocationsCompanion extends UpdateCompanion { this.createdAt = const Value.absent(), this.updatedAt = const Value.absent(), this.rowid = const Value.absent(), - }) : latitude = Value(latitude), - longitude = Value(longitude); + }) : latitude = Value(latitude), + longitude = Value(longitude); static Insertable custom({ Expression? channelCid, Expression? messageId, @@ -3340,17 +3314,18 @@ class LocationsCompanion extends UpdateCompanion { }); } - LocationsCompanion copyWith( - {Value? channelCid, - Value? messageId, - Value? userId, - Value? latitude, - Value? longitude, - Value? createdByDeviceId, - Value? endAt, - Value? createdAt, - Value? updatedAt, - Value? rowid}) { + LocationsCompanion copyWith({ + Value? channelCid, + Value? messageId, + Value? userId, + Value? latitude, + Value? longitude, + Value? createdByDeviceId, + Value? endAt, + Value? createdAt, + Value? updatedAt, + Value? rowid, + }) { return LocationsCompanion( channelCid: channelCid ?? this.channelCid, messageId: messageId ?? this.messageId, @@ -3419,8 +3394,7 @@ class LocationsCompanion extends UpdateCompanion { } } -class $PinnedMessagesTable extends PinnedMessages - with TableInfo<$PinnedMessagesTable, PinnedMessageEntity> { +class $PinnedMessagesTable extends PinnedMessages with TableInfo<$PinnedMessagesTable, PinnedMessageEntity> { @override final GeneratedDatabase attachedDatabase; final String? _alias; @@ -3428,252 +3402,335 @@ class $PinnedMessagesTable extends PinnedMessages static const VerificationMeta _idMeta = const VerificationMeta('id'); @override late final GeneratedColumn id = GeneratedColumn( - 'id', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true); - static const VerificationMeta _messageTextMeta = - const VerificationMeta('messageText'); + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _messageTextMeta = const VerificationMeta('messageText'); @override late final GeneratedColumn messageText = GeneratedColumn( - 'message_text', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); - @override - late final GeneratedColumnWithTypeConverter, String> - attachments = GeneratedColumn('attachments', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true) - .withConverter>( - $PinnedMessagesTable.$converterattachments); + 'message_text', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + late final GeneratedColumnWithTypeConverter, String> attachments = GeneratedColumn( + 'attachments', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ).withConverter>($PinnedMessagesTable.$converterattachments); static const VerificationMeta _stateMeta = const VerificationMeta('state'); @override late final GeneratedColumn state = GeneratedColumn( - 'state', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true); + 'state', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); static const VerificationMeta _typeMeta = const VerificationMeta('type'); @override late final GeneratedColumn type = GeneratedColumn( - 'type', aliasedName, false, - type: DriftSqlType.string, - requiredDuringInsert: false, - defaultValue: const Constant('regular')); - @override - late final GeneratedColumnWithTypeConverter, String> - mentionedUsers = GeneratedColumn( - 'mentioned_users', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true) - .withConverter>( - $PinnedMessagesTable.$convertermentionedUsers); - @override - late final GeneratedColumnWithTypeConverter?, - String> reactionGroups = GeneratedColumn( - 'reaction_groups', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false) - .withConverter?>( - $PinnedMessagesTable.$converterreactionGroupsn); - static const VerificationMeta _parentIdMeta = - const VerificationMeta('parentId'); + 'type', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const Constant('regular'), + ); + @override + late final GeneratedColumnWithTypeConverter, String> mentionedUsers = GeneratedColumn( + 'mentioned_users', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ).withConverter>($PinnedMessagesTable.$convertermentionedUsers); + @override + late final GeneratedColumnWithTypeConverter?, String> reactionGroups = + GeneratedColumn( + 'reaction_groups', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ).withConverter?>($PinnedMessagesTable.$converterreactionGroupsn); + static const VerificationMeta _parentIdMeta = const VerificationMeta('parentId'); @override late final GeneratedColumn parentId = GeneratedColumn( - 'parent_id', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); - static const VerificationMeta _quotedMessageIdMeta = - const VerificationMeta('quotedMessageId'); + 'parent_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _quotedMessageIdMeta = const VerificationMeta('quotedMessageId'); @override late final GeneratedColumn quotedMessageId = GeneratedColumn( - 'quoted_message_id', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); + 'quoted_message_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); static const VerificationMeta _pollIdMeta = const VerificationMeta('pollId'); @override late final GeneratedColumn pollId = GeneratedColumn( - 'poll_id', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); - static const VerificationMeta _replyCountMeta = - const VerificationMeta('replyCount'); + 'poll_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _replyCountMeta = const VerificationMeta('replyCount'); @override late final GeneratedColumn replyCount = GeneratedColumn( - 'reply_count', aliasedName, true, - type: DriftSqlType.int, requiredDuringInsert: false); - static const VerificationMeta _showInChannelMeta = - const VerificationMeta('showInChannel'); + 'reply_count', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + static const VerificationMeta _showInChannelMeta = const VerificationMeta('showInChannel'); @override late final GeneratedColumn showInChannel = GeneratedColumn( - 'show_in_channel', aliasedName, true, - type: DriftSqlType.bool, - requiredDuringInsert: false, - defaultConstraints: GeneratedColumn.constraintIsAlways( - 'CHECK ("show_in_channel" IN (0, 1))')); - static const VerificationMeta _shadowedMeta = - const VerificationMeta('shadowed'); + 'show_in_channel', + aliasedName, + true, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways('CHECK ("show_in_channel" IN (0, 1))'), + ); + static const VerificationMeta _shadowedMeta = const VerificationMeta('shadowed'); @override late final GeneratedColumn shadowed = GeneratedColumn( - 'shadowed', aliasedName, false, - type: DriftSqlType.bool, - requiredDuringInsert: false, - defaultConstraints: - GeneratedColumn.constraintIsAlways('CHECK ("shadowed" IN (0, 1))'), - defaultValue: const Constant(false)); - static const VerificationMeta _commandMeta = - const VerificationMeta('command'); + 'shadowed', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways('CHECK ("shadowed" IN (0, 1))'), + defaultValue: const Constant(false), + ); + static const VerificationMeta _commandMeta = const VerificationMeta('command'); @override late final GeneratedColumn command = GeneratedColumn( - 'command', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); - static const VerificationMeta _localCreatedAtMeta = - const VerificationMeta('localCreatedAt'); - @override - late final GeneratedColumn localCreatedAt = - GeneratedColumn('local_created_at', aliasedName, true, - type: DriftSqlType.dateTime, requiredDuringInsert: false); - static const VerificationMeta _remoteCreatedAtMeta = - const VerificationMeta('remoteCreatedAt'); - @override - late final GeneratedColumn remoteCreatedAt = - GeneratedColumn('remote_created_at', aliasedName, true, - type: DriftSqlType.dateTime, requiredDuringInsert: false); - static const VerificationMeta _localUpdatedAtMeta = - const VerificationMeta('localUpdatedAt'); - @override - late final GeneratedColumn localUpdatedAt = - GeneratedColumn('local_updated_at', aliasedName, true, - type: DriftSqlType.dateTime, requiredDuringInsert: false); - static const VerificationMeta _remoteUpdatedAtMeta = - const VerificationMeta('remoteUpdatedAt'); - @override - late final GeneratedColumn remoteUpdatedAt = - GeneratedColumn('remote_updated_at', aliasedName, true, - type: DriftSqlType.dateTime, requiredDuringInsert: false); - static const VerificationMeta _localDeletedAtMeta = - const VerificationMeta('localDeletedAt'); - @override - late final GeneratedColumn localDeletedAt = - GeneratedColumn('local_deleted_at', aliasedName, true, - type: DriftSqlType.dateTime, requiredDuringInsert: false); - static const VerificationMeta _remoteDeletedAtMeta = - const VerificationMeta('remoteDeletedAt'); - @override - late final GeneratedColumn remoteDeletedAt = - GeneratedColumn('remote_deleted_at', aliasedName, true, - type: DriftSqlType.dateTime, requiredDuringInsert: false); - static const VerificationMeta _deletedForMeMeta = - const VerificationMeta('deletedForMe'); + 'command', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _localCreatedAtMeta = const VerificationMeta('localCreatedAt'); + @override + late final GeneratedColumn localCreatedAt = GeneratedColumn( + 'local_created_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + static const VerificationMeta _remoteCreatedAtMeta = const VerificationMeta('remoteCreatedAt'); + @override + late final GeneratedColumn remoteCreatedAt = GeneratedColumn( + 'remote_created_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + static const VerificationMeta _localUpdatedAtMeta = const VerificationMeta('localUpdatedAt'); + @override + late final GeneratedColumn localUpdatedAt = GeneratedColumn( + 'local_updated_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + static const VerificationMeta _remoteUpdatedAtMeta = const VerificationMeta('remoteUpdatedAt'); + @override + late final GeneratedColumn remoteUpdatedAt = GeneratedColumn( + 'remote_updated_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + static const VerificationMeta _localDeletedAtMeta = const VerificationMeta('localDeletedAt'); + @override + late final GeneratedColumn localDeletedAt = GeneratedColumn( + 'local_deleted_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + static const VerificationMeta _remoteDeletedAtMeta = const VerificationMeta('remoteDeletedAt'); + @override + late final GeneratedColumn remoteDeletedAt = GeneratedColumn( + 'remote_deleted_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + static const VerificationMeta _deletedForMeMeta = const VerificationMeta('deletedForMe'); @override late final GeneratedColumn deletedForMe = GeneratedColumn( - 'deleted_for_me', aliasedName, true, - type: DriftSqlType.bool, - requiredDuringInsert: false, - defaultConstraints: GeneratedColumn.constraintIsAlways( - 'CHECK ("deleted_for_me" IN (0, 1))')); - static const VerificationMeta _messageTextUpdatedAtMeta = - const VerificationMeta('messageTextUpdatedAt'); - @override - late final GeneratedColumn messageTextUpdatedAt = - GeneratedColumn('message_text_updated_at', aliasedName, true, - type: DriftSqlType.dateTime, requiredDuringInsert: false); + 'deleted_for_me', + aliasedName, + true, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways('CHECK ("deleted_for_me" IN (0, 1))'), + ); + static const VerificationMeta _messageTextUpdatedAtMeta = const VerificationMeta('messageTextUpdatedAt'); + @override + late final GeneratedColumn messageTextUpdatedAt = GeneratedColumn( + 'message_text_updated_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); static const VerificationMeta _userIdMeta = const VerificationMeta('userId'); @override late final GeneratedColumn userId = GeneratedColumn( - 'user_id', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); - static const VerificationMeta _channelRoleMeta = - const VerificationMeta('channelRole'); + 'user_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _channelRoleMeta = const VerificationMeta('channelRole'); @override late final GeneratedColumn channelRole = GeneratedColumn( - 'channel_role', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); + 'channel_role', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); static const VerificationMeta _pinnedMeta = const VerificationMeta('pinned'); @override late final GeneratedColumn pinned = GeneratedColumn( - 'pinned', aliasedName, false, - type: DriftSqlType.bool, - requiredDuringInsert: false, - defaultConstraints: - GeneratedColumn.constraintIsAlways('CHECK ("pinned" IN (0, 1))'), - defaultValue: const Constant(false)); - static const VerificationMeta _pinnedAtMeta = - const VerificationMeta('pinnedAt'); + 'pinned', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways('CHECK ("pinned" IN (0, 1))'), + defaultValue: const Constant(false), + ); + static const VerificationMeta _pinnedAtMeta = const VerificationMeta('pinnedAt'); @override late final GeneratedColumn pinnedAt = GeneratedColumn( - 'pinned_at', aliasedName, true, - type: DriftSqlType.dateTime, requiredDuringInsert: false); - static const VerificationMeta _pinExpiresMeta = - const VerificationMeta('pinExpires'); + 'pinned_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + static const VerificationMeta _pinExpiresMeta = const VerificationMeta('pinExpires'); @override late final GeneratedColumn pinExpires = GeneratedColumn( - 'pin_expires', aliasedName, true, - type: DriftSqlType.dateTime, requiredDuringInsert: false); - static const VerificationMeta _pinnedByUserIdMeta = - const VerificationMeta('pinnedByUserId'); + 'pin_expires', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + static const VerificationMeta _pinnedByUserIdMeta = const VerificationMeta('pinnedByUserId'); @override late final GeneratedColumn pinnedByUserId = GeneratedColumn( - 'pinned_by_user_id', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); - static const VerificationMeta _channelCidMeta = - const VerificationMeta('channelCid'); + 'pinned_by_user_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _channelCidMeta = const VerificationMeta('channelCid'); @override late final GeneratedColumn channelCid = GeneratedColumn( - 'channel_cid', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true); - @override - late final GeneratedColumnWithTypeConverter?, String> - i18n = GeneratedColumn('i18n', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false) - .withConverter?>( - $PinnedMessagesTable.$converteri18n); - @override - late final GeneratedColumnWithTypeConverter?, String> - restrictedVisibility = GeneratedColumn( - 'restricted_visibility', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false) - .withConverter?>( - $PinnedMessagesTable.$converterrestrictedVisibilityn); - @override - late final GeneratedColumnWithTypeConverter?, String> - extraData = GeneratedColumn('extra_data', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false) - .withConverter?>( - $PinnedMessagesTable.$converterextraDatan); + 'channel_cid', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + late final GeneratedColumnWithTypeConverter?, String> i18n = GeneratedColumn( + 'i18n', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ).withConverter?>($PinnedMessagesTable.$converteri18n); + @override + late final GeneratedColumnWithTypeConverter?, String> restrictedVisibility = GeneratedColumn( + 'restricted_visibility', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ).withConverter?>($PinnedMessagesTable.$converterrestrictedVisibilityn); + @override + late final GeneratedColumnWithTypeConverter?, String> extraData = GeneratedColumn( + 'extra_data', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ).withConverter?>($PinnedMessagesTable.$converterextraDatan); @override List get $columns => [ - id, - messageText, - attachments, - state, - type, - mentionedUsers, - reactionGroups, - parentId, - quotedMessageId, - pollId, - replyCount, - showInChannel, - shadowed, - command, - localCreatedAt, - remoteCreatedAt, - localUpdatedAt, - remoteUpdatedAt, - localDeletedAt, - remoteDeletedAt, - deletedForMe, - messageTextUpdatedAt, - userId, - channelRole, - pinned, - pinnedAt, - pinExpires, - pinnedByUserId, - channelCid, - i18n, - restrictedVisibility, - extraData - ]; + id, + messageText, + attachments, + state, + type, + mentionedUsers, + reactionGroups, + parentId, + quotedMessageId, + pollId, + replyCount, + showInChannel, + shadowed, + command, + localCreatedAt, + remoteCreatedAt, + localUpdatedAt, + remoteUpdatedAt, + localDeletedAt, + remoteDeletedAt, + deletedForMe, + messageTextUpdatedAt, + userId, + channelRole, + pinned, + pinnedAt, + pinExpires, + pinnedByUserId, + channelCid, + i18n, + restrictedVisibility, + extraData, + ]; @override String get aliasedName => _alias ?? actualTableName; @override String get actualTableName => $name; static const String $name = 'pinned_messages'; @override - VerificationContext validateIntegrity( - Insertable instance, - {bool isInserting = false}) { + VerificationContext validateIntegrity(Insertable instance, {bool isInserting = false}) { final context = VerificationContext(); final data = instance.toColumns(true); if (data.containsKey('id')) { @@ -3682,138 +3739,111 @@ class $PinnedMessagesTable extends PinnedMessages context.missing(_idMeta); } if (data.containsKey('message_text')) { - context.handle( - _messageTextMeta, - messageText.isAcceptableOrUnknown( - data['message_text']!, _messageTextMeta)); + context.handle(_messageTextMeta, messageText.isAcceptableOrUnknown(data['message_text']!, _messageTextMeta)); } if (data.containsKey('state')) { - context.handle( - _stateMeta, state.isAcceptableOrUnknown(data['state']!, _stateMeta)); + context.handle(_stateMeta, state.isAcceptableOrUnknown(data['state']!, _stateMeta)); } else if (isInserting) { context.missing(_stateMeta); } if (data.containsKey('type')) { - context.handle( - _typeMeta, type.isAcceptableOrUnknown(data['type']!, _typeMeta)); + context.handle(_typeMeta, type.isAcceptableOrUnknown(data['type']!, _typeMeta)); } if (data.containsKey('parent_id')) { - context.handle(_parentIdMeta, - parentId.isAcceptableOrUnknown(data['parent_id']!, _parentIdMeta)); + context.handle(_parentIdMeta, parentId.isAcceptableOrUnknown(data['parent_id']!, _parentIdMeta)); } if (data.containsKey('quoted_message_id')) { context.handle( - _quotedMessageIdMeta, - quotedMessageId.isAcceptableOrUnknown( - data['quoted_message_id']!, _quotedMessageIdMeta)); + _quotedMessageIdMeta, + quotedMessageId.isAcceptableOrUnknown(data['quoted_message_id']!, _quotedMessageIdMeta), + ); } if (data.containsKey('poll_id')) { - context.handle(_pollIdMeta, - pollId.isAcceptableOrUnknown(data['poll_id']!, _pollIdMeta)); + context.handle(_pollIdMeta, pollId.isAcceptableOrUnknown(data['poll_id']!, _pollIdMeta)); } if (data.containsKey('reply_count')) { - context.handle( - _replyCountMeta, - replyCount.isAcceptableOrUnknown( - data['reply_count']!, _replyCountMeta)); + context.handle(_replyCountMeta, replyCount.isAcceptableOrUnknown(data['reply_count']!, _replyCountMeta)); } if (data.containsKey('show_in_channel')) { context.handle( - _showInChannelMeta, - showInChannel.isAcceptableOrUnknown( - data['show_in_channel']!, _showInChannelMeta)); + _showInChannelMeta, + showInChannel.isAcceptableOrUnknown(data['show_in_channel']!, _showInChannelMeta), + ); } if (data.containsKey('shadowed')) { - context.handle(_shadowedMeta, - shadowed.isAcceptableOrUnknown(data['shadowed']!, _shadowedMeta)); + context.handle(_shadowedMeta, shadowed.isAcceptableOrUnknown(data['shadowed']!, _shadowedMeta)); } if (data.containsKey('command')) { - context.handle(_commandMeta, - command.isAcceptableOrUnknown(data['command']!, _commandMeta)); + context.handle(_commandMeta, command.isAcceptableOrUnknown(data['command']!, _commandMeta)); } if (data.containsKey('local_created_at')) { context.handle( - _localCreatedAtMeta, - localCreatedAt.isAcceptableOrUnknown( - data['local_created_at']!, _localCreatedAtMeta)); + _localCreatedAtMeta, + localCreatedAt.isAcceptableOrUnknown(data['local_created_at']!, _localCreatedAtMeta), + ); } if (data.containsKey('remote_created_at')) { context.handle( - _remoteCreatedAtMeta, - remoteCreatedAt.isAcceptableOrUnknown( - data['remote_created_at']!, _remoteCreatedAtMeta)); + _remoteCreatedAtMeta, + remoteCreatedAt.isAcceptableOrUnknown(data['remote_created_at']!, _remoteCreatedAtMeta), + ); } if (data.containsKey('local_updated_at')) { context.handle( - _localUpdatedAtMeta, - localUpdatedAt.isAcceptableOrUnknown( - data['local_updated_at']!, _localUpdatedAtMeta)); + _localUpdatedAtMeta, + localUpdatedAt.isAcceptableOrUnknown(data['local_updated_at']!, _localUpdatedAtMeta), + ); } if (data.containsKey('remote_updated_at')) { context.handle( - _remoteUpdatedAtMeta, - remoteUpdatedAt.isAcceptableOrUnknown( - data['remote_updated_at']!, _remoteUpdatedAtMeta)); + _remoteUpdatedAtMeta, + remoteUpdatedAt.isAcceptableOrUnknown(data['remote_updated_at']!, _remoteUpdatedAtMeta), + ); } if (data.containsKey('local_deleted_at')) { context.handle( - _localDeletedAtMeta, - localDeletedAt.isAcceptableOrUnknown( - data['local_deleted_at']!, _localDeletedAtMeta)); + _localDeletedAtMeta, + localDeletedAt.isAcceptableOrUnknown(data['local_deleted_at']!, _localDeletedAtMeta), + ); } if (data.containsKey('remote_deleted_at')) { context.handle( - _remoteDeletedAtMeta, - remoteDeletedAt.isAcceptableOrUnknown( - data['remote_deleted_at']!, _remoteDeletedAtMeta)); + _remoteDeletedAtMeta, + remoteDeletedAt.isAcceptableOrUnknown(data['remote_deleted_at']!, _remoteDeletedAtMeta), + ); } if (data.containsKey('deleted_for_me')) { - context.handle( - _deletedForMeMeta, - deletedForMe.isAcceptableOrUnknown( - data['deleted_for_me']!, _deletedForMeMeta)); + context.handle(_deletedForMeMeta, deletedForMe.isAcceptableOrUnknown(data['deleted_for_me']!, _deletedForMeMeta)); } if (data.containsKey('message_text_updated_at')) { context.handle( - _messageTextUpdatedAtMeta, - messageTextUpdatedAt.isAcceptableOrUnknown( - data['message_text_updated_at']!, _messageTextUpdatedAtMeta)); + _messageTextUpdatedAtMeta, + messageTextUpdatedAt.isAcceptableOrUnknown(data['message_text_updated_at']!, _messageTextUpdatedAtMeta), + ); } if (data.containsKey('user_id')) { - context.handle(_userIdMeta, - userId.isAcceptableOrUnknown(data['user_id']!, _userIdMeta)); + context.handle(_userIdMeta, userId.isAcceptableOrUnknown(data['user_id']!, _userIdMeta)); } if (data.containsKey('channel_role')) { - context.handle( - _channelRoleMeta, - channelRole.isAcceptableOrUnknown( - data['channel_role']!, _channelRoleMeta)); + context.handle(_channelRoleMeta, channelRole.isAcceptableOrUnknown(data['channel_role']!, _channelRoleMeta)); } if (data.containsKey('pinned')) { - context.handle(_pinnedMeta, - pinned.isAcceptableOrUnknown(data['pinned']!, _pinnedMeta)); + context.handle(_pinnedMeta, pinned.isAcceptableOrUnknown(data['pinned']!, _pinnedMeta)); } if (data.containsKey('pinned_at')) { - context.handle(_pinnedAtMeta, - pinnedAt.isAcceptableOrUnknown(data['pinned_at']!, _pinnedAtMeta)); + context.handle(_pinnedAtMeta, pinnedAt.isAcceptableOrUnknown(data['pinned_at']!, _pinnedAtMeta)); } if (data.containsKey('pin_expires')) { - context.handle( - _pinExpiresMeta, - pinExpires.isAcceptableOrUnknown( - data['pin_expires']!, _pinExpiresMeta)); + context.handle(_pinExpiresMeta, pinExpires.isAcceptableOrUnknown(data['pin_expires']!, _pinExpiresMeta)); } if (data.containsKey('pinned_by_user_id')) { context.handle( - _pinnedByUserIdMeta, - pinnedByUserId.isAcceptableOrUnknown( - data['pinned_by_user_id']!, _pinnedByUserIdMeta)); + _pinnedByUserIdMeta, + pinnedByUserId.isAcceptableOrUnknown(data['pinned_by_user_id']!, _pinnedByUserIdMeta), + ); } if (data.containsKey('channel_cid')) { - context.handle( - _channelCidMeta, - channelCid.isAcceptableOrUnknown( - data['channel_cid']!, _channelCidMeta)); + context.handle(_channelCidMeta, channelCid.isAcceptableOrUnknown(data['channel_cid']!, _channelCidMeta)); } else if (isInserting) { context.missing(_channelCidMeta); } @@ -3826,77 +3856,77 @@ class $PinnedMessagesTable extends PinnedMessages PinnedMessageEntity map(Map data, {String? tablePrefix}) { final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; return PinnedMessageEntity( - id: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}id'])!, - messageText: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}message_text']), + id: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}id'])!, + messageText: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}message_text']), attachments: $PinnedMessagesTable.$converterattachments.fromSql( - attachedDatabase.typeMapping.read( - DriftSqlType.string, data['${effectivePrefix}attachments'])!), - state: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}state'])!, - type: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}type'])!, + attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}attachments'])!, + ), + state: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}state'])!, + type: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}type'])!, mentionedUsers: $PinnedMessagesTable.$convertermentionedUsers.fromSql( - attachedDatabase.typeMapping.read( - DriftSqlType.string, data['${effectivePrefix}mentioned_users'])!), + attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}mentioned_users'])!, + ), reactionGroups: $PinnedMessagesTable.$converterreactionGroupsn.fromSql( - attachedDatabase.typeMapping.read( - DriftSqlType.string, data['${effectivePrefix}reaction_groups'])), - parentId: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}parent_id']), + attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}reaction_groups']), + ), + parentId: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}parent_id']), quotedMessageId: attachedDatabase.typeMapping.read( - DriftSqlType.string, data['${effectivePrefix}quoted_message_id']), - pollId: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}poll_id']), - replyCount: attachedDatabase.typeMapping - .read(DriftSqlType.int, data['${effectivePrefix}reply_count']), - showInChannel: attachedDatabase.typeMapping - .read(DriftSqlType.bool, data['${effectivePrefix}show_in_channel']), - shadowed: attachedDatabase.typeMapping - .read(DriftSqlType.bool, data['${effectivePrefix}shadowed'])!, - command: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}command']), + DriftSqlType.string, + data['${effectivePrefix}quoted_message_id'], + ), + pollId: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}poll_id']), + replyCount: attachedDatabase.typeMapping.read(DriftSqlType.int, data['${effectivePrefix}reply_count']), + showInChannel: attachedDatabase.typeMapping.read(DriftSqlType.bool, data['${effectivePrefix}show_in_channel']), + shadowed: attachedDatabase.typeMapping.read(DriftSqlType.bool, data['${effectivePrefix}shadowed'])!, + command: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}command']), localCreatedAt: attachedDatabase.typeMapping.read( - DriftSqlType.dateTime, data['${effectivePrefix}local_created_at']), + DriftSqlType.dateTime, + data['${effectivePrefix}local_created_at'], + ), remoteCreatedAt: attachedDatabase.typeMapping.read( - DriftSqlType.dateTime, data['${effectivePrefix}remote_created_at']), + DriftSqlType.dateTime, + data['${effectivePrefix}remote_created_at'], + ), localUpdatedAt: attachedDatabase.typeMapping.read( - DriftSqlType.dateTime, data['${effectivePrefix}local_updated_at']), + DriftSqlType.dateTime, + data['${effectivePrefix}local_updated_at'], + ), remoteUpdatedAt: attachedDatabase.typeMapping.read( - DriftSqlType.dateTime, data['${effectivePrefix}remote_updated_at']), + DriftSqlType.dateTime, + data['${effectivePrefix}remote_updated_at'], + ), localDeletedAt: attachedDatabase.typeMapping.read( - DriftSqlType.dateTime, data['${effectivePrefix}local_deleted_at']), + DriftSqlType.dateTime, + data['${effectivePrefix}local_deleted_at'], + ), remoteDeletedAt: attachedDatabase.typeMapping.read( - DriftSqlType.dateTime, data['${effectivePrefix}remote_deleted_at']), - deletedForMe: attachedDatabase.typeMapping - .read(DriftSqlType.bool, data['${effectivePrefix}deleted_for_me']), + DriftSqlType.dateTime, + data['${effectivePrefix}remote_deleted_at'], + ), + deletedForMe: attachedDatabase.typeMapping.read(DriftSqlType.bool, data['${effectivePrefix}deleted_for_me']), messageTextUpdatedAt: attachedDatabase.typeMapping.read( - DriftSqlType.dateTime, - data['${effectivePrefix}message_text_updated_at']), - userId: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}user_id']), - channelRole: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}channel_role']), - pinned: attachedDatabase.typeMapping - .read(DriftSqlType.bool, data['${effectivePrefix}pinned'])!, - pinnedAt: attachedDatabase.typeMapping - .read(DriftSqlType.dateTime, data['${effectivePrefix}pinned_at']), - pinExpires: attachedDatabase.typeMapping - .read(DriftSqlType.dateTime, data['${effectivePrefix}pin_expires']), + DriftSqlType.dateTime, + data['${effectivePrefix}message_text_updated_at'], + ), + userId: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}user_id']), + channelRole: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}channel_role']), + pinned: attachedDatabase.typeMapping.read(DriftSqlType.bool, data['${effectivePrefix}pinned'])!, + pinnedAt: attachedDatabase.typeMapping.read(DriftSqlType.dateTime, data['${effectivePrefix}pinned_at']), + pinExpires: attachedDatabase.typeMapping.read(DriftSqlType.dateTime, data['${effectivePrefix}pin_expires']), pinnedByUserId: attachedDatabase.typeMapping.read( - DriftSqlType.string, data['${effectivePrefix}pinned_by_user_id']), - channelCid: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}channel_cid'])!, - i18n: $PinnedMessagesTable.$converteri18n.fromSql(attachedDatabase - .typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}i18n'])), - restrictedVisibility: $PinnedMessagesTable.$converterrestrictedVisibilityn - .fromSql(attachedDatabase.typeMapping.read(DriftSqlType.string, - data['${effectivePrefix}restricted_visibility'])), + DriftSqlType.string, + data['${effectivePrefix}pinned_by_user_id'], + ), + channelCid: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}channel_cid'])!, + i18n: $PinnedMessagesTable.$converteri18n.fromSql( + attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}i18n']), + ), + restrictedVisibility: $PinnedMessagesTable.$converterrestrictedVisibilityn.fromSql( + attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}restricted_visibility']), + ), extraData: $PinnedMessagesTable.$converterextraDatan.fromSql( - attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}extra_data'])), + attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}extra_data']), + ), ); } @@ -3905,29 +3935,24 @@ class $PinnedMessagesTable extends PinnedMessages return $PinnedMessagesTable(attachedDatabase, alias); } - static TypeConverter, String> $converterattachments = - ListConverter(); - static TypeConverter, String> $convertermentionedUsers = - ListConverter(); - static TypeConverter, String> - $converterreactionGroups = ReactionGroupsConverter(); - static TypeConverter?, String?> - $converterreactionGroupsn = - NullAwareTypeConverter.wrap($converterreactionGroups); - static TypeConverter?, String?> $converteri18n = - NullableMapConverter(); - static TypeConverter, String> $converterrestrictedVisibility = - ListConverter(); - static TypeConverter?, String?> $converterrestrictedVisibilityn = - NullAwareTypeConverter.wrap($converterrestrictedVisibility); - static TypeConverter, String> $converterextraData = - MapConverter(); - static TypeConverter?, String?> $converterextraDatan = - NullAwareTypeConverter.wrap($converterextraData); + static TypeConverter, String> $converterattachments = ListConverter(); + static TypeConverter, String> $convertermentionedUsers = ListConverter(); + static TypeConverter, String> $converterreactionGroups = ReactionGroupsConverter(); + static TypeConverter?, String?> $converterreactionGroupsn = NullAwareTypeConverter.wrap( + $converterreactionGroups, + ); + static TypeConverter?, String?> $converteri18n = NullableMapConverter(); + static TypeConverter, String> $converterrestrictedVisibility = ListConverter(); + static TypeConverter?, String?> $converterrestrictedVisibilityn = NullAwareTypeConverter.wrap( + $converterrestrictedVisibility, + ); + static TypeConverter, String> $converterextraData = MapConverter(); + static TypeConverter?, String?> $converterextraDatan = NullAwareTypeConverter.wrap( + $converterextraData, + ); } -class PinnedMessageEntity extends DataClass - implements Insertable { +class PinnedMessageEntity extends DataClass implements Insertable { /// The message id final String id; @@ -4024,39 +4049,40 @@ class PinnedMessageEntity extends DataClass /// Message custom extraData final Map? extraData; - const PinnedMessageEntity( - {required this.id, - this.messageText, - required this.attachments, - required this.state, - required this.type, - required this.mentionedUsers, - this.reactionGroups, - this.parentId, - this.quotedMessageId, - this.pollId, - this.replyCount, - this.showInChannel, - required this.shadowed, - this.command, - this.localCreatedAt, - this.remoteCreatedAt, - this.localUpdatedAt, - this.remoteUpdatedAt, - this.localDeletedAt, - this.remoteDeletedAt, - this.deletedForMe, - this.messageTextUpdatedAt, - this.userId, - this.channelRole, - required this.pinned, - this.pinnedAt, - this.pinExpires, - this.pinnedByUserId, - required this.channelCid, - this.i18n, - this.restrictedVisibility, - this.extraData}); + const PinnedMessageEntity({ + required this.id, + this.messageText, + required this.attachments, + required this.state, + required this.type, + required this.mentionedUsers, + this.reactionGroups, + this.parentId, + this.quotedMessageId, + this.pollId, + this.replyCount, + this.showInChannel, + required this.shadowed, + this.command, + this.localCreatedAt, + this.remoteCreatedAt, + this.localUpdatedAt, + this.remoteUpdatedAt, + this.localDeletedAt, + this.remoteDeletedAt, + this.deletedForMe, + this.messageTextUpdatedAt, + this.userId, + this.channelRole, + required this.pinned, + this.pinnedAt, + this.pinExpires, + this.pinnedByUserId, + required this.channelCid, + this.i18n, + this.restrictedVisibility, + this.extraData, + }); @override Map toColumns(bool nullToAbsent) { final map = {}; @@ -4065,18 +4091,15 @@ class PinnedMessageEntity extends DataClass map['message_text'] = Variable(messageText); } { - map['attachments'] = Variable( - $PinnedMessagesTable.$converterattachments.toSql(attachments)); + map['attachments'] = Variable($PinnedMessagesTable.$converterattachments.toSql(attachments)); } map['state'] = Variable(state); map['type'] = Variable(type); { - map['mentioned_users'] = Variable( - $PinnedMessagesTable.$convertermentionedUsers.toSql(mentionedUsers)); + map['mentioned_users'] = Variable($PinnedMessagesTable.$convertermentionedUsers.toSql(mentionedUsers)); } if (!nullToAbsent || reactionGroups != null) { - map['reaction_groups'] = Variable( - $PinnedMessagesTable.$converterreactionGroupsn.toSql(reactionGroups)); + map['reaction_groups'] = Variable($PinnedMessagesTable.$converterreactionGroupsn.toSql(reactionGroups)); } if (!nullToAbsent || parentId != null) { map['parent_id'] = Variable(parentId); @@ -4139,23 +4162,20 @@ class PinnedMessageEntity extends DataClass } map['channel_cid'] = Variable(channelCid); if (!nullToAbsent || i18n != null) { - map['i18n'] = - Variable($PinnedMessagesTable.$converteri18n.toSql(i18n)); + map['i18n'] = Variable($PinnedMessagesTable.$converteri18n.toSql(i18n)); } if (!nullToAbsent || restrictedVisibility != null) { - map['restricted_visibility'] = Variable($PinnedMessagesTable - .$converterrestrictedVisibilityn - .toSql(restrictedVisibility)); + map['restricted_visibility'] = Variable( + $PinnedMessagesTable.$converterrestrictedVisibilityn.toSql(restrictedVisibility), + ); } if (!nullToAbsent || extraData != null) { - map['extra_data'] = Variable( - $PinnedMessagesTable.$converterextraDatan.toSql(extraData)); + map['extra_data'] = Variable($PinnedMessagesTable.$converterextraDatan.toSql(extraData)); } return map; } - factory PinnedMessageEntity.fromJson(Map json, - {ValueSerializer? serializer}) { + factory PinnedMessageEntity.fromJson(Map json, {ValueSerializer? serializer}) { serializer ??= driftRuntimeOptions.defaultSerializer; return PinnedMessageEntity( id: serializer.fromJson(json['id']), @@ -4164,8 +4184,7 @@ class PinnedMessageEntity extends DataClass state: serializer.fromJson(json['state']), type: serializer.fromJson(json['type']), mentionedUsers: serializer.fromJson>(json['mentionedUsers']), - reactionGroups: serializer - .fromJson?>(json['reactionGroups']), + reactionGroups: serializer.fromJson?>(json['reactionGroups']), parentId: serializer.fromJson(json['parentId']), quotedMessageId: serializer.fromJson(json['quotedMessageId']), pollId: serializer.fromJson(json['pollId']), @@ -4180,8 +4199,7 @@ class PinnedMessageEntity extends DataClass localDeletedAt: serializer.fromJson(json['localDeletedAt']), remoteDeletedAt: serializer.fromJson(json['remoteDeletedAt']), deletedForMe: serializer.fromJson(json['deletedForMe']), - messageTextUpdatedAt: - serializer.fromJson(json['messageTextUpdatedAt']), + messageTextUpdatedAt: serializer.fromJson(json['messageTextUpdatedAt']), userId: serializer.fromJson(json['userId']), channelRole: serializer.fromJson(json['channelRole']), pinned: serializer.fromJson(json['pinned']), @@ -4190,8 +4208,7 @@ class PinnedMessageEntity extends DataClass pinnedByUserId: serializer.fromJson(json['pinnedByUserId']), channelCid: serializer.fromJson(json['channelCid']), i18n: serializer.fromJson?>(json['i18n']), - restrictedVisibility: - serializer.fromJson?>(json['restrictedVisibility']), + restrictedVisibility: serializer.fromJson?>(json['restrictedVisibility']), extraData: serializer.fromJson?>(json['extraData']), ); } @@ -4205,8 +4222,7 @@ class PinnedMessageEntity extends DataClass 'state': serializer.toJson(state), 'type': serializer.toJson(type), 'mentionedUsers': serializer.toJson>(mentionedUsers), - 'reactionGroups': - serializer.toJson?>(reactionGroups), + 'reactionGroups': serializer.toJson?>(reactionGroups), 'parentId': serializer.toJson(parentId), 'quotedMessageId': serializer.toJson(quotedMessageId), 'pollId': serializer.toJson(pollId), @@ -4221,8 +4237,7 @@ class PinnedMessageEntity extends DataClass 'localDeletedAt': serializer.toJson(localDeletedAt), 'remoteDeletedAt': serializer.toJson(remoteDeletedAt), 'deletedForMe': serializer.toJson(deletedForMe), - 'messageTextUpdatedAt': - serializer.toJson(messageTextUpdatedAt), + 'messageTextUpdatedAt': serializer.toJson(messageTextUpdatedAt), 'userId': serializer.toJson(userId), 'channelRole': serializer.toJson(channelRole), 'pinned': serializer.toJson(pinned), @@ -4231,162 +4246,111 @@ class PinnedMessageEntity extends DataClass 'pinnedByUserId': serializer.toJson(pinnedByUserId), 'channelCid': serializer.toJson(channelCid), 'i18n': serializer.toJson?>(i18n), - 'restrictedVisibility': - serializer.toJson?>(restrictedVisibility), + 'restrictedVisibility': serializer.toJson?>(restrictedVisibility), 'extraData': serializer.toJson?>(extraData), }; } - PinnedMessageEntity copyWith( - {String? id, - Value messageText = const Value.absent(), - List? attachments, - String? state, - String? type, - List? mentionedUsers, - Value?> reactionGroups = - const Value.absent(), - Value parentId = const Value.absent(), - Value quotedMessageId = const Value.absent(), - Value pollId = const Value.absent(), - Value replyCount = const Value.absent(), - Value showInChannel = const Value.absent(), - bool? shadowed, - Value command = const Value.absent(), - Value localCreatedAt = const Value.absent(), - Value remoteCreatedAt = const Value.absent(), - Value localUpdatedAt = const Value.absent(), - Value remoteUpdatedAt = const Value.absent(), - Value localDeletedAt = const Value.absent(), - Value remoteDeletedAt = const Value.absent(), - Value deletedForMe = const Value.absent(), - Value messageTextUpdatedAt = const Value.absent(), - Value userId = const Value.absent(), - Value channelRole = const Value.absent(), - bool? pinned, - Value pinnedAt = const Value.absent(), - Value pinExpires = const Value.absent(), - Value pinnedByUserId = const Value.absent(), - String? channelCid, - Value?> i18n = const Value.absent(), - Value?> restrictedVisibility = const Value.absent(), - Value?> extraData = const Value.absent()}) => - PinnedMessageEntity( - id: id ?? this.id, - messageText: messageText.present ? messageText.value : this.messageText, - attachments: attachments ?? this.attachments, - state: state ?? this.state, - type: type ?? this.type, - mentionedUsers: mentionedUsers ?? this.mentionedUsers, - reactionGroups: - reactionGroups.present ? reactionGroups.value : this.reactionGroups, - parentId: parentId.present ? parentId.value : this.parentId, - quotedMessageId: quotedMessageId.present - ? quotedMessageId.value - : this.quotedMessageId, - pollId: pollId.present ? pollId.value : this.pollId, - replyCount: replyCount.present ? replyCount.value : this.replyCount, - showInChannel: - showInChannel.present ? showInChannel.value : this.showInChannel, - shadowed: shadowed ?? this.shadowed, - command: command.present ? command.value : this.command, - localCreatedAt: - localCreatedAt.present ? localCreatedAt.value : this.localCreatedAt, - remoteCreatedAt: remoteCreatedAt.present - ? remoteCreatedAt.value - : this.remoteCreatedAt, - localUpdatedAt: - localUpdatedAt.present ? localUpdatedAt.value : this.localUpdatedAt, - remoteUpdatedAt: remoteUpdatedAt.present - ? remoteUpdatedAt.value - : this.remoteUpdatedAt, - localDeletedAt: - localDeletedAt.present ? localDeletedAt.value : this.localDeletedAt, - remoteDeletedAt: remoteDeletedAt.present - ? remoteDeletedAt.value - : this.remoteDeletedAt, - deletedForMe: - deletedForMe.present ? deletedForMe.value : this.deletedForMe, - messageTextUpdatedAt: messageTextUpdatedAt.present - ? messageTextUpdatedAt.value - : this.messageTextUpdatedAt, - userId: userId.present ? userId.value : this.userId, - channelRole: channelRole.present ? channelRole.value : this.channelRole, - pinned: pinned ?? this.pinned, - pinnedAt: pinnedAt.present ? pinnedAt.value : this.pinnedAt, - pinExpires: pinExpires.present ? pinExpires.value : this.pinExpires, - pinnedByUserId: - pinnedByUserId.present ? pinnedByUserId.value : this.pinnedByUserId, - channelCid: channelCid ?? this.channelCid, - i18n: i18n.present ? i18n.value : this.i18n, - restrictedVisibility: restrictedVisibility.present - ? restrictedVisibility.value - : this.restrictedVisibility, - extraData: extraData.present ? extraData.value : this.extraData, - ); + PinnedMessageEntity copyWith({ + String? id, + Value messageText = const Value.absent(), + List? attachments, + String? state, + String? type, + List? mentionedUsers, + Value?> reactionGroups = const Value.absent(), + Value parentId = const Value.absent(), + Value quotedMessageId = const Value.absent(), + Value pollId = const Value.absent(), + Value replyCount = const Value.absent(), + Value showInChannel = const Value.absent(), + bool? shadowed, + Value command = const Value.absent(), + Value localCreatedAt = const Value.absent(), + Value remoteCreatedAt = const Value.absent(), + Value localUpdatedAt = const Value.absent(), + Value remoteUpdatedAt = const Value.absent(), + Value localDeletedAt = const Value.absent(), + Value remoteDeletedAt = const Value.absent(), + Value deletedForMe = const Value.absent(), + Value messageTextUpdatedAt = const Value.absent(), + Value userId = const Value.absent(), + Value channelRole = const Value.absent(), + bool? pinned, + Value pinnedAt = const Value.absent(), + Value pinExpires = const Value.absent(), + Value pinnedByUserId = const Value.absent(), + String? channelCid, + Value?> i18n = const Value.absent(), + Value?> restrictedVisibility = const Value.absent(), + Value?> extraData = const Value.absent(), + }) => PinnedMessageEntity( + id: id ?? this.id, + messageText: messageText.present ? messageText.value : this.messageText, + attachments: attachments ?? this.attachments, + state: state ?? this.state, + type: type ?? this.type, + mentionedUsers: mentionedUsers ?? this.mentionedUsers, + reactionGroups: reactionGroups.present ? reactionGroups.value : this.reactionGroups, + parentId: parentId.present ? parentId.value : this.parentId, + quotedMessageId: quotedMessageId.present ? quotedMessageId.value : this.quotedMessageId, + pollId: pollId.present ? pollId.value : this.pollId, + replyCount: replyCount.present ? replyCount.value : this.replyCount, + showInChannel: showInChannel.present ? showInChannel.value : this.showInChannel, + shadowed: shadowed ?? this.shadowed, + command: command.present ? command.value : this.command, + localCreatedAt: localCreatedAt.present ? localCreatedAt.value : this.localCreatedAt, + remoteCreatedAt: remoteCreatedAt.present ? remoteCreatedAt.value : this.remoteCreatedAt, + localUpdatedAt: localUpdatedAt.present ? localUpdatedAt.value : this.localUpdatedAt, + remoteUpdatedAt: remoteUpdatedAt.present ? remoteUpdatedAt.value : this.remoteUpdatedAt, + localDeletedAt: localDeletedAt.present ? localDeletedAt.value : this.localDeletedAt, + remoteDeletedAt: remoteDeletedAt.present ? remoteDeletedAt.value : this.remoteDeletedAt, + deletedForMe: deletedForMe.present ? deletedForMe.value : this.deletedForMe, + messageTextUpdatedAt: messageTextUpdatedAt.present ? messageTextUpdatedAt.value : this.messageTextUpdatedAt, + userId: userId.present ? userId.value : this.userId, + channelRole: channelRole.present ? channelRole.value : this.channelRole, + pinned: pinned ?? this.pinned, + pinnedAt: pinnedAt.present ? pinnedAt.value : this.pinnedAt, + pinExpires: pinExpires.present ? pinExpires.value : this.pinExpires, + pinnedByUserId: pinnedByUserId.present ? pinnedByUserId.value : this.pinnedByUserId, + channelCid: channelCid ?? this.channelCid, + i18n: i18n.present ? i18n.value : this.i18n, + restrictedVisibility: restrictedVisibility.present ? restrictedVisibility.value : this.restrictedVisibility, + extraData: extraData.present ? extraData.value : this.extraData, + ); PinnedMessageEntity copyWithCompanion(PinnedMessagesCompanion data) { return PinnedMessageEntity( id: data.id.present ? data.id.value : this.id, - messageText: - data.messageText.present ? data.messageText.value : this.messageText, - attachments: - data.attachments.present ? data.attachments.value : this.attachments, + messageText: data.messageText.present ? data.messageText.value : this.messageText, + attachments: data.attachments.present ? data.attachments.value : this.attachments, state: data.state.present ? data.state.value : this.state, type: data.type.present ? data.type.value : this.type, - mentionedUsers: data.mentionedUsers.present - ? data.mentionedUsers.value - : this.mentionedUsers, - reactionGroups: data.reactionGroups.present - ? data.reactionGroups.value - : this.reactionGroups, + mentionedUsers: data.mentionedUsers.present ? data.mentionedUsers.value : this.mentionedUsers, + reactionGroups: data.reactionGroups.present ? data.reactionGroups.value : this.reactionGroups, parentId: data.parentId.present ? data.parentId.value : this.parentId, - quotedMessageId: data.quotedMessageId.present - ? data.quotedMessageId.value - : this.quotedMessageId, + quotedMessageId: data.quotedMessageId.present ? data.quotedMessageId.value : this.quotedMessageId, pollId: data.pollId.present ? data.pollId.value : this.pollId, - replyCount: - data.replyCount.present ? data.replyCount.value : this.replyCount, - showInChannel: data.showInChannel.present - ? data.showInChannel.value - : this.showInChannel, + replyCount: data.replyCount.present ? data.replyCount.value : this.replyCount, + showInChannel: data.showInChannel.present ? data.showInChannel.value : this.showInChannel, shadowed: data.shadowed.present ? data.shadowed.value : this.shadowed, command: data.command.present ? data.command.value : this.command, - localCreatedAt: data.localCreatedAt.present - ? data.localCreatedAt.value - : this.localCreatedAt, - remoteCreatedAt: data.remoteCreatedAt.present - ? data.remoteCreatedAt.value - : this.remoteCreatedAt, - localUpdatedAt: data.localUpdatedAt.present - ? data.localUpdatedAt.value - : this.localUpdatedAt, - remoteUpdatedAt: data.remoteUpdatedAt.present - ? data.remoteUpdatedAt.value - : this.remoteUpdatedAt, - localDeletedAt: data.localDeletedAt.present - ? data.localDeletedAt.value - : this.localDeletedAt, - remoteDeletedAt: data.remoteDeletedAt.present - ? data.remoteDeletedAt.value - : this.remoteDeletedAt, - deletedForMe: data.deletedForMe.present - ? data.deletedForMe.value - : this.deletedForMe, + localCreatedAt: data.localCreatedAt.present ? data.localCreatedAt.value : this.localCreatedAt, + remoteCreatedAt: data.remoteCreatedAt.present ? data.remoteCreatedAt.value : this.remoteCreatedAt, + localUpdatedAt: data.localUpdatedAt.present ? data.localUpdatedAt.value : this.localUpdatedAt, + remoteUpdatedAt: data.remoteUpdatedAt.present ? data.remoteUpdatedAt.value : this.remoteUpdatedAt, + localDeletedAt: data.localDeletedAt.present ? data.localDeletedAt.value : this.localDeletedAt, + remoteDeletedAt: data.remoteDeletedAt.present ? data.remoteDeletedAt.value : this.remoteDeletedAt, + deletedForMe: data.deletedForMe.present ? data.deletedForMe.value : this.deletedForMe, messageTextUpdatedAt: data.messageTextUpdatedAt.present ? data.messageTextUpdatedAt.value : this.messageTextUpdatedAt, userId: data.userId.present ? data.userId.value : this.userId, - channelRole: - data.channelRole.present ? data.channelRole.value : this.channelRole, + channelRole: data.channelRole.present ? data.channelRole.value : this.channelRole, pinned: data.pinned.present ? data.pinned.value : this.pinned, pinnedAt: data.pinnedAt.present ? data.pinnedAt.value : this.pinnedAt, - pinExpires: - data.pinExpires.present ? data.pinExpires.value : this.pinExpires, - pinnedByUserId: data.pinnedByUserId.present - ? data.pinnedByUserId.value - : this.pinnedByUserId, - channelCid: - data.channelCid.present ? data.channelCid.value : this.channelCid, + pinExpires: data.pinExpires.present ? data.pinExpires.value : this.pinExpires, + pinnedByUserId: data.pinnedByUserId.present ? data.pinnedByUserId.value : this.pinnedByUserId, + channelCid: data.channelCid.present ? data.channelCid.value : this.channelCid, i18n: data.i18n.present ? data.i18n.value : this.i18n, restrictedVisibility: data.restrictedVisibility.present ? data.restrictedVisibility.value @@ -4436,39 +4400,39 @@ class PinnedMessageEntity extends DataClass @override int get hashCode => Object.hashAll([ - id, - messageText, - attachments, - state, - type, - mentionedUsers, - reactionGroups, - parentId, - quotedMessageId, - pollId, - replyCount, - showInChannel, - shadowed, - command, - localCreatedAt, - remoteCreatedAt, - localUpdatedAt, - remoteUpdatedAt, - localDeletedAt, - remoteDeletedAt, - deletedForMe, - messageTextUpdatedAt, - userId, - channelRole, - pinned, - pinnedAt, - pinExpires, - pinnedByUserId, - channelCid, - i18n, - restrictedVisibility, - extraData - ]); + id, + messageText, + attachments, + state, + type, + mentionedUsers, + reactionGroups, + parentId, + quotedMessageId, + pollId, + replyCount, + showInChannel, + shadowed, + command, + localCreatedAt, + remoteCreatedAt, + localUpdatedAt, + remoteUpdatedAt, + localDeletedAt, + remoteDeletedAt, + deletedForMe, + messageTextUpdatedAt, + userId, + channelRole, + pinned, + pinnedAt, + pinExpires, + pinnedByUserId, + channelCid, + i18n, + restrictedVisibility, + extraData, + ]); @override bool operator ==(Object other) => identical(this, other) || @@ -4610,11 +4574,11 @@ class PinnedMessagesCompanion extends UpdateCompanion { this.restrictedVisibility = const Value.absent(), this.extraData = const Value.absent(), this.rowid = const Value.absent(), - }) : id = Value(id), - attachments = Value(attachments), - state = Value(state), - mentionedUsers = Value(mentionedUsers), - channelCid = Value(channelCid); + }) : id = Value(id), + attachments = Value(attachments), + state = Value(state), + mentionedUsers = Value(mentionedUsers), + channelCid = Value(channelCid); static Insertable custom({ Expression? id, Expression? messageText, @@ -4672,8 +4636,7 @@ class PinnedMessagesCompanion extends UpdateCompanion { if (localDeletedAt != null) 'local_deleted_at': localDeletedAt, if (remoteDeletedAt != null) 'remote_deleted_at': remoteDeletedAt, if (deletedForMe != null) 'deleted_for_me': deletedForMe, - if (messageTextUpdatedAt != null) - 'message_text_updated_at': messageTextUpdatedAt, + if (messageTextUpdatedAt != null) 'message_text_updated_at': messageTextUpdatedAt, if (userId != null) 'user_id': userId, if (channelRole != null) 'channel_role': channelRole, if (pinned != null) 'pinned': pinned, @@ -4682,47 +4645,47 @@ class PinnedMessagesCompanion extends UpdateCompanion { if (pinnedByUserId != null) 'pinned_by_user_id': pinnedByUserId, if (channelCid != null) 'channel_cid': channelCid, if (i18n != null) 'i18n': i18n, - if (restrictedVisibility != null) - 'restricted_visibility': restrictedVisibility, + if (restrictedVisibility != null) 'restricted_visibility': restrictedVisibility, if (extraData != null) 'extra_data': extraData, if (rowid != null) 'rowid': rowid, }); } - PinnedMessagesCompanion copyWith( - {Value? id, - Value? messageText, - Value>? attachments, - Value? state, - Value? type, - Value>? mentionedUsers, - Value?>? reactionGroups, - Value? parentId, - Value? quotedMessageId, - Value? pollId, - Value? replyCount, - Value? showInChannel, - Value? shadowed, - Value? command, - Value? localCreatedAt, - Value? remoteCreatedAt, - Value? localUpdatedAt, - Value? remoteUpdatedAt, - Value? localDeletedAt, - Value? remoteDeletedAt, - Value? deletedForMe, - Value? messageTextUpdatedAt, - Value? userId, - Value? channelRole, - Value? pinned, - Value? pinnedAt, - Value? pinExpires, - Value? pinnedByUserId, - Value? channelCid, - Value?>? i18n, - Value?>? restrictedVisibility, - Value?>? extraData, - Value? rowid}) { + PinnedMessagesCompanion copyWith({ + Value? id, + Value? messageText, + Value>? attachments, + Value? state, + Value? type, + Value>? mentionedUsers, + Value?>? reactionGroups, + Value? parentId, + Value? quotedMessageId, + Value? pollId, + Value? replyCount, + Value? showInChannel, + Value? shadowed, + Value? command, + Value? localCreatedAt, + Value? remoteCreatedAt, + Value? localUpdatedAt, + Value? remoteUpdatedAt, + Value? localDeletedAt, + Value? remoteDeletedAt, + Value? deletedForMe, + Value? messageTextUpdatedAt, + Value? userId, + Value? channelRole, + Value? pinned, + Value? pinnedAt, + Value? pinExpires, + Value? pinnedByUserId, + Value? channelCid, + Value?>? i18n, + Value?>? restrictedVisibility, + Value?>? extraData, + Value? rowid, + }) { return PinnedMessagesCompanion( id: id ?? this.id, messageText: messageText ?? this.messageText, @@ -4770,8 +4733,7 @@ class PinnedMessagesCompanion extends UpdateCompanion { map['message_text'] = Variable(messageText.value); } if (attachments.present) { - map['attachments'] = Variable( - $PinnedMessagesTable.$converterattachments.toSql(attachments.value)); + map['attachments'] = Variable($PinnedMessagesTable.$converterattachments.toSql(attachments.value)); } if (state.present) { map['state'] = Variable(state.value); @@ -4780,14 +4742,14 @@ class PinnedMessagesCompanion extends UpdateCompanion { map['type'] = Variable(type.value); } if (mentionedUsers.present) { - map['mentioned_users'] = Variable($PinnedMessagesTable - .$convertermentionedUsers - .toSql(mentionedUsers.value)); + map['mentioned_users'] = Variable( + $PinnedMessagesTable.$convertermentionedUsers.toSql(mentionedUsers.value), + ); } if (reactionGroups.present) { - map['reaction_groups'] = Variable($PinnedMessagesTable - .$converterreactionGroupsn - .toSql(reactionGroups.value)); + map['reaction_groups'] = Variable( + $PinnedMessagesTable.$converterreactionGroupsn.toSql(reactionGroups.value), + ); } if (parentId.present) { map['parent_id'] = Variable(parentId.value); @@ -4832,8 +4794,7 @@ class PinnedMessagesCompanion extends UpdateCompanion { map['deleted_for_me'] = Variable(deletedForMe.value); } if (messageTextUpdatedAt.present) { - map['message_text_updated_at'] = - Variable(messageTextUpdatedAt.value); + map['message_text_updated_at'] = Variable(messageTextUpdatedAt.value); } if (userId.present) { map['user_id'] = Variable(userId.value); @@ -4857,17 +4818,15 @@ class PinnedMessagesCompanion extends UpdateCompanion { map['channel_cid'] = Variable(channelCid.value); } if (i18n.present) { - map['i18n'] = Variable( - $PinnedMessagesTable.$converteri18n.toSql(i18n.value)); + map['i18n'] = Variable($PinnedMessagesTable.$converteri18n.toSql(i18n.value)); } if (restrictedVisibility.present) { - map['restricted_visibility'] = Variable($PinnedMessagesTable - .$converterrestrictedVisibilityn - .toSql(restrictedVisibility.value)); + map['restricted_visibility'] = Variable( + $PinnedMessagesTable.$converterrestrictedVisibilityn.toSql(restrictedVisibility.value), + ); } if (extraData.present) { - map['extra_data'] = Variable( - $PinnedMessagesTable.$converterextraDatan.toSql(extraData.value)); + map['extra_data'] = Variable($PinnedMessagesTable.$converterextraDatan.toSql(extraData.value)); } if (rowid.present) { map['rowid'] = Variable(rowid.value); @@ -4924,158 +4883,192 @@ class $PollsTable extends Polls with TableInfo<$PollsTable, PollEntity> { static const VerificationMeta _idMeta = const VerificationMeta('id'); @override late final GeneratedColumn id = GeneratedColumn( - 'id', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true); + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); static const VerificationMeta _nameMeta = const VerificationMeta('name'); @override late final GeneratedColumn name = GeneratedColumn( - 'name', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true); - static const VerificationMeta _descriptionMeta = - const VerificationMeta('description'); + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _descriptionMeta = const VerificationMeta('description'); @override late final GeneratedColumn description = GeneratedColumn( - 'description', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); - @override - late final GeneratedColumnWithTypeConverter, String> options = - GeneratedColumn('options', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true) - .withConverter>($PollsTable.$converteroptions); - @override - late final GeneratedColumnWithTypeConverter - votingVisibility = GeneratedColumn( - 'voting_visibility', aliasedName, false, - type: DriftSqlType.string, - requiredDuringInsert: false, - defaultValue: const Constant('public')) - .withConverter( - $PollsTable.$convertervotingVisibility); - static const VerificationMeta _enforceUniqueVoteMeta = - const VerificationMeta('enforceUniqueVote'); + 'description', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + late final GeneratedColumnWithTypeConverter, String> options = GeneratedColumn( + 'options', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ).withConverter>($PollsTable.$converteroptions); + @override + late final GeneratedColumnWithTypeConverter votingVisibility = GeneratedColumn( + 'voting_visibility', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const Constant('public'), + ).withConverter($PollsTable.$convertervotingVisibility); + static const VerificationMeta _enforceUniqueVoteMeta = const VerificationMeta('enforceUniqueVote'); @override late final GeneratedColumn enforceUniqueVote = GeneratedColumn( - 'enforce_unique_vote', aliasedName, false, - type: DriftSqlType.bool, - requiredDuringInsert: false, - defaultConstraints: GeneratedColumn.constraintIsAlways( - 'CHECK ("enforce_unique_vote" IN (0, 1))'), - defaultValue: const Constant(false)); - static const VerificationMeta _maxVotesAllowedMeta = - const VerificationMeta('maxVotesAllowed'); + 'enforce_unique_vote', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways('CHECK ("enforce_unique_vote" IN (0, 1))'), + defaultValue: const Constant(false), + ); + static const VerificationMeta _maxVotesAllowedMeta = const VerificationMeta('maxVotesAllowed'); @override late final GeneratedColumn maxVotesAllowed = GeneratedColumn( - 'max_votes_allowed', aliasedName, true, - type: DriftSqlType.int, requiredDuringInsert: false); - static const VerificationMeta _allowUserSuggestedOptionsMeta = - const VerificationMeta('allowUserSuggestedOptions'); - @override - late final GeneratedColumn allowUserSuggestedOptions = - GeneratedColumn('allow_user_suggested_options', aliasedName, false, - type: DriftSqlType.bool, - requiredDuringInsert: false, - defaultConstraints: GeneratedColumn.constraintIsAlways( - 'CHECK ("allow_user_suggested_options" IN (0, 1))'), - defaultValue: const Constant(false)); - static const VerificationMeta _allowAnswersMeta = - const VerificationMeta('allowAnswers'); + 'max_votes_allowed', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + static const VerificationMeta _allowUserSuggestedOptionsMeta = const VerificationMeta('allowUserSuggestedOptions'); + @override + late final GeneratedColumn allowUserSuggestedOptions = GeneratedColumn( + 'allow_user_suggested_options', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways('CHECK ("allow_user_suggested_options" IN (0, 1))'), + defaultValue: const Constant(false), + ); + static const VerificationMeta _allowAnswersMeta = const VerificationMeta('allowAnswers'); @override late final GeneratedColumn allowAnswers = GeneratedColumn( - 'allow_answers', aliasedName, false, - type: DriftSqlType.bool, - requiredDuringInsert: false, - defaultConstraints: GeneratedColumn.constraintIsAlways( - 'CHECK ("allow_answers" IN (0, 1))'), - defaultValue: const Constant(false)); - static const VerificationMeta _isClosedMeta = - const VerificationMeta('isClosed'); + 'allow_answers', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways('CHECK ("allow_answers" IN (0, 1))'), + defaultValue: const Constant(false), + ); + static const VerificationMeta _isClosedMeta = const VerificationMeta('isClosed'); @override late final GeneratedColumn isClosed = GeneratedColumn( - 'is_closed', aliasedName, false, - type: DriftSqlType.bool, - requiredDuringInsert: false, - defaultConstraints: - GeneratedColumn.constraintIsAlways('CHECK ("is_closed" IN (0, 1))'), - defaultValue: const Constant(false)); - static const VerificationMeta _answersCountMeta = - const VerificationMeta('answersCount'); + 'is_closed', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways('CHECK ("is_closed" IN (0, 1))'), + defaultValue: const Constant(false), + ); + static const VerificationMeta _answersCountMeta = const VerificationMeta('answersCount'); @override late final GeneratedColumn answersCount = GeneratedColumn( - 'answers_count', aliasedName, false, - type: DriftSqlType.int, - requiredDuringInsert: false, - defaultValue: const Constant(0)); - @override - late final GeneratedColumnWithTypeConverter, String> - voteCountsByOption = GeneratedColumn( - 'vote_counts_by_option', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true) - .withConverter>( - $PollsTable.$convertervoteCountsByOption); - static const VerificationMeta _voteCountMeta = - const VerificationMeta('voteCount'); + 'answers_count', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const Constant(0), + ); + @override + late final GeneratedColumnWithTypeConverter, String> voteCountsByOption = GeneratedColumn( + 'vote_counts_by_option', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ).withConverter>($PollsTable.$convertervoteCountsByOption); + static const VerificationMeta _voteCountMeta = const VerificationMeta('voteCount'); @override late final GeneratedColumn voteCount = GeneratedColumn( - 'vote_count', aliasedName, false, - type: DriftSqlType.int, - requiredDuringInsert: false, - defaultValue: const Constant(0)); - static const VerificationMeta _createdByIdMeta = - const VerificationMeta('createdById'); + 'vote_count', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const Constant(0), + ); + static const VerificationMeta _createdByIdMeta = const VerificationMeta('createdById'); @override late final GeneratedColumn createdById = GeneratedColumn( - 'created_by_id', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); - static const VerificationMeta _createdAtMeta = - const VerificationMeta('createdAt'); + 'created_by_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _createdAtMeta = const VerificationMeta('createdAt'); @override late final GeneratedColumn createdAt = GeneratedColumn( - 'created_at', aliasedName, false, - type: DriftSqlType.dateTime, - requiredDuringInsert: false, - defaultValue: currentDateAndTime); - static const VerificationMeta _updatedAtMeta = - const VerificationMeta('updatedAt'); + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime, + ); + static const VerificationMeta _updatedAtMeta = const VerificationMeta('updatedAt'); @override late final GeneratedColumn updatedAt = GeneratedColumn( - 'updated_at', aliasedName, false, - type: DriftSqlType.dateTime, - requiredDuringInsert: false, - defaultValue: currentDateAndTime); - @override - late final GeneratedColumnWithTypeConverter?, String> - extraData = GeneratedColumn('extra_data', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false) - .withConverter?>( - $PollsTable.$converterextraDatan); + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime, + ); + @override + late final GeneratedColumnWithTypeConverter?, String> extraData = GeneratedColumn( + 'extra_data', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ).withConverter?>($PollsTable.$converterextraDatan); @override List get $columns => [ - id, - name, - description, - options, - votingVisibility, - enforceUniqueVote, - maxVotesAllowed, - allowUserSuggestedOptions, - allowAnswers, - isClosed, - answersCount, - voteCountsByOption, - voteCount, - createdById, - createdAt, - updatedAt, - extraData - ]; + id, + name, + description, + options, + votingVisibility, + enforceUniqueVote, + maxVotesAllowed, + allowUserSuggestedOptions, + allowAnswers, + isClosed, + answersCount, + voteCountsByOption, + voteCount, + createdById, + createdAt, + updatedAt, + extraData, + ]; @override String get aliasedName => _alias ?? actualTableName; @override String get actualTableName => $name; static const String $name = 'polls'; @override - VerificationContext validateIntegrity(Insertable instance, - {bool isInserting = false}) { + VerificationContext validateIntegrity(Insertable instance, {bool isInserting = false}) { final context = VerificationContext(); final data = instance.toColumns(true); if (data.containsKey('id')) { @@ -5084,69 +5077,54 @@ class $PollsTable extends Polls with TableInfo<$PollsTable, PollEntity> { context.missing(_idMeta); } if (data.containsKey('name')) { - context.handle( - _nameMeta, name.isAcceptableOrUnknown(data['name']!, _nameMeta)); + context.handle(_nameMeta, name.isAcceptableOrUnknown(data['name']!, _nameMeta)); } else if (isInserting) { context.missing(_nameMeta); } if (data.containsKey('description')) { - context.handle( - _descriptionMeta, - description.isAcceptableOrUnknown( - data['description']!, _descriptionMeta)); + context.handle(_descriptionMeta, description.isAcceptableOrUnknown(data['description']!, _descriptionMeta)); } if (data.containsKey('enforce_unique_vote')) { context.handle( - _enforceUniqueVoteMeta, - enforceUniqueVote.isAcceptableOrUnknown( - data['enforce_unique_vote']!, _enforceUniqueVoteMeta)); + _enforceUniqueVoteMeta, + enforceUniqueVote.isAcceptableOrUnknown(data['enforce_unique_vote']!, _enforceUniqueVoteMeta), + ); } if (data.containsKey('max_votes_allowed')) { context.handle( - _maxVotesAllowedMeta, - maxVotesAllowed.isAcceptableOrUnknown( - data['max_votes_allowed']!, _maxVotesAllowedMeta)); + _maxVotesAllowedMeta, + maxVotesAllowed.isAcceptableOrUnknown(data['max_votes_allowed']!, _maxVotesAllowedMeta), + ); } if (data.containsKey('allow_user_suggested_options')) { context.handle( + _allowUserSuggestedOptionsMeta, + allowUserSuggestedOptions.isAcceptableOrUnknown( + data['allow_user_suggested_options']!, _allowUserSuggestedOptionsMeta, - allowUserSuggestedOptions.isAcceptableOrUnknown( - data['allow_user_suggested_options']!, - _allowUserSuggestedOptionsMeta)); + ), + ); } if (data.containsKey('allow_answers')) { - context.handle( - _allowAnswersMeta, - allowAnswers.isAcceptableOrUnknown( - data['allow_answers']!, _allowAnswersMeta)); + context.handle(_allowAnswersMeta, allowAnswers.isAcceptableOrUnknown(data['allow_answers']!, _allowAnswersMeta)); } if (data.containsKey('is_closed')) { - context.handle(_isClosedMeta, - isClosed.isAcceptableOrUnknown(data['is_closed']!, _isClosedMeta)); + context.handle(_isClosedMeta, isClosed.isAcceptableOrUnknown(data['is_closed']!, _isClosedMeta)); } if (data.containsKey('answers_count')) { - context.handle( - _answersCountMeta, - answersCount.isAcceptableOrUnknown( - data['answers_count']!, _answersCountMeta)); + context.handle(_answersCountMeta, answersCount.isAcceptableOrUnknown(data['answers_count']!, _answersCountMeta)); } if (data.containsKey('vote_count')) { - context.handle(_voteCountMeta, - voteCount.isAcceptableOrUnknown(data['vote_count']!, _voteCountMeta)); + context.handle(_voteCountMeta, voteCount.isAcceptableOrUnknown(data['vote_count']!, _voteCountMeta)); } if (data.containsKey('created_by_id')) { - context.handle( - _createdByIdMeta, - createdById.isAcceptableOrUnknown( - data['created_by_id']!, _createdByIdMeta)); + context.handle(_createdByIdMeta, createdById.isAcceptableOrUnknown(data['created_by_id']!, _createdByIdMeta)); } if (data.containsKey('created_at')) { - context.handle(_createdAtMeta, - createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); + context.handle(_createdAtMeta, createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); } if (data.containsKey('updated_at')) { - context.handle(_updatedAtMeta, - updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta)); + context.handle(_updatedAtMeta, updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta)); } return context; } @@ -5157,45 +5135,37 @@ class $PollsTable extends Polls with TableInfo<$PollsTable, PollEntity> { PollEntity map(Map data, {String? tablePrefix}) { final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; return PollEntity( - id: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}id'])!, - name: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}name'])!, - description: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}description']), - options: $PollsTable.$converteroptions.fromSql(attachedDatabase - .typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}options'])!), + id: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}id'])!, + name: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}name'])!, + description: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}description']), + options: $PollsTable.$converteroptions.fromSql( + attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}options'])!, + ), votingVisibility: $PollsTable.$convertervotingVisibility.fromSql( - attachedDatabase.typeMapping.read(DriftSqlType.string, - data['${effectivePrefix}voting_visibility'])!), + attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}voting_visibility'])!, + ), enforceUniqueVote: attachedDatabase.typeMapping.read( - DriftSqlType.bool, data['${effectivePrefix}enforce_unique_vote'])!, - maxVotesAllowed: attachedDatabase.typeMapping - .read(DriftSqlType.int, data['${effectivePrefix}max_votes_allowed']), + DriftSqlType.bool, + data['${effectivePrefix}enforce_unique_vote'], + )!, + maxVotesAllowed: attachedDatabase.typeMapping.read(DriftSqlType.int, data['${effectivePrefix}max_votes_allowed']), allowUserSuggestedOptions: attachedDatabase.typeMapping.read( - DriftSqlType.bool, - data['${effectivePrefix}allow_user_suggested_options'])!, - allowAnswers: attachedDatabase.typeMapping - .read(DriftSqlType.bool, data['${effectivePrefix}allow_answers'])!, - isClosed: attachedDatabase.typeMapping - .read(DriftSqlType.bool, data['${effectivePrefix}is_closed'])!, - answersCount: attachedDatabase.typeMapping - .read(DriftSqlType.int, data['${effectivePrefix}answers_count'])!, + DriftSqlType.bool, + data['${effectivePrefix}allow_user_suggested_options'], + )!, + allowAnswers: attachedDatabase.typeMapping.read(DriftSqlType.bool, data['${effectivePrefix}allow_answers'])!, + isClosed: attachedDatabase.typeMapping.read(DriftSqlType.bool, data['${effectivePrefix}is_closed'])!, + answersCount: attachedDatabase.typeMapping.read(DriftSqlType.int, data['${effectivePrefix}answers_count'])!, voteCountsByOption: $PollsTable.$convertervoteCountsByOption.fromSql( - attachedDatabase.typeMapping.read(DriftSqlType.string, - data['${effectivePrefix}vote_counts_by_option'])!), - voteCount: attachedDatabase.typeMapping - .read(DriftSqlType.int, data['${effectivePrefix}vote_count'])!, - createdById: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}created_by_id']), - createdAt: attachedDatabase.typeMapping - .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, - updatedAt: attachedDatabase.typeMapping - .read(DriftSqlType.dateTime, data['${effectivePrefix}updated_at'])!, - extraData: $PollsTable.$converterextraDatan.fromSql(attachedDatabase - .typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}extra_data'])), + attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}vote_counts_by_option'])!, + ), + voteCount: attachedDatabase.typeMapping.read(DriftSqlType.int, data['${effectivePrefix}vote_count'])!, + createdById: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}created_by_id']), + createdAt: attachedDatabase.typeMapping.read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + updatedAt: attachedDatabase.typeMapping.read(DriftSqlType.dateTime, data['${effectivePrefix}updated_at'])!, + extraData: $PollsTable.$converterextraDatan.fromSql( + attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}extra_data']), + ), ); } @@ -5204,16 +5174,13 @@ class $PollsTable extends Polls with TableInfo<$PollsTable, PollEntity> { return $PollsTable(attachedDatabase, alias); } - static TypeConverter, String> $converteroptions = - ListConverter(); - static TypeConverter $convertervotingVisibility = - const VotingVisibilityConverter(); - static TypeConverter, String> $convertervoteCountsByOption = - MapConverter(); - static TypeConverter, String> $converterextraData = - MapConverter(); - static TypeConverter?, String?> $converterextraDatan = - NullAwareTypeConverter.wrap($converterextraData); + static TypeConverter, String> $converteroptions = ListConverter(); + static TypeConverter $convertervotingVisibility = const VotingVisibilityConverter(); + static TypeConverter, String> $convertervoteCountsByOption = MapConverter(); + static TypeConverter, String> $converterextraData = MapConverter(); + static TypeConverter?, String?> $converterextraDatan = NullAwareTypeConverter.wrap( + $converterextraData, + ); } class PollEntity extends DataClass implements Insertable { @@ -5275,24 +5242,25 @@ class PollEntity extends DataClass implements Insertable { /// Map of custom poll extraData final Map? extraData; - const PollEntity( - {required this.id, - required this.name, - this.description, - required this.options, - required this.votingVisibility, - required this.enforceUniqueVote, - this.maxVotesAllowed, - required this.allowUserSuggestedOptions, - required this.allowAnswers, - required this.isClosed, - required this.answersCount, - required this.voteCountsByOption, - required this.voteCount, - this.createdById, - required this.createdAt, - required this.updatedAt, - this.extraData}); + const PollEntity({ + required this.id, + required this.name, + this.description, + required this.options, + required this.votingVisibility, + required this.enforceUniqueVote, + this.maxVotesAllowed, + required this.allowUserSuggestedOptions, + required this.allowAnswers, + required this.isClosed, + required this.answersCount, + required this.voteCountsByOption, + required this.voteCount, + this.createdById, + required this.createdAt, + required this.updatedAt, + this.extraData, + }); @override Map toColumns(bool nullToAbsent) { final map = {}; @@ -5302,25 +5270,23 @@ class PollEntity extends DataClass implements Insertable { map['description'] = Variable(description); } { - map['options'] = - Variable($PollsTable.$converteroptions.toSql(options)); + map['options'] = Variable($PollsTable.$converteroptions.toSql(options)); } { - map['voting_visibility'] = Variable( - $PollsTable.$convertervotingVisibility.toSql(votingVisibility)); + map['voting_visibility'] = Variable($PollsTable.$convertervotingVisibility.toSql(votingVisibility)); } map['enforce_unique_vote'] = Variable(enforceUniqueVote); if (!nullToAbsent || maxVotesAllowed != null) { map['max_votes_allowed'] = Variable(maxVotesAllowed); } - map['allow_user_suggested_options'] = - Variable(allowUserSuggestedOptions); + map['allow_user_suggested_options'] = Variable(allowUserSuggestedOptions); map['allow_answers'] = Variable(allowAnswers); map['is_closed'] = Variable(isClosed); map['answers_count'] = Variable(answersCount); { map['vote_counts_by_option'] = Variable( - $PollsTable.$convertervoteCountsByOption.toSql(voteCountsByOption)); + $PollsTable.$convertervoteCountsByOption.toSql(voteCountsByOption), + ); } map['vote_count'] = Variable(voteCount); if (!nullToAbsent || createdById != null) { @@ -5329,31 +5295,26 @@ class PollEntity extends DataClass implements Insertable { map['created_at'] = Variable(createdAt); map['updated_at'] = Variable(updatedAt); if (!nullToAbsent || extraData != null) { - map['extra_data'] = - Variable($PollsTable.$converterextraDatan.toSql(extraData)); + map['extra_data'] = Variable($PollsTable.$converterextraDatan.toSql(extraData)); } return map; } - factory PollEntity.fromJson(Map json, - {ValueSerializer? serializer}) { + factory PollEntity.fromJson(Map json, {ValueSerializer? serializer}) { serializer ??= driftRuntimeOptions.defaultSerializer; return PollEntity( id: serializer.fromJson(json['id']), name: serializer.fromJson(json['name']), description: serializer.fromJson(json['description']), options: serializer.fromJson>(json['options']), - votingVisibility: - serializer.fromJson(json['votingVisibility']), + votingVisibility: serializer.fromJson(json['votingVisibility']), enforceUniqueVote: serializer.fromJson(json['enforceUniqueVote']), maxVotesAllowed: serializer.fromJson(json['maxVotesAllowed']), - allowUserSuggestedOptions: - serializer.fromJson(json['allowUserSuggestedOptions']), + allowUserSuggestedOptions: serializer.fromJson(json['allowUserSuggestedOptions']), allowAnswers: serializer.fromJson(json['allowAnswers']), isClosed: serializer.fromJson(json['isClosed']), answersCount: serializer.fromJson(json['answersCount']), - voteCountsByOption: - serializer.fromJson>(json['voteCountsByOption']), + voteCountsByOption: serializer.fromJson>(json['voteCountsByOption']), voteCount: serializer.fromJson(json['voteCount']), createdById: serializer.fromJson(json['createdById']), createdAt: serializer.fromJson(json['createdAt']), @@ -5372,13 +5333,11 @@ class PollEntity extends DataClass implements Insertable { 'votingVisibility': serializer.toJson(votingVisibility), 'enforceUniqueVote': serializer.toJson(enforceUniqueVote), 'maxVotesAllowed': serializer.toJson(maxVotesAllowed), - 'allowUserSuggestedOptions': - serializer.toJson(allowUserSuggestedOptions), + 'allowUserSuggestedOptions': serializer.toJson(allowUserSuggestedOptions), 'allowAnswers': serializer.toJson(allowAnswers), 'isClosed': serializer.toJson(isClosed), 'answersCount': serializer.toJson(answersCount), - 'voteCountsByOption': - serializer.toJson>(voteCountsByOption), + 'voteCountsByOption': serializer.toJson>(voteCountsByOption), 'voteCount': serializer.toJson(voteCount), 'createdById': serializer.toJson(createdById), 'createdAt': serializer.toJson(createdAt), @@ -5387,78 +5346,61 @@ class PollEntity extends DataClass implements Insertable { }; } - PollEntity copyWith( - {String? id, - String? name, - Value description = const Value.absent(), - List? options, - VotingVisibility? votingVisibility, - bool? enforceUniqueVote, - Value maxVotesAllowed = const Value.absent(), - bool? allowUserSuggestedOptions, - bool? allowAnswers, - bool? isClosed, - int? answersCount, - Map? voteCountsByOption, - int? voteCount, - Value createdById = const Value.absent(), - DateTime? createdAt, - DateTime? updatedAt, - Value?> extraData = const Value.absent()}) => - PollEntity( - id: id ?? this.id, - name: name ?? this.name, - description: description.present ? description.value : this.description, - options: options ?? this.options, - votingVisibility: votingVisibility ?? this.votingVisibility, - enforceUniqueVote: enforceUniqueVote ?? this.enforceUniqueVote, - maxVotesAllowed: maxVotesAllowed.present - ? maxVotesAllowed.value - : this.maxVotesAllowed, - allowUserSuggestedOptions: - allowUserSuggestedOptions ?? this.allowUserSuggestedOptions, - allowAnswers: allowAnswers ?? this.allowAnswers, - isClosed: isClosed ?? this.isClosed, - answersCount: answersCount ?? this.answersCount, - voteCountsByOption: voteCountsByOption ?? this.voteCountsByOption, - voteCount: voteCount ?? this.voteCount, - createdById: createdById.present ? createdById.value : this.createdById, - createdAt: createdAt ?? this.createdAt, - updatedAt: updatedAt ?? this.updatedAt, - extraData: extraData.present ? extraData.value : this.extraData, - ); + PollEntity copyWith({ + String? id, + String? name, + Value description = const Value.absent(), + List? options, + VotingVisibility? votingVisibility, + bool? enforceUniqueVote, + Value maxVotesAllowed = const Value.absent(), + bool? allowUserSuggestedOptions, + bool? allowAnswers, + bool? isClosed, + int? answersCount, + Map? voteCountsByOption, + int? voteCount, + Value createdById = const Value.absent(), + DateTime? createdAt, + DateTime? updatedAt, + Value?> extraData = const Value.absent(), + }) => PollEntity( + id: id ?? this.id, + name: name ?? this.name, + description: description.present ? description.value : this.description, + options: options ?? this.options, + votingVisibility: votingVisibility ?? this.votingVisibility, + enforceUniqueVote: enforceUniqueVote ?? this.enforceUniqueVote, + maxVotesAllowed: maxVotesAllowed.present ? maxVotesAllowed.value : this.maxVotesAllowed, + allowUserSuggestedOptions: allowUserSuggestedOptions ?? this.allowUserSuggestedOptions, + allowAnswers: allowAnswers ?? this.allowAnswers, + isClosed: isClosed ?? this.isClosed, + answersCount: answersCount ?? this.answersCount, + voteCountsByOption: voteCountsByOption ?? this.voteCountsByOption, + voteCount: voteCount ?? this.voteCount, + createdById: createdById.present ? createdById.value : this.createdById, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + extraData: extraData.present ? extraData.value : this.extraData, + ); PollEntity copyWithCompanion(PollsCompanion data) { return PollEntity( id: data.id.present ? data.id.value : this.id, name: data.name.present ? data.name.value : this.name, - description: - data.description.present ? data.description.value : this.description, + description: data.description.present ? data.description.value : this.description, options: data.options.present ? data.options.value : this.options, - votingVisibility: data.votingVisibility.present - ? data.votingVisibility.value - : this.votingVisibility, - enforceUniqueVote: data.enforceUniqueVote.present - ? data.enforceUniqueVote.value - : this.enforceUniqueVote, - maxVotesAllowed: data.maxVotesAllowed.present - ? data.maxVotesAllowed.value - : this.maxVotesAllowed, + votingVisibility: data.votingVisibility.present ? data.votingVisibility.value : this.votingVisibility, + enforceUniqueVote: data.enforceUniqueVote.present ? data.enforceUniqueVote.value : this.enforceUniqueVote, + maxVotesAllowed: data.maxVotesAllowed.present ? data.maxVotesAllowed.value : this.maxVotesAllowed, allowUserSuggestedOptions: data.allowUserSuggestedOptions.present ? data.allowUserSuggestedOptions.value : this.allowUserSuggestedOptions, - allowAnswers: data.allowAnswers.present - ? data.allowAnswers.value - : this.allowAnswers, + allowAnswers: data.allowAnswers.present ? data.allowAnswers.value : this.allowAnswers, isClosed: data.isClosed.present ? data.isClosed.value : this.isClosed, - answersCount: data.answersCount.present - ? data.answersCount.value - : this.answersCount, - voteCountsByOption: data.voteCountsByOption.present - ? data.voteCountsByOption.value - : this.voteCountsByOption, + answersCount: data.answersCount.present ? data.answersCount.value : this.answersCount, + voteCountsByOption: data.voteCountsByOption.present ? data.voteCountsByOption.value : this.voteCountsByOption, voteCount: data.voteCount.present ? data.voteCount.value : this.voteCount, - createdById: - data.createdById.present ? data.createdById.value : this.createdById, + createdById: data.createdById.present ? data.createdById.value : this.createdById, createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, extraData: data.extraData.present ? data.extraData.value : this.extraData, @@ -5491,23 +5433,24 @@ class PollEntity extends DataClass implements Insertable { @override int get hashCode => Object.hash( - id, - name, - description, - options, - votingVisibility, - enforceUniqueVote, - maxVotesAllowed, - allowUserSuggestedOptions, - allowAnswers, - isClosed, - answersCount, - voteCountsByOption, - voteCount, - createdById, - createdAt, - updatedAt, - extraData); + id, + name, + description, + options, + votingVisibility, + enforceUniqueVote, + maxVotesAllowed, + allowUserSuggestedOptions, + allowAnswers, + isClosed, + answersCount, + voteCountsByOption, + voteCount, + createdById, + createdAt, + updatedAt, + extraData, + ); @override bool operator ==(Object other) => identical(this, other) || @@ -5589,10 +5532,10 @@ class PollsCompanion extends UpdateCompanion { this.updatedAt = const Value.absent(), this.extraData = const Value.absent(), this.rowid = const Value.absent(), - }) : id = Value(id), - name = Value(name), - options = Value(options), - voteCountsByOption = Value(voteCountsByOption); + }) : id = Value(id), + name = Value(name), + options = Value(options), + voteCountsByOption = Value(voteCountsByOption); static Insertable custom({ Expression? id, Expression? name, @@ -5621,13 +5564,11 @@ class PollsCompanion extends UpdateCompanion { if (votingVisibility != null) 'voting_visibility': votingVisibility, if (enforceUniqueVote != null) 'enforce_unique_vote': enforceUniqueVote, if (maxVotesAllowed != null) 'max_votes_allowed': maxVotesAllowed, - if (allowUserSuggestedOptions != null) - 'allow_user_suggested_options': allowUserSuggestedOptions, + if (allowUserSuggestedOptions != null) 'allow_user_suggested_options': allowUserSuggestedOptions, if (allowAnswers != null) 'allow_answers': allowAnswers, if (isClosed != null) 'is_closed': isClosed, if (answersCount != null) 'answers_count': answersCount, - if (voteCountsByOption != null) - 'vote_counts_by_option': voteCountsByOption, + if (voteCountsByOption != null) 'vote_counts_by_option': voteCountsByOption, if (voteCount != null) 'vote_count': voteCount, if (createdById != null) 'created_by_id': createdById, if (createdAt != null) 'created_at': createdAt, @@ -5637,25 +5578,26 @@ class PollsCompanion extends UpdateCompanion { }); } - PollsCompanion copyWith( - {Value? id, - Value? name, - Value? description, - Value>? options, - Value? votingVisibility, - Value? enforceUniqueVote, - Value? maxVotesAllowed, - Value? allowUserSuggestedOptions, - Value? allowAnswers, - Value? isClosed, - Value? answersCount, - Value>? voteCountsByOption, - Value? voteCount, - Value? createdById, - Value? createdAt, - Value? updatedAt, - Value?>? extraData, - Value? rowid}) { + PollsCompanion copyWith({ + Value? id, + Value? name, + Value? description, + Value>? options, + Value? votingVisibility, + Value? enforceUniqueVote, + Value? maxVotesAllowed, + Value? allowUserSuggestedOptions, + Value? allowAnswers, + Value? isClosed, + Value? answersCount, + Value>? voteCountsByOption, + Value? voteCount, + Value? createdById, + Value? createdAt, + Value? updatedAt, + Value?>? extraData, + Value? rowid, + }) { return PollsCompanion( id: id ?? this.id, name: name ?? this.name, @@ -5664,8 +5606,7 @@ class PollsCompanion extends UpdateCompanion { votingVisibility: votingVisibility ?? this.votingVisibility, enforceUniqueVote: enforceUniqueVote ?? this.enforceUniqueVote, maxVotesAllowed: maxVotesAllowed ?? this.maxVotesAllowed, - allowUserSuggestedOptions: - allowUserSuggestedOptions ?? this.allowUserSuggestedOptions, + allowUserSuggestedOptions: allowUserSuggestedOptions ?? this.allowUserSuggestedOptions, allowAnswers: allowAnswers ?? this.allowAnswers, isClosed: isClosed ?? this.isClosed, answersCount: answersCount ?? this.answersCount, @@ -5692,12 +5633,10 @@ class PollsCompanion extends UpdateCompanion { map['description'] = Variable(description.value); } if (options.present) { - map['options'] = - Variable($PollsTable.$converteroptions.toSql(options.value)); + map['options'] = Variable($PollsTable.$converteroptions.toSql(options.value)); } if (votingVisibility.present) { - map['voting_visibility'] = Variable( - $PollsTable.$convertervotingVisibility.toSql(votingVisibility.value)); + map['voting_visibility'] = Variable($PollsTable.$convertervotingVisibility.toSql(votingVisibility.value)); } if (enforceUniqueVote.present) { map['enforce_unique_vote'] = Variable(enforceUniqueVote.value); @@ -5706,8 +5645,7 @@ class PollsCompanion extends UpdateCompanion { map['max_votes_allowed'] = Variable(maxVotesAllowed.value); } if (allowUserSuggestedOptions.present) { - map['allow_user_suggested_options'] = - Variable(allowUserSuggestedOptions.value); + map['allow_user_suggested_options'] = Variable(allowUserSuggestedOptions.value); } if (allowAnswers.present) { map['allow_answers'] = Variable(allowAnswers.value); @@ -5719,9 +5657,9 @@ class PollsCompanion extends UpdateCompanion { map['answers_count'] = Variable(answersCount.value); } if (voteCountsByOption.present) { - map['vote_counts_by_option'] = Variable($PollsTable - .$convertervoteCountsByOption - .toSql(voteCountsByOption.value)); + map['vote_counts_by_option'] = Variable( + $PollsTable.$convertervoteCountsByOption.toSql(voteCountsByOption.value), + ); } if (voteCount.present) { map['vote_count'] = Variable(voteCount.value); @@ -5736,8 +5674,7 @@ class PollsCompanion extends UpdateCompanion { map['updated_at'] = Variable(updatedAt.value); } if (extraData.present) { - map['extra_data'] = Variable( - $PollsTable.$converterextraDatan.toSql(extraData.value)); + map['extra_data'] = Variable($PollsTable.$converterextraDatan.toSql(extraData.value)); } if (rowid.present) { map['rowid'] = Variable(rowid.value); @@ -5771,8 +5708,7 @@ class PollsCompanion extends UpdateCompanion { } } -class $PollVotesTable extends PollVotes - with TableInfo<$PollVotesTable, PollVoteEntity> { +class $PollVotesTable extends PollVotes with TableInfo<$PollVotesTable, PollVoteEntity> { @override final GeneratedDatabase attachedDatabase; final String? _alias; @@ -5780,90 +5716,100 @@ class $PollVotesTable extends PollVotes static const VerificationMeta _idMeta = const VerificationMeta('id'); @override late final GeneratedColumn id = GeneratedColumn( - 'id', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); + 'id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); static const VerificationMeta _pollIdMeta = const VerificationMeta('pollId'); @override late final GeneratedColumn pollId = GeneratedColumn( - 'poll_id', aliasedName, true, - type: DriftSqlType.string, - requiredDuringInsert: false, - defaultConstraints: GeneratedColumn.constraintIsAlways( - 'REFERENCES polls (id) ON DELETE CASCADE')); - static const VerificationMeta _optionIdMeta = - const VerificationMeta('optionId'); + 'poll_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways('REFERENCES polls (id) ON DELETE CASCADE'), + ); + static const VerificationMeta _optionIdMeta = const VerificationMeta('optionId'); @override late final GeneratedColumn optionId = GeneratedColumn( - 'option_id', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); - static const VerificationMeta _answerTextMeta = - const VerificationMeta('answerText'); + 'option_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _answerTextMeta = const VerificationMeta('answerText'); @override late final GeneratedColumn answerText = GeneratedColumn( - 'answer_text', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); - static const VerificationMeta _createdAtMeta = - const VerificationMeta('createdAt'); + 'answer_text', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _createdAtMeta = const VerificationMeta('createdAt'); @override late final GeneratedColumn createdAt = GeneratedColumn( - 'created_at', aliasedName, false, - type: DriftSqlType.dateTime, - requiredDuringInsert: false, - defaultValue: currentDateAndTime); - static const VerificationMeta _updatedAtMeta = - const VerificationMeta('updatedAt'); + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime, + ); + static const VerificationMeta _updatedAtMeta = const VerificationMeta('updatedAt'); @override late final GeneratedColumn updatedAt = GeneratedColumn( - 'updated_at', aliasedName, false, - type: DriftSqlType.dateTime, - requiredDuringInsert: false, - defaultValue: currentDateAndTime); + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime, + ); static const VerificationMeta _userIdMeta = const VerificationMeta('userId'); @override late final GeneratedColumn userId = GeneratedColumn( - 'user_id', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); + 'user_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); @override - List get $columns => - [id, pollId, optionId, answerText, createdAt, updatedAt, userId]; + List get $columns => [id, pollId, optionId, answerText, createdAt, updatedAt, userId]; @override String get aliasedName => _alias ?? actualTableName; @override String get actualTableName => $name; static const String $name = 'poll_votes'; @override - VerificationContext validateIntegrity(Insertable instance, - {bool isInserting = false}) { + VerificationContext validateIntegrity(Insertable instance, {bool isInserting = false}) { final context = VerificationContext(); final data = instance.toColumns(true); if (data.containsKey('id')) { context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); } if (data.containsKey('poll_id')) { - context.handle(_pollIdMeta, - pollId.isAcceptableOrUnknown(data['poll_id']!, _pollIdMeta)); + context.handle(_pollIdMeta, pollId.isAcceptableOrUnknown(data['poll_id']!, _pollIdMeta)); } if (data.containsKey('option_id')) { - context.handle(_optionIdMeta, - optionId.isAcceptableOrUnknown(data['option_id']!, _optionIdMeta)); + context.handle(_optionIdMeta, optionId.isAcceptableOrUnknown(data['option_id']!, _optionIdMeta)); } if (data.containsKey('answer_text')) { - context.handle( - _answerTextMeta, - answerText.isAcceptableOrUnknown( - data['answer_text']!, _answerTextMeta)); + context.handle(_answerTextMeta, answerText.isAcceptableOrUnknown(data['answer_text']!, _answerTextMeta)); } if (data.containsKey('created_at')) { - context.handle(_createdAtMeta, - createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); + context.handle(_createdAtMeta, createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); } if (data.containsKey('updated_at')) { - context.handle(_updatedAtMeta, - updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta)); + context.handle(_updatedAtMeta, updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta)); } if (data.containsKey('user_id')) { - context.handle(_userIdMeta, - userId.isAcceptableOrUnknown(data['user_id']!, _userIdMeta)); + context.handle(_userIdMeta, userId.isAcceptableOrUnknown(data['user_id']!, _userIdMeta)); } return context; } @@ -5874,20 +5820,13 @@ class $PollVotesTable extends PollVotes PollVoteEntity map(Map data, {String? tablePrefix}) { final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; return PollVoteEntity( - id: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}id']), - pollId: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}poll_id']), - optionId: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}option_id']), - answerText: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}answer_text']), - createdAt: attachedDatabase.typeMapping - .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, - updatedAt: attachedDatabase.typeMapping - .read(DriftSqlType.dateTime, data['${effectivePrefix}updated_at'])!, - userId: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}user_id']), + id: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}id']), + pollId: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}poll_id']), + optionId: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}option_id']), + answerText: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}answer_text']), + createdAt: attachedDatabase.typeMapping.read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + updatedAt: attachedDatabase.typeMapping.read(DriftSqlType.dateTime, data['${effectivePrefix}updated_at'])!, + userId: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}user_id']), ); } @@ -5924,14 +5863,15 @@ class PollVoteEntity extends DataClass implements Insertable { /// /// Nullable if the poll is anonymous. final String? userId; - const PollVoteEntity( - {this.id, - this.pollId, - this.optionId, - this.answerText, - required this.createdAt, - required this.updatedAt, - this.userId}); + const PollVoteEntity({ + this.id, + this.pollId, + this.optionId, + this.answerText, + required this.createdAt, + required this.updatedAt, + this.userId, + }); @override Map toColumns(bool nullToAbsent) { final map = {}; @@ -5955,8 +5895,7 @@ class PollVoteEntity extends DataClass implements Insertable { return map; } - factory PollVoteEntity.fromJson(Map json, - {ValueSerializer? serializer}) { + factory PollVoteEntity.fromJson(Map json, {ValueSerializer? serializer}) { serializer ??= driftRuntimeOptions.defaultSerializer; return PollVoteEntity( id: serializer.fromJson(json['id']), @@ -5982,30 +5921,29 @@ class PollVoteEntity extends DataClass implements Insertable { }; } - PollVoteEntity copyWith( - {Value id = const Value.absent(), - Value pollId = const Value.absent(), - Value optionId = const Value.absent(), - Value answerText = const Value.absent(), - DateTime? createdAt, - DateTime? updatedAt, - Value userId = const Value.absent()}) => - PollVoteEntity( - id: id.present ? id.value : this.id, - pollId: pollId.present ? pollId.value : this.pollId, - optionId: optionId.present ? optionId.value : this.optionId, - answerText: answerText.present ? answerText.value : this.answerText, - createdAt: createdAt ?? this.createdAt, - updatedAt: updatedAt ?? this.updatedAt, - userId: userId.present ? userId.value : this.userId, - ); + PollVoteEntity copyWith({ + Value id = const Value.absent(), + Value pollId = const Value.absent(), + Value optionId = const Value.absent(), + Value answerText = const Value.absent(), + DateTime? createdAt, + DateTime? updatedAt, + Value userId = const Value.absent(), + }) => PollVoteEntity( + id: id.present ? id.value : this.id, + pollId: pollId.present ? pollId.value : this.pollId, + optionId: optionId.present ? optionId.value : this.optionId, + answerText: answerText.present ? answerText.value : this.answerText, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + userId: userId.present ? userId.value : this.userId, + ); PollVoteEntity copyWithCompanion(PollVotesCompanion data) { return PollVoteEntity( id: data.id.present ? data.id.value : this.id, pollId: data.pollId.present ? data.pollId.value : this.pollId, optionId: data.optionId.present ? data.optionId.value : this.optionId, - answerText: - data.answerText.present ? data.answerText.value : this.answerText, + answerText: data.answerText.present ? data.answerText.value : this.answerText, createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, userId: data.userId.present ? data.userId.value : this.userId, @@ -6027,8 +5965,7 @@ class PollVoteEntity extends DataClass implements Insertable { } @override - int get hashCode => Object.hash( - id, pollId, optionId, answerText, createdAt, updatedAt, userId); + int get hashCode => Object.hash(id, pollId, optionId, answerText, createdAt, updatedAt, userId); @override bool operator ==(Object other) => identical(this, other) || @@ -6093,15 +6030,16 @@ class PollVotesCompanion extends UpdateCompanion { }); } - PollVotesCompanion copyWith( - {Value? id, - Value? pollId, - Value? optionId, - Value? answerText, - Value? createdAt, - Value? updatedAt, - Value? userId, - Value? rowid}) { + PollVotesCompanion copyWith({ + Value? id, + Value? pollId, + Value? optionId, + Value? answerText, + Value? createdAt, + Value? updatedAt, + Value? userId, + Value? rowid, + }) { return PollVotesCompanion( id: id ?? this.id, pollId: pollId ?? this.pollId, @@ -6169,108 +6107,111 @@ class $PinnedMessageReactionsTable extends PinnedMessageReactions static const VerificationMeta _userIdMeta = const VerificationMeta('userId'); @override late final GeneratedColumn userId = GeneratedColumn( - 'user_id', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); - static const VerificationMeta _messageIdMeta = - const VerificationMeta('messageId'); + 'user_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _messageIdMeta = const VerificationMeta('messageId'); @override late final GeneratedColumn messageId = GeneratedColumn( - 'message_id', aliasedName, true, - type: DriftSqlType.string, - requiredDuringInsert: false, - defaultConstraints: GeneratedColumn.constraintIsAlways( - 'REFERENCES pinned_messages (id) ON DELETE CASCADE')); + 'message_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways('REFERENCES pinned_messages (id) ON DELETE CASCADE'), + ); static const VerificationMeta _typeMeta = const VerificationMeta('type'); @override late final GeneratedColumn type = GeneratedColumn( - 'type', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true); - static const VerificationMeta _emojiCodeMeta = - const VerificationMeta('emojiCode'); + 'type', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _emojiCodeMeta = const VerificationMeta('emojiCode'); @override late final GeneratedColumn emojiCode = GeneratedColumn( - 'emoji_code', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); - static const VerificationMeta _createdAtMeta = - const VerificationMeta('createdAt'); + 'emoji_code', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _createdAtMeta = const VerificationMeta('createdAt'); @override late final GeneratedColumn createdAt = GeneratedColumn( - 'created_at', aliasedName, false, - type: DriftSqlType.dateTime, - requiredDuringInsert: false, - defaultValue: currentDateAndTime); - static const VerificationMeta _updatedAtMeta = - const VerificationMeta('updatedAt'); + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime, + ); + static const VerificationMeta _updatedAtMeta = const VerificationMeta('updatedAt'); @override late final GeneratedColumn updatedAt = GeneratedColumn( - 'updated_at', aliasedName, false, - type: DriftSqlType.dateTime, - requiredDuringInsert: false, - defaultValue: currentDateAndTime); + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime, + ); static const VerificationMeta _scoreMeta = const VerificationMeta('score'); @override late final GeneratedColumn score = GeneratedColumn( - 'score', aliasedName, false, - type: DriftSqlType.int, - requiredDuringInsert: false, - defaultValue: const Constant(0)); - @override - late final GeneratedColumnWithTypeConverter?, String> - extraData = GeneratedColumn('extra_data', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false) - .withConverter?>( - $PinnedMessageReactionsTable.$converterextraDatan); - @override - List get $columns => [ - userId, - messageId, - type, - emojiCode, - createdAt, - updatedAt, - score, - extraData - ]; + 'score', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const Constant(0), + ); + @override + late final GeneratedColumnWithTypeConverter?, String> extraData = GeneratedColumn( + 'extra_data', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ).withConverter?>($PinnedMessageReactionsTable.$converterextraDatan); + @override + List get $columns => [userId, messageId, type, emojiCode, createdAt, updatedAt, score, extraData]; @override String get aliasedName => _alias ?? actualTableName; @override String get actualTableName => $name; static const String $name = 'pinned_message_reactions'; @override - VerificationContext validateIntegrity( - Insertable instance, - {bool isInserting = false}) { + VerificationContext validateIntegrity(Insertable instance, {bool isInserting = false}) { final context = VerificationContext(); final data = instance.toColumns(true); if (data.containsKey('user_id')) { - context.handle(_userIdMeta, - userId.isAcceptableOrUnknown(data['user_id']!, _userIdMeta)); + context.handle(_userIdMeta, userId.isAcceptableOrUnknown(data['user_id']!, _userIdMeta)); } if (data.containsKey('message_id')) { - context.handle(_messageIdMeta, - messageId.isAcceptableOrUnknown(data['message_id']!, _messageIdMeta)); + context.handle(_messageIdMeta, messageId.isAcceptableOrUnknown(data['message_id']!, _messageIdMeta)); } if (data.containsKey('type')) { - context.handle( - _typeMeta, type.isAcceptableOrUnknown(data['type']!, _typeMeta)); + context.handle(_typeMeta, type.isAcceptableOrUnknown(data['type']!, _typeMeta)); } else if (isInserting) { context.missing(_typeMeta); } if (data.containsKey('emoji_code')) { - context.handle(_emojiCodeMeta, - emojiCode.isAcceptableOrUnknown(data['emoji_code']!, _emojiCodeMeta)); + context.handle(_emojiCodeMeta, emojiCode.isAcceptableOrUnknown(data['emoji_code']!, _emojiCodeMeta)); } if (data.containsKey('created_at')) { - context.handle(_createdAtMeta, - createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); + context.handle(_createdAtMeta, createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); } if (data.containsKey('updated_at')) { - context.handle(_updatedAtMeta, - updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta)); + context.handle(_updatedAtMeta, updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta)); } if (data.containsKey('score')) { - context.handle( - _scoreMeta, score.isAcceptableOrUnknown(data['score']!, _scoreMeta)); + context.handle(_scoreMeta, score.isAcceptableOrUnknown(data['score']!, _scoreMeta)); } return context; } @@ -6278,27 +6219,19 @@ class $PinnedMessageReactionsTable extends PinnedMessageReactions @override Set get $primaryKey => {messageId, type, userId}; @override - PinnedMessageReactionEntity map(Map data, - {String? tablePrefix}) { + PinnedMessageReactionEntity map(Map data, {String? tablePrefix}) { final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; return PinnedMessageReactionEntity( - userId: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}user_id']), - messageId: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}message_id']), - type: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}type'])!, - emojiCode: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}emoji_code']), - createdAt: attachedDatabase.typeMapping - .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, - updatedAt: attachedDatabase.typeMapping - .read(DriftSqlType.dateTime, data['${effectivePrefix}updated_at'])!, - score: attachedDatabase.typeMapping - .read(DriftSqlType.int, data['${effectivePrefix}score'])!, + userId: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}user_id']), + messageId: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}message_id']), + type: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}type'])!, + emojiCode: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}emoji_code']), + createdAt: attachedDatabase.typeMapping.read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + updatedAt: attachedDatabase.typeMapping.read(DriftSqlType.dateTime, data['${effectivePrefix}updated_at'])!, + score: attachedDatabase.typeMapping.read(DriftSqlType.int, data['${effectivePrefix}score'])!, extraData: $PinnedMessageReactionsTable.$converterextraDatan.fromSql( - attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}extra_data'])), + attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}extra_data']), + ), ); } @@ -6307,14 +6240,13 @@ class $PinnedMessageReactionsTable extends PinnedMessageReactions return $PinnedMessageReactionsTable(attachedDatabase, alias); } - static TypeConverter, String> $converterextraData = - MapConverter(); - static TypeConverter?, String?> $converterextraDatan = - NullAwareTypeConverter.wrap($converterextraData); + static TypeConverter, String> $converterextraData = MapConverter(); + static TypeConverter?, String?> $converterextraDatan = NullAwareTypeConverter.wrap( + $converterextraData, + ); } -class PinnedMessageReactionEntity extends DataClass - implements Insertable { +class PinnedMessageReactionEntity extends DataClass implements Insertable { /// The id of the user that sent the reaction final String? userId; @@ -6338,15 +6270,16 @@ class PinnedMessageReactionEntity extends DataClass /// Reaction custom extraData final Map? extraData; - const PinnedMessageReactionEntity( - {this.userId, - this.messageId, - required this.type, - this.emojiCode, - required this.createdAt, - required this.updatedAt, - required this.score, - this.extraData}); + const PinnedMessageReactionEntity({ + this.userId, + this.messageId, + required this.type, + this.emojiCode, + required this.createdAt, + required this.updatedAt, + required this.score, + this.extraData, + }); @override Map toColumns(bool nullToAbsent) { final map = {}; @@ -6364,14 +6297,12 @@ class PinnedMessageReactionEntity extends DataClass map['updated_at'] = Variable(updatedAt); map['score'] = Variable(score); if (!nullToAbsent || extraData != null) { - map['extra_data'] = Variable( - $PinnedMessageReactionsTable.$converterextraDatan.toSql(extraData)); + map['extra_data'] = Variable($PinnedMessageReactionsTable.$converterextraDatan.toSql(extraData)); } return map; } - factory PinnedMessageReactionEntity.fromJson(Map json, - {ValueSerializer? serializer}) { + factory PinnedMessageReactionEntity.fromJson(Map json, {ValueSerializer? serializer}) { serializer ??= driftRuntimeOptions.defaultSerializer; return PinnedMessageReactionEntity( userId: serializer.fromJson(json['userId']), @@ -6399,27 +6330,26 @@ class PinnedMessageReactionEntity extends DataClass }; } - PinnedMessageReactionEntity copyWith( - {Value userId = const Value.absent(), - Value messageId = const Value.absent(), - String? type, - Value emojiCode = const Value.absent(), - DateTime? createdAt, - DateTime? updatedAt, - int? score, - Value?> extraData = const Value.absent()}) => - PinnedMessageReactionEntity( - userId: userId.present ? userId.value : this.userId, - messageId: messageId.present ? messageId.value : this.messageId, - type: type ?? this.type, - emojiCode: emojiCode.present ? emojiCode.value : this.emojiCode, - createdAt: createdAt ?? this.createdAt, - updatedAt: updatedAt ?? this.updatedAt, - score: score ?? this.score, - extraData: extraData.present ? extraData.value : this.extraData, - ); - PinnedMessageReactionEntity copyWithCompanion( - PinnedMessageReactionsCompanion data) { + PinnedMessageReactionEntity copyWith({ + Value userId = const Value.absent(), + Value messageId = const Value.absent(), + String? type, + Value emojiCode = const Value.absent(), + DateTime? createdAt, + DateTime? updatedAt, + int? score, + Value?> extraData = const Value.absent(), + }) => PinnedMessageReactionEntity( + userId: userId.present ? userId.value : this.userId, + messageId: messageId.present ? messageId.value : this.messageId, + type: type ?? this.type, + emojiCode: emojiCode.present ? emojiCode.value : this.emojiCode, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + score: score ?? this.score, + extraData: extraData.present ? extraData.value : this.extraData, + ); + PinnedMessageReactionEntity copyWithCompanion(PinnedMessageReactionsCompanion data) { return PinnedMessageReactionEntity( userId: data.userId.present ? data.userId.value : this.userId, messageId: data.messageId.present ? data.messageId.value : this.messageId, @@ -6448,8 +6378,7 @@ class PinnedMessageReactionEntity extends DataClass } @override - int get hashCode => Object.hash(userId, messageId, type, emojiCode, createdAt, - updatedAt, score, extraData); + int get hashCode => Object.hash(userId, messageId, type, emojiCode, createdAt, updatedAt, score, extraData); @override bool operator ==(Object other) => identical(this, other) || @@ -6464,8 +6393,7 @@ class PinnedMessageReactionEntity extends DataClass other.extraData == this.extraData); } -class PinnedMessageReactionsCompanion - extends UpdateCompanion { +class PinnedMessageReactionsCompanion extends UpdateCompanion { final Value userId; final Value messageId; final Value type; @@ -6521,16 +6449,17 @@ class PinnedMessageReactionsCompanion }); } - PinnedMessageReactionsCompanion copyWith( - {Value? userId, - Value? messageId, - Value? type, - Value? emojiCode, - Value? createdAt, - Value? updatedAt, - Value? score, - Value?>? extraData, - Value? rowid}) { + PinnedMessageReactionsCompanion copyWith({ + Value? userId, + Value? messageId, + Value? type, + Value? emojiCode, + Value? createdAt, + Value? updatedAt, + Value? score, + Value?>? extraData, + Value? rowid, + }) { return PinnedMessageReactionsCompanion( userId: userId ?? this.userId, messageId: messageId ?? this.messageId, @@ -6569,9 +6498,7 @@ class PinnedMessageReactionsCompanion map['score'] = Variable(score.value); } if (extraData.present) { - map['extra_data'] = Variable($PinnedMessageReactionsTable - .$converterextraDatan - .toSql(extraData.value)); + map['extra_data'] = Variable($PinnedMessageReactionsTable.$converterextraDatan.toSql(extraData.value)); } if (rowid.present) { map['rowid'] = Variable(rowid.value); @@ -6596,8 +6523,7 @@ class PinnedMessageReactionsCompanion } } -class $ReactionsTable extends Reactions - with TableInfo<$ReactionsTable, ReactionEntity> { +class $ReactionsTable extends Reactions with TableInfo<$ReactionsTable, ReactionEntity> { @override final GeneratedDatabase attachedDatabase; final String? _alias; @@ -6605,107 +6531,111 @@ class $ReactionsTable extends Reactions static const VerificationMeta _userIdMeta = const VerificationMeta('userId'); @override late final GeneratedColumn userId = GeneratedColumn( - 'user_id', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); - static const VerificationMeta _messageIdMeta = - const VerificationMeta('messageId'); + 'user_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _messageIdMeta = const VerificationMeta('messageId'); @override late final GeneratedColumn messageId = GeneratedColumn( - 'message_id', aliasedName, true, - type: DriftSqlType.string, - requiredDuringInsert: false, - defaultConstraints: GeneratedColumn.constraintIsAlways( - 'REFERENCES messages (id) ON DELETE CASCADE')); + 'message_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways('REFERENCES messages (id) ON DELETE CASCADE'), + ); static const VerificationMeta _typeMeta = const VerificationMeta('type'); @override late final GeneratedColumn type = GeneratedColumn( - 'type', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true); - static const VerificationMeta _emojiCodeMeta = - const VerificationMeta('emojiCode'); + 'type', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _emojiCodeMeta = const VerificationMeta('emojiCode'); @override late final GeneratedColumn emojiCode = GeneratedColumn( - 'emoji_code', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); - static const VerificationMeta _createdAtMeta = - const VerificationMeta('createdAt'); + 'emoji_code', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _createdAtMeta = const VerificationMeta('createdAt'); @override late final GeneratedColumn createdAt = GeneratedColumn( - 'created_at', aliasedName, false, - type: DriftSqlType.dateTime, - requiredDuringInsert: false, - defaultValue: currentDateAndTime); - static const VerificationMeta _updatedAtMeta = - const VerificationMeta('updatedAt'); + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime, + ); + static const VerificationMeta _updatedAtMeta = const VerificationMeta('updatedAt'); @override late final GeneratedColumn updatedAt = GeneratedColumn( - 'updated_at', aliasedName, false, - type: DriftSqlType.dateTime, - requiredDuringInsert: false, - defaultValue: currentDateAndTime); + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime, + ); static const VerificationMeta _scoreMeta = const VerificationMeta('score'); @override late final GeneratedColumn score = GeneratedColumn( - 'score', aliasedName, false, - type: DriftSqlType.int, - requiredDuringInsert: false, - defaultValue: const Constant(0)); - @override - late final GeneratedColumnWithTypeConverter?, String> - extraData = GeneratedColumn('extra_data', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false) - .withConverter?>( - $ReactionsTable.$converterextraDatan); - @override - List get $columns => [ - userId, - messageId, - type, - emojiCode, - createdAt, - updatedAt, - score, - extraData - ]; + 'score', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const Constant(0), + ); + @override + late final GeneratedColumnWithTypeConverter?, String> extraData = GeneratedColumn( + 'extra_data', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ).withConverter?>($ReactionsTable.$converterextraDatan); + @override + List get $columns => [userId, messageId, type, emojiCode, createdAt, updatedAt, score, extraData]; @override String get aliasedName => _alias ?? actualTableName; @override String get actualTableName => $name; static const String $name = 'reactions'; @override - VerificationContext validateIntegrity(Insertable instance, - {bool isInserting = false}) { + VerificationContext validateIntegrity(Insertable instance, {bool isInserting = false}) { final context = VerificationContext(); final data = instance.toColumns(true); if (data.containsKey('user_id')) { - context.handle(_userIdMeta, - userId.isAcceptableOrUnknown(data['user_id']!, _userIdMeta)); + context.handle(_userIdMeta, userId.isAcceptableOrUnknown(data['user_id']!, _userIdMeta)); } if (data.containsKey('message_id')) { - context.handle(_messageIdMeta, - messageId.isAcceptableOrUnknown(data['message_id']!, _messageIdMeta)); + context.handle(_messageIdMeta, messageId.isAcceptableOrUnknown(data['message_id']!, _messageIdMeta)); } if (data.containsKey('type')) { - context.handle( - _typeMeta, type.isAcceptableOrUnknown(data['type']!, _typeMeta)); + context.handle(_typeMeta, type.isAcceptableOrUnknown(data['type']!, _typeMeta)); } else if (isInserting) { context.missing(_typeMeta); } if (data.containsKey('emoji_code')) { - context.handle(_emojiCodeMeta, - emojiCode.isAcceptableOrUnknown(data['emoji_code']!, _emojiCodeMeta)); + context.handle(_emojiCodeMeta, emojiCode.isAcceptableOrUnknown(data['emoji_code']!, _emojiCodeMeta)); } if (data.containsKey('created_at')) { - context.handle(_createdAtMeta, - createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); + context.handle(_createdAtMeta, createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); } if (data.containsKey('updated_at')) { - context.handle(_updatedAtMeta, - updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta)); + context.handle(_updatedAtMeta, updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta)); } if (data.containsKey('score')) { - context.handle( - _scoreMeta, score.isAcceptableOrUnknown(data['score']!, _scoreMeta)); + context.handle(_scoreMeta, score.isAcceptableOrUnknown(data['score']!, _scoreMeta)); } return context; } @@ -6716,23 +6646,16 @@ class $ReactionsTable extends Reactions ReactionEntity map(Map data, {String? tablePrefix}) { final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; return ReactionEntity( - userId: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}user_id']), - messageId: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}message_id']), - type: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}type'])!, - emojiCode: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}emoji_code']), - createdAt: attachedDatabase.typeMapping - .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, - updatedAt: attachedDatabase.typeMapping - .read(DriftSqlType.dateTime, data['${effectivePrefix}updated_at'])!, - score: attachedDatabase.typeMapping - .read(DriftSqlType.int, data['${effectivePrefix}score'])!, - extraData: $ReactionsTable.$converterextraDatan.fromSql(attachedDatabase - .typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}extra_data'])), + userId: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}user_id']), + messageId: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}message_id']), + type: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}type'])!, + emojiCode: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}emoji_code']), + createdAt: attachedDatabase.typeMapping.read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + updatedAt: attachedDatabase.typeMapping.read(DriftSqlType.dateTime, data['${effectivePrefix}updated_at'])!, + score: attachedDatabase.typeMapping.read(DriftSqlType.int, data['${effectivePrefix}score'])!, + extraData: $ReactionsTable.$converterextraDatan.fromSql( + attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}extra_data']), + ), ); } @@ -6741,10 +6664,10 @@ class $ReactionsTable extends Reactions return $ReactionsTable(attachedDatabase, alias); } - static TypeConverter, String> $converterextraData = - MapConverter(); - static TypeConverter?, String?> $converterextraDatan = - NullAwareTypeConverter.wrap($converterextraData); + static TypeConverter, String> $converterextraData = MapConverter(); + static TypeConverter?, String?> $converterextraDatan = NullAwareTypeConverter.wrap( + $converterextraData, + ); } class ReactionEntity extends DataClass implements Insertable { @@ -6771,15 +6694,16 @@ class ReactionEntity extends DataClass implements Insertable { /// Reaction custom extraData final Map? extraData; - const ReactionEntity( - {this.userId, - this.messageId, - required this.type, - this.emojiCode, - required this.createdAt, - required this.updatedAt, - required this.score, - this.extraData}); + const ReactionEntity({ + this.userId, + this.messageId, + required this.type, + this.emojiCode, + required this.createdAt, + required this.updatedAt, + required this.score, + this.extraData, + }); @override Map toColumns(bool nullToAbsent) { final map = {}; @@ -6797,14 +6721,12 @@ class ReactionEntity extends DataClass implements Insertable { map['updated_at'] = Variable(updatedAt); map['score'] = Variable(score); if (!nullToAbsent || extraData != null) { - map['extra_data'] = Variable( - $ReactionsTable.$converterextraDatan.toSql(extraData)); + map['extra_data'] = Variable($ReactionsTable.$converterextraDatan.toSql(extraData)); } return map; } - factory ReactionEntity.fromJson(Map json, - {ValueSerializer? serializer}) { + factory ReactionEntity.fromJson(Map json, {ValueSerializer? serializer}) { serializer ??= driftRuntimeOptions.defaultSerializer; return ReactionEntity( userId: serializer.fromJson(json['userId']), @@ -6832,25 +6754,25 @@ class ReactionEntity extends DataClass implements Insertable { }; } - ReactionEntity copyWith( - {Value userId = const Value.absent(), - Value messageId = const Value.absent(), - String? type, - Value emojiCode = const Value.absent(), - DateTime? createdAt, - DateTime? updatedAt, - int? score, - Value?> extraData = const Value.absent()}) => - ReactionEntity( - userId: userId.present ? userId.value : this.userId, - messageId: messageId.present ? messageId.value : this.messageId, - type: type ?? this.type, - emojiCode: emojiCode.present ? emojiCode.value : this.emojiCode, - createdAt: createdAt ?? this.createdAt, - updatedAt: updatedAt ?? this.updatedAt, - score: score ?? this.score, - extraData: extraData.present ? extraData.value : this.extraData, - ); + ReactionEntity copyWith({ + Value userId = const Value.absent(), + Value messageId = const Value.absent(), + String? type, + Value emojiCode = const Value.absent(), + DateTime? createdAt, + DateTime? updatedAt, + int? score, + Value?> extraData = const Value.absent(), + }) => ReactionEntity( + userId: userId.present ? userId.value : this.userId, + messageId: messageId.present ? messageId.value : this.messageId, + type: type ?? this.type, + emojiCode: emojiCode.present ? emojiCode.value : this.emojiCode, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + score: score ?? this.score, + extraData: extraData.present ? extraData.value : this.extraData, + ); ReactionEntity copyWithCompanion(ReactionsCompanion data) { return ReactionEntity( userId: data.userId.present ? data.userId.value : this.userId, @@ -6880,8 +6802,7 @@ class ReactionEntity extends DataClass implements Insertable { } @override - int get hashCode => Object.hash(userId, messageId, type, emojiCode, createdAt, - updatedAt, score, extraData); + int get hashCode => Object.hash(userId, messageId, type, emojiCode, createdAt, updatedAt, score, extraData); @override bool operator ==(Object other) => identical(this, other) || @@ -6952,16 +6873,17 @@ class ReactionsCompanion extends UpdateCompanion { }); } - ReactionsCompanion copyWith( - {Value? userId, - Value? messageId, - Value? type, - Value? emojiCode, - Value? createdAt, - Value? updatedAt, - Value? score, - Value?>? extraData, - Value? rowid}) { + ReactionsCompanion copyWith({ + Value? userId, + Value? messageId, + Value? type, + Value? emojiCode, + Value? createdAt, + Value? updatedAt, + Value? score, + Value?>? extraData, + Value? rowid, + }) { return ReactionsCompanion( userId: userId ?? this.userId, messageId: messageId ?? this.messageId, @@ -7000,8 +6922,7 @@ class ReactionsCompanion extends UpdateCompanion { map['score'] = Variable(score.value); } if (extraData.present) { - map['extra_data'] = Variable( - $ReactionsTable.$converterextraDatan.toSql(extraData.value)); + map['extra_data'] = Variable($ReactionsTable.$converterextraDatan.toSql(extraData.value)); } if (rowid.present) { map['rowid'] = Variable(rowid.value); @@ -7034,94 +6955,125 @@ class $UsersTable extends Users with TableInfo<$UsersTable, UserEntity> { static const VerificationMeta _idMeta = const VerificationMeta('id'); @override late final GeneratedColumn id = GeneratedColumn( - 'id', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true); + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); static const VerificationMeta _roleMeta = const VerificationMeta('role'); @override late final GeneratedColumn role = GeneratedColumn( - 'role', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); - static const VerificationMeta _languageMeta = - const VerificationMeta('language'); + 'role', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _languageMeta = const VerificationMeta('language'); @override late final GeneratedColumn language = GeneratedColumn( - 'language', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); - static const VerificationMeta _createdAtMeta = - const VerificationMeta('createdAt'); + 'language', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _createdAtMeta = const VerificationMeta('createdAt'); @override late final GeneratedColumn createdAt = GeneratedColumn( - 'created_at', aliasedName, true, - type: DriftSqlType.dateTime, requiredDuringInsert: false); - static const VerificationMeta _updatedAtMeta = - const VerificationMeta('updatedAt'); + 'created_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + static const VerificationMeta _updatedAtMeta = const VerificationMeta('updatedAt'); @override late final GeneratedColumn updatedAt = GeneratedColumn( - 'updated_at', aliasedName, true, - type: DriftSqlType.dateTime, requiredDuringInsert: false); - static const VerificationMeta _lastActiveMeta = - const VerificationMeta('lastActive'); + 'updated_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + static const VerificationMeta _lastActiveMeta = const VerificationMeta('lastActive'); @override late final GeneratedColumn lastActive = GeneratedColumn( - 'last_active', aliasedName, true, - type: DriftSqlType.dateTime, requiredDuringInsert: false); + 'last_active', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); static const VerificationMeta _onlineMeta = const VerificationMeta('online'); @override late final GeneratedColumn online = GeneratedColumn( - 'online', aliasedName, false, - type: DriftSqlType.bool, - requiredDuringInsert: false, - defaultConstraints: - GeneratedColumn.constraintIsAlways('CHECK ("online" IN (0, 1))'), - defaultValue: const Constant(false)); + 'online', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways('CHECK ("online" IN (0, 1))'), + defaultValue: const Constant(false), + ); static const VerificationMeta _bannedMeta = const VerificationMeta('banned'); @override late final GeneratedColumn banned = GeneratedColumn( - 'banned', aliasedName, false, - type: DriftSqlType.bool, - requiredDuringInsert: false, - defaultConstraints: - GeneratedColumn.constraintIsAlways('CHECK ("banned" IN (0, 1))'), - defaultValue: const Constant(false)); - @override - late final GeneratedColumnWithTypeConverter?, String> - teamsRole = GeneratedColumn('teams_role', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false) - .withConverter?>( - $UsersTable.$converterteamsRolen); - static const VerificationMeta _avgResponseTimeMeta = - const VerificationMeta('avgResponseTime'); + 'banned', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways('CHECK ("banned" IN (0, 1))'), + defaultValue: const Constant(false), + ); + @override + late final GeneratedColumnWithTypeConverter?, String> teamsRole = GeneratedColumn( + 'teams_role', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ).withConverter?>($UsersTable.$converterteamsRolen); + static const VerificationMeta _avgResponseTimeMeta = const VerificationMeta('avgResponseTime'); @override late final GeneratedColumn avgResponseTime = GeneratedColumn( - 'avg_response_time', aliasedName, true, - type: DriftSqlType.int, requiredDuringInsert: false); - @override - late final GeneratedColumnWithTypeConverter, String> - extraData = GeneratedColumn('extra_data', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true) - .withConverter>($UsersTable.$converterextraData); + 'avg_response_time', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + @override + late final GeneratedColumnWithTypeConverter, String> extraData = GeneratedColumn( + 'extra_data', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ).withConverter>($UsersTable.$converterextraData); @override List get $columns => [ - id, - role, - language, - createdAt, - updatedAt, - lastActive, - online, - banned, - teamsRole, - avgResponseTime, - extraData - ]; + id, + role, + language, + createdAt, + updatedAt, + lastActive, + online, + banned, + teamsRole, + avgResponseTime, + extraData, + ]; @override String get aliasedName => _alias ?? actualTableName; @override String get actualTableName => $name; static const String $name = 'users'; @override - VerificationContext validateIntegrity(Insertable instance, - {bool isInserting = false}) { + VerificationContext validateIntegrity(Insertable instance, {bool isInserting = false}) { final context = VerificationContext(); final data = instance.toColumns(true); if (data.containsKey('id')) { @@ -7130,40 +7082,31 @@ class $UsersTable extends Users with TableInfo<$UsersTable, UserEntity> { context.missing(_idMeta); } if (data.containsKey('role')) { - context.handle( - _roleMeta, role.isAcceptableOrUnknown(data['role']!, _roleMeta)); + context.handle(_roleMeta, role.isAcceptableOrUnknown(data['role']!, _roleMeta)); } if (data.containsKey('language')) { - context.handle(_languageMeta, - language.isAcceptableOrUnknown(data['language']!, _languageMeta)); + context.handle(_languageMeta, language.isAcceptableOrUnknown(data['language']!, _languageMeta)); } if (data.containsKey('created_at')) { - context.handle(_createdAtMeta, - createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); + context.handle(_createdAtMeta, createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); } if (data.containsKey('updated_at')) { - context.handle(_updatedAtMeta, - updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta)); + context.handle(_updatedAtMeta, updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta)); } if (data.containsKey('last_active')) { - context.handle( - _lastActiveMeta, - lastActive.isAcceptableOrUnknown( - data['last_active']!, _lastActiveMeta)); + context.handle(_lastActiveMeta, lastActive.isAcceptableOrUnknown(data['last_active']!, _lastActiveMeta)); } if (data.containsKey('online')) { - context.handle(_onlineMeta, - online.isAcceptableOrUnknown(data['online']!, _onlineMeta)); + context.handle(_onlineMeta, online.isAcceptableOrUnknown(data['online']!, _onlineMeta)); } if (data.containsKey('banned')) { - context.handle(_bannedMeta, - banned.isAcceptableOrUnknown(data['banned']!, _bannedMeta)); + context.handle(_bannedMeta, banned.isAcceptableOrUnknown(data['banned']!, _bannedMeta)); } if (data.containsKey('avg_response_time')) { context.handle( - _avgResponseTimeMeta, - avgResponseTime.isAcceptableOrUnknown( - data['avg_response_time']!, _avgResponseTimeMeta)); + _avgResponseTimeMeta, + avgResponseTime.isAcceptableOrUnknown(data['avg_response_time']!, _avgResponseTimeMeta), + ); } return context; } @@ -7174,30 +7117,21 @@ class $UsersTable extends Users with TableInfo<$UsersTable, UserEntity> { UserEntity map(Map data, {String? tablePrefix}) { final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; return UserEntity( - id: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}id'])!, - role: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}role']), - language: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}language']), - createdAt: attachedDatabase.typeMapping - .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at']), - updatedAt: attachedDatabase.typeMapping - .read(DriftSqlType.dateTime, data['${effectivePrefix}updated_at']), - lastActive: attachedDatabase.typeMapping - .read(DriftSqlType.dateTime, data['${effectivePrefix}last_active']), - online: attachedDatabase.typeMapping - .read(DriftSqlType.bool, data['${effectivePrefix}online'])!, - banned: attachedDatabase.typeMapping - .read(DriftSqlType.bool, data['${effectivePrefix}banned'])!, - teamsRole: $UsersTable.$converterteamsRolen.fromSql(attachedDatabase - .typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}teams_role'])), - avgResponseTime: attachedDatabase.typeMapping - .read(DriftSqlType.int, data['${effectivePrefix}avg_response_time']), - extraData: $UsersTable.$converterextraData.fromSql(attachedDatabase - .typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}extra_data'])!), + id: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}id'])!, + role: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}role']), + language: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}language']), + createdAt: attachedDatabase.typeMapping.read(DriftSqlType.dateTime, data['${effectivePrefix}created_at']), + updatedAt: attachedDatabase.typeMapping.read(DriftSqlType.dateTime, data['${effectivePrefix}updated_at']), + lastActive: attachedDatabase.typeMapping.read(DriftSqlType.dateTime, data['${effectivePrefix}last_active']), + online: attachedDatabase.typeMapping.read(DriftSqlType.bool, data['${effectivePrefix}online'])!, + banned: attachedDatabase.typeMapping.read(DriftSqlType.bool, data['${effectivePrefix}banned'])!, + teamsRole: $UsersTable.$converterteamsRolen.fromSql( + attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}teams_role']), + ), + avgResponseTime: attachedDatabase.typeMapping.read(DriftSqlType.int, data['${effectivePrefix}avg_response_time']), + extraData: $UsersTable.$converterextraData.fromSql( + attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}extra_data'])!, + ), ); } @@ -7206,12 +7140,11 @@ class $UsersTable extends Users with TableInfo<$UsersTable, UserEntity> { return $UsersTable(attachedDatabase, alias); } - static TypeConverter, String> $converterteamsRole = - MapConverter(); - static TypeConverter?, String?> $converterteamsRolen = - NullAwareTypeConverter.wrap($converterteamsRole); - static TypeConverter, String> $converterextraData = - MapConverter(); + static TypeConverter, String> $converterteamsRole = MapConverter(); + static TypeConverter?, String?> $converterteamsRolen = NullAwareTypeConverter.wrap( + $converterteamsRole, + ); + static TypeConverter, String> $converterextraData = MapConverter(); } class UserEntity extends DataClass implements Insertable { @@ -7249,18 +7182,19 @@ class UserEntity extends DataClass implements Insertable { /// Map of custom user extraData final Map extraData; - const UserEntity( - {required this.id, - this.role, - this.language, - this.createdAt, - this.updatedAt, - this.lastActive, - required this.online, - required this.banned, - this.teamsRole, - this.avgResponseTime, - required this.extraData}); + const UserEntity({ + required this.id, + this.role, + this.language, + this.createdAt, + this.updatedAt, + this.lastActive, + required this.online, + required this.banned, + this.teamsRole, + this.avgResponseTime, + required this.extraData, + }); @override Map toColumns(bool nullToAbsent) { final map = {}; @@ -7283,21 +7217,18 @@ class UserEntity extends DataClass implements Insertable { map['online'] = Variable(online); map['banned'] = Variable(banned); if (!nullToAbsent || teamsRole != null) { - map['teams_role'] = - Variable($UsersTable.$converterteamsRolen.toSql(teamsRole)); + map['teams_role'] = Variable($UsersTable.$converterteamsRolen.toSql(teamsRole)); } if (!nullToAbsent || avgResponseTime != null) { map['avg_response_time'] = Variable(avgResponseTime); } { - map['extra_data'] = - Variable($UsersTable.$converterextraData.toSql(extraData)); + map['extra_data'] = Variable($UsersTable.$converterextraData.toSql(extraData)); } return map; } - factory UserEntity.fromJson(Map json, - {ValueSerializer? serializer}) { + factory UserEntity.fromJson(Map json, {ValueSerializer? serializer}) { serializer ??= driftRuntimeOptions.defaultSerializer; return UserEntity( id: serializer.fromJson(json['id']), @@ -7331,33 +7262,31 @@ class UserEntity extends DataClass implements Insertable { }; } - UserEntity copyWith( - {String? id, - Value role = const Value.absent(), - Value language = const Value.absent(), - Value createdAt = const Value.absent(), - Value updatedAt = const Value.absent(), - Value lastActive = const Value.absent(), - bool? online, - bool? banned, - Value?> teamsRole = const Value.absent(), - Value avgResponseTime = const Value.absent(), - Map? extraData}) => - UserEntity( - id: id ?? this.id, - role: role.present ? role.value : this.role, - language: language.present ? language.value : this.language, - createdAt: createdAt.present ? createdAt.value : this.createdAt, - updatedAt: updatedAt.present ? updatedAt.value : this.updatedAt, - lastActive: lastActive.present ? lastActive.value : this.lastActive, - online: online ?? this.online, - banned: banned ?? this.banned, - teamsRole: teamsRole.present ? teamsRole.value : this.teamsRole, - avgResponseTime: avgResponseTime.present - ? avgResponseTime.value - : this.avgResponseTime, - extraData: extraData ?? this.extraData, - ); + UserEntity copyWith({ + String? id, + Value role = const Value.absent(), + Value language = const Value.absent(), + Value createdAt = const Value.absent(), + Value updatedAt = const Value.absent(), + Value lastActive = const Value.absent(), + bool? online, + bool? banned, + Value?> teamsRole = const Value.absent(), + Value avgResponseTime = const Value.absent(), + Map? extraData, + }) => UserEntity( + id: id ?? this.id, + role: role.present ? role.value : this.role, + language: language.present ? language.value : this.language, + createdAt: createdAt.present ? createdAt.value : this.createdAt, + updatedAt: updatedAt.present ? updatedAt.value : this.updatedAt, + lastActive: lastActive.present ? lastActive.value : this.lastActive, + online: online ?? this.online, + banned: banned ?? this.banned, + teamsRole: teamsRole.present ? teamsRole.value : this.teamsRole, + avgResponseTime: avgResponseTime.present ? avgResponseTime.value : this.avgResponseTime, + extraData: extraData ?? this.extraData, + ); UserEntity copyWithCompanion(UsersCompanion data) { return UserEntity( id: data.id.present ? data.id.value : this.id, @@ -7365,14 +7294,11 @@ class UserEntity extends DataClass implements Insertable { language: data.language.present ? data.language.value : this.language, createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, - lastActive: - data.lastActive.present ? data.lastActive.value : this.lastActive, + lastActive: data.lastActive.present ? data.lastActive.value : this.lastActive, online: data.online.present ? data.online.value : this.online, banned: data.banned.present ? data.banned.value : this.banned, teamsRole: data.teamsRole.present ? data.teamsRole.value : this.teamsRole, - avgResponseTime: data.avgResponseTime.present - ? data.avgResponseTime.value - : this.avgResponseTime, + avgResponseTime: data.avgResponseTime.present ? data.avgResponseTime.value : this.avgResponseTime, extraData: data.extraData.present ? data.extraData.value : this.extraData, ); } @@ -7396,8 +7322,19 @@ class UserEntity extends DataClass implements Insertable { } @override - int get hashCode => Object.hash(id, role, language, createdAt, updatedAt, - lastActive, online, banned, teamsRole, avgResponseTime, extraData); + int get hashCode => Object.hash( + id, + role, + language, + createdAt, + updatedAt, + lastActive, + online, + banned, + teamsRole, + avgResponseTime, + extraData, + ); @override bool operator ==(Object other) => identical(this, other) || @@ -7455,8 +7392,8 @@ class UsersCompanion extends UpdateCompanion { this.avgResponseTime = const Value.absent(), required Map extraData, this.rowid = const Value.absent(), - }) : id = Value(id), - extraData = Value(extraData); + }) : id = Value(id), + extraData = Value(extraData); static Insertable custom({ Expression? id, Expression? role, @@ -7487,19 +7424,20 @@ class UsersCompanion extends UpdateCompanion { }); } - UsersCompanion copyWith( - {Value? id, - Value? role, - Value? language, - Value? createdAt, - Value? updatedAt, - Value? lastActive, - Value? online, - Value? banned, - Value?>? teamsRole, - Value? avgResponseTime, - Value>? extraData, - Value? rowid}) { + UsersCompanion copyWith({ + Value? id, + Value? role, + Value? language, + Value? createdAt, + Value? updatedAt, + Value? lastActive, + Value? online, + Value? banned, + Value?>? teamsRole, + Value? avgResponseTime, + Value>? extraData, + Value? rowid, + }) { return UsersCompanion( id: id ?? this.id, role: role ?? this.role, @@ -7544,15 +7482,13 @@ class UsersCompanion extends UpdateCompanion { map['banned'] = Variable(banned.value); } if (teamsRole.present) { - map['teams_role'] = Variable( - $UsersTable.$converterteamsRolen.toSql(teamsRole.value)); + map['teams_role'] = Variable($UsersTable.$converterteamsRolen.toSql(teamsRole.value)); } if (avgResponseTime.present) { map['avg_response_time'] = Variable(avgResponseTime.value); } if (extraData.present) { - map['extra_data'] = Variable( - $UsersTable.$converterextraData.toSql(extraData.value)); + map['extra_data'] = Variable($UsersTable.$converterextraData.toSql(extraData.value)); } if (rowid.present) { map['rowid'] = Variable(rowid.value); @@ -7580,8 +7516,7 @@ class UsersCompanion extends UpdateCompanion { } } -class $MembersTable extends Members - with TableInfo<$MembersTable, MemberEntity> { +class $MembersTable extends Members with TableInfo<$MembersTable, MemberEntity> { @override final GeneratedDatabase attachedDatabase; final String? _alias; @@ -7589,215 +7524,224 @@ class $MembersTable extends Members static const VerificationMeta _userIdMeta = const VerificationMeta('userId'); @override late final GeneratedColumn userId = GeneratedColumn( - 'user_id', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true); - static const VerificationMeta _channelCidMeta = - const VerificationMeta('channelCid'); + 'user_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _channelCidMeta = const VerificationMeta('channelCid'); @override late final GeneratedColumn channelCid = GeneratedColumn( - 'channel_cid', aliasedName, false, - type: DriftSqlType.string, - requiredDuringInsert: true, - defaultConstraints: GeneratedColumn.constraintIsAlways( - 'REFERENCES channels (cid) ON DELETE CASCADE')); - static const VerificationMeta _channelRoleMeta = - const VerificationMeta('channelRole'); + 'channel_cid', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways('REFERENCES channels (cid) ON DELETE CASCADE'), + ); + static const VerificationMeta _channelRoleMeta = const VerificationMeta('channelRole'); @override late final GeneratedColumn channelRole = GeneratedColumn( - 'channel_role', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); - static const VerificationMeta _inviteAcceptedAtMeta = - const VerificationMeta('inviteAcceptedAt'); - @override - late final GeneratedColumn inviteAcceptedAt = - GeneratedColumn('invite_accepted_at', aliasedName, true, - type: DriftSqlType.dateTime, requiredDuringInsert: false); - static const VerificationMeta _inviteRejectedAtMeta = - const VerificationMeta('inviteRejectedAt'); - @override - late final GeneratedColumn inviteRejectedAt = - GeneratedColumn('invite_rejected_at', aliasedName, true, - type: DriftSqlType.dateTime, requiredDuringInsert: false); - static const VerificationMeta _invitedMeta = - const VerificationMeta('invited'); + 'channel_role', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _inviteAcceptedAtMeta = const VerificationMeta('inviteAcceptedAt'); + @override + late final GeneratedColumn inviteAcceptedAt = GeneratedColumn( + 'invite_accepted_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + static const VerificationMeta _inviteRejectedAtMeta = const VerificationMeta('inviteRejectedAt'); + @override + late final GeneratedColumn inviteRejectedAt = GeneratedColumn( + 'invite_rejected_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + static const VerificationMeta _invitedMeta = const VerificationMeta('invited'); @override late final GeneratedColumn invited = GeneratedColumn( - 'invited', aliasedName, false, - type: DriftSqlType.bool, - requiredDuringInsert: false, - defaultConstraints: - GeneratedColumn.constraintIsAlways('CHECK ("invited" IN (0, 1))'), - defaultValue: const Constant(false)); + 'invited', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways('CHECK ("invited" IN (0, 1))'), + defaultValue: const Constant(false), + ); static const VerificationMeta _bannedMeta = const VerificationMeta('banned'); @override late final GeneratedColumn banned = GeneratedColumn( - 'banned', aliasedName, false, - type: DriftSqlType.bool, - requiredDuringInsert: false, - defaultConstraints: - GeneratedColumn.constraintIsAlways('CHECK ("banned" IN (0, 1))'), - defaultValue: const Constant(false)); - static const VerificationMeta _shadowBannedMeta = - const VerificationMeta('shadowBanned'); + 'banned', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways('CHECK ("banned" IN (0, 1))'), + defaultValue: const Constant(false), + ); + static const VerificationMeta _shadowBannedMeta = const VerificationMeta('shadowBanned'); @override late final GeneratedColumn shadowBanned = GeneratedColumn( - 'shadow_banned', aliasedName, false, - type: DriftSqlType.bool, - requiredDuringInsert: false, - defaultConstraints: GeneratedColumn.constraintIsAlways( - 'CHECK ("shadow_banned" IN (0, 1))'), - defaultValue: const Constant(false)); - static const VerificationMeta _pinnedAtMeta = - const VerificationMeta('pinnedAt'); + 'shadow_banned', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways('CHECK ("shadow_banned" IN (0, 1))'), + defaultValue: const Constant(false), + ); + static const VerificationMeta _pinnedAtMeta = const VerificationMeta('pinnedAt'); @override late final GeneratedColumn pinnedAt = GeneratedColumn( - 'pinned_at', aliasedName, true, - type: DriftSqlType.dateTime, - requiredDuringInsert: false, - defaultValue: const Constant(null)); - static const VerificationMeta _archivedAtMeta = - const VerificationMeta('archivedAt'); + 'pinned_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const Constant(null), + ); + static const VerificationMeta _archivedAtMeta = const VerificationMeta('archivedAt'); @override late final GeneratedColumn archivedAt = GeneratedColumn( - 'archived_at', aliasedName, true, - type: DriftSqlType.dateTime, - requiredDuringInsert: false, - defaultValue: const Constant(null)); - static const VerificationMeta _isModeratorMeta = - const VerificationMeta('isModerator'); + 'archived_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const Constant(null), + ); + static const VerificationMeta _isModeratorMeta = const VerificationMeta('isModerator'); @override late final GeneratedColumn isModerator = GeneratedColumn( - 'is_moderator', aliasedName, false, - type: DriftSqlType.bool, - requiredDuringInsert: false, - defaultConstraints: GeneratedColumn.constraintIsAlways( - 'CHECK ("is_moderator" IN (0, 1))'), - defaultValue: const Constant(false)); - @override - late final GeneratedColumnWithTypeConverter?, String> - extraData = GeneratedColumn('extra_data', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false) - .withConverter?>( - $MembersTable.$converterextraDatan); - static const VerificationMeta _createdAtMeta = - const VerificationMeta('createdAt'); + 'is_moderator', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways('CHECK ("is_moderator" IN (0, 1))'), + defaultValue: const Constant(false), + ); + @override + late final GeneratedColumnWithTypeConverter?, String> extraData = GeneratedColumn( + 'extra_data', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ).withConverter?>($MembersTable.$converterextraDatan); + static const VerificationMeta _createdAtMeta = const VerificationMeta('createdAt'); @override late final GeneratedColumn createdAt = GeneratedColumn( - 'created_at', aliasedName, false, - type: DriftSqlType.dateTime, - requiredDuringInsert: false, - defaultValue: currentDateAndTime); - static const VerificationMeta _updatedAtMeta = - const VerificationMeta('updatedAt'); + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime, + ); + static const VerificationMeta _updatedAtMeta = const VerificationMeta('updatedAt'); @override late final GeneratedColumn updatedAt = GeneratedColumn( - 'updated_at', aliasedName, false, - type: DriftSqlType.dateTime, - requiredDuringInsert: false, - defaultValue: currentDateAndTime); - @override - late final GeneratedColumnWithTypeConverter, String> - deletedMessages = GeneratedColumn( - 'deleted_messages', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true) - .withConverter>($MembersTable.$converterdeletedMessages); + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime, + ); + @override + late final GeneratedColumnWithTypeConverter, String> deletedMessages = GeneratedColumn( + 'deleted_messages', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ).withConverter>($MembersTable.$converterdeletedMessages); @override List get $columns => [ - userId, - channelCid, - channelRole, - inviteAcceptedAt, - inviteRejectedAt, - invited, - banned, - shadowBanned, - pinnedAt, - archivedAt, - isModerator, - extraData, - createdAt, - updatedAt, - deletedMessages - ]; + userId, + channelCid, + channelRole, + inviteAcceptedAt, + inviteRejectedAt, + invited, + banned, + shadowBanned, + pinnedAt, + archivedAt, + isModerator, + extraData, + createdAt, + updatedAt, + deletedMessages, + ]; @override String get aliasedName => _alias ?? actualTableName; @override String get actualTableName => $name; static const String $name = 'members'; @override - VerificationContext validateIntegrity(Insertable instance, - {bool isInserting = false}) { + VerificationContext validateIntegrity(Insertable instance, {bool isInserting = false}) { final context = VerificationContext(); final data = instance.toColumns(true); if (data.containsKey('user_id')) { - context.handle(_userIdMeta, - userId.isAcceptableOrUnknown(data['user_id']!, _userIdMeta)); + context.handle(_userIdMeta, userId.isAcceptableOrUnknown(data['user_id']!, _userIdMeta)); } else if (isInserting) { context.missing(_userIdMeta); } if (data.containsKey('channel_cid')) { - context.handle( - _channelCidMeta, - channelCid.isAcceptableOrUnknown( - data['channel_cid']!, _channelCidMeta)); + context.handle(_channelCidMeta, channelCid.isAcceptableOrUnknown(data['channel_cid']!, _channelCidMeta)); } else if (isInserting) { context.missing(_channelCidMeta); } if (data.containsKey('channel_role')) { - context.handle( - _channelRoleMeta, - channelRole.isAcceptableOrUnknown( - data['channel_role']!, _channelRoleMeta)); + context.handle(_channelRoleMeta, channelRole.isAcceptableOrUnknown(data['channel_role']!, _channelRoleMeta)); } if (data.containsKey('invite_accepted_at')) { context.handle( - _inviteAcceptedAtMeta, - inviteAcceptedAt.isAcceptableOrUnknown( - data['invite_accepted_at']!, _inviteAcceptedAtMeta)); + _inviteAcceptedAtMeta, + inviteAcceptedAt.isAcceptableOrUnknown(data['invite_accepted_at']!, _inviteAcceptedAtMeta), + ); } if (data.containsKey('invite_rejected_at')) { context.handle( - _inviteRejectedAtMeta, - inviteRejectedAt.isAcceptableOrUnknown( - data['invite_rejected_at']!, _inviteRejectedAtMeta)); + _inviteRejectedAtMeta, + inviteRejectedAt.isAcceptableOrUnknown(data['invite_rejected_at']!, _inviteRejectedAtMeta), + ); } if (data.containsKey('invited')) { - context.handle(_invitedMeta, - invited.isAcceptableOrUnknown(data['invited']!, _invitedMeta)); + context.handle(_invitedMeta, invited.isAcceptableOrUnknown(data['invited']!, _invitedMeta)); } if (data.containsKey('banned')) { - context.handle(_bannedMeta, - banned.isAcceptableOrUnknown(data['banned']!, _bannedMeta)); + context.handle(_bannedMeta, banned.isAcceptableOrUnknown(data['banned']!, _bannedMeta)); } if (data.containsKey('shadow_banned')) { - context.handle( - _shadowBannedMeta, - shadowBanned.isAcceptableOrUnknown( - data['shadow_banned']!, _shadowBannedMeta)); + context.handle(_shadowBannedMeta, shadowBanned.isAcceptableOrUnknown(data['shadow_banned']!, _shadowBannedMeta)); } if (data.containsKey('pinned_at')) { - context.handle(_pinnedAtMeta, - pinnedAt.isAcceptableOrUnknown(data['pinned_at']!, _pinnedAtMeta)); + context.handle(_pinnedAtMeta, pinnedAt.isAcceptableOrUnknown(data['pinned_at']!, _pinnedAtMeta)); } if (data.containsKey('archived_at')) { - context.handle( - _archivedAtMeta, - archivedAt.isAcceptableOrUnknown( - data['archived_at']!, _archivedAtMeta)); + context.handle(_archivedAtMeta, archivedAt.isAcceptableOrUnknown(data['archived_at']!, _archivedAtMeta)); } if (data.containsKey('is_moderator')) { - context.handle( - _isModeratorMeta, - isModerator.isAcceptableOrUnknown( - data['is_moderator']!, _isModeratorMeta)); + context.handle(_isModeratorMeta, isModerator.isAcceptableOrUnknown(data['is_moderator']!, _isModeratorMeta)); } if (data.containsKey('created_at')) { - context.handle(_createdAtMeta, - createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); + context.handle(_createdAtMeta, createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); } if (data.containsKey('updated_at')) { - context.handle(_updatedAtMeta, - updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta)); + context.handle(_updatedAtMeta, updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta)); } return context; } @@ -7808,38 +7752,31 @@ class $MembersTable extends Members MemberEntity map(Map data, {String? tablePrefix}) { final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; return MemberEntity( - userId: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}user_id'])!, - channelCid: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}channel_cid'])!, - channelRole: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}channel_role']), + userId: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}user_id'])!, + channelCid: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}channel_cid'])!, + channelRole: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}channel_role']), inviteAcceptedAt: attachedDatabase.typeMapping.read( - DriftSqlType.dateTime, data['${effectivePrefix}invite_accepted_at']), + DriftSqlType.dateTime, + data['${effectivePrefix}invite_accepted_at'], + ), inviteRejectedAt: attachedDatabase.typeMapping.read( - DriftSqlType.dateTime, data['${effectivePrefix}invite_rejected_at']), - invited: attachedDatabase.typeMapping - .read(DriftSqlType.bool, data['${effectivePrefix}invited'])!, - banned: attachedDatabase.typeMapping - .read(DriftSqlType.bool, data['${effectivePrefix}banned'])!, - shadowBanned: attachedDatabase.typeMapping - .read(DriftSqlType.bool, data['${effectivePrefix}shadow_banned'])!, - pinnedAt: attachedDatabase.typeMapping - .read(DriftSqlType.dateTime, data['${effectivePrefix}pinned_at']), - archivedAt: attachedDatabase.typeMapping - .read(DriftSqlType.dateTime, data['${effectivePrefix}archived_at']), - isModerator: attachedDatabase.typeMapping - .read(DriftSqlType.bool, data['${effectivePrefix}is_moderator'])!, - extraData: $MembersTable.$converterextraDatan.fromSql(attachedDatabase - .typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}extra_data'])), - createdAt: attachedDatabase.typeMapping - .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, - updatedAt: attachedDatabase.typeMapping - .read(DriftSqlType.dateTime, data['${effectivePrefix}updated_at'])!, + DriftSqlType.dateTime, + data['${effectivePrefix}invite_rejected_at'], + ), + invited: attachedDatabase.typeMapping.read(DriftSqlType.bool, data['${effectivePrefix}invited'])!, + banned: attachedDatabase.typeMapping.read(DriftSqlType.bool, data['${effectivePrefix}banned'])!, + shadowBanned: attachedDatabase.typeMapping.read(DriftSqlType.bool, data['${effectivePrefix}shadow_banned'])!, + pinnedAt: attachedDatabase.typeMapping.read(DriftSqlType.dateTime, data['${effectivePrefix}pinned_at']), + archivedAt: attachedDatabase.typeMapping.read(DriftSqlType.dateTime, data['${effectivePrefix}archived_at']), + isModerator: attachedDatabase.typeMapping.read(DriftSqlType.bool, data['${effectivePrefix}is_moderator'])!, + extraData: $MembersTable.$converterextraDatan.fromSql( + attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}extra_data']), + ), + createdAt: attachedDatabase.typeMapping.read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + updatedAt: attachedDatabase.typeMapping.read(DriftSqlType.dateTime, data['${effectivePrefix}updated_at'])!, deletedMessages: $MembersTable.$converterdeletedMessages.fromSql( - attachedDatabase.typeMapping.read(DriftSqlType.string, - data['${effectivePrefix}deleted_messages'])!), + attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}deleted_messages'])!, + ), ); } @@ -7848,12 +7785,11 @@ class $MembersTable extends Members return $MembersTable(attachedDatabase, alias); } - static TypeConverter, String> $converterextraData = - MapConverter(); - static TypeConverter?, String?> $converterextraDatan = - NullAwareTypeConverter.wrap($converterextraData); - static TypeConverter, String> $converterdeletedMessages = - ListConverter(); + static TypeConverter, String> $converterextraData = MapConverter(); + static TypeConverter?, String?> $converterextraDatan = NullAwareTypeConverter.wrap( + $converterextraData, + ); + static TypeConverter, String> $converterdeletedMessages = ListConverter(); } class MemberEntity extends DataClass implements Insertable { @@ -7904,22 +7840,23 @@ class MemberEntity extends DataClass implements Insertable { /// These messages are now marked deleted for this member, but are still /// kept as regular to other channel members. final List deletedMessages; - const MemberEntity( - {required this.userId, - required this.channelCid, - this.channelRole, - this.inviteAcceptedAt, - this.inviteRejectedAt, - required this.invited, - required this.banned, - required this.shadowBanned, - this.pinnedAt, - this.archivedAt, - required this.isModerator, - this.extraData, - required this.createdAt, - required this.updatedAt, - required this.deletedMessages}); + const MemberEntity({ + required this.userId, + required this.channelCid, + this.channelRole, + this.inviteAcceptedAt, + this.inviteRejectedAt, + required this.invited, + required this.banned, + required this.shadowBanned, + this.pinnedAt, + this.archivedAt, + required this.isModerator, + this.extraData, + required this.createdAt, + required this.updatedAt, + required this.deletedMessages, + }); @override Map toColumns(bool nullToAbsent) { final map = {}; @@ -7945,29 +7882,24 @@ class MemberEntity extends DataClass implements Insertable { } map['is_moderator'] = Variable(isModerator); if (!nullToAbsent || extraData != null) { - map['extra_data'] = - Variable($MembersTable.$converterextraDatan.toSql(extraData)); + map['extra_data'] = Variable($MembersTable.$converterextraDatan.toSql(extraData)); } map['created_at'] = Variable(createdAt); map['updated_at'] = Variable(updatedAt); { - map['deleted_messages'] = Variable( - $MembersTable.$converterdeletedMessages.toSql(deletedMessages)); + map['deleted_messages'] = Variable($MembersTable.$converterdeletedMessages.toSql(deletedMessages)); } return map; } - factory MemberEntity.fromJson(Map json, - {ValueSerializer? serializer}) { + factory MemberEntity.fromJson(Map json, {ValueSerializer? serializer}) { serializer ??= driftRuntimeOptions.defaultSerializer; return MemberEntity( userId: serializer.fromJson(json['userId']), channelCid: serializer.fromJson(json['channelCid']), channelRole: serializer.fromJson(json['channelRole']), - inviteAcceptedAt: - serializer.fromJson(json['inviteAcceptedAt']), - inviteRejectedAt: - serializer.fromJson(json['inviteRejectedAt']), + inviteAcceptedAt: serializer.fromJson(json['inviteAcceptedAt']), + inviteRejectedAt: serializer.fromJson(json['inviteRejectedAt']), invited: serializer.fromJson(json['invited']), banned: serializer.fromJson(json['banned']), shadowBanned: serializer.fromJson(json['shadowBanned']), @@ -7977,8 +7909,7 @@ class MemberEntity extends DataClass implements Insertable { extraData: serializer.fromJson?>(json['extraData']), createdAt: serializer.fromJson(json['createdAt']), updatedAt: serializer.fromJson(json['updatedAt']), - deletedMessages: - serializer.fromJson>(json['deletedMessages']), + deletedMessages: serializer.fromJson>(json['deletedMessages']), ); } @override @@ -8003,72 +7934,56 @@ class MemberEntity extends DataClass implements Insertable { }; } - MemberEntity copyWith( - {String? userId, - String? channelCid, - Value channelRole = const Value.absent(), - Value inviteAcceptedAt = const Value.absent(), - Value inviteRejectedAt = const Value.absent(), - bool? invited, - bool? banned, - bool? shadowBanned, - Value pinnedAt = const Value.absent(), - Value archivedAt = const Value.absent(), - bool? isModerator, - Value?> extraData = const Value.absent(), - DateTime? createdAt, - DateTime? updatedAt, - List? deletedMessages}) => - MemberEntity( - userId: userId ?? this.userId, - channelCid: channelCid ?? this.channelCid, - channelRole: channelRole.present ? channelRole.value : this.channelRole, - inviteAcceptedAt: inviteAcceptedAt.present - ? inviteAcceptedAt.value - : this.inviteAcceptedAt, - inviteRejectedAt: inviteRejectedAt.present - ? inviteRejectedAt.value - : this.inviteRejectedAt, - invited: invited ?? this.invited, - banned: banned ?? this.banned, - shadowBanned: shadowBanned ?? this.shadowBanned, - pinnedAt: pinnedAt.present ? pinnedAt.value : this.pinnedAt, - archivedAt: archivedAt.present ? archivedAt.value : this.archivedAt, - isModerator: isModerator ?? this.isModerator, - extraData: extraData.present ? extraData.value : this.extraData, - createdAt: createdAt ?? this.createdAt, - updatedAt: updatedAt ?? this.updatedAt, - deletedMessages: deletedMessages ?? this.deletedMessages, - ); + MemberEntity copyWith({ + String? userId, + String? channelCid, + Value channelRole = const Value.absent(), + Value inviteAcceptedAt = const Value.absent(), + Value inviteRejectedAt = const Value.absent(), + bool? invited, + bool? banned, + bool? shadowBanned, + Value pinnedAt = const Value.absent(), + Value archivedAt = const Value.absent(), + bool? isModerator, + Value?> extraData = const Value.absent(), + DateTime? createdAt, + DateTime? updatedAt, + List? deletedMessages, + }) => MemberEntity( + userId: userId ?? this.userId, + channelCid: channelCid ?? this.channelCid, + channelRole: channelRole.present ? channelRole.value : this.channelRole, + inviteAcceptedAt: inviteAcceptedAt.present ? inviteAcceptedAt.value : this.inviteAcceptedAt, + inviteRejectedAt: inviteRejectedAt.present ? inviteRejectedAt.value : this.inviteRejectedAt, + invited: invited ?? this.invited, + banned: banned ?? this.banned, + shadowBanned: shadowBanned ?? this.shadowBanned, + pinnedAt: pinnedAt.present ? pinnedAt.value : this.pinnedAt, + archivedAt: archivedAt.present ? archivedAt.value : this.archivedAt, + isModerator: isModerator ?? this.isModerator, + extraData: extraData.present ? extraData.value : this.extraData, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedMessages: deletedMessages ?? this.deletedMessages, + ); MemberEntity copyWithCompanion(MembersCompanion data) { return MemberEntity( userId: data.userId.present ? data.userId.value : this.userId, - channelCid: - data.channelCid.present ? data.channelCid.value : this.channelCid, - channelRole: - data.channelRole.present ? data.channelRole.value : this.channelRole, - inviteAcceptedAt: data.inviteAcceptedAt.present - ? data.inviteAcceptedAt.value - : this.inviteAcceptedAt, - inviteRejectedAt: data.inviteRejectedAt.present - ? data.inviteRejectedAt.value - : this.inviteRejectedAt, + channelCid: data.channelCid.present ? data.channelCid.value : this.channelCid, + channelRole: data.channelRole.present ? data.channelRole.value : this.channelRole, + inviteAcceptedAt: data.inviteAcceptedAt.present ? data.inviteAcceptedAt.value : this.inviteAcceptedAt, + inviteRejectedAt: data.inviteRejectedAt.present ? data.inviteRejectedAt.value : this.inviteRejectedAt, invited: data.invited.present ? data.invited.value : this.invited, banned: data.banned.present ? data.banned.value : this.banned, - shadowBanned: data.shadowBanned.present - ? data.shadowBanned.value - : this.shadowBanned, + shadowBanned: data.shadowBanned.present ? data.shadowBanned.value : this.shadowBanned, pinnedAt: data.pinnedAt.present ? data.pinnedAt.value : this.pinnedAt, - archivedAt: - data.archivedAt.present ? data.archivedAt.value : this.archivedAt, - isModerator: - data.isModerator.present ? data.isModerator.value : this.isModerator, + archivedAt: data.archivedAt.present ? data.archivedAt.value : this.archivedAt, + isModerator: data.isModerator.present ? data.isModerator.value : this.isModerator, extraData: data.extraData.present ? data.extraData.value : this.extraData, createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, - deletedMessages: data.deletedMessages.present - ? data.deletedMessages.value - : this.deletedMessages, + deletedMessages: data.deletedMessages.present ? data.deletedMessages.value : this.deletedMessages, ); } @@ -8096,21 +8011,22 @@ class MemberEntity extends DataClass implements Insertable { @override int get hashCode => Object.hash( - userId, - channelCid, - channelRole, - inviteAcceptedAt, - inviteRejectedAt, - invited, - banned, - shadowBanned, - pinnedAt, - archivedAt, - isModerator, - extraData, - createdAt, - updatedAt, - deletedMessages); + userId, + channelCid, + channelRole, + inviteAcceptedAt, + inviteRejectedAt, + invited, + banned, + shadowBanned, + pinnedAt, + archivedAt, + isModerator, + extraData, + createdAt, + updatedAt, + deletedMessages, + ); @override bool operator ==(Object other) => identical(this, other) || @@ -8184,9 +8100,9 @@ class MembersCompanion extends UpdateCompanion { this.updatedAt = const Value.absent(), required List deletedMessages, this.rowid = const Value.absent(), - }) : userId = Value(userId), - channelCid = Value(channelCid), - deletedMessages = Value(deletedMessages); + }) : userId = Value(userId), + channelCid = Value(channelCid), + deletedMessages = Value(deletedMessages); static Insertable custom({ Expression? userId, Expression? channelCid, @@ -8225,23 +8141,24 @@ class MembersCompanion extends UpdateCompanion { }); } - MembersCompanion copyWith( - {Value? userId, - Value? channelCid, - Value? channelRole, - Value? inviteAcceptedAt, - Value? inviteRejectedAt, - Value? invited, - Value? banned, - Value? shadowBanned, - Value? pinnedAt, - Value? archivedAt, - Value? isModerator, - Value?>? extraData, - Value? createdAt, - Value? updatedAt, - Value>? deletedMessages, - Value? rowid}) { + MembersCompanion copyWith({ + Value? userId, + Value? channelCid, + Value? channelRole, + Value? inviteAcceptedAt, + Value? inviteRejectedAt, + Value? invited, + Value? banned, + Value? shadowBanned, + Value? pinnedAt, + Value? archivedAt, + Value? isModerator, + Value?>? extraData, + Value? createdAt, + Value? updatedAt, + Value>? deletedMessages, + Value? rowid, + }) { return MembersCompanion( userId: userId ?? this.userId, channelCid: channelCid ?? this.channelCid, @@ -8299,8 +8216,7 @@ class MembersCompanion extends UpdateCompanion { map['is_moderator'] = Variable(isModerator.value); } if (extraData.present) { - map['extra_data'] = Variable( - $MembersTable.$converterextraDatan.toSql(extraData.value)); + map['extra_data'] = Variable($MembersTable.$converterextraDatan.toSql(extraData.value)); } if (createdAt.present) { map['created_at'] = Variable(createdAt.value); @@ -8309,8 +8225,7 @@ class MembersCompanion extends UpdateCompanion { map['updated_at'] = Variable(updatedAt.value); } if (deletedMessages.present) { - map['deleted_messages'] = Variable( - $MembersTable.$converterdeletedMessages.toSql(deletedMessages.value)); + map['deleted_messages'] = Variable($MembersTable.$converterdeletedMessages.toSql(deletedMessages.value)); } if (rowid.present) { map['rowid'] = Variable(rowid.value); @@ -8347,115 +8262,128 @@ class $ReadsTable extends Reads with TableInfo<$ReadsTable, ReadEntity> { final GeneratedDatabase attachedDatabase; final String? _alias; $ReadsTable(this.attachedDatabase, [this._alias]); - static const VerificationMeta _lastReadMeta = - const VerificationMeta('lastRead'); + static const VerificationMeta _lastReadMeta = const VerificationMeta('lastRead'); @override late final GeneratedColumn lastRead = GeneratedColumn( - 'last_read', aliasedName, false, - type: DriftSqlType.dateTime, requiredDuringInsert: true); + 'last_read', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: true, + ); static const VerificationMeta _userIdMeta = const VerificationMeta('userId'); @override late final GeneratedColumn userId = GeneratedColumn( - 'user_id', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true); - static const VerificationMeta _channelCidMeta = - const VerificationMeta('channelCid'); + 'user_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _channelCidMeta = const VerificationMeta('channelCid'); @override late final GeneratedColumn channelCid = GeneratedColumn( - 'channel_cid', aliasedName, false, - type: DriftSqlType.string, - requiredDuringInsert: true, - defaultConstraints: GeneratedColumn.constraintIsAlways( - 'REFERENCES channels (cid) ON DELETE CASCADE')); - static const VerificationMeta _unreadMessagesMeta = - const VerificationMeta('unreadMessages'); + 'channel_cid', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways('REFERENCES channels (cid) ON DELETE CASCADE'), + ); + static const VerificationMeta _unreadMessagesMeta = const VerificationMeta('unreadMessages'); @override late final GeneratedColumn unreadMessages = GeneratedColumn( - 'unread_messages', aliasedName, false, - type: DriftSqlType.int, - requiredDuringInsert: false, - defaultValue: const Constant(0)); - static const VerificationMeta _lastReadMessageIdMeta = - const VerificationMeta('lastReadMessageId'); - @override - late final GeneratedColumn lastReadMessageId = - GeneratedColumn('last_read_message_id', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); - static const VerificationMeta _lastDeliveredAtMeta = - const VerificationMeta('lastDeliveredAt'); - @override - late final GeneratedColumn lastDeliveredAt = - GeneratedColumn('last_delivered_at', aliasedName, true, - type: DriftSqlType.dateTime, requiredDuringInsert: false); - static const VerificationMeta _lastDeliveredMessageIdMeta = - const VerificationMeta('lastDeliveredMessageId'); - @override - late final GeneratedColumn lastDeliveredMessageId = - GeneratedColumn('last_delivered_message_id', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); + 'unread_messages', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const Constant(0), + ); + static const VerificationMeta _lastReadMessageIdMeta = const VerificationMeta('lastReadMessageId'); + @override + late final GeneratedColumn lastReadMessageId = GeneratedColumn( + 'last_read_message_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _lastDeliveredAtMeta = const VerificationMeta('lastDeliveredAt'); + @override + late final GeneratedColumn lastDeliveredAt = GeneratedColumn( + 'last_delivered_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + static const VerificationMeta _lastDeliveredMessageIdMeta = const VerificationMeta('lastDeliveredMessageId'); + @override + late final GeneratedColumn lastDeliveredMessageId = GeneratedColumn( + 'last_delivered_message_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); @override List get $columns => [ - lastRead, - userId, - channelCid, - unreadMessages, - lastReadMessageId, - lastDeliveredAt, - lastDeliveredMessageId - ]; + lastRead, + userId, + channelCid, + unreadMessages, + lastReadMessageId, + lastDeliveredAt, + lastDeliveredMessageId, + ]; @override String get aliasedName => _alias ?? actualTableName; @override String get actualTableName => $name; static const String $name = 'reads'; @override - VerificationContext validateIntegrity(Insertable instance, - {bool isInserting = false}) { + VerificationContext validateIntegrity(Insertable instance, {bool isInserting = false}) { final context = VerificationContext(); final data = instance.toColumns(true); if (data.containsKey('last_read')) { - context.handle(_lastReadMeta, - lastRead.isAcceptableOrUnknown(data['last_read']!, _lastReadMeta)); + context.handle(_lastReadMeta, lastRead.isAcceptableOrUnknown(data['last_read']!, _lastReadMeta)); } else if (isInserting) { context.missing(_lastReadMeta); } if (data.containsKey('user_id')) { - context.handle(_userIdMeta, - userId.isAcceptableOrUnknown(data['user_id']!, _userIdMeta)); + context.handle(_userIdMeta, userId.isAcceptableOrUnknown(data['user_id']!, _userIdMeta)); } else if (isInserting) { context.missing(_userIdMeta); } if (data.containsKey('channel_cid')) { - context.handle( - _channelCidMeta, - channelCid.isAcceptableOrUnknown( - data['channel_cid']!, _channelCidMeta)); + context.handle(_channelCidMeta, channelCid.isAcceptableOrUnknown(data['channel_cid']!, _channelCidMeta)); } else if (isInserting) { context.missing(_channelCidMeta); } if (data.containsKey('unread_messages')) { context.handle( - _unreadMessagesMeta, - unreadMessages.isAcceptableOrUnknown( - data['unread_messages']!, _unreadMessagesMeta)); + _unreadMessagesMeta, + unreadMessages.isAcceptableOrUnknown(data['unread_messages']!, _unreadMessagesMeta), + ); } if (data.containsKey('last_read_message_id')) { context.handle( - _lastReadMessageIdMeta, - lastReadMessageId.isAcceptableOrUnknown( - data['last_read_message_id']!, _lastReadMessageIdMeta)); + _lastReadMessageIdMeta, + lastReadMessageId.isAcceptableOrUnknown(data['last_read_message_id']!, _lastReadMessageIdMeta), + ); } if (data.containsKey('last_delivered_at')) { context.handle( - _lastDeliveredAtMeta, - lastDeliveredAt.isAcceptableOrUnknown( - data['last_delivered_at']!, _lastDeliveredAtMeta)); + _lastDeliveredAtMeta, + lastDeliveredAt.isAcceptableOrUnknown(data['last_delivered_at']!, _lastDeliveredAtMeta), + ); } if (data.containsKey('last_delivered_message_id')) { context.handle( - _lastDeliveredMessageIdMeta, - lastDeliveredMessageId.isAcceptableOrUnknown( - data['last_delivered_message_id']!, _lastDeliveredMessageIdMeta)); + _lastDeliveredMessageIdMeta, + lastDeliveredMessageId.isAcceptableOrUnknown(data['last_delivered_message_id']!, _lastDeliveredMessageIdMeta), + ); } return context; } @@ -8466,21 +8394,22 @@ class $ReadsTable extends Reads with TableInfo<$ReadsTable, ReadEntity> { ReadEntity map(Map data, {String? tablePrefix}) { final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; return ReadEntity( - lastRead: attachedDatabase.typeMapping - .read(DriftSqlType.dateTime, data['${effectivePrefix}last_read'])!, - userId: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}user_id'])!, - channelCid: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}channel_cid'])!, - unreadMessages: attachedDatabase.typeMapping - .read(DriftSqlType.int, data['${effectivePrefix}unread_messages'])!, + lastRead: attachedDatabase.typeMapping.read(DriftSqlType.dateTime, data['${effectivePrefix}last_read'])!, + userId: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}user_id'])!, + channelCid: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}channel_cid'])!, + unreadMessages: attachedDatabase.typeMapping.read(DriftSqlType.int, data['${effectivePrefix}unread_messages'])!, lastReadMessageId: attachedDatabase.typeMapping.read( - DriftSqlType.string, data['${effectivePrefix}last_read_message_id']), + DriftSqlType.string, + data['${effectivePrefix}last_read_message_id'], + ), lastDeliveredAt: attachedDatabase.typeMapping.read( - DriftSqlType.dateTime, data['${effectivePrefix}last_delivered_at']), + DriftSqlType.dateTime, + data['${effectivePrefix}last_delivered_at'], + ), lastDeliveredMessageId: attachedDatabase.typeMapping.read( - DriftSqlType.string, - data['${effectivePrefix}last_delivered_message_id']), + DriftSqlType.string, + data['${effectivePrefix}last_delivered_message_id'], + ), ); } @@ -8511,14 +8440,15 @@ class ReadEntity extends DataClass implements Insertable { /// Id of the last delivered message final String? lastDeliveredMessageId; - const ReadEntity( - {required this.lastRead, - required this.userId, - required this.channelCid, - required this.unreadMessages, - this.lastReadMessageId, - this.lastDeliveredAt, - this.lastDeliveredMessageId}); + const ReadEntity({ + required this.lastRead, + required this.userId, + required this.channelCid, + required this.unreadMessages, + this.lastReadMessageId, + this.lastDeliveredAt, + this.lastDeliveredMessageId, + }); @override Map toColumns(bool nullToAbsent) { final map = {}; @@ -8533,25 +8463,21 @@ class ReadEntity extends DataClass implements Insertable { map['last_delivered_at'] = Variable(lastDeliveredAt); } if (!nullToAbsent || lastDeliveredMessageId != null) { - map['last_delivered_message_id'] = - Variable(lastDeliveredMessageId); + map['last_delivered_message_id'] = Variable(lastDeliveredMessageId); } return map; } - factory ReadEntity.fromJson(Map json, - {ValueSerializer? serializer}) { + factory ReadEntity.fromJson(Map json, {ValueSerializer? serializer}) { serializer ??= driftRuntimeOptions.defaultSerializer; return ReadEntity( lastRead: serializer.fromJson(json['lastRead']), userId: serializer.fromJson(json['userId']), channelCid: serializer.fromJson(json['channelCid']), unreadMessages: serializer.fromJson(json['unreadMessages']), - lastReadMessageId: - serializer.fromJson(json['lastReadMessageId']), + lastReadMessageId: serializer.fromJson(json['lastReadMessageId']), lastDeliveredAt: serializer.fromJson(json['lastDeliveredAt']), - lastDeliveredMessageId: - serializer.fromJson(json['lastDeliveredMessageId']), + lastDeliveredMessageId: serializer.fromJson(json['lastDeliveredMessageId']), ); } @override @@ -8564,49 +8490,35 @@ class ReadEntity extends DataClass implements Insertable { 'unreadMessages': serializer.toJson(unreadMessages), 'lastReadMessageId': serializer.toJson(lastReadMessageId), 'lastDeliveredAt': serializer.toJson(lastDeliveredAt), - 'lastDeliveredMessageId': - serializer.toJson(lastDeliveredMessageId), + 'lastDeliveredMessageId': serializer.toJson(lastDeliveredMessageId), }; } - ReadEntity copyWith( - {DateTime? lastRead, - String? userId, - String? channelCid, - int? unreadMessages, - Value lastReadMessageId = const Value.absent(), - Value lastDeliveredAt = const Value.absent(), - Value lastDeliveredMessageId = const Value.absent()}) => - ReadEntity( - lastRead: lastRead ?? this.lastRead, - userId: userId ?? this.userId, - channelCid: channelCid ?? this.channelCid, - unreadMessages: unreadMessages ?? this.unreadMessages, - lastReadMessageId: lastReadMessageId.present - ? lastReadMessageId.value - : this.lastReadMessageId, - lastDeliveredAt: lastDeliveredAt.present - ? lastDeliveredAt.value - : this.lastDeliveredAt, - lastDeliveredMessageId: lastDeliveredMessageId.present - ? lastDeliveredMessageId.value - : this.lastDeliveredMessageId, - ); + ReadEntity copyWith({ + DateTime? lastRead, + String? userId, + String? channelCid, + int? unreadMessages, + Value lastReadMessageId = const Value.absent(), + Value lastDeliveredAt = const Value.absent(), + Value lastDeliveredMessageId = const Value.absent(), + }) => ReadEntity( + lastRead: lastRead ?? this.lastRead, + userId: userId ?? this.userId, + channelCid: channelCid ?? this.channelCid, + unreadMessages: unreadMessages ?? this.unreadMessages, + lastReadMessageId: lastReadMessageId.present ? lastReadMessageId.value : this.lastReadMessageId, + lastDeliveredAt: lastDeliveredAt.present ? lastDeliveredAt.value : this.lastDeliveredAt, + lastDeliveredMessageId: lastDeliveredMessageId.present ? lastDeliveredMessageId.value : this.lastDeliveredMessageId, + ); ReadEntity copyWithCompanion(ReadsCompanion data) { return ReadEntity( lastRead: data.lastRead.present ? data.lastRead.value : this.lastRead, userId: data.userId.present ? data.userId.value : this.userId, - channelCid: - data.channelCid.present ? data.channelCid.value : this.channelCid, - unreadMessages: data.unreadMessages.present - ? data.unreadMessages.value - : this.unreadMessages, - lastReadMessageId: data.lastReadMessageId.present - ? data.lastReadMessageId.value - : this.lastReadMessageId, - lastDeliveredAt: data.lastDeliveredAt.present - ? data.lastDeliveredAt.value - : this.lastDeliveredAt, + channelCid: data.channelCid.present ? data.channelCid.value : this.channelCid, + unreadMessages: data.unreadMessages.present ? data.unreadMessages.value : this.unreadMessages, + lastReadMessageId: data.lastReadMessageId.present ? data.lastReadMessageId.value : this.lastReadMessageId, + lastDeliveredAt: data.lastDeliveredAt.present ? data.lastDeliveredAt.value : this.lastDeliveredAt, lastDeliveredMessageId: data.lastDeliveredMessageId.present ? data.lastDeliveredMessageId.value : this.lastDeliveredMessageId, @@ -8628,8 +8540,15 @@ class ReadEntity extends DataClass implements Insertable { } @override - int get hashCode => Object.hash(lastRead, userId, channelCid, unreadMessages, - lastReadMessageId, lastDeliveredAt, lastDeliveredMessageId); + int get hashCode => Object.hash( + lastRead, + userId, + channelCid, + unreadMessages, + lastReadMessageId, + lastDeliveredAt, + lastDeliveredMessageId, + ); @override bool operator ==(Object other) => identical(this, other) || @@ -8671,9 +8590,9 @@ class ReadsCompanion extends UpdateCompanion { this.lastDeliveredAt = const Value.absent(), this.lastDeliveredMessageId = const Value.absent(), this.rowid = const Value.absent(), - }) : lastRead = Value(lastRead), - userId = Value(userId), - channelCid = Value(channelCid); + }) : lastRead = Value(lastRead), + userId = Value(userId), + channelCid = Value(channelCid); static Insertable custom({ Expression? lastRead, Expression? userId, @@ -8691,21 +8610,21 @@ class ReadsCompanion extends UpdateCompanion { if (unreadMessages != null) 'unread_messages': unreadMessages, if (lastReadMessageId != null) 'last_read_message_id': lastReadMessageId, if (lastDeliveredAt != null) 'last_delivered_at': lastDeliveredAt, - if (lastDeliveredMessageId != null) - 'last_delivered_message_id': lastDeliveredMessageId, + if (lastDeliveredMessageId != null) 'last_delivered_message_id': lastDeliveredMessageId, if (rowid != null) 'rowid': rowid, }); } - ReadsCompanion copyWith( - {Value? lastRead, - Value? userId, - Value? channelCid, - Value? unreadMessages, - Value? lastReadMessageId, - Value? lastDeliveredAt, - Value? lastDeliveredMessageId, - Value? rowid}) { + ReadsCompanion copyWith({ + Value? lastRead, + Value? userId, + Value? channelCid, + Value? unreadMessages, + Value? lastReadMessageId, + Value? lastDeliveredAt, + Value? lastDeliveredMessageId, + Value? rowid, + }) { return ReadsCompanion( lastRead: lastRead ?? this.lastRead, userId: userId ?? this.userId, @@ -8713,8 +8632,7 @@ class ReadsCompanion extends UpdateCompanion { unreadMessages: unreadMessages ?? this.unreadMessages, lastReadMessageId: lastReadMessageId ?? this.lastReadMessageId, lastDeliveredAt: lastDeliveredAt ?? this.lastDeliveredAt, - lastDeliveredMessageId: - lastDeliveredMessageId ?? this.lastDeliveredMessageId, + lastDeliveredMessageId: lastDeliveredMessageId ?? this.lastDeliveredMessageId, rowid: rowid ?? this.rowid, ); } @@ -8741,8 +8659,7 @@ class ReadsCompanion extends UpdateCompanion { map['last_delivered_at'] = Variable(lastDeliveredAt.value); } if (lastDeliveredMessageId.present) { - map['last_delivered_message_id'] = - Variable(lastDeliveredMessageId.value); + map['last_delivered_message_id'] = Variable(lastDeliveredMessageId.value); } if (rowid.present) { map['rowid'] = Variable(rowid.value); @@ -8766,24 +8683,29 @@ class ReadsCompanion extends UpdateCompanion { } } -class $ChannelQueriesTable extends ChannelQueries - with TableInfo<$ChannelQueriesTable, ChannelQueryEntity> { +class $ChannelQueriesTable extends ChannelQueries with TableInfo<$ChannelQueriesTable, ChannelQueryEntity> { @override final GeneratedDatabase attachedDatabase; final String? _alias; $ChannelQueriesTable(this.attachedDatabase, [this._alias]); - static const VerificationMeta _queryHashMeta = - const VerificationMeta('queryHash'); + static const VerificationMeta _queryHashMeta = const VerificationMeta('queryHash'); @override late final GeneratedColumn queryHash = GeneratedColumn( - 'query_hash', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true); - static const VerificationMeta _channelCidMeta = - const VerificationMeta('channelCid'); + 'query_hash', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _channelCidMeta = const VerificationMeta('channelCid'); @override late final GeneratedColumn channelCid = GeneratedColumn( - 'channel_cid', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true); + 'channel_cid', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); @override List get $columns => [queryHash, channelCid]; @override @@ -8792,21 +8714,16 @@ class $ChannelQueriesTable extends ChannelQueries String get actualTableName => $name; static const String $name = 'channel_queries'; @override - VerificationContext validateIntegrity(Insertable instance, - {bool isInserting = false}) { + VerificationContext validateIntegrity(Insertable instance, {bool isInserting = false}) { final context = VerificationContext(); final data = instance.toColumns(true); if (data.containsKey('query_hash')) { - context.handle(_queryHashMeta, - queryHash.isAcceptableOrUnknown(data['query_hash']!, _queryHashMeta)); + context.handle(_queryHashMeta, queryHash.isAcceptableOrUnknown(data['query_hash']!, _queryHashMeta)); } else if (isInserting) { context.missing(_queryHashMeta); } if (data.containsKey('channel_cid')) { - context.handle( - _channelCidMeta, - channelCid.isAcceptableOrUnknown( - data['channel_cid']!, _channelCidMeta)); + context.handle(_channelCidMeta, channelCid.isAcceptableOrUnknown(data['channel_cid']!, _channelCidMeta)); } else if (isInserting) { context.missing(_channelCidMeta); } @@ -8819,10 +8736,8 @@ class $ChannelQueriesTable extends ChannelQueries ChannelQueryEntity map(Map data, {String? tablePrefix}) { final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; return ChannelQueryEntity( - queryHash: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}query_hash'])!, - channelCid: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}channel_cid'])!, + queryHash: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}query_hash'])!, + channelCid: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}channel_cid'])!, ); } @@ -8832,8 +8747,7 @@ class $ChannelQueriesTable extends ChannelQueries } } -class ChannelQueryEntity extends DataClass - implements Insertable { +class ChannelQueryEntity extends DataClass implements Insertable { /// The unique hash of this query final String queryHash; @@ -8848,8 +8762,7 @@ class ChannelQueryEntity extends DataClass return map; } - factory ChannelQueryEntity.fromJson(Map json, - {ValueSerializer? serializer}) { + factory ChannelQueryEntity.fromJson(Map json, {ValueSerializer? serializer}) { serializer ??= driftRuntimeOptions.defaultSerializer; return ChannelQueryEntity( queryHash: serializer.fromJson(json['queryHash']), @@ -8865,16 +8778,14 @@ class ChannelQueryEntity extends DataClass }; } - ChannelQueryEntity copyWith({String? queryHash, String? channelCid}) => - ChannelQueryEntity( - queryHash: queryHash ?? this.queryHash, - channelCid: channelCid ?? this.channelCid, - ); + ChannelQueryEntity copyWith({String? queryHash, String? channelCid}) => ChannelQueryEntity( + queryHash: queryHash ?? this.queryHash, + channelCid: channelCid ?? this.channelCid, + ); ChannelQueryEntity copyWithCompanion(ChannelQueriesCompanion data) { return ChannelQueryEntity( queryHash: data.queryHash.present ? data.queryHash.value : this.queryHash, - channelCid: - data.channelCid.present ? data.channelCid.value : this.channelCid, + channelCid: data.channelCid.present ? data.channelCid.value : this.channelCid, ); } @@ -8892,9 +8803,7 @@ class ChannelQueryEntity extends DataClass @override bool operator ==(Object other) => identical(this, other) || - (other is ChannelQueryEntity && - other.queryHash == this.queryHash && - other.channelCid == this.channelCid); + (other is ChannelQueryEntity && other.queryHash == this.queryHash && other.channelCid == this.channelCid); } class ChannelQueriesCompanion extends UpdateCompanion { @@ -8910,8 +8819,8 @@ class ChannelQueriesCompanion extends UpdateCompanion { required String queryHash, required String channelCid, this.rowid = const Value.absent(), - }) : queryHash = Value(queryHash), - channelCid = Value(channelCid); + }) : queryHash = Value(queryHash), + channelCid = Value(channelCid); static Insertable custom({ Expression? queryHash, Expression? channelCid, @@ -8924,10 +8833,7 @@ class ChannelQueriesCompanion extends UpdateCompanion { }); } - ChannelQueriesCompanion copyWith( - {Value? queryHash, - Value? channelCid, - Value? rowid}) { + ChannelQueriesCompanion copyWith({Value? queryHash, Value? channelCid, Value? rowid}) { return ChannelQueriesCompanion( queryHash: queryHash ?? this.queryHash, channelCid: channelCid ?? this.channelCid, @@ -8961,8 +8867,7 @@ class ChannelQueriesCompanion extends UpdateCompanion { } } -class $ConnectionEventsTable extends ConnectionEvents - with TableInfo<$ConnectionEventsTable, ConnectionEventEntity> { +class $ConnectionEventsTable extends ConnectionEvents with TableInfo<$ConnectionEventsTable, ConnectionEventEntity> { @override final GeneratedDatabase attachedDatabase; final String? _alias; @@ -8970,96 +8875,101 @@ class $ConnectionEventsTable extends ConnectionEvents static const VerificationMeta _idMeta = const VerificationMeta('id'); @override late final GeneratedColumn id = GeneratedColumn( - 'id', aliasedName, false, - type: DriftSqlType.int, requiredDuringInsert: false); + 'id', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); static const VerificationMeta _typeMeta = const VerificationMeta('type'); @override late final GeneratedColumn type = GeneratedColumn( - 'type', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true); - @override - late final GeneratedColumnWithTypeConverter?, String> - ownUser = GeneratedColumn('own_user', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false) - .withConverter?>( - $ConnectionEventsTable.$converterownUsern); - static const VerificationMeta _totalUnreadCountMeta = - const VerificationMeta('totalUnreadCount'); + 'type', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + late final GeneratedColumnWithTypeConverter?, String> ownUser = GeneratedColumn( + 'own_user', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ).withConverter?>($ConnectionEventsTable.$converterownUsern); + static const VerificationMeta _totalUnreadCountMeta = const VerificationMeta('totalUnreadCount'); @override late final GeneratedColumn totalUnreadCount = GeneratedColumn( - 'total_unread_count', aliasedName, true, - type: DriftSqlType.int, requiredDuringInsert: false); - static const VerificationMeta _unreadChannelsMeta = - const VerificationMeta('unreadChannels'); + 'total_unread_count', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + static const VerificationMeta _unreadChannelsMeta = const VerificationMeta('unreadChannels'); @override late final GeneratedColumn unreadChannels = GeneratedColumn( - 'unread_channels', aliasedName, true, - type: DriftSqlType.int, requiredDuringInsert: false); - static const VerificationMeta _lastEventAtMeta = - const VerificationMeta('lastEventAt'); + 'unread_channels', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + static const VerificationMeta _lastEventAtMeta = const VerificationMeta('lastEventAt'); @override late final GeneratedColumn lastEventAt = GeneratedColumn( - 'last_event_at', aliasedName, true, - type: DriftSqlType.dateTime, requiredDuringInsert: false); - static const VerificationMeta _lastSyncAtMeta = - const VerificationMeta('lastSyncAt'); + 'last_event_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + static const VerificationMeta _lastSyncAtMeta = const VerificationMeta('lastSyncAt'); @override late final GeneratedColumn lastSyncAt = GeneratedColumn( - 'last_sync_at', aliasedName, true, - type: DriftSqlType.dateTime, requiredDuringInsert: false); + 'last_sync_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); @override - List get $columns => [ - id, - type, - ownUser, - totalUnreadCount, - unreadChannels, - lastEventAt, - lastSyncAt - ]; + List get $columns => [id, type, ownUser, totalUnreadCount, unreadChannels, lastEventAt, lastSyncAt]; @override String get aliasedName => _alias ?? actualTableName; @override String get actualTableName => $name; static const String $name = 'connection_events'; @override - VerificationContext validateIntegrity( - Insertable instance, - {bool isInserting = false}) { + VerificationContext validateIntegrity(Insertable instance, {bool isInserting = false}) { final context = VerificationContext(); final data = instance.toColumns(true); if (data.containsKey('id')) { context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); } if (data.containsKey('type')) { - context.handle( - _typeMeta, type.isAcceptableOrUnknown(data['type']!, _typeMeta)); + context.handle(_typeMeta, type.isAcceptableOrUnknown(data['type']!, _typeMeta)); } else if (isInserting) { context.missing(_typeMeta); } if (data.containsKey('total_unread_count')) { context.handle( - _totalUnreadCountMeta, - totalUnreadCount.isAcceptableOrUnknown( - data['total_unread_count']!, _totalUnreadCountMeta)); + _totalUnreadCountMeta, + totalUnreadCount.isAcceptableOrUnknown(data['total_unread_count']!, _totalUnreadCountMeta), + ); } if (data.containsKey('unread_channels')) { context.handle( - _unreadChannelsMeta, - unreadChannels.isAcceptableOrUnknown( - data['unread_channels']!, _unreadChannelsMeta)); + _unreadChannelsMeta, + unreadChannels.isAcceptableOrUnknown(data['unread_channels']!, _unreadChannelsMeta), + ); } if (data.containsKey('last_event_at')) { - context.handle( - _lastEventAtMeta, - lastEventAt.isAcceptableOrUnknown( - data['last_event_at']!, _lastEventAtMeta)); + context.handle(_lastEventAtMeta, lastEventAt.isAcceptableOrUnknown(data['last_event_at']!, _lastEventAtMeta)); } if (data.containsKey('last_sync_at')) { - context.handle( - _lastSyncAtMeta, - lastSyncAt.isAcceptableOrUnknown( - data['last_sync_at']!, _lastSyncAtMeta)); + context.handle(_lastSyncAtMeta, lastSyncAt.isAcceptableOrUnknown(data['last_sync_at']!, _lastSyncAtMeta)); } return context; } @@ -9070,21 +8980,18 @@ class $ConnectionEventsTable extends ConnectionEvents ConnectionEventEntity map(Map data, {String? tablePrefix}) { final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; return ConnectionEventEntity( - id: attachedDatabase.typeMapping - .read(DriftSqlType.int, data['${effectivePrefix}id'])!, - type: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}type'])!, + id: attachedDatabase.typeMapping.read(DriftSqlType.int, data['${effectivePrefix}id'])!, + type: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}type'])!, ownUser: $ConnectionEventsTable.$converterownUsern.fromSql( - attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}own_user'])), - totalUnreadCount: attachedDatabase.typeMapping - .read(DriftSqlType.int, data['${effectivePrefix}total_unread_count']), - unreadChannels: attachedDatabase.typeMapping - .read(DriftSqlType.int, data['${effectivePrefix}unread_channels']), - lastEventAt: attachedDatabase.typeMapping - .read(DriftSqlType.dateTime, data['${effectivePrefix}last_event_at']), - lastSyncAt: attachedDatabase.typeMapping - .read(DriftSqlType.dateTime, data['${effectivePrefix}last_sync_at']), + attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}own_user']), + ), + totalUnreadCount: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}total_unread_count'], + ), + unreadChannels: attachedDatabase.typeMapping.read(DriftSqlType.int, data['${effectivePrefix}unread_channels']), + lastEventAt: attachedDatabase.typeMapping.read(DriftSqlType.dateTime, data['${effectivePrefix}last_event_at']), + lastSyncAt: attachedDatabase.typeMapping.read(DriftSqlType.dateTime, data['${effectivePrefix}last_sync_at']), ); } @@ -9093,14 +9000,13 @@ class $ConnectionEventsTable extends ConnectionEvents return $ConnectionEventsTable(attachedDatabase, alias); } - static TypeConverter, String> $converterownUser = - MapConverter(); - static TypeConverter?, String?> $converterownUsern = - NullAwareTypeConverter.wrap($converterownUser); + static TypeConverter, String> $converterownUser = MapConverter(); + static TypeConverter?, String?> $converterownUsern = NullAwareTypeConverter.wrap( + $converterownUser, + ); } -class ConnectionEventEntity extends DataClass - implements Insertable { +class ConnectionEventEntity extends DataClass implements Insertable { /// event id final int id; @@ -9121,22 +9027,22 @@ class ConnectionEventEntity extends DataClass /// DateTime of the last sync final DateTime? lastSyncAt; - const ConnectionEventEntity( - {required this.id, - required this.type, - this.ownUser, - this.totalUnreadCount, - this.unreadChannels, - this.lastEventAt, - this.lastSyncAt}); + const ConnectionEventEntity({ + required this.id, + required this.type, + this.ownUser, + this.totalUnreadCount, + this.unreadChannels, + this.lastEventAt, + this.lastSyncAt, + }); @override Map toColumns(bool nullToAbsent) { final map = {}; map['id'] = Variable(id); map['type'] = Variable(type); if (!nullToAbsent || ownUser != null) { - map['own_user'] = Variable( - $ConnectionEventsTable.$converterownUsern.toSql(ownUser)); + map['own_user'] = Variable($ConnectionEventsTable.$converterownUsern.toSql(ownUser)); } if (!nullToAbsent || totalUnreadCount != null) { map['total_unread_count'] = Variable(totalUnreadCount); @@ -9153,8 +9059,7 @@ class ConnectionEventEntity extends DataClass return map; } - factory ConnectionEventEntity.fromJson(Map json, - {ValueSerializer? serializer}) { + factory ConnectionEventEntity.fromJson(Map json, {ValueSerializer? serializer}) { serializer ??= driftRuntimeOptions.defaultSerializer; return ConnectionEventEntity( id: serializer.fromJson(json['id']), @@ -9180,41 +9085,32 @@ class ConnectionEventEntity extends DataClass }; } - ConnectionEventEntity copyWith( - {int? id, - String? type, - Value?> ownUser = const Value.absent(), - Value totalUnreadCount = const Value.absent(), - Value unreadChannels = const Value.absent(), - Value lastEventAt = const Value.absent(), - Value lastSyncAt = const Value.absent()}) => - ConnectionEventEntity( - id: id ?? this.id, - type: type ?? this.type, - ownUser: ownUser.present ? ownUser.value : this.ownUser, - totalUnreadCount: totalUnreadCount.present - ? totalUnreadCount.value - : this.totalUnreadCount, - unreadChannels: - unreadChannels.present ? unreadChannels.value : this.unreadChannels, - lastEventAt: lastEventAt.present ? lastEventAt.value : this.lastEventAt, - lastSyncAt: lastSyncAt.present ? lastSyncAt.value : this.lastSyncAt, - ); + ConnectionEventEntity copyWith({ + int? id, + String? type, + Value?> ownUser = const Value.absent(), + Value totalUnreadCount = const Value.absent(), + Value unreadChannels = const Value.absent(), + Value lastEventAt = const Value.absent(), + Value lastSyncAt = const Value.absent(), + }) => ConnectionEventEntity( + id: id ?? this.id, + type: type ?? this.type, + ownUser: ownUser.present ? ownUser.value : this.ownUser, + totalUnreadCount: totalUnreadCount.present ? totalUnreadCount.value : this.totalUnreadCount, + unreadChannels: unreadChannels.present ? unreadChannels.value : this.unreadChannels, + lastEventAt: lastEventAt.present ? lastEventAt.value : this.lastEventAt, + lastSyncAt: lastSyncAt.present ? lastSyncAt.value : this.lastSyncAt, + ); ConnectionEventEntity copyWithCompanion(ConnectionEventsCompanion data) { return ConnectionEventEntity( id: data.id.present ? data.id.value : this.id, type: data.type.present ? data.type.value : this.type, ownUser: data.ownUser.present ? data.ownUser.value : this.ownUser, - totalUnreadCount: data.totalUnreadCount.present - ? data.totalUnreadCount.value - : this.totalUnreadCount, - unreadChannels: data.unreadChannels.present - ? data.unreadChannels.value - : this.unreadChannels, - lastEventAt: - data.lastEventAt.present ? data.lastEventAt.value : this.lastEventAt, - lastSyncAt: - data.lastSyncAt.present ? data.lastSyncAt.value : this.lastSyncAt, + totalUnreadCount: data.totalUnreadCount.present ? data.totalUnreadCount.value : this.totalUnreadCount, + unreadChannels: data.unreadChannels.present ? data.unreadChannels.value : this.unreadChannels, + lastEventAt: data.lastEventAt.present ? data.lastEventAt.value : this.lastEventAt, + lastSyncAt: data.lastSyncAt.present ? data.lastSyncAt.value : this.lastSyncAt, ); } @@ -9233,8 +9129,7 @@ class ConnectionEventEntity extends DataClass } @override - int get hashCode => Object.hash(id, type, ownUser, totalUnreadCount, - unreadChannels, lastEventAt, lastSyncAt); + int get hashCode => Object.hash(id, type, ownUser, totalUnreadCount, unreadChannels, lastEventAt, lastSyncAt); @override bool operator ==(Object other) => identical(this, other) || @@ -9294,14 +9189,15 @@ class ConnectionEventsCompanion extends UpdateCompanion { }); } - ConnectionEventsCompanion copyWith( - {Value? id, - Value? type, - Value?>? ownUser, - Value? totalUnreadCount, - Value? unreadChannels, - Value? lastEventAt, - Value? lastSyncAt}) { + ConnectionEventsCompanion copyWith({ + Value? id, + Value? type, + Value?>? ownUser, + Value? totalUnreadCount, + Value? unreadChannels, + Value? lastEventAt, + Value? lastSyncAt, + }) { return ConnectionEventsCompanion( id: id ?? this.id, type: type ?? this.type, @@ -9323,8 +9219,7 @@ class ConnectionEventsCompanion extends UpdateCompanion { map['type'] = Variable(type.value); } if (ownUser.present) { - map['own_user'] = Variable( - $ConnectionEventsTable.$converterownUsern.toSql(ownUser.value)); + map['own_user'] = Variable($ConnectionEventsTable.$converterownUsern.toSql(ownUser.value)); } if (totalUnreadCount.present) { map['total_unread_count'] = Variable(totalUnreadCount.value); @@ -9366,251 +9261,235 @@ abstract class _$DriftChatDatabase extends GeneratedDatabase { late final $PinnedMessagesTable pinnedMessages = $PinnedMessagesTable(this); late final $PollsTable polls = $PollsTable(this); late final $PollVotesTable pollVotes = $PollVotesTable(this); - late final $PinnedMessageReactionsTable pinnedMessageReactions = - $PinnedMessageReactionsTable(this); + late final $PinnedMessageReactionsTable pinnedMessageReactions = $PinnedMessageReactionsTable(this); late final $ReactionsTable reactions = $ReactionsTable(this); late final $UsersTable users = $UsersTable(this); late final $MembersTable members = $MembersTable(this); late final $ReadsTable reads = $ReadsTable(this); late final $ChannelQueriesTable channelQueries = $ChannelQueriesTable(this); - late final $ConnectionEventsTable connectionEvents = - $ConnectionEventsTable(this); + late final $ConnectionEventsTable connectionEvents = $ConnectionEventsTable(this); late final UserDao userDao = UserDao(this as DriftChatDatabase); late final ChannelDao channelDao = ChannelDao(this as DriftChatDatabase); late final MessageDao messageDao = MessageDao(this as DriftChatDatabase); - late final DraftMessageDao draftMessageDao = - DraftMessageDao(this as DriftChatDatabase); + late final DraftMessageDao draftMessageDao = DraftMessageDao(this as DriftChatDatabase); late final LocationDao locationDao = LocationDao(this as DriftChatDatabase); - late final PinnedMessageDao pinnedMessageDao = - PinnedMessageDao(this as DriftChatDatabase); - late final PinnedMessageReactionDao pinnedMessageReactionDao = - PinnedMessageReactionDao(this as DriftChatDatabase); + late final PinnedMessageDao pinnedMessageDao = PinnedMessageDao(this as DriftChatDatabase); + late final PinnedMessageReactionDao pinnedMessageReactionDao = PinnedMessageReactionDao(this as DriftChatDatabase); late final MemberDao memberDao = MemberDao(this as DriftChatDatabase); late final PollDao pollDao = PollDao(this as DriftChatDatabase); late final PollVoteDao pollVoteDao = PollVoteDao(this as DriftChatDatabase); late final ReactionDao reactionDao = ReactionDao(this as DriftChatDatabase); late final ReadDao readDao = ReadDao(this as DriftChatDatabase); - late final ChannelQueryDao channelQueryDao = - ChannelQueryDao(this as DriftChatDatabase); - late final ConnectionEventDao connectionEventDao = - ConnectionEventDao(this as DriftChatDatabase); + late final ChannelQueryDao channelQueryDao = ChannelQueryDao(this as DriftChatDatabase); + late final ConnectionEventDao connectionEventDao = ConnectionEventDao(this as DriftChatDatabase); @override - Iterable> get allTables => - allSchemaEntities.whereType>(); + Iterable> get allTables => allSchemaEntities.whereType>(); @override List get allSchemaEntities => [ - channels, - messages, - draftMessages, - locations, - pinnedMessages, - polls, - pollVotes, - pinnedMessageReactions, - reactions, - users, - members, - reads, - channelQueries, - connectionEvents - ]; + channels, + messages, + draftMessages, + locations, + pinnedMessages, + polls, + pollVotes, + pinnedMessageReactions, + reactions, + users, + members, + reads, + channelQueries, + connectionEvents, + ]; @override StreamQueryUpdateRules get streamUpdateRules => const StreamQueryUpdateRules( - [ - WritePropagation( - on: TableUpdateQuery.onTableName('channels', - limitUpdateKind: UpdateKind.delete), - result: [ - TableUpdate('messages', kind: UpdateKind.delete), - ], - ), - WritePropagation( - on: TableUpdateQuery.onTableName('messages', - limitUpdateKind: UpdateKind.delete), - result: [ - TableUpdate('draft_messages', kind: UpdateKind.delete), - ], - ), - WritePropagation( - on: TableUpdateQuery.onTableName('channels', - limitUpdateKind: UpdateKind.delete), - result: [ - TableUpdate('draft_messages', kind: UpdateKind.delete), - ], - ), - WritePropagation( - on: TableUpdateQuery.onTableName('channels', - limitUpdateKind: UpdateKind.delete), - result: [ - TableUpdate('locations', kind: UpdateKind.delete), - ], - ), - WritePropagation( - on: TableUpdateQuery.onTableName('messages', - limitUpdateKind: UpdateKind.delete), - result: [ - TableUpdate('locations', kind: UpdateKind.delete), - ], - ), - WritePropagation( - on: TableUpdateQuery.onTableName('polls', - limitUpdateKind: UpdateKind.delete), - result: [ - TableUpdate('poll_votes', kind: UpdateKind.delete), - ], - ), - WritePropagation( - on: TableUpdateQuery.onTableName('pinned_messages', - limitUpdateKind: UpdateKind.delete), - result: [ - TableUpdate('pinned_message_reactions', kind: UpdateKind.delete), - ], - ), - WritePropagation( - on: TableUpdateQuery.onTableName('messages', - limitUpdateKind: UpdateKind.delete), - result: [ - TableUpdate('reactions', kind: UpdateKind.delete), - ], - ), - WritePropagation( - on: TableUpdateQuery.onTableName('channels', - limitUpdateKind: UpdateKind.delete), - result: [ - TableUpdate('members', kind: UpdateKind.delete), - ], - ), - WritePropagation( - on: TableUpdateQuery.onTableName('channels', - limitUpdateKind: UpdateKind.delete), - result: [ - TableUpdate('reads', kind: UpdateKind.delete), - ], - ), + [ + WritePropagation( + on: TableUpdateQuery.onTableName('channels', limitUpdateKind: UpdateKind.delete), + result: [ + TableUpdate('messages', kind: UpdateKind.delete), ], - ); + ), + WritePropagation( + on: TableUpdateQuery.onTableName('messages', limitUpdateKind: UpdateKind.delete), + result: [ + TableUpdate('draft_messages', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName('channels', limitUpdateKind: UpdateKind.delete), + result: [ + TableUpdate('draft_messages', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName('channels', limitUpdateKind: UpdateKind.delete), + result: [ + TableUpdate('locations', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName('messages', limitUpdateKind: UpdateKind.delete), + result: [ + TableUpdate('locations', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName('polls', limitUpdateKind: UpdateKind.delete), + result: [ + TableUpdate('poll_votes', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName('pinned_messages', limitUpdateKind: UpdateKind.delete), + result: [ + TableUpdate('pinned_message_reactions', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName('messages', limitUpdateKind: UpdateKind.delete), + result: [ + TableUpdate('reactions', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName('channels', limitUpdateKind: UpdateKind.delete), + result: [ + TableUpdate('members', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName('channels', limitUpdateKind: UpdateKind.delete), + result: [ + TableUpdate('reads', kind: UpdateKind.delete), + ], + ), + ], + ); } -typedef $$ChannelsTableCreateCompanionBuilder = ChannelsCompanion Function({ - required String id, - required String type, - required String cid, - Value?> ownCapabilities, - required Map config, - Value frozen, - Value lastMessageAt, - Value createdAt, - Value updatedAt, - Value deletedAt, - Value memberCount, - Value messageCount, - Value createdById, - Value?> filterTags, - Value?> extraData, - Value rowid, -}); -typedef $$ChannelsTableUpdateCompanionBuilder = ChannelsCompanion Function({ - Value id, - Value type, - Value cid, - Value?> ownCapabilities, - Value> config, - Value frozen, - Value lastMessageAt, - Value createdAt, - Value updatedAt, - Value deletedAt, - Value memberCount, - Value messageCount, - Value createdById, - Value?> filterTags, - Value?> extraData, - Value rowid, -}); - -final class $$ChannelsTableReferences - extends BaseReferences<_$DriftChatDatabase, $ChannelsTable, ChannelEntity> { +typedef $$ChannelsTableCreateCompanionBuilder = + ChannelsCompanion Function({ + required String id, + required String type, + required String cid, + Value?> ownCapabilities, + required Map config, + Value frozen, + Value lastMessageAt, + Value createdAt, + Value updatedAt, + Value deletedAt, + Value memberCount, + Value messageCount, + Value createdById, + Value?> filterTags, + Value?> extraData, + Value rowid, + }); +typedef $$ChannelsTableUpdateCompanionBuilder = + ChannelsCompanion Function({ + Value id, + Value type, + Value cid, + Value?> ownCapabilities, + Value> config, + Value frozen, + Value lastMessageAt, + Value createdAt, + Value updatedAt, + Value deletedAt, + Value memberCount, + Value messageCount, + Value createdById, + Value?> filterTags, + Value?> extraData, + Value rowid, + }); + +final class $$ChannelsTableReferences extends BaseReferences<_$DriftChatDatabase, $ChannelsTable, ChannelEntity> { $$ChannelsTableReferences(super.$_db, super.$_table, super.$_typedResult); - static MultiTypedResultKey<$MessagesTable, List> - _messagesRefsTable(_$DriftChatDatabase db) => - MultiTypedResultKey.fromTable(db.messages, - aliasName: $_aliasNameGenerator( - db.channels.cid, db.messages.channelCid)); + static MultiTypedResultKey<$MessagesTable, List> _messagesRefsTable(_$DriftChatDatabase db) => + MultiTypedResultKey.fromTable( + db.messages, + aliasName: $_aliasNameGenerator(db.channels.cid, db.messages.channelCid), + ); $$MessagesTableProcessedTableManager get messagesRefs { - final manager = $$MessagesTableTableManager($_db, $_db.messages).filter( - (f) => f.channelCid.cid.sqlEquals($_itemColumn('cid')!)); + final manager = $$MessagesTableTableManager( + $_db, + $_db.messages, + ).filter((f) => f.channelCid.cid.sqlEquals($_itemColumn('cid')!)); final cache = $_typedResult.readTableOrNull(_messagesRefsTable($_db)); - return ProcessedTableManager( - manager.$state.copyWith(prefetchedData: cache)); + return ProcessedTableManager(manager.$state.copyWith(prefetchedData: cache)); } - static MultiTypedResultKey<$DraftMessagesTable, List> - _draftMessagesRefsTable(_$DriftChatDatabase db) => - MultiTypedResultKey.fromTable(db.draftMessages, - aliasName: $_aliasNameGenerator( - db.channels.cid, db.draftMessages.channelCid)); + static MultiTypedResultKey<$DraftMessagesTable, List> _draftMessagesRefsTable( + _$DriftChatDatabase db, + ) => MultiTypedResultKey.fromTable( + db.draftMessages, + aliasName: $_aliasNameGenerator(db.channels.cid, db.draftMessages.channelCid), + ); $$DraftMessagesTableProcessedTableManager get draftMessagesRefs { - final manager = $$DraftMessagesTableTableManager($_db, $_db.draftMessages) - .filter( - (f) => f.channelCid.cid.sqlEquals($_itemColumn('cid')!)); + final manager = $$DraftMessagesTableTableManager( + $_db, + $_db.draftMessages, + ).filter((f) => f.channelCid.cid.sqlEquals($_itemColumn('cid')!)); final cache = $_typedResult.readTableOrNull(_draftMessagesRefsTable($_db)); - return ProcessedTableManager( - manager.$state.copyWith(prefetchedData: cache)); + return ProcessedTableManager(manager.$state.copyWith(prefetchedData: cache)); } - static MultiTypedResultKey<$LocationsTable, List> - _locationsRefsTable(_$DriftChatDatabase db) => - MultiTypedResultKey.fromTable(db.locations, - aliasName: $_aliasNameGenerator( - db.channels.cid, db.locations.channelCid)); + static MultiTypedResultKey<$LocationsTable, List> _locationsRefsTable(_$DriftChatDatabase db) => + MultiTypedResultKey.fromTable( + db.locations, + aliasName: $_aliasNameGenerator(db.channels.cid, db.locations.channelCid), + ); $$LocationsTableProcessedTableManager get locationsRefs { - final manager = $$LocationsTableTableManager($_db, $_db.locations).filter( - (f) => f.channelCid.cid.sqlEquals($_itemColumn('cid')!)); + final manager = $$LocationsTableTableManager( + $_db, + $_db.locations, + ).filter((f) => f.channelCid.cid.sqlEquals($_itemColumn('cid')!)); final cache = $_typedResult.readTableOrNull(_locationsRefsTable($_db)); - return ProcessedTableManager( - manager.$state.copyWith(prefetchedData: cache)); + return ProcessedTableManager(manager.$state.copyWith(prefetchedData: cache)); } - static MultiTypedResultKey<$MembersTable, List> - _membersRefsTable(_$DriftChatDatabase db) => - MultiTypedResultKey.fromTable(db.members, - aliasName: - $_aliasNameGenerator(db.channels.cid, db.members.channelCid)); + static MultiTypedResultKey<$MembersTable, List> _membersRefsTable(_$DriftChatDatabase db) => + MultiTypedResultKey.fromTable( + db.members, + aliasName: $_aliasNameGenerator(db.channels.cid, db.members.channelCid), + ); $$MembersTableProcessedTableManager get membersRefs { - final manager = $$MembersTableTableManager($_db, $_db.members).filter( - (f) => f.channelCid.cid.sqlEquals($_itemColumn('cid')!)); + final manager = $$MembersTableTableManager( + $_db, + $_db.members, + ).filter((f) => f.channelCid.cid.sqlEquals($_itemColumn('cid')!)); final cache = $_typedResult.readTableOrNull(_membersRefsTable($_db)); - return ProcessedTableManager( - manager.$state.copyWith(prefetchedData: cache)); + return ProcessedTableManager(manager.$state.copyWith(prefetchedData: cache)); } - static MultiTypedResultKey<$ReadsTable, List> _readsRefsTable( - _$DriftChatDatabase db) => - MultiTypedResultKey.fromTable(db.reads, - aliasName: - $_aliasNameGenerator(db.channels.cid, db.reads.channelCid)); + static MultiTypedResultKey<$ReadsTable, List> _readsRefsTable(_$DriftChatDatabase db) => + MultiTypedResultKey.fromTable(db.reads, aliasName: $_aliasNameGenerator(db.channels.cid, db.reads.channelCid)); $$ReadsTableProcessedTableManager get readsRefs { - final manager = $$ReadsTableTableManager($_db, $_db.reads).filter( - (f) => f.channelCid.cid.sqlEquals($_itemColumn('cid')!)); + final manager = $$ReadsTableTableManager( + $_db, + $_db.reads, + ).filter((f) => f.channelCid.cid.sqlEquals($_itemColumn('cid')!)); final cache = $_typedResult.readTableOrNull(_readsRefsTable($_db)); - return ProcessedTableManager( - manager.$state.copyWith(prefetchedData: cache)); + return ProcessedTableManager(manager.$state.copyWith(prefetchedData: cache)); } } -class $$ChannelsTableFilterComposer - extends Composer<_$DriftChatDatabase, $ChannelsTable> { +class $$ChannelsTableFilterComposer extends Composer<_$DriftChatDatabase, $ChannelsTable> { $$ChannelsTableFilterComposer({ required super.$db, required super.$table, @@ -9618,169 +9497,140 @@ class $$ChannelsTableFilterComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnFilters get id => $composableBuilder( - column: $table.id, builder: (column) => ColumnFilters(column)); + ColumnFilters get id => $composableBuilder(column: $table.id, builder: (column) => ColumnFilters(column)); - ColumnFilters get type => $composableBuilder( - column: $table.type, builder: (column) => ColumnFilters(column)); + ColumnFilters get type => $composableBuilder(column: $table.type, builder: (column) => ColumnFilters(column)); - ColumnFilters get cid => $composableBuilder( - column: $table.cid, builder: (column) => ColumnFilters(column)); + ColumnFilters get cid => $composableBuilder(column: $table.cid, builder: (column) => ColumnFilters(column)); - ColumnWithTypeConverterFilters?, List, String> - get ownCapabilities => $composableBuilder( - column: $table.ownCapabilities, - builder: (column) => ColumnWithTypeConverterFilters(column)); + ColumnWithTypeConverterFilters?, List, String> get ownCapabilities => + $composableBuilder(column: $table.ownCapabilities, builder: (column) => ColumnWithTypeConverterFilters(column)); - ColumnWithTypeConverterFilters, Map, - String> - get config => $composableBuilder( - column: $table.config, - builder: (column) => ColumnWithTypeConverterFilters(column)); + ColumnWithTypeConverterFilters, Map, String> get config => + $composableBuilder(column: $table.config, builder: (column) => ColumnWithTypeConverterFilters(column)); - ColumnFilters get frozen => $composableBuilder( - column: $table.frozen, builder: (column) => ColumnFilters(column)); + ColumnFilters get frozen => + $composableBuilder(column: $table.frozen, builder: (column) => ColumnFilters(column)); - ColumnFilters get lastMessageAt => $composableBuilder( - column: $table.lastMessageAt, builder: (column) => ColumnFilters(column)); + ColumnFilters get lastMessageAt => + $composableBuilder(column: $table.lastMessageAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get createdAt => $composableBuilder( - column: $table.createdAt, builder: (column) => ColumnFilters(column)); + ColumnFilters get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get updatedAt => $composableBuilder( - column: $table.updatedAt, builder: (column) => ColumnFilters(column)); + ColumnFilters get updatedAt => + $composableBuilder(column: $table.updatedAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get deletedAt => $composableBuilder( - column: $table.deletedAt, builder: (column) => ColumnFilters(column)); + ColumnFilters get deletedAt => + $composableBuilder(column: $table.deletedAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get memberCount => $composableBuilder( - column: $table.memberCount, builder: (column) => ColumnFilters(column)); + ColumnFilters get memberCount => + $composableBuilder(column: $table.memberCount, builder: (column) => ColumnFilters(column)); - ColumnFilters get messageCount => $composableBuilder( - column: $table.messageCount, builder: (column) => ColumnFilters(column)); + ColumnFilters get messageCount => + $composableBuilder(column: $table.messageCount, builder: (column) => ColumnFilters(column)); - ColumnFilters get createdById => $composableBuilder( - column: $table.createdById, builder: (column) => ColumnFilters(column)); + ColumnFilters get createdById => + $composableBuilder(column: $table.createdById, builder: (column) => ColumnFilters(column)); - ColumnWithTypeConverterFilters?, List, String> - get filterTags => $composableBuilder( - column: $table.filterTags, - builder: (column) => ColumnWithTypeConverterFilters(column)); + ColumnWithTypeConverterFilters?, List, String> get filterTags => + $composableBuilder(column: $table.filterTags, builder: (column) => ColumnWithTypeConverterFilters(column)); - ColumnWithTypeConverterFilters?, Map, - String> - get extraData => $composableBuilder( - column: $table.extraData, - builder: (column) => ColumnWithTypeConverterFilters(column)); + ColumnWithTypeConverterFilters?, Map, String> get extraData => + $composableBuilder(column: $table.extraData, builder: (column) => ColumnWithTypeConverterFilters(column)); - Expression messagesRefs( - Expression Function($$MessagesTableFilterComposer f) f) { + Expression messagesRefs(Expression Function($$MessagesTableFilterComposer f) f) { final $$MessagesTableFilterComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.cid, - referencedTable: $db.messages, - getReferencedColumn: (t) => t.channelCid, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$MessagesTableFilterComposer( - $db: $db, - $table: $db.messages, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.cid, + referencedTable: $db.messages, + getReferencedColumn: (t) => t.channelCid, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$MessagesTableFilterComposer( + $db: $db, + $table: $db.messages, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return f(composer); } - Expression draftMessagesRefs( - Expression Function($$DraftMessagesTableFilterComposer f) f) { + Expression draftMessagesRefs(Expression Function($$DraftMessagesTableFilterComposer f) f) { final $$DraftMessagesTableFilterComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.cid, - referencedTable: $db.draftMessages, - getReferencedColumn: (t) => t.channelCid, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$DraftMessagesTableFilterComposer( - $db: $db, - $table: $db.draftMessages, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.cid, + referencedTable: $db.draftMessages, + getReferencedColumn: (t) => t.channelCid, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$DraftMessagesTableFilterComposer( + $db: $db, + $table: $db.draftMessages, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return f(composer); } - Expression locationsRefs( - Expression Function($$LocationsTableFilterComposer f) f) { + Expression locationsRefs(Expression Function($$LocationsTableFilterComposer f) f) { final $$LocationsTableFilterComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.cid, - referencedTable: $db.locations, - getReferencedColumn: (t) => t.channelCid, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$LocationsTableFilterComposer( - $db: $db, - $table: $db.locations, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.cid, + referencedTable: $db.locations, + getReferencedColumn: (t) => t.channelCid, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$LocationsTableFilterComposer( + $db: $db, + $table: $db.locations, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return f(composer); } - Expression membersRefs( - Expression Function($$MembersTableFilterComposer f) f) { + Expression membersRefs(Expression Function($$MembersTableFilterComposer f) f) { final $$MembersTableFilterComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.cid, - referencedTable: $db.members, - getReferencedColumn: (t) => t.channelCid, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$MembersTableFilterComposer( - $db: $db, - $table: $db.members, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.cid, + referencedTable: $db.members, + getReferencedColumn: (t) => t.channelCid, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$MembersTableFilterComposer( + $db: $db, + $table: $db.members, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return f(composer); } - Expression readsRefs( - Expression Function($$ReadsTableFilterComposer f) f) { + Expression readsRefs(Expression Function($$ReadsTableFilterComposer f) f) { final $$ReadsTableFilterComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.cid, - referencedTable: $db.reads, - getReferencedColumn: (t) => t.channelCid, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$ReadsTableFilterComposer( - $db: $db, - $table: $db.reads, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.cid, + referencedTable: $db.reads, + getReferencedColumn: (t) => t.channelCid, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$ReadsTableFilterComposer( + $db: $db, + $table: $db.reads, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return f(composer); } } -class $$ChannelsTableOrderingComposer - extends Composer<_$DriftChatDatabase, $ChannelsTable> { +class $$ChannelsTableOrderingComposer extends Composer<_$DriftChatDatabase, $ChannelsTable> { $$ChannelsTableOrderingComposer({ required super.$db, required super.$table, @@ -9788,57 +9638,52 @@ class $$ChannelsTableOrderingComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnOrderings get id => $composableBuilder( - column: $table.id, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get id => $composableBuilder(column: $table.id, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get type => $composableBuilder( - column: $table.type, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get type => + $composableBuilder(column: $table.type, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get cid => $composableBuilder( - column: $table.cid, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get cid => + $composableBuilder(column: $table.cid, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get ownCapabilities => $composableBuilder( - column: $table.ownCapabilities, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get ownCapabilities => + $composableBuilder(column: $table.ownCapabilities, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get config => $composableBuilder( - column: $table.config, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get config => + $composableBuilder(column: $table.config, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get frozen => $composableBuilder( - column: $table.frozen, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get frozen => + $composableBuilder(column: $table.frozen, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get lastMessageAt => $composableBuilder( - column: $table.lastMessageAt, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get lastMessageAt => + $composableBuilder(column: $table.lastMessageAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get createdAt => $composableBuilder( - column: $table.createdAt, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get updatedAt => $composableBuilder( - column: $table.updatedAt, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get updatedAt => + $composableBuilder(column: $table.updatedAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get deletedAt => $composableBuilder( - column: $table.deletedAt, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get deletedAt => + $composableBuilder(column: $table.deletedAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get memberCount => $composableBuilder( - column: $table.memberCount, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get memberCount => + $composableBuilder(column: $table.memberCount, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get messageCount => $composableBuilder( - column: $table.messageCount, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get messageCount => + $composableBuilder(column: $table.messageCount, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get createdById => $composableBuilder( - column: $table.createdById, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get createdById => + $composableBuilder(column: $table.createdById, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get filterTags => $composableBuilder( - column: $table.filterTags, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get filterTags => + $composableBuilder(column: $table.filterTags, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get extraData => $composableBuilder( - column: $table.extraData, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get extraData => + $composableBuilder(column: $table.extraData, builder: (column) => ColumnOrderings(column)); } -class $$ChannelsTableAnnotationComposer - extends Composer<_$DriftChatDatabase, $ChannelsTable> { +class $$ChannelsTableAnnotationComposer extends Composer<_$DriftChatDatabase, $ChannelsTable> { $$ChannelsTableAnnotationComposer({ required super.$db, required super.$table, @@ -9846,507 +9691,470 @@ class $$ChannelsTableAnnotationComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - GeneratedColumn get id => - $composableBuilder(column: $table.id, builder: (column) => column); + GeneratedColumn get id => $composableBuilder(column: $table.id, builder: (column) => column); - GeneratedColumn get type => - $composableBuilder(column: $table.type, builder: (column) => column); + GeneratedColumn get type => $composableBuilder(column: $table.type, builder: (column) => column); - GeneratedColumn get cid => - $composableBuilder(column: $table.cid, builder: (column) => column); + GeneratedColumn get cid => $composableBuilder(column: $table.cid, builder: (column) => column); GeneratedColumnWithTypeConverter?, String> get ownCapabilities => - $composableBuilder( - column: $table.ownCapabilities, builder: (column) => column); + $composableBuilder(column: $table.ownCapabilities, builder: (column) => column); GeneratedColumnWithTypeConverter, String> get config => $composableBuilder(column: $table.config, builder: (column) => column); - GeneratedColumn get frozen => - $composableBuilder(column: $table.frozen, builder: (column) => column); + GeneratedColumn get frozen => $composableBuilder(column: $table.frozen, builder: (column) => column); - GeneratedColumn get lastMessageAt => $composableBuilder( - column: $table.lastMessageAt, builder: (column) => column); + GeneratedColumn get lastMessageAt => + $composableBuilder(column: $table.lastMessageAt, builder: (column) => column); - GeneratedColumn get createdAt => - $composableBuilder(column: $table.createdAt, builder: (column) => column); + GeneratedColumn get createdAt => $composableBuilder(column: $table.createdAt, builder: (column) => column); - GeneratedColumn get updatedAt => - $composableBuilder(column: $table.updatedAt, builder: (column) => column); + GeneratedColumn get updatedAt => $composableBuilder(column: $table.updatedAt, builder: (column) => column); - GeneratedColumn get deletedAt => - $composableBuilder(column: $table.deletedAt, builder: (column) => column); + GeneratedColumn get deletedAt => $composableBuilder(column: $table.deletedAt, builder: (column) => column); - GeneratedColumn get memberCount => $composableBuilder( - column: $table.memberCount, builder: (column) => column); + GeneratedColumn get memberCount => $composableBuilder(column: $table.memberCount, builder: (column) => column); - GeneratedColumn get messageCount => $composableBuilder( - column: $table.messageCount, builder: (column) => column); + GeneratedColumn get messageCount => $composableBuilder(column: $table.messageCount, builder: (column) => column); - GeneratedColumn get createdById => $composableBuilder( - column: $table.createdById, builder: (column) => column); + GeneratedColumn get createdById => + $composableBuilder(column: $table.createdById, builder: (column) => column); GeneratedColumnWithTypeConverter?, String> get filterTags => - $composableBuilder( - column: $table.filterTags, builder: (column) => column); + $composableBuilder(column: $table.filterTags, builder: (column) => column); - GeneratedColumnWithTypeConverter?, String> - get extraData => $composableBuilder( - column: $table.extraData, builder: (column) => column); + GeneratedColumnWithTypeConverter?, String> get extraData => + $composableBuilder(column: $table.extraData, builder: (column) => column); - Expression messagesRefs( - Expression Function($$MessagesTableAnnotationComposer a) f) { + Expression messagesRefs(Expression Function($$MessagesTableAnnotationComposer a) f) { final $$MessagesTableAnnotationComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.cid, - referencedTable: $db.messages, - getReferencedColumn: (t) => t.channelCid, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$MessagesTableAnnotationComposer( - $db: $db, - $table: $db.messages, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.cid, + referencedTable: $db.messages, + getReferencedColumn: (t) => t.channelCid, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$MessagesTableAnnotationComposer( + $db: $db, + $table: $db.messages, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return f(composer); } Expression draftMessagesRefs( - Expression Function($$DraftMessagesTableAnnotationComposer a) f) { + Expression Function($$DraftMessagesTableAnnotationComposer a) f, + ) { final $$DraftMessagesTableAnnotationComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.cid, - referencedTable: $db.draftMessages, - getReferencedColumn: (t) => t.channelCid, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$DraftMessagesTableAnnotationComposer( - $db: $db, - $table: $db.draftMessages, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.cid, + referencedTable: $db.draftMessages, + getReferencedColumn: (t) => t.channelCid, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$DraftMessagesTableAnnotationComposer( + $db: $db, + $table: $db.draftMessages, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return f(composer); } - Expression locationsRefs( - Expression Function($$LocationsTableAnnotationComposer a) f) { + Expression locationsRefs(Expression Function($$LocationsTableAnnotationComposer a) f) { final $$LocationsTableAnnotationComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.cid, - referencedTable: $db.locations, - getReferencedColumn: (t) => t.channelCid, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$LocationsTableAnnotationComposer( - $db: $db, - $table: $db.locations, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.cid, + referencedTable: $db.locations, + getReferencedColumn: (t) => t.channelCid, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$LocationsTableAnnotationComposer( + $db: $db, + $table: $db.locations, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return f(composer); } - Expression membersRefs( - Expression Function($$MembersTableAnnotationComposer a) f) { + Expression membersRefs(Expression Function($$MembersTableAnnotationComposer a) f) { final $$MembersTableAnnotationComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.cid, - referencedTable: $db.members, - getReferencedColumn: (t) => t.channelCid, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$MembersTableAnnotationComposer( - $db: $db, - $table: $db.members, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.cid, + referencedTable: $db.members, + getReferencedColumn: (t) => t.channelCid, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$MembersTableAnnotationComposer( + $db: $db, + $table: $db.members, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return f(composer); } - Expression readsRefs( - Expression Function($$ReadsTableAnnotationComposer a) f) { + Expression readsRefs(Expression Function($$ReadsTableAnnotationComposer a) f) { final $$ReadsTableAnnotationComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.cid, - referencedTable: $db.reads, - getReferencedColumn: (t) => t.channelCid, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$ReadsTableAnnotationComposer( - $db: $db, - $table: $db.reads, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.cid, + referencedTable: $db.reads, + getReferencedColumn: (t) => t.channelCid, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$ReadsTableAnnotationComposer( + $db: $db, + $table: $db.reads, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return f(composer); } } -class $$ChannelsTableTableManager extends RootTableManager< - _$DriftChatDatabase, - $ChannelsTable, - ChannelEntity, - $$ChannelsTableFilterComposer, - $$ChannelsTableOrderingComposer, - $$ChannelsTableAnnotationComposer, - $$ChannelsTableCreateCompanionBuilder, - $$ChannelsTableUpdateCompanionBuilder, - (ChannelEntity, $$ChannelsTableReferences), - ChannelEntity, - PrefetchHooks Function( - {bool messagesRefs, - bool draftMessagesRefs, - bool locationsRefs, - bool membersRefs, - bool readsRefs})> { +class $$ChannelsTableTableManager + extends + RootTableManager< + _$DriftChatDatabase, + $ChannelsTable, + ChannelEntity, + $$ChannelsTableFilterComposer, + $$ChannelsTableOrderingComposer, + $$ChannelsTableAnnotationComposer, + $$ChannelsTableCreateCompanionBuilder, + $$ChannelsTableUpdateCompanionBuilder, + (ChannelEntity, $$ChannelsTableReferences), + ChannelEntity, + PrefetchHooks Function({ + bool messagesRefs, + bool draftMessagesRefs, + bool locationsRefs, + bool membersRefs, + bool readsRefs, + }) + > { $$ChannelsTableTableManager(_$DriftChatDatabase db, $ChannelsTable table) - : super(TableManagerState( + : super( + TableManagerState( db: db, table: table, - createFilteringComposer: () => - $$ChannelsTableFilterComposer($db: db, $table: table), - createOrderingComposer: () => - $$ChannelsTableOrderingComposer($db: db, $table: table), - createComputedFieldComposer: () => - $$ChannelsTableAnnotationComposer($db: db, $table: table), - updateCompanionCallback: ({ - Value id = const Value.absent(), - Value type = const Value.absent(), - Value cid = const Value.absent(), - Value?> ownCapabilities = const Value.absent(), - Value> config = const Value.absent(), - Value frozen = const Value.absent(), - Value lastMessageAt = const Value.absent(), - Value createdAt = const Value.absent(), - Value updatedAt = const Value.absent(), - Value deletedAt = const Value.absent(), - Value memberCount = const Value.absent(), - Value messageCount = const Value.absent(), - Value createdById = const Value.absent(), - Value?> filterTags = const Value.absent(), - Value?> extraData = const Value.absent(), - Value rowid = const Value.absent(), - }) => - ChannelsCompanion( - id: id, - type: type, - cid: cid, - ownCapabilities: ownCapabilities, - config: config, - frozen: frozen, - lastMessageAt: lastMessageAt, - createdAt: createdAt, - updatedAt: updatedAt, - deletedAt: deletedAt, - memberCount: memberCount, - messageCount: messageCount, - createdById: createdById, - filterTags: filterTags, - extraData: extraData, - rowid: rowid, - ), - createCompanionCallback: ({ - required String id, - required String type, - required String cid, - Value?> ownCapabilities = const Value.absent(), - required Map config, - Value frozen = const Value.absent(), - Value lastMessageAt = const Value.absent(), - Value createdAt = const Value.absent(), - Value updatedAt = const Value.absent(), - Value deletedAt = const Value.absent(), - Value memberCount = const Value.absent(), - Value messageCount = const Value.absent(), - Value createdById = const Value.absent(), - Value?> filterTags = const Value.absent(), - Value?> extraData = const Value.absent(), - Value rowid = const Value.absent(), - }) => - ChannelsCompanion.insert( - id: id, - type: type, - cid: cid, - ownCapabilities: ownCapabilities, - config: config, - frozen: frozen, - lastMessageAt: lastMessageAt, - createdAt: createdAt, - updatedAt: updatedAt, - deletedAt: deletedAt, - memberCount: memberCount, - messageCount: messageCount, - createdById: createdById, - filterTags: filterTags, - extraData: extraData, - rowid: rowid, - ), - withReferenceMapper: (p0) => p0 - .map((e) => - (e.readTable(table), $$ChannelsTableReferences(db, table, e))) - .toList(), - prefetchHooksCallback: ( - {messagesRefs = false, - draftMessagesRefs = false, - locationsRefs = false, - membersRefs = false, - readsRefs = false}) { - return PrefetchHooks( - db: db, - explicitlyWatchedTables: [ - if (messagesRefs) db.messages, - if (draftMessagesRefs) db.draftMessages, - if (locationsRefs) db.locations, - if (membersRefs) db.members, - if (readsRefs) db.reads - ], - addJoins: null, - getPrefetchedDataCallback: (items) async { - return [ - if (messagesRefs) - await $_getPrefetchedData( - currentTable: table, - referencedTable: - $$ChannelsTableReferences._messagesRefsTable(db), - managerFromTypedResult: (p0) => - $$ChannelsTableReferences(db, table, p0) - .messagesRefs, - referencedItemsForCurrentItem: - (item, referencedItems) => referencedItems - .where((e) => e.channelCid == item.cid), - typedResults: items), - if (draftMessagesRefs) - await $_getPrefetchedData( - currentTable: table, - referencedTable: $$ChannelsTableReferences - ._draftMessagesRefsTable(db), - managerFromTypedResult: (p0) => - $$ChannelsTableReferences(db, table, p0) - .draftMessagesRefs, - referencedItemsForCurrentItem: - (item, referencedItems) => referencedItems - .where((e) => e.channelCid == item.cid), - typedResults: items), - if (locationsRefs) - await $_getPrefetchedData( - currentTable: table, - referencedTable: - $$ChannelsTableReferences._locationsRefsTable(db), - managerFromTypedResult: (p0) => - $$ChannelsTableReferences(db, table, p0) - .locationsRefs, - referencedItemsForCurrentItem: - (item, referencedItems) => referencedItems - .where((e) => e.channelCid == item.cid), - typedResults: items), - if (membersRefs) - await $_getPrefetchedData( - currentTable: table, - referencedTable: - $$ChannelsTableReferences._membersRefsTable(db), - managerFromTypedResult: (p0) => - $$ChannelsTableReferences(db, table, p0) - .membersRefs, - referencedItemsForCurrentItem: - (item, referencedItems) => referencedItems - .where((e) => e.channelCid == item.cid), - typedResults: items), - if (readsRefs) - await $_getPrefetchedData( - currentTable: table, - referencedTable: - $$ChannelsTableReferences._readsRefsTable(db), - managerFromTypedResult: (p0) => - $$ChannelsTableReferences(db, table, p0).readsRefs, - referencedItemsForCurrentItem: - (item, referencedItems) => referencedItems - .where((e) => e.channelCid == item.cid), - typedResults: items) - ]; + createFilteringComposer: () => $$ChannelsTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => $$ChannelsTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => $$ChannelsTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value id = const Value.absent(), + Value type = const Value.absent(), + Value cid = const Value.absent(), + Value?> ownCapabilities = const Value.absent(), + Value> config = const Value.absent(), + Value frozen = const Value.absent(), + Value lastMessageAt = const Value.absent(), + Value createdAt = const Value.absent(), + Value updatedAt = const Value.absent(), + Value deletedAt = const Value.absent(), + Value memberCount = const Value.absent(), + Value messageCount = const Value.absent(), + Value createdById = const Value.absent(), + Value?> filterTags = const Value.absent(), + Value?> extraData = const Value.absent(), + Value rowid = const Value.absent(), + }) => ChannelsCompanion( + id: id, + type: type, + cid: cid, + ownCapabilities: ownCapabilities, + config: config, + frozen: frozen, + lastMessageAt: lastMessageAt, + createdAt: createdAt, + updatedAt: updatedAt, + deletedAt: deletedAt, + memberCount: memberCount, + messageCount: messageCount, + createdById: createdById, + filterTags: filterTags, + extraData: extraData, + rowid: rowid, + ), + createCompanionCallback: + ({ + required String id, + required String type, + required String cid, + Value?> ownCapabilities = const Value.absent(), + required Map config, + Value frozen = const Value.absent(), + Value lastMessageAt = const Value.absent(), + Value createdAt = const Value.absent(), + Value updatedAt = const Value.absent(), + Value deletedAt = const Value.absent(), + Value memberCount = const Value.absent(), + Value messageCount = const Value.absent(), + Value createdById = const Value.absent(), + Value?> filterTags = const Value.absent(), + Value?> extraData = const Value.absent(), + Value rowid = const Value.absent(), + }) => ChannelsCompanion.insert( + id: id, + type: type, + cid: cid, + ownCapabilities: ownCapabilities, + config: config, + frozen: frozen, + lastMessageAt: lastMessageAt, + createdAt: createdAt, + updatedAt: updatedAt, + deletedAt: deletedAt, + memberCount: memberCount, + messageCount: messageCount, + createdById: createdById, + filterTags: filterTags, + extraData: extraData, + rowid: rowid, + ), + withReferenceMapper: (p0) => + p0.map((e) => (e.readTable(table), $$ChannelsTableReferences(db, table, e))).toList(), + prefetchHooksCallback: + ({ + messagesRefs = false, + draftMessagesRefs = false, + locationsRefs = false, + membersRefs = false, + readsRefs = false, + }) { + return PrefetchHooks( + db: db, + explicitlyWatchedTables: [ + if (messagesRefs) db.messages, + if (draftMessagesRefs) db.draftMessages, + if (locationsRefs) db.locations, + if (membersRefs) db.members, + if (readsRefs) db.reads, + ], + addJoins: null, + getPrefetchedDataCallback: (items) async { + return [ + if (messagesRefs) + await $_getPrefetchedData( + currentTable: table, + referencedTable: $$ChannelsTableReferences._messagesRefsTable(db), + managerFromTypedResult: (p0) => $$ChannelsTableReferences(db, table, p0).messagesRefs, + referencedItemsForCurrentItem: (item, referencedItems) => + referencedItems.where((e) => e.channelCid == item.cid), + typedResults: items, + ), + if (draftMessagesRefs) + await $_getPrefetchedData( + currentTable: table, + referencedTable: $$ChannelsTableReferences._draftMessagesRefsTable(db), + managerFromTypedResult: (p0) => $$ChannelsTableReferences(db, table, p0).draftMessagesRefs, + referencedItemsForCurrentItem: (item, referencedItems) => + referencedItems.where((e) => e.channelCid == item.cid), + typedResults: items, + ), + if (locationsRefs) + await $_getPrefetchedData( + currentTable: table, + referencedTable: $$ChannelsTableReferences._locationsRefsTable(db), + managerFromTypedResult: (p0) => $$ChannelsTableReferences(db, table, p0).locationsRefs, + referencedItemsForCurrentItem: (item, referencedItems) => + referencedItems.where((e) => e.channelCid == item.cid), + typedResults: items, + ), + if (membersRefs) + await $_getPrefetchedData( + currentTable: table, + referencedTable: $$ChannelsTableReferences._membersRefsTable(db), + managerFromTypedResult: (p0) => $$ChannelsTableReferences(db, table, p0).membersRefs, + referencedItemsForCurrentItem: (item, referencedItems) => + referencedItems.where((e) => e.channelCid == item.cid), + typedResults: items, + ), + if (readsRefs) + await $_getPrefetchedData( + currentTable: table, + referencedTable: $$ChannelsTableReferences._readsRefsTable(db), + managerFromTypedResult: (p0) => $$ChannelsTableReferences(db, table, p0).readsRefs, + referencedItemsForCurrentItem: (item, referencedItems) => + referencedItems.where((e) => e.channelCid == item.cid), + typedResults: items, + ), + ]; + }, + ); }, - ); - }, - )); + ), + ); } -typedef $$ChannelsTableProcessedTableManager = ProcessedTableManager< - _$DriftChatDatabase, - $ChannelsTable, - ChannelEntity, - $$ChannelsTableFilterComposer, - $$ChannelsTableOrderingComposer, - $$ChannelsTableAnnotationComposer, - $$ChannelsTableCreateCompanionBuilder, - $$ChannelsTableUpdateCompanionBuilder, - (ChannelEntity, $$ChannelsTableReferences), - ChannelEntity, - PrefetchHooks Function( - {bool messagesRefs, +typedef $$ChannelsTableProcessedTableManager = + ProcessedTableManager< + _$DriftChatDatabase, + $ChannelsTable, + ChannelEntity, + $$ChannelsTableFilterComposer, + $$ChannelsTableOrderingComposer, + $$ChannelsTableAnnotationComposer, + $$ChannelsTableCreateCompanionBuilder, + $$ChannelsTableUpdateCompanionBuilder, + (ChannelEntity, $$ChannelsTableReferences), + ChannelEntity, + PrefetchHooks Function({ + bool messagesRefs, bool draftMessagesRefs, bool locationsRefs, bool membersRefs, - bool readsRefs})>; -typedef $$MessagesTableCreateCompanionBuilder = MessagesCompanion Function({ - required String id, - Value messageText, - required List attachments, - required String state, - Value type, - required List mentionedUsers, - Value?> reactionGroups, - Value parentId, - Value quotedMessageId, - Value pollId, - Value replyCount, - Value showInChannel, - Value shadowed, - Value command, - Value localCreatedAt, - Value remoteCreatedAt, - Value localUpdatedAt, - Value remoteUpdatedAt, - Value localDeletedAt, - Value remoteDeletedAt, - Value deletedForMe, - Value messageTextUpdatedAt, - Value userId, - Value channelRole, - Value pinned, - Value pinnedAt, - Value pinExpires, - Value pinnedByUserId, - required String channelCid, - Value?> i18n, - Value?> restrictedVisibility, - Value?> extraData, - Value rowid, -}); -typedef $$MessagesTableUpdateCompanionBuilder = MessagesCompanion Function({ - Value id, - Value messageText, - Value> attachments, - Value state, - Value type, - Value> mentionedUsers, - Value?> reactionGroups, - Value parentId, - Value quotedMessageId, - Value pollId, - Value replyCount, - Value showInChannel, - Value shadowed, - Value command, - Value localCreatedAt, - Value remoteCreatedAt, - Value localUpdatedAt, - Value remoteUpdatedAt, - Value localDeletedAt, - Value remoteDeletedAt, - Value deletedForMe, - Value messageTextUpdatedAt, - Value userId, - Value channelRole, - Value pinned, - Value pinnedAt, - Value pinExpires, - Value pinnedByUserId, - Value channelCid, - Value?> i18n, - Value?> restrictedVisibility, - Value?> extraData, - Value rowid, -}); - -final class $$MessagesTableReferences - extends BaseReferences<_$DriftChatDatabase, $MessagesTable, MessageEntity> { + bool readsRefs, + }) + >; +typedef $$MessagesTableCreateCompanionBuilder = + MessagesCompanion Function({ + required String id, + Value messageText, + required List attachments, + required String state, + Value type, + required List mentionedUsers, + Value?> reactionGroups, + Value parentId, + Value quotedMessageId, + Value pollId, + Value replyCount, + Value showInChannel, + Value shadowed, + Value command, + Value localCreatedAt, + Value remoteCreatedAt, + Value localUpdatedAt, + Value remoteUpdatedAt, + Value localDeletedAt, + Value remoteDeletedAt, + Value deletedForMe, + Value messageTextUpdatedAt, + Value userId, + Value channelRole, + Value pinned, + Value pinnedAt, + Value pinExpires, + Value pinnedByUserId, + required String channelCid, + Value?> i18n, + Value?> restrictedVisibility, + Value?> extraData, + Value rowid, + }); +typedef $$MessagesTableUpdateCompanionBuilder = + MessagesCompanion Function({ + Value id, + Value messageText, + Value> attachments, + Value state, + Value type, + Value> mentionedUsers, + Value?> reactionGroups, + Value parentId, + Value quotedMessageId, + Value pollId, + Value replyCount, + Value showInChannel, + Value shadowed, + Value command, + Value localCreatedAt, + Value remoteCreatedAt, + Value localUpdatedAt, + Value remoteUpdatedAt, + Value localDeletedAt, + Value remoteDeletedAt, + Value deletedForMe, + Value messageTextUpdatedAt, + Value userId, + Value channelRole, + Value pinned, + Value pinnedAt, + Value pinExpires, + Value pinnedByUserId, + Value channelCid, + Value?> i18n, + Value?> restrictedVisibility, + Value?> extraData, + Value rowid, + }); + +final class $$MessagesTableReferences extends BaseReferences<_$DriftChatDatabase, $MessagesTable, MessageEntity> { $$MessagesTableReferences(super.$_db, super.$_table, super.$_typedResult); static $ChannelsTable _channelCidTable(_$DriftChatDatabase db) => - db.channels.createAlias( - $_aliasNameGenerator(db.messages.channelCid, db.channels.cid)); + db.channels.createAlias($_aliasNameGenerator(db.messages.channelCid, db.channels.cid)); $$ChannelsTableProcessedTableManager get channelCid { final $_column = $_itemColumn('channel_cid')!; - final manager = $$ChannelsTableTableManager($_db, $_db.channels) - .filter((f) => f.cid.sqlEquals($_column)); + final manager = $$ChannelsTableTableManager($_db, $_db.channels).filter((f) => f.cid.sqlEquals($_column)); final item = $_typedResult.readTableOrNull(_channelCidTable($_db)); if (item == null) return manager; - return ProcessedTableManager( - manager.$state.copyWith(prefetchedData: [item])); + return ProcessedTableManager(manager.$state.copyWith(prefetchedData: [item])); } - static MultiTypedResultKey<$DraftMessagesTable, List> - _draftMessagesRefsTable(_$DriftChatDatabase db) => - MultiTypedResultKey.fromTable(db.draftMessages, - aliasName: $_aliasNameGenerator( - db.messages.id, db.draftMessages.parentId)); + static MultiTypedResultKey<$DraftMessagesTable, List> _draftMessagesRefsTable( + _$DriftChatDatabase db, + ) => MultiTypedResultKey.fromTable( + db.draftMessages, + aliasName: $_aliasNameGenerator(db.messages.id, db.draftMessages.parentId), + ); $$DraftMessagesTableProcessedTableManager get draftMessagesRefs { - final manager = $$DraftMessagesTableTableManager($_db, $_db.draftMessages) - .filter((f) => f.parentId.id.sqlEquals($_itemColumn('id')!)); + final manager = $$DraftMessagesTableTableManager( + $_db, + $_db.draftMessages, + ).filter((f) => f.parentId.id.sqlEquals($_itemColumn('id')!)); final cache = $_typedResult.readTableOrNull(_draftMessagesRefsTable($_db)); - return ProcessedTableManager( - manager.$state.copyWith(prefetchedData: cache)); + return ProcessedTableManager(manager.$state.copyWith(prefetchedData: cache)); } - static MultiTypedResultKey<$LocationsTable, List> - _locationsRefsTable(_$DriftChatDatabase db) => - MultiTypedResultKey.fromTable(db.locations, - aliasName: - $_aliasNameGenerator(db.messages.id, db.locations.messageId)); + static MultiTypedResultKey<$LocationsTable, List> _locationsRefsTable(_$DriftChatDatabase db) => + MultiTypedResultKey.fromTable( + db.locations, + aliasName: $_aliasNameGenerator(db.messages.id, db.locations.messageId), + ); $$LocationsTableProcessedTableManager get locationsRefs { - final manager = $$LocationsTableTableManager($_db, $_db.locations) - .filter((f) => f.messageId.id.sqlEquals($_itemColumn('id')!)); + final manager = $$LocationsTableTableManager( + $_db, + $_db.locations, + ).filter((f) => f.messageId.id.sqlEquals($_itemColumn('id')!)); final cache = $_typedResult.readTableOrNull(_locationsRefsTable($_db)); - return ProcessedTableManager( - manager.$state.copyWith(prefetchedData: cache)); + return ProcessedTableManager(manager.$state.copyWith(prefetchedData: cache)); } - static MultiTypedResultKey<$ReactionsTable, List> - _reactionsRefsTable(_$DriftChatDatabase db) => - MultiTypedResultKey.fromTable(db.reactions, - aliasName: - $_aliasNameGenerator(db.messages.id, db.reactions.messageId)); + static MultiTypedResultKey<$ReactionsTable, List> _reactionsRefsTable(_$DriftChatDatabase db) => + MultiTypedResultKey.fromTable( + db.reactions, + aliasName: $_aliasNameGenerator(db.messages.id, db.reactions.messageId), + ); $$ReactionsTableProcessedTableManager get reactionsRefs { - final manager = $$ReactionsTableTableManager($_db, $_db.reactions) - .filter((f) => f.messageId.id.sqlEquals($_itemColumn('id')!)); + final manager = $$ReactionsTableTableManager( + $_db, + $_db.reactions, + ).filter((f) => f.messageId.id.sqlEquals($_itemColumn('id')!)); final cache = $_typedResult.readTableOrNull(_reactionsRefsTable($_db)); - return ProcessedTableManager( - manager.$state.copyWith(prefetchedData: cache)); + return ProcessedTableManager(manager.$state.copyWith(prefetchedData: cache)); } } -class $$MessagesTableFilterComposer - extends Composer<_$DriftChatDatabase, $MessagesTable> { +class $$MessagesTableFilterComposer extends Composer<_$DriftChatDatabase, $MessagesTable> { $$MessagesTableFilterComposer({ required super.$db, required super.$table, @@ -10354,209 +10162,173 @@ class $$MessagesTableFilterComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnFilters get id => $composableBuilder( - column: $table.id, builder: (column) => ColumnFilters(column)); + ColumnFilters get id => $composableBuilder(column: $table.id, builder: (column) => ColumnFilters(column)); - ColumnFilters get messageText => $composableBuilder( - column: $table.messageText, builder: (column) => ColumnFilters(column)); + ColumnFilters get messageText => + $composableBuilder(column: $table.messageText, builder: (column) => ColumnFilters(column)); - ColumnWithTypeConverterFilters, List, String> - get attachments => $composableBuilder( - column: $table.attachments, - builder: (column) => ColumnWithTypeConverterFilters(column)); + ColumnWithTypeConverterFilters, List, String> get attachments => + $composableBuilder(column: $table.attachments, builder: (column) => ColumnWithTypeConverterFilters(column)); - ColumnFilters get state => $composableBuilder( - column: $table.state, builder: (column) => ColumnFilters(column)); + ColumnFilters get state => + $composableBuilder(column: $table.state, builder: (column) => ColumnFilters(column)); - ColumnFilters get type => $composableBuilder( - column: $table.type, builder: (column) => ColumnFilters(column)); + ColumnFilters get type => $composableBuilder(column: $table.type, builder: (column) => ColumnFilters(column)); - ColumnWithTypeConverterFilters, List, String> - get mentionedUsers => $composableBuilder( - column: $table.mentionedUsers, - builder: (column) => ColumnWithTypeConverterFilters(column)); + ColumnWithTypeConverterFilters, List, String> get mentionedUsers => + $composableBuilder(column: $table.mentionedUsers, builder: (column) => ColumnWithTypeConverterFilters(column)); - ColumnWithTypeConverterFilters?, - Map, String> - get reactionGroups => $composableBuilder( - column: $table.reactionGroups, - builder: (column) => ColumnWithTypeConverterFilters(column)); + ColumnWithTypeConverterFilters?, Map, String> get reactionGroups => + $composableBuilder(column: $table.reactionGroups, builder: (column) => ColumnWithTypeConverterFilters(column)); - ColumnFilters get parentId => $composableBuilder( - column: $table.parentId, builder: (column) => ColumnFilters(column)); + ColumnFilters get parentId => + $composableBuilder(column: $table.parentId, builder: (column) => ColumnFilters(column)); - ColumnFilters get quotedMessageId => $composableBuilder( - column: $table.quotedMessageId, - builder: (column) => ColumnFilters(column)); + ColumnFilters get quotedMessageId => + $composableBuilder(column: $table.quotedMessageId, builder: (column) => ColumnFilters(column)); - ColumnFilters get pollId => $composableBuilder( - column: $table.pollId, builder: (column) => ColumnFilters(column)); + ColumnFilters get pollId => + $composableBuilder(column: $table.pollId, builder: (column) => ColumnFilters(column)); - ColumnFilters get replyCount => $composableBuilder( - column: $table.replyCount, builder: (column) => ColumnFilters(column)); + ColumnFilters get replyCount => + $composableBuilder(column: $table.replyCount, builder: (column) => ColumnFilters(column)); - ColumnFilters get showInChannel => $composableBuilder( - column: $table.showInChannel, builder: (column) => ColumnFilters(column)); + ColumnFilters get showInChannel => + $composableBuilder(column: $table.showInChannel, builder: (column) => ColumnFilters(column)); - ColumnFilters get shadowed => $composableBuilder( - column: $table.shadowed, builder: (column) => ColumnFilters(column)); + ColumnFilters get shadowed => + $composableBuilder(column: $table.shadowed, builder: (column) => ColumnFilters(column)); - ColumnFilters get command => $composableBuilder( - column: $table.command, builder: (column) => ColumnFilters(column)); + ColumnFilters get command => + $composableBuilder(column: $table.command, builder: (column) => ColumnFilters(column)); - ColumnFilters get localCreatedAt => $composableBuilder( - column: $table.localCreatedAt, - builder: (column) => ColumnFilters(column)); + ColumnFilters get localCreatedAt => + $composableBuilder(column: $table.localCreatedAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get remoteCreatedAt => $composableBuilder( - column: $table.remoteCreatedAt, - builder: (column) => ColumnFilters(column)); + ColumnFilters get remoteCreatedAt => + $composableBuilder(column: $table.remoteCreatedAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get localUpdatedAt => $composableBuilder( - column: $table.localUpdatedAt, - builder: (column) => ColumnFilters(column)); + ColumnFilters get localUpdatedAt => + $composableBuilder(column: $table.localUpdatedAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get remoteUpdatedAt => $composableBuilder( - column: $table.remoteUpdatedAt, - builder: (column) => ColumnFilters(column)); + ColumnFilters get remoteUpdatedAt => + $composableBuilder(column: $table.remoteUpdatedAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get localDeletedAt => $composableBuilder( - column: $table.localDeletedAt, - builder: (column) => ColumnFilters(column)); + ColumnFilters get localDeletedAt => + $composableBuilder(column: $table.localDeletedAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get remoteDeletedAt => $composableBuilder( - column: $table.remoteDeletedAt, - builder: (column) => ColumnFilters(column)); + ColumnFilters get remoteDeletedAt => + $composableBuilder(column: $table.remoteDeletedAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get deletedForMe => $composableBuilder( - column: $table.deletedForMe, builder: (column) => ColumnFilters(column)); + ColumnFilters get deletedForMe => + $composableBuilder(column: $table.deletedForMe, builder: (column) => ColumnFilters(column)); - ColumnFilters get messageTextUpdatedAt => $composableBuilder( - column: $table.messageTextUpdatedAt, - builder: (column) => ColumnFilters(column)); + ColumnFilters get messageTextUpdatedAt => + $composableBuilder(column: $table.messageTextUpdatedAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get userId => $composableBuilder( - column: $table.userId, builder: (column) => ColumnFilters(column)); + ColumnFilters get userId => + $composableBuilder(column: $table.userId, builder: (column) => ColumnFilters(column)); - ColumnFilters get channelRole => $composableBuilder( - column: $table.channelRole, builder: (column) => ColumnFilters(column)); + ColumnFilters get channelRole => + $composableBuilder(column: $table.channelRole, builder: (column) => ColumnFilters(column)); - ColumnFilters get pinned => $composableBuilder( - column: $table.pinned, builder: (column) => ColumnFilters(column)); + ColumnFilters get pinned => + $composableBuilder(column: $table.pinned, builder: (column) => ColumnFilters(column)); - ColumnFilters get pinnedAt => $composableBuilder( - column: $table.pinnedAt, builder: (column) => ColumnFilters(column)); + ColumnFilters get pinnedAt => + $composableBuilder(column: $table.pinnedAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get pinExpires => $composableBuilder( - column: $table.pinExpires, builder: (column) => ColumnFilters(column)); + ColumnFilters get pinExpires => + $composableBuilder(column: $table.pinExpires, builder: (column) => ColumnFilters(column)); - ColumnFilters get pinnedByUserId => $composableBuilder( - column: $table.pinnedByUserId, - builder: (column) => ColumnFilters(column)); + ColumnFilters get pinnedByUserId => + $composableBuilder(column: $table.pinnedByUserId, builder: (column) => ColumnFilters(column)); - ColumnWithTypeConverterFilters?, Map, - String> - get i18n => $composableBuilder( - column: $table.i18n, - builder: (column) => ColumnWithTypeConverterFilters(column)); + ColumnWithTypeConverterFilters?, Map, String> get i18n => + $composableBuilder(column: $table.i18n, builder: (column) => ColumnWithTypeConverterFilters(column)); - ColumnWithTypeConverterFilters?, List, String> - get restrictedVisibility => $composableBuilder( - column: $table.restrictedVisibility, - builder: (column) => ColumnWithTypeConverterFilters(column)); + ColumnWithTypeConverterFilters?, List, String> get restrictedVisibility => $composableBuilder( + column: $table.restrictedVisibility, + builder: (column) => ColumnWithTypeConverterFilters(column), + ); - ColumnWithTypeConverterFilters?, Map, - String> - get extraData => $composableBuilder( - column: $table.extraData, - builder: (column) => ColumnWithTypeConverterFilters(column)); + ColumnWithTypeConverterFilters?, Map, String> get extraData => + $composableBuilder(column: $table.extraData, builder: (column) => ColumnWithTypeConverterFilters(column)); $$ChannelsTableFilterComposer get channelCid { final $$ChannelsTableFilterComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.channelCid, - referencedTable: $db.channels, - getReferencedColumn: (t) => t.cid, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$ChannelsTableFilterComposer( - $db: $db, - $table: $db.channels, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.channelCid, + referencedTable: $db.channels, + getReferencedColumn: (t) => t.cid, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$ChannelsTableFilterComposer( + $db: $db, + $table: $db.channels, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return composer; } - Expression draftMessagesRefs( - Expression Function($$DraftMessagesTableFilterComposer f) f) { + Expression draftMessagesRefs(Expression Function($$DraftMessagesTableFilterComposer f) f) { final $$DraftMessagesTableFilterComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.id, - referencedTable: $db.draftMessages, - getReferencedColumn: (t) => t.parentId, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$DraftMessagesTableFilterComposer( - $db: $db, - $table: $db.draftMessages, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.draftMessages, + getReferencedColumn: (t) => t.parentId, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$DraftMessagesTableFilterComposer( + $db: $db, + $table: $db.draftMessages, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return f(composer); } - Expression locationsRefs( - Expression Function($$LocationsTableFilterComposer f) f) { + Expression locationsRefs(Expression Function($$LocationsTableFilterComposer f) f) { final $$LocationsTableFilterComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.id, - referencedTable: $db.locations, - getReferencedColumn: (t) => t.messageId, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$LocationsTableFilterComposer( - $db: $db, - $table: $db.locations, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.locations, + getReferencedColumn: (t) => t.messageId, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$LocationsTableFilterComposer( + $db: $db, + $table: $db.locations, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return f(composer); } - Expression reactionsRefs( - Expression Function($$ReactionsTableFilterComposer f) f) { + Expression reactionsRefs(Expression Function($$ReactionsTableFilterComposer f) f) { final $$ReactionsTableFilterComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.id, - referencedTable: $db.reactions, - getReferencedColumn: (t) => t.messageId, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$ReactionsTableFilterComposer( - $db: $db, - $table: $db.reactions, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.reactions, + getReferencedColumn: (t) => t.messageId, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$ReactionsTableFilterComposer( + $db: $db, + $table: $db.reactions, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return f(composer); } } -class $$MessagesTableOrderingComposer - extends Composer<_$DriftChatDatabase, $MessagesTable> { +class $$MessagesTableOrderingComposer extends Composer<_$DriftChatDatabase, $MessagesTable> { $$MessagesTableOrderingComposer({ required super.$db, required super.$table, @@ -10564,136 +10336,118 @@ class $$MessagesTableOrderingComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnOrderings get id => $composableBuilder( - column: $table.id, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get id => $composableBuilder(column: $table.id, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get messageText => $composableBuilder( - column: $table.messageText, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get messageText => + $composableBuilder(column: $table.messageText, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get attachments => $composableBuilder( - column: $table.attachments, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get attachments => + $composableBuilder(column: $table.attachments, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get state => $composableBuilder( - column: $table.state, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get state => + $composableBuilder(column: $table.state, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get type => $composableBuilder( - column: $table.type, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get type => + $composableBuilder(column: $table.type, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get mentionedUsers => $composableBuilder( - column: $table.mentionedUsers, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get mentionedUsers => + $composableBuilder(column: $table.mentionedUsers, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get reactionGroups => $composableBuilder( - column: $table.reactionGroups, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get reactionGroups => + $composableBuilder(column: $table.reactionGroups, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get parentId => $composableBuilder( - column: $table.parentId, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get parentId => + $composableBuilder(column: $table.parentId, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get quotedMessageId => $composableBuilder( - column: $table.quotedMessageId, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get quotedMessageId => + $composableBuilder(column: $table.quotedMessageId, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get pollId => $composableBuilder( - column: $table.pollId, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get pollId => + $composableBuilder(column: $table.pollId, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get replyCount => $composableBuilder( - column: $table.replyCount, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get replyCount => + $composableBuilder(column: $table.replyCount, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get showInChannel => $composableBuilder( - column: $table.showInChannel, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get showInChannel => + $composableBuilder(column: $table.showInChannel, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get shadowed => $composableBuilder( - column: $table.shadowed, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get shadowed => + $composableBuilder(column: $table.shadowed, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get command => $composableBuilder( - column: $table.command, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get command => + $composableBuilder(column: $table.command, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get localCreatedAt => $composableBuilder( - column: $table.localCreatedAt, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get localCreatedAt => + $composableBuilder(column: $table.localCreatedAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get remoteCreatedAt => $composableBuilder( - column: $table.remoteCreatedAt, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get remoteCreatedAt => + $composableBuilder(column: $table.remoteCreatedAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get localUpdatedAt => $composableBuilder( - column: $table.localUpdatedAt, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get localUpdatedAt => + $composableBuilder(column: $table.localUpdatedAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get remoteUpdatedAt => $composableBuilder( - column: $table.remoteUpdatedAt, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get remoteUpdatedAt => + $composableBuilder(column: $table.remoteUpdatedAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get localDeletedAt => $composableBuilder( - column: $table.localDeletedAt, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get localDeletedAt => + $composableBuilder(column: $table.localDeletedAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get remoteDeletedAt => $composableBuilder( - column: $table.remoteDeletedAt, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get remoteDeletedAt => + $composableBuilder(column: $table.remoteDeletedAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get deletedForMe => $composableBuilder( - column: $table.deletedForMe, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get deletedForMe => + $composableBuilder(column: $table.deletedForMe, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get messageTextUpdatedAt => $composableBuilder( - column: $table.messageTextUpdatedAt, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get messageTextUpdatedAt => + $composableBuilder(column: $table.messageTextUpdatedAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get userId => $composableBuilder( - column: $table.userId, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get userId => + $composableBuilder(column: $table.userId, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get channelRole => $composableBuilder( - column: $table.channelRole, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get channelRole => + $composableBuilder(column: $table.channelRole, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get pinned => $composableBuilder( - column: $table.pinned, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get pinned => + $composableBuilder(column: $table.pinned, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get pinnedAt => $composableBuilder( - column: $table.pinnedAt, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get pinnedAt => + $composableBuilder(column: $table.pinnedAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get pinExpires => $composableBuilder( - column: $table.pinExpires, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get pinExpires => + $composableBuilder(column: $table.pinExpires, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get pinnedByUserId => $composableBuilder( - column: $table.pinnedByUserId, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get pinnedByUserId => + $composableBuilder(column: $table.pinnedByUserId, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get i18n => $composableBuilder( - column: $table.i18n, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get i18n => + $composableBuilder(column: $table.i18n, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get restrictedVisibility => $composableBuilder( - column: $table.restrictedVisibility, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get restrictedVisibility => + $composableBuilder(column: $table.restrictedVisibility, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get extraData => $composableBuilder( - column: $table.extraData, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get extraData => + $composableBuilder(column: $table.extraData, builder: (column) => ColumnOrderings(column)); $$ChannelsTableOrderingComposer get channelCid { final $$ChannelsTableOrderingComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.channelCid, - referencedTable: $db.channels, - getReferencedColumn: (t) => t.cid, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$ChannelsTableOrderingComposer( - $db: $db, - $table: $db.channels, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.channelCid, + referencedTable: $db.channels, + getReferencedColumn: (t) => t.cid, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$ChannelsTableOrderingComposer( + $db: $db, + $table: $db.channels, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return composer; } } -class $$MessagesTableAnnotationComposer - extends Composer<_$DriftChatDatabase, $MessagesTable> { +class $$MessagesTableAnnotationComposer extends Composer<_$DriftChatDatabase, $MessagesTable> { $$MessagesTableAnnotationComposer({ required super.$db, required super.$table, @@ -10701,537 +10455,484 @@ class $$MessagesTableAnnotationComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - GeneratedColumn get id => - $composableBuilder(column: $table.id, builder: (column) => column); + GeneratedColumn get id => $composableBuilder(column: $table.id, builder: (column) => column); - GeneratedColumn get messageText => $composableBuilder( - column: $table.messageText, builder: (column) => column); + GeneratedColumn get messageText => + $composableBuilder(column: $table.messageText, builder: (column) => column); GeneratedColumnWithTypeConverter, String> get attachments => - $composableBuilder( - column: $table.attachments, builder: (column) => column); + $composableBuilder(column: $table.attachments, builder: (column) => column); - GeneratedColumn get state => - $composableBuilder(column: $table.state, builder: (column) => column); + GeneratedColumn get state => $composableBuilder(column: $table.state, builder: (column) => column); - GeneratedColumn get type => - $composableBuilder(column: $table.type, builder: (column) => column); + GeneratedColumn get type => $composableBuilder(column: $table.type, builder: (column) => column); GeneratedColumnWithTypeConverter, String> get mentionedUsers => - $composableBuilder( - column: $table.mentionedUsers, builder: (column) => column); + $composableBuilder(column: $table.mentionedUsers, builder: (column) => column); - GeneratedColumnWithTypeConverter?, String> - get reactionGroups => $composableBuilder( - column: $table.reactionGroups, builder: (column) => column); + GeneratedColumnWithTypeConverter?, String> get reactionGroups => + $composableBuilder(column: $table.reactionGroups, builder: (column) => column); - GeneratedColumn get parentId => - $composableBuilder(column: $table.parentId, builder: (column) => column); + GeneratedColumn get parentId => $composableBuilder(column: $table.parentId, builder: (column) => column); - GeneratedColumn get quotedMessageId => $composableBuilder( - column: $table.quotedMessageId, builder: (column) => column); + GeneratedColumn get quotedMessageId => + $composableBuilder(column: $table.quotedMessageId, builder: (column) => column); - GeneratedColumn get pollId => - $composableBuilder(column: $table.pollId, builder: (column) => column); + GeneratedColumn get pollId => $composableBuilder(column: $table.pollId, builder: (column) => column); - GeneratedColumn get replyCount => $composableBuilder( - column: $table.replyCount, builder: (column) => column); + GeneratedColumn get replyCount => $composableBuilder(column: $table.replyCount, builder: (column) => column); - GeneratedColumn get showInChannel => $composableBuilder( - column: $table.showInChannel, builder: (column) => column); + GeneratedColumn get showInChannel => + $composableBuilder(column: $table.showInChannel, builder: (column) => column); - GeneratedColumn get shadowed => - $composableBuilder(column: $table.shadowed, builder: (column) => column); + GeneratedColumn get shadowed => $composableBuilder(column: $table.shadowed, builder: (column) => column); - GeneratedColumn get command => - $composableBuilder(column: $table.command, builder: (column) => column); + GeneratedColumn get command => $composableBuilder(column: $table.command, builder: (column) => column); - GeneratedColumn get localCreatedAt => $composableBuilder( - column: $table.localCreatedAt, builder: (column) => column); + GeneratedColumn get localCreatedAt => + $composableBuilder(column: $table.localCreatedAt, builder: (column) => column); - GeneratedColumn get remoteCreatedAt => $composableBuilder( - column: $table.remoteCreatedAt, builder: (column) => column); + GeneratedColumn get remoteCreatedAt => + $composableBuilder(column: $table.remoteCreatedAt, builder: (column) => column); - GeneratedColumn get localUpdatedAt => $composableBuilder( - column: $table.localUpdatedAt, builder: (column) => column); + GeneratedColumn get localUpdatedAt => + $composableBuilder(column: $table.localUpdatedAt, builder: (column) => column); - GeneratedColumn get remoteUpdatedAt => $composableBuilder( - column: $table.remoteUpdatedAt, builder: (column) => column); + GeneratedColumn get remoteUpdatedAt => + $composableBuilder(column: $table.remoteUpdatedAt, builder: (column) => column); - GeneratedColumn get localDeletedAt => $composableBuilder( - column: $table.localDeletedAt, builder: (column) => column); + GeneratedColumn get localDeletedAt => + $composableBuilder(column: $table.localDeletedAt, builder: (column) => column); - GeneratedColumn get remoteDeletedAt => $composableBuilder( - column: $table.remoteDeletedAt, builder: (column) => column); + GeneratedColumn get remoteDeletedAt => + $composableBuilder(column: $table.remoteDeletedAt, builder: (column) => column); - GeneratedColumn get deletedForMe => $composableBuilder( - column: $table.deletedForMe, builder: (column) => column); + GeneratedColumn get deletedForMe => + $composableBuilder(column: $table.deletedForMe, builder: (column) => column); - GeneratedColumn get messageTextUpdatedAt => $composableBuilder( - column: $table.messageTextUpdatedAt, builder: (column) => column); + GeneratedColumn get messageTextUpdatedAt => + $composableBuilder(column: $table.messageTextUpdatedAt, builder: (column) => column); - GeneratedColumn get userId => - $composableBuilder(column: $table.userId, builder: (column) => column); + GeneratedColumn get userId => $composableBuilder(column: $table.userId, builder: (column) => column); - GeneratedColumn get channelRole => $composableBuilder( - column: $table.channelRole, builder: (column) => column); + GeneratedColumn get channelRole => + $composableBuilder(column: $table.channelRole, builder: (column) => column); - GeneratedColumn get pinned => - $composableBuilder(column: $table.pinned, builder: (column) => column); + GeneratedColumn get pinned => $composableBuilder(column: $table.pinned, builder: (column) => column); - GeneratedColumn get pinnedAt => - $composableBuilder(column: $table.pinnedAt, builder: (column) => column); + GeneratedColumn get pinnedAt => $composableBuilder(column: $table.pinnedAt, builder: (column) => column); - GeneratedColumn get pinExpires => $composableBuilder( - column: $table.pinExpires, builder: (column) => column); + GeneratedColumn get pinExpires => + $composableBuilder(column: $table.pinExpires, builder: (column) => column); - GeneratedColumn get pinnedByUserId => $composableBuilder( - column: $table.pinnedByUserId, builder: (column) => column); + GeneratedColumn get pinnedByUserId => + $composableBuilder(column: $table.pinnedByUserId, builder: (column) => column); GeneratedColumnWithTypeConverter?, String> get i18n => $composableBuilder(column: $table.i18n, builder: (column) => column); - GeneratedColumnWithTypeConverter?, String> - get restrictedVisibility => $composableBuilder( - column: $table.restrictedVisibility, builder: (column) => column); + GeneratedColumnWithTypeConverter?, String> get restrictedVisibility => + $composableBuilder(column: $table.restrictedVisibility, builder: (column) => column); - GeneratedColumnWithTypeConverter?, String> - get extraData => $composableBuilder( - column: $table.extraData, builder: (column) => column); + GeneratedColumnWithTypeConverter?, String> get extraData => + $composableBuilder(column: $table.extraData, builder: (column) => column); $$ChannelsTableAnnotationComposer get channelCid { final $$ChannelsTableAnnotationComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.channelCid, - referencedTable: $db.channels, - getReferencedColumn: (t) => t.cid, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$ChannelsTableAnnotationComposer( - $db: $db, - $table: $db.channels, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.channelCid, + referencedTable: $db.channels, + getReferencedColumn: (t) => t.cid, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$ChannelsTableAnnotationComposer( + $db: $db, + $table: $db.channels, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return composer; } Expression draftMessagesRefs( - Expression Function($$DraftMessagesTableAnnotationComposer a) f) { + Expression Function($$DraftMessagesTableAnnotationComposer a) f, + ) { final $$DraftMessagesTableAnnotationComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.id, - referencedTable: $db.draftMessages, - getReferencedColumn: (t) => t.parentId, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$DraftMessagesTableAnnotationComposer( - $db: $db, - $table: $db.draftMessages, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.draftMessages, + getReferencedColumn: (t) => t.parentId, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$DraftMessagesTableAnnotationComposer( + $db: $db, + $table: $db.draftMessages, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return f(composer); } - Expression locationsRefs( - Expression Function($$LocationsTableAnnotationComposer a) f) { + Expression locationsRefs(Expression Function($$LocationsTableAnnotationComposer a) f) { final $$LocationsTableAnnotationComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.id, - referencedTable: $db.locations, - getReferencedColumn: (t) => t.messageId, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$LocationsTableAnnotationComposer( - $db: $db, - $table: $db.locations, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.locations, + getReferencedColumn: (t) => t.messageId, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$LocationsTableAnnotationComposer( + $db: $db, + $table: $db.locations, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return f(composer); } - Expression reactionsRefs( - Expression Function($$ReactionsTableAnnotationComposer a) f) { + Expression reactionsRefs(Expression Function($$ReactionsTableAnnotationComposer a) f) { final $$ReactionsTableAnnotationComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.id, - referencedTable: $db.reactions, - getReferencedColumn: (t) => t.messageId, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$ReactionsTableAnnotationComposer( - $db: $db, - $table: $db.reactions, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.reactions, + getReferencedColumn: (t) => t.messageId, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$ReactionsTableAnnotationComposer( + $db: $db, + $table: $db.reactions, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return f(composer); } } -class $$MessagesTableTableManager extends RootTableManager< - _$DriftChatDatabase, - $MessagesTable, - MessageEntity, - $$MessagesTableFilterComposer, - $$MessagesTableOrderingComposer, - $$MessagesTableAnnotationComposer, - $$MessagesTableCreateCompanionBuilder, - $$MessagesTableUpdateCompanionBuilder, - (MessageEntity, $$MessagesTableReferences), - MessageEntity, - PrefetchHooks Function( - {bool channelCid, - bool draftMessagesRefs, - bool locationsRefs, - bool reactionsRefs})> { +class $$MessagesTableTableManager + extends + RootTableManager< + _$DriftChatDatabase, + $MessagesTable, + MessageEntity, + $$MessagesTableFilterComposer, + $$MessagesTableOrderingComposer, + $$MessagesTableAnnotationComposer, + $$MessagesTableCreateCompanionBuilder, + $$MessagesTableUpdateCompanionBuilder, + (MessageEntity, $$MessagesTableReferences), + MessageEntity, + PrefetchHooks Function({bool channelCid, bool draftMessagesRefs, bool locationsRefs, bool reactionsRefs}) + > { $$MessagesTableTableManager(_$DriftChatDatabase db, $MessagesTable table) - : super(TableManagerState( + : super( + TableManagerState( db: db, table: table, - createFilteringComposer: () => - $$MessagesTableFilterComposer($db: db, $table: table), - createOrderingComposer: () => - $$MessagesTableOrderingComposer($db: db, $table: table), - createComputedFieldComposer: () => - $$MessagesTableAnnotationComposer($db: db, $table: table), - updateCompanionCallback: ({ - Value id = const Value.absent(), - Value messageText = const Value.absent(), - Value> attachments = const Value.absent(), - Value state = const Value.absent(), - Value type = const Value.absent(), - Value> mentionedUsers = const Value.absent(), - Value?> reactionGroups = - const Value.absent(), - Value parentId = const Value.absent(), - Value quotedMessageId = const Value.absent(), - Value pollId = const Value.absent(), - Value replyCount = const Value.absent(), - Value showInChannel = const Value.absent(), - Value shadowed = const Value.absent(), - Value command = const Value.absent(), - Value localCreatedAt = const Value.absent(), - Value remoteCreatedAt = const Value.absent(), - Value localUpdatedAt = const Value.absent(), - Value remoteUpdatedAt = const Value.absent(), - Value localDeletedAt = const Value.absent(), - Value remoteDeletedAt = const Value.absent(), - Value deletedForMe = const Value.absent(), - Value messageTextUpdatedAt = const Value.absent(), - Value userId = const Value.absent(), - Value channelRole = const Value.absent(), - Value pinned = const Value.absent(), - Value pinnedAt = const Value.absent(), - Value pinExpires = const Value.absent(), - Value pinnedByUserId = const Value.absent(), - Value channelCid = const Value.absent(), - Value?> i18n = const Value.absent(), - Value?> restrictedVisibility = const Value.absent(), - Value?> extraData = const Value.absent(), - Value rowid = const Value.absent(), - }) => - MessagesCompanion( - id: id, - messageText: messageText, - attachments: attachments, - state: state, - type: type, - mentionedUsers: mentionedUsers, - reactionGroups: reactionGroups, - parentId: parentId, - quotedMessageId: quotedMessageId, - pollId: pollId, - replyCount: replyCount, - showInChannel: showInChannel, - shadowed: shadowed, - command: command, - localCreatedAt: localCreatedAt, - remoteCreatedAt: remoteCreatedAt, - localUpdatedAt: localUpdatedAt, - remoteUpdatedAt: remoteUpdatedAt, - localDeletedAt: localDeletedAt, - remoteDeletedAt: remoteDeletedAt, - deletedForMe: deletedForMe, - messageTextUpdatedAt: messageTextUpdatedAt, - userId: userId, - channelRole: channelRole, - pinned: pinned, - pinnedAt: pinnedAt, - pinExpires: pinExpires, - pinnedByUserId: pinnedByUserId, - channelCid: channelCid, - i18n: i18n, - restrictedVisibility: restrictedVisibility, - extraData: extraData, - rowid: rowid, - ), - createCompanionCallback: ({ - required String id, - Value messageText = const Value.absent(), - required List attachments, - required String state, - Value type = const Value.absent(), - required List mentionedUsers, - Value?> reactionGroups = - const Value.absent(), - Value parentId = const Value.absent(), - Value quotedMessageId = const Value.absent(), - Value pollId = const Value.absent(), - Value replyCount = const Value.absent(), - Value showInChannel = const Value.absent(), - Value shadowed = const Value.absent(), - Value command = const Value.absent(), - Value localCreatedAt = const Value.absent(), - Value remoteCreatedAt = const Value.absent(), - Value localUpdatedAt = const Value.absent(), - Value remoteUpdatedAt = const Value.absent(), - Value localDeletedAt = const Value.absent(), - Value remoteDeletedAt = const Value.absent(), - Value deletedForMe = const Value.absent(), - Value messageTextUpdatedAt = const Value.absent(), - Value userId = const Value.absent(), - Value channelRole = const Value.absent(), - Value pinned = const Value.absent(), - Value pinnedAt = const Value.absent(), - Value pinExpires = const Value.absent(), - Value pinnedByUserId = const Value.absent(), - required String channelCid, - Value?> i18n = const Value.absent(), - Value?> restrictedVisibility = const Value.absent(), - Value?> extraData = const Value.absent(), - Value rowid = const Value.absent(), - }) => - MessagesCompanion.insert( - id: id, - messageText: messageText, - attachments: attachments, - state: state, - type: type, - mentionedUsers: mentionedUsers, - reactionGroups: reactionGroups, - parentId: parentId, - quotedMessageId: quotedMessageId, - pollId: pollId, - replyCount: replyCount, - showInChannel: showInChannel, - shadowed: shadowed, - command: command, - localCreatedAt: localCreatedAt, - remoteCreatedAt: remoteCreatedAt, - localUpdatedAt: localUpdatedAt, - remoteUpdatedAt: remoteUpdatedAt, - localDeletedAt: localDeletedAt, - remoteDeletedAt: remoteDeletedAt, - deletedForMe: deletedForMe, - messageTextUpdatedAt: messageTextUpdatedAt, - userId: userId, - channelRole: channelRole, - pinned: pinned, - pinnedAt: pinnedAt, - pinExpires: pinExpires, - pinnedByUserId: pinnedByUserId, - channelCid: channelCid, - i18n: i18n, - restrictedVisibility: restrictedVisibility, - extraData: extraData, - rowid: rowid, - ), - withReferenceMapper: (p0) => p0 - .map((e) => - (e.readTable(table), $$MessagesTableReferences(db, table, e))) - .toList(), - prefetchHooksCallback: ( - {channelCid = false, - draftMessagesRefs = false, - locationsRefs = false, - reactionsRefs = false}) { - return PrefetchHooks( - db: db, - explicitlyWatchedTables: [ - if (draftMessagesRefs) db.draftMessages, - if (locationsRefs) db.locations, - if (reactionsRefs) db.reactions - ], - addJoins: < - T extends TableManagerState< - dynamic, - dynamic, - dynamic, - dynamic, - dynamic, - dynamic, - dynamic, - dynamic, - dynamic, - dynamic, - dynamic>>(state) { - if (channelCid) { - state = state.withJoin( - currentTable: table, - currentColumn: table.channelCid, - referencedTable: - $$MessagesTableReferences._channelCidTable(db), - referencedColumn: - $$MessagesTableReferences._channelCidTable(db).cid, - ) as T; - } - - return state; - }, - getPrefetchedDataCallback: (items) async { - return [ - if (draftMessagesRefs) - await $_getPrefetchedData( - currentTable: table, - referencedTable: $$MessagesTableReferences - ._draftMessagesRefsTable(db), - managerFromTypedResult: (p0) => - $$MessagesTableReferences(db, table, p0) - .draftMessagesRefs, - referencedItemsForCurrentItem: (item, - referencedItems) => - referencedItems.where((e) => e.parentId == item.id), - typedResults: items), - if (locationsRefs) - await $_getPrefetchedData( - currentTable: table, - referencedTable: - $$MessagesTableReferences._locationsRefsTable(db), - managerFromTypedResult: (p0) => - $$MessagesTableReferences(db, table, p0) - .locationsRefs, - referencedItemsForCurrentItem: - (item, referencedItems) => referencedItems - .where((e) => e.messageId == item.id), - typedResults: items), - if (reactionsRefs) - await $_getPrefetchedData( - currentTable: table, - referencedTable: - $$MessagesTableReferences._reactionsRefsTable(db), - managerFromTypedResult: (p0) => - $$MessagesTableReferences(db, table, p0) - .reactionsRefs, - referencedItemsForCurrentItem: - (item, referencedItems) => referencedItems - .where((e) => e.messageId == item.id), - typedResults: items) - ]; + createFilteringComposer: () => $$MessagesTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => $$MessagesTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => $$MessagesTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value id = const Value.absent(), + Value messageText = const Value.absent(), + Value> attachments = const Value.absent(), + Value state = const Value.absent(), + Value type = const Value.absent(), + Value> mentionedUsers = const Value.absent(), + Value?> reactionGroups = const Value.absent(), + Value parentId = const Value.absent(), + Value quotedMessageId = const Value.absent(), + Value pollId = const Value.absent(), + Value replyCount = const Value.absent(), + Value showInChannel = const Value.absent(), + Value shadowed = const Value.absent(), + Value command = const Value.absent(), + Value localCreatedAt = const Value.absent(), + Value remoteCreatedAt = const Value.absent(), + Value localUpdatedAt = const Value.absent(), + Value remoteUpdatedAt = const Value.absent(), + Value localDeletedAt = const Value.absent(), + Value remoteDeletedAt = const Value.absent(), + Value deletedForMe = const Value.absent(), + Value messageTextUpdatedAt = const Value.absent(), + Value userId = const Value.absent(), + Value channelRole = const Value.absent(), + Value pinned = const Value.absent(), + Value pinnedAt = const Value.absent(), + Value pinExpires = const Value.absent(), + Value pinnedByUserId = const Value.absent(), + Value channelCid = const Value.absent(), + Value?> i18n = const Value.absent(), + Value?> restrictedVisibility = const Value.absent(), + Value?> extraData = const Value.absent(), + Value rowid = const Value.absent(), + }) => MessagesCompanion( + id: id, + messageText: messageText, + attachments: attachments, + state: state, + type: type, + mentionedUsers: mentionedUsers, + reactionGroups: reactionGroups, + parentId: parentId, + quotedMessageId: quotedMessageId, + pollId: pollId, + replyCount: replyCount, + showInChannel: showInChannel, + shadowed: shadowed, + command: command, + localCreatedAt: localCreatedAt, + remoteCreatedAt: remoteCreatedAt, + localUpdatedAt: localUpdatedAt, + remoteUpdatedAt: remoteUpdatedAt, + localDeletedAt: localDeletedAt, + remoteDeletedAt: remoteDeletedAt, + deletedForMe: deletedForMe, + messageTextUpdatedAt: messageTextUpdatedAt, + userId: userId, + channelRole: channelRole, + pinned: pinned, + pinnedAt: pinnedAt, + pinExpires: pinExpires, + pinnedByUserId: pinnedByUserId, + channelCid: channelCid, + i18n: i18n, + restrictedVisibility: restrictedVisibility, + extraData: extraData, + rowid: rowid, + ), + createCompanionCallback: + ({ + required String id, + Value messageText = const Value.absent(), + required List attachments, + required String state, + Value type = const Value.absent(), + required List mentionedUsers, + Value?> reactionGroups = const Value.absent(), + Value parentId = const Value.absent(), + Value quotedMessageId = const Value.absent(), + Value pollId = const Value.absent(), + Value replyCount = const Value.absent(), + Value showInChannel = const Value.absent(), + Value shadowed = const Value.absent(), + Value command = const Value.absent(), + Value localCreatedAt = const Value.absent(), + Value remoteCreatedAt = const Value.absent(), + Value localUpdatedAt = const Value.absent(), + Value remoteUpdatedAt = const Value.absent(), + Value localDeletedAt = const Value.absent(), + Value remoteDeletedAt = const Value.absent(), + Value deletedForMe = const Value.absent(), + Value messageTextUpdatedAt = const Value.absent(), + Value userId = const Value.absent(), + Value channelRole = const Value.absent(), + Value pinned = const Value.absent(), + Value pinnedAt = const Value.absent(), + Value pinExpires = const Value.absent(), + Value pinnedByUserId = const Value.absent(), + required String channelCid, + Value?> i18n = const Value.absent(), + Value?> restrictedVisibility = const Value.absent(), + Value?> extraData = const Value.absent(), + Value rowid = const Value.absent(), + }) => MessagesCompanion.insert( + id: id, + messageText: messageText, + attachments: attachments, + state: state, + type: type, + mentionedUsers: mentionedUsers, + reactionGroups: reactionGroups, + parentId: parentId, + quotedMessageId: quotedMessageId, + pollId: pollId, + replyCount: replyCount, + showInChannel: showInChannel, + shadowed: shadowed, + command: command, + localCreatedAt: localCreatedAt, + remoteCreatedAt: remoteCreatedAt, + localUpdatedAt: localUpdatedAt, + remoteUpdatedAt: remoteUpdatedAt, + localDeletedAt: localDeletedAt, + remoteDeletedAt: remoteDeletedAt, + deletedForMe: deletedForMe, + messageTextUpdatedAt: messageTextUpdatedAt, + userId: userId, + channelRole: channelRole, + pinned: pinned, + pinnedAt: pinnedAt, + pinExpires: pinExpires, + pinnedByUserId: pinnedByUserId, + channelCid: channelCid, + i18n: i18n, + restrictedVisibility: restrictedVisibility, + extraData: extraData, + rowid: rowid, + ), + withReferenceMapper: (p0) => + p0.map((e) => (e.readTable(table), $$MessagesTableReferences(db, table, e))).toList(), + prefetchHooksCallback: + ({channelCid = false, draftMessagesRefs = false, locationsRefs = false, reactionsRefs = false}) { + return PrefetchHooks( + db: db, + explicitlyWatchedTables: [ + if (draftMessagesRefs) db.draftMessages, + if (locationsRefs) db.locations, + if (reactionsRefs) db.reactions, + ], + addJoins: + < + T extends TableManagerState< + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic + > + >(state) { + if (channelCid) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.channelCid, + referencedTable: $$MessagesTableReferences._channelCidTable(db), + referencedColumn: $$MessagesTableReferences._channelCidTable(db).cid, + ) + as T; + } + + return state; + }, + getPrefetchedDataCallback: (items) async { + return [ + if (draftMessagesRefs) + await $_getPrefetchedData( + currentTable: table, + referencedTable: $$MessagesTableReferences._draftMessagesRefsTable(db), + managerFromTypedResult: (p0) => $$MessagesTableReferences(db, table, p0).draftMessagesRefs, + referencedItemsForCurrentItem: (item, referencedItems) => + referencedItems.where((e) => e.parentId == item.id), + typedResults: items, + ), + if (locationsRefs) + await $_getPrefetchedData( + currentTable: table, + referencedTable: $$MessagesTableReferences._locationsRefsTable(db), + managerFromTypedResult: (p0) => $$MessagesTableReferences(db, table, p0).locationsRefs, + referencedItemsForCurrentItem: (item, referencedItems) => + referencedItems.where((e) => e.messageId == item.id), + typedResults: items, + ), + if (reactionsRefs) + await $_getPrefetchedData( + currentTable: table, + referencedTable: $$MessagesTableReferences._reactionsRefsTable(db), + managerFromTypedResult: (p0) => $$MessagesTableReferences(db, table, p0).reactionsRefs, + referencedItemsForCurrentItem: (item, referencedItems) => + referencedItems.where((e) => e.messageId == item.id), + typedResults: items, + ), + ]; + }, + ); }, - ); - }, - )); + ), + ); } -typedef $$MessagesTableProcessedTableManager = ProcessedTableManager< - _$DriftChatDatabase, - $MessagesTable, - MessageEntity, - $$MessagesTableFilterComposer, - $$MessagesTableOrderingComposer, - $$MessagesTableAnnotationComposer, - $$MessagesTableCreateCompanionBuilder, - $$MessagesTableUpdateCompanionBuilder, - (MessageEntity, $$MessagesTableReferences), - MessageEntity, - PrefetchHooks Function( - {bool channelCid, - bool draftMessagesRefs, - bool locationsRefs, - bool reactionsRefs})>; -typedef $$DraftMessagesTableCreateCompanionBuilder = DraftMessagesCompanion - Function({ - required String id, - Value messageText, - required List attachments, - Value type, - required List mentionedUsers, - Value parentId, - Value quotedMessageId, - Value pollId, - Value showInChannel, - Value command, - Value silent, - Value createdAt, - required String channelCid, - Value?> extraData, - Value rowid, -}); -typedef $$DraftMessagesTableUpdateCompanionBuilder = DraftMessagesCompanion - Function({ - Value id, - Value messageText, - Value> attachments, - Value type, - Value> mentionedUsers, - Value parentId, - Value quotedMessageId, - Value pollId, - Value showInChannel, - Value command, - Value silent, - Value createdAt, - Value channelCid, - Value?> extraData, - Value rowid, -}); - -final class $$DraftMessagesTableReferences extends BaseReferences< - _$DriftChatDatabase, $DraftMessagesTable, DraftMessageEntity> { - $$DraftMessagesTableReferences( - super.$_db, super.$_table, super.$_typedResult); +typedef $$MessagesTableProcessedTableManager = + ProcessedTableManager< + _$DriftChatDatabase, + $MessagesTable, + MessageEntity, + $$MessagesTableFilterComposer, + $$MessagesTableOrderingComposer, + $$MessagesTableAnnotationComposer, + $$MessagesTableCreateCompanionBuilder, + $$MessagesTableUpdateCompanionBuilder, + (MessageEntity, $$MessagesTableReferences), + MessageEntity, + PrefetchHooks Function({bool channelCid, bool draftMessagesRefs, bool locationsRefs, bool reactionsRefs}) + >; +typedef $$DraftMessagesTableCreateCompanionBuilder = + DraftMessagesCompanion Function({ + required String id, + Value messageText, + required List attachments, + Value type, + required List mentionedUsers, + Value parentId, + Value quotedMessageId, + Value pollId, + Value showInChannel, + Value command, + Value silent, + Value createdAt, + required String channelCid, + Value?> extraData, + Value rowid, + }); +typedef $$DraftMessagesTableUpdateCompanionBuilder = + DraftMessagesCompanion Function({ + Value id, + Value messageText, + Value> attachments, + Value type, + Value> mentionedUsers, + Value parentId, + Value quotedMessageId, + Value pollId, + Value showInChannel, + Value command, + Value silent, + Value createdAt, + Value channelCid, + Value?> extraData, + Value rowid, + }); + +final class $$DraftMessagesTableReferences + extends BaseReferences<_$DriftChatDatabase, $DraftMessagesTable, DraftMessageEntity> { + $$DraftMessagesTableReferences(super.$_db, super.$_table, super.$_typedResult); static $MessagesTable _parentIdTable(_$DriftChatDatabase db) => - db.messages.createAlias( - $_aliasNameGenerator(db.draftMessages.parentId, db.messages.id)); + db.messages.createAlias($_aliasNameGenerator(db.draftMessages.parentId, db.messages.id)); $$MessagesTableProcessedTableManager? get parentId { final $_column = $_itemColumn('parent_id'); if ($_column == null) return null; - final manager = $$MessagesTableTableManager($_db, $_db.messages) - .filter((f) => f.id.sqlEquals($_column)); + final manager = $$MessagesTableTableManager($_db, $_db.messages).filter((f) => f.id.sqlEquals($_column)); final item = $_typedResult.readTableOrNull(_parentIdTable($_db)); if (item == null) return manager; - return ProcessedTableManager( - manager.$state.copyWith(prefetchedData: [item])); + return ProcessedTableManager(manager.$state.copyWith(prefetchedData: [item])); } static $ChannelsTable _channelCidTable(_$DriftChatDatabase db) => - db.channels.createAlias( - $_aliasNameGenerator(db.draftMessages.channelCid, db.channels.cid)); + db.channels.createAlias($_aliasNameGenerator(db.draftMessages.channelCid, db.channels.cid)); $$ChannelsTableProcessedTableManager get channelCid { final $_column = $_itemColumn('channel_cid')!; - final manager = $$ChannelsTableTableManager($_db, $_db.channels) - .filter((f) => f.cid.sqlEquals($_column)); + final manager = $$ChannelsTableTableManager($_db, $_db.channels).filter((f) => f.cid.sqlEquals($_column)); final item = $_typedResult.readTableOrNull(_channelCidTable($_db)); if (item == null) return manager; - return ProcessedTableManager( - manager.$state.copyWith(prefetchedData: [item])); + return ProcessedTableManager(manager.$state.copyWith(prefetchedData: [item])); } } -class $$DraftMessagesTableFilterComposer - extends Composer<_$DriftChatDatabase, $DraftMessagesTable> { +class $$DraftMessagesTableFilterComposer extends Composer<_$DriftChatDatabase, $DraftMessagesTable> { $$DraftMessagesTableFilterComposer({ required super.$db, required super.$table, @@ -11239,93 +10940,78 @@ class $$DraftMessagesTableFilterComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnFilters get id => $composableBuilder( - column: $table.id, builder: (column) => ColumnFilters(column)); + ColumnFilters get id => $composableBuilder(column: $table.id, builder: (column) => ColumnFilters(column)); - ColumnFilters get messageText => $composableBuilder( - column: $table.messageText, builder: (column) => ColumnFilters(column)); + ColumnFilters get messageText => + $composableBuilder(column: $table.messageText, builder: (column) => ColumnFilters(column)); - ColumnWithTypeConverterFilters, List, String> - get attachments => $composableBuilder( - column: $table.attachments, - builder: (column) => ColumnWithTypeConverterFilters(column)); + ColumnWithTypeConverterFilters, List, String> get attachments => + $composableBuilder(column: $table.attachments, builder: (column) => ColumnWithTypeConverterFilters(column)); - ColumnFilters get type => $composableBuilder( - column: $table.type, builder: (column) => ColumnFilters(column)); + ColumnFilters get type => $composableBuilder(column: $table.type, builder: (column) => ColumnFilters(column)); - ColumnWithTypeConverterFilters, List, String> - get mentionedUsers => $composableBuilder( - column: $table.mentionedUsers, - builder: (column) => ColumnWithTypeConverterFilters(column)); + ColumnWithTypeConverterFilters, List, String> get mentionedUsers => + $composableBuilder(column: $table.mentionedUsers, builder: (column) => ColumnWithTypeConverterFilters(column)); - ColumnFilters get quotedMessageId => $composableBuilder( - column: $table.quotedMessageId, - builder: (column) => ColumnFilters(column)); + ColumnFilters get quotedMessageId => + $composableBuilder(column: $table.quotedMessageId, builder: (column) => ColumnFilters(column)); - ColumnFilters get pollId => $composableBuilder( - column: $table.pollId, builder: (column) => ColumnFilters(column)); + ColumnFilters get pollId => + $composableBuilder(column: $table.pollId, builder: (column) => ColumnFilters(column)); - ColumnFilters get showInChannel => $composableBuilder( - column: $table.showInChannel, builder: (column) => ColumnFilters(column)); + ColumnFilters get showInChannel => + $composableBuilder(column: $table.showInChannel, builder: (column) => ColumnFilters(column)); - ColumnFilters get command => $composableBuilder( - column: $table.command, builder: (column) => ColumnFilters(column)); + ColumnFilters get command => + $composableBuilder(column: $table.command, builder: (column) => ColumnFilters(column)); - ColumnFilters get silent => $composableBuilder( - column: $table.silent, builder: (column) => ColumnFilters(column)); + ColumnFilters get silent => + $composableBuilder(column: $table.silent, builder: (column) => ColumnFilters(column)); - ColumnFilters get createdAt => $composableBuilder( - column: $table.createdAt, builder: (column) => ColumnFilters(column)); + ColumnFilters get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => ColumnFilters(column)); - ColumnWithTypeConverterFilters?, Map, - String> - get extraData => $composableBuilder( - column: $table.extraData, - builder: (column) => ColumnWithTypeConverterFilters(column)); + ColumnWithTypeConverterFilters?, Map, String> get extraData => + $composableBuilder(column: $table.extraData, builder: (column) => ColumnWithTypeConverterFilters(column)); $$MessagesTableFilterComposer get parentId { final $$MessagesTableFilterComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.parentId, - referencedTable: $db.messages, - getReferencedColumn: (t) => t.id, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$MessagesTableFilterComposer( - $db: $db, - $table: $db.messages, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.parentId, + referencedTable: $db.messages, + getReferencedColumn: (t) => t.id, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$MessagesTableFilterComposer( + $db: $db, + $table: $db.messages, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return composer; } $$ChannelsTableFilterComposer get channelCid { final $$ChannelsTableFilterComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.channelCid, - referencedTable: $db.channels, - getReferencedColumn: (t) => t.cid, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$ChannelsTableFilterComposer( - $db: $db, - $table: $db.channels, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.channelCid, + referencedTable: $db.channels, + getReferencedColumn: (t) => t.cid, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$ChannelsTableFilterComposer( + $db: $db, + $table: $db.channels, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return composer; } } -class $$DraftMessagesTableOrderingComposer - extends Composer<_$DriftChatDatabase, $DraftMessagesTable> { +class $$DraftMessagesTableOrderingComposer extends Composer<_$DriftChatDatabase, $DraftMessagesTable> { $$DraftMessagesTableOrderingComposer({ required super.$db, required super.$table, @@ -11333,88 +11019,79 @@ class $$DraftMessagesTableOrderingComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnOrderings get id => $composableBuilder( - column: $table.id, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get id => $composableBuilder(column: $table.id, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get messageText => $composableBuilder( - column: $table.messageText, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get messageText => + $composableBuilder(column: $table.messageText, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get attachments => $composableBuilder( - column: $table.attachments, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get attachments => + $composableBuilder(column: $table.attachments, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get type => $composableBuilder( - column: $table.type, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get type => + $composableBuilder(column: $table.type, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get mentionedUsers => $composableBuilder( - column: $table.mentionedUsers, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get mentionedUsers => + $composableBuilder(column: $table.mentionedUsers, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get quotedMessageId => $composableBuilder( - column: $table.quotedMessageId, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get quotedMessageId => + $composableBuilder(column: $table.quotedMessageId, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get pollId => $composableBuilder( - column: $table.pollId, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get pollId => + $composableBuilder(column: $table.pollId, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get showInChannel => $composableBuilder( - column: $table.showInChannel, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get showInChannel => + $composableBuilder(column: $table.showInChannel, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get command => $composableBuilder( - column: $table.command, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get command => + $composableBuilder(column: $table.command, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get silent => $composableBuilder( - column: $table.silent, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get silent => + $composableBuilder(column: $table.silent, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get createdAt => $composableBuilder( - column: $table.createdAt, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get extraData => $composableBuilder( - column: $table.extraData, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get extraData => + $composableBuilder(column: $table.extraData, builder: (column) => ColumnOrderings(column)); $$MessagesTableOrderingComposer get parentId { final $$MessagesTableOrderingComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.parentId, - referencedTable: $db.messages, - getReferencedColumn: (t) => t.id, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$MessagesTableOrderingComposer( - $db: $db, - $table: $db.messages, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.parentId, + referencedTable: $db.messages, + getReferencedColumn: (t) => t.id, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$MessagesTableOrderingComposer( + $db: $db, + $table: $db.messages, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return composer; } $$ChannelsTableOrderingComposer get channelCid { final $$ChannelsTableOrderingComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.channelCid, - referencedTable: $db.channels, - getReferencedColumn: (t) => t.cid, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$ChannelsTableOrderingComposer( - $db: $db, - $table: $db.channels, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.channelCid, + referencedTable: $db.channels, + getReferencedColumn: (t) => t.cid, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$ChannelsTableOrderingComposer( + $db: $db, + $table: $db.channels, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return composer; } } -class $$DraftMessagesTableAnnotationComposer - extends Composer<_$DriftChatDatabase, $DraftMessagesTable> { +class $$DraftMessagesTableAnnotationComposer extends Composer<_$DriftChatDatabase, $DraftMessagesTable> { $$DraftMessagesTableAnnotationComposer({ required super.$db, required super.$table, @@ -11422,189 +11099,173 @@ class $$DraftMessagesTableAnnotationComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - GeneratedColumn get id => - $composableBuilder(column: $table.id, builder: (column) => column); + GeneratedColumn get id => $composableBuilder(column: $table.id, builder: (column) => column); - GeneratedColumn get messageText => $composableBuilder( - column: $table.messageText, builder: (column) => column); + GeneratedColumn get messageText => + $composableBuilder(column: $table.messageText, builder: (column) => column); GeneratedColumnWithTypeConverter, String> get attachments => - $composableBuilder( - column: $table.attachments, builder: (column) => column); + $composableBuilder(column: $table.attachments, builder: (column) => column); - GeneratedColumn get type => - $composableBuilder(column: $table.type, builder: (column) => column); + GeneratedColumn get type => $composableBuilder(column: $table.type, builder: (column) => column); GeneratedColumnWithTypeConverter, String> get mentionedUsers => - $composableBuilder( - column: $table.mentionedUsers, builder: (column) => column); + $composableBuilder(column: $table.mentionedUsers, builder: (column) => column); - GeneratedColumn get quotedMessageId => $composableBuilder( - column: $table.quotedMessageId, builder: (column) => column); + GeneratedColumn get quotedMessageId => + $composableBuilder(column: $table.quotedMessageId, builder: (column) => column); - GeneratedColumn get pollId => - $composableBuilder(column: $table.pollId, builder: (column) => column); + GeneratedColumn get pollId => $composableBuilder(column: $table.pollId, builder: (column) => column); - GeneratedColumn get showInChannel => $composableBuilder( - column: $table.showInChannel, builder: (column) => column); + GeneratedColumn get showInChannel => + $composableBuilder(column: $table.showInChannel, builder: (column) => column); - GeneratedColumn get command => - $composableBuilder(column: $table.command, builder: (column) => column); + GeneratedColumn get command => $composableBuilder(column: $table.command, builder: (column) => column); - GeneratedColumn get silent => - $composableBuilder(column: $table.silent, builder: (column) => column); + GeneratedColumn get silent => $composableBuilder(column: $table.silent, builder: (column) => column); - GeneratedColumn get createdAt => - $composableBuilder(column: $table.createdAt, builder: (column) => column); + GeneratedColumn get createdAt => $composableBuilder(column: $table.createdAt, builder: (column) => column); - GeneratedColumnWithTypeConverter?, String> - get extraData => $composableBuilder( - column: $table.extraData, builder: (column) => column); + GeneratedColumnWithTypeConverter?, String> get extraData => + $composableBuilder(column: $table.extraData, builder: (column) => column); $$MessagesTableAnnotationComposer get parentId { final $$MessagesTableAnnotationComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.parentId, - referencedTable: $db.messages, - getReferencedColumn: (t) => t.id, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$MessagesTableAnnotationComposer( - $db: $db, - $table: $db.messages, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.parentId, + referencedTable: $db.messages, + getReferencedColumn: (t) => t.id, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$MessagesTableAnnotationComposer( + $db: $db, + $table: $db.messages, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return composer; } $$ChannelsTableAnnotationComposer get channelCid { final $$ChannelsTableAnnotationComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.channelCid, - referencedTable: $db.channels, - getReferencedColumn: (t) => t.cid, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$ChannelsTableAnnotationComposer( - $db: $db, - $table: $db.channels, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.channelCid, + referencedTable: $db.channels, + getReferencedColumn: (t) => t.cid, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$ChannelsTableAnnotationComposer( + $db: $db, + $table: $db.channels, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return composer; } } -class $$DraftMessagesTableTableManager extends RootTableManager< - _$DriftChatDatabase, - $DraftMessagesTable, - DraftMessageEntity, - $$DraftMessagesTableFilterComposer, - $$DraftMessagesTableOrderingComposer, - $$DraftMessagesTableAnnotationComposer, - $$DraftMessagesTableCreateCompanionBuilder, - $$DraftMessagesTableUpdateCompanionBuilder, - (DraftMessageEntity, $$DraftMessagesTableReferences), - DraftMessageEntity, - PrefetchHooks Function({bool parentId, bool channelCid})> { - $$DraftMessagesTableTableManager( - _$DriftChatDatabase db, $DraftMessagesTable table) - : super(TableManagerState( +class $$DraftMessagesTableTableManager + extends + RootTableManager< + _$DriftChatDatabase, + $DraftMessagesTable, + DraftMessageEntity, + $$DraftMessagesTableFilterComposer, + $$DraftMessagesTableOrderingComposer, + $$DraftMessagesTableAnnotationComposer, + $$DraftMessagesTableCreateCompanionBuilder, + $$DraftMessagesTableUpdateCompanionBuilder, + (DraftMessageEntity, $$DraftMessagesTableReferences), + DraftMessageEntity, + PrefetchHooks Function({bool parentId, bool channelCid}) + > { + $$DraftMessagesTableTableManager(_$DriftChatDatabase db, $DraftMessagesTable table) + : super( + TableManagerState( db: db, table: table, - createFilteringComposer: () => - $$DraftMessagesTableFilterComposer($db: db, $table: table), - createOrderingComposer: () => - $$DraftMessagesTableOrderingComposer($db: db, $table: table), - createComputedFieldComposer: () => - $$DraftMessagesTableAnnotationComposer($db: db, $table: table), - updateCompanionCallback: ({ - Value id = const Value.absent(), - Value messageText = const Value.absent(), - Value> attachments = const Value.absent(), - Value type = const Value.absent(), - Value> mentionedUsers = const Value.absent(), - Value parentId = const Value.absent(), - Value quotedMessageId = const Value.absent(), - Value pollId = const Value.absent(), - Value showInChannel = const Value.absent(), - Value command = const Value.absent(), - Value silent = const Value.absent(), - Value createdAt = const Value.absent(), - Value channelCid = const Value.absent(), - Value?> extraData = const Value.absent(), - Value rowid = const Value.absent(), - }) => - DraftMessagesCompanion( - id: id, - messageText: messageText, - attachments: attachments, - type: type, - mentionedUsers: mentionedUsers, - parentId: parentId, - quotedMessageId: quotedMessageId, - pollId: pollId, - showInChannel: showInChannel, - command: command, - silent: silent, - createdAt: createdAt, - channelCid: channelCid, - extraData: extraData, - rowid: rowid, - ), - createCompanionCallback: ({ - required String id, - Value messageText = const Value.absent(), - required List attachments, - Value type = const Value.absent(), - required List mentionedUsers, - Value parentId = const Value.absent(), - Value quotedMessageId = const Value.absent(), - Value pollId = const Value.absent(), - Value showInChannel = const Value.absent(), - Value command = const Value.absent(), - Value silent = const Value.absent(), - Value createdAt = const Value.absent(), - required String channelCid, - Value?> extraData = const Value.absent(), - Value rowid = const Value.absent(), - }) => - DraftMessagesCompanion.insert( - id: id, - messageText: messageText, - attachments: attachments, - type: type, - mentionedUsers: mentionedUsers, - parentId: parentId, - quotedMessageId: quotedMessageId, - pollId: pollId, - showInChannel: showInChannel, - command: command, - silent: silent, - createdAt: createdAt, - channelCid: channelCid, - extraData: extraData, - rowid: rowid, - ), - withReferenceMapper: (p0) => p0 - .map((e) => ( - e.readTable(table), - $$DraftMessagesTableReferences(db, table, e) - )) - .toList(), + createFilteringComposer: () => $$DraftMessagesTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => $$DraftMessagesTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => $$DraftMessagesTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value id = const Value.absent(), + Value messageText = const Value.absent(), + Value> attachments = const Value.absent(), + Value type = const Value.absent(), + Value> mentionedUsers = const Value.absent(), + Value parentId = const Value.absent(), + Value quotedMessageId = const Value.absent(), + Value pollId = const Value.absent(), + Value showInChannel = const Value.absent(), + Value command = const Value.absent(), + Value silent = const Value.absent(), + Value createdAt = const Value.absent(), + Value channelCid = const Value.absent(), + Value?> extraData = const Value.absent(), + Value rowid = const Value.absent(), + }) => DraftMessagesCompanion( + id: id, + messageText: messageText, + attachments: attachments, + type: type, + mentionedUsers: mentionedUsers, + parentId: parentId, + quotedMessageId: quotedMessageId, + pollId: pollId, + showInChannel: showInChannel, + command: command, + silent: silent, + createdAt: createdAt, + channelCid: channelCid, + extraData: extraData, + rowid: rowid, + ), + createCompanionCallback: + ({ + required String id, + Value messageText = const Value.absent(), + required List attachments, + Value type = const Value.absent(), + required List mentionedUsers, + Value parentId = const Value.absent(), + Value quotedMessageId = const Value.absent(), + Value pollId = const Value.absent(), + Value showInChannel = const Value.absent(), + Value command = const Value.absent(), + Value silent = const Value.absent(), + Value createdAt = const Value.absent(), + required String channelCid, + Value?> extraData = const Value.absent(), + Value rowid = const Value.absent(), + }) => DraftMessagesCompanion.insert( + id: id, + messageText: messageText, + attachments: attachments, + type: type, + mentionedUsers: mentionedUsers, + parentId: parentId, + quotedMessageId: quotedMessageId, + pollId: pollId, + showInChannel: showInChannel, + command: command, + silent: silent, + createdAt: createdAt, + channelCid: channelCid, + extraData: extraData, + rowid: rowid, + ), + withReferenceMapper: (p0) => + p0.map((e) => (e.readTable(table), $$DraftMessagesTableReferences(db, table, e))).toList(), prefetchHooksCallback: ({parentId = false, channelCid = false}) { return PrefetchHooks( db: db, explicitlyWatchedTables: [], - addJoins: < - T extends TableManagerState< + addJoins: + < + T extends TableManagerState< dynamic, dynamic, dynamic, @@ -11615,112 +11276,111 @@ class $$DraftMessagesTableTableManager extends RootTableManager< dynamic, dynamic, dynamic, - dynamic>>(state) { - if (parentId) { - state = state.withJoin( - currentTable: table, - currentColumn: table.parentId, - referencedTable: - $$DraftMessagesTableReferences._parentIdTable(db), - referencedColumn: - $$DraftMessagesTableReferences._parentIdTable(db).id, - ) as T; - } - if (channelCid) { - state = state.withJoin( - currentTable: table, - currentColumn: table.channelCid, - referencedTable: - $$DraftMessagesTableReferences._channelCidTable(db), - referencedColumn: - $$DraftMessagesTableReferences._channelCidTable(db).cid, - ) as T; - } - - return state; - }, + dynamic + > + >(state) { + if (parentId) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.parentId, + referencedTable: $$DraftMessagesTableReferences._parentIdTable(db), + referencedColumn: $$DraftMessagesTableReferences._parentIdTable(db).id, + ) + as T; + } + if (channelCid) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.channelCid, + referencedTable: $$DraftMessagesTableReferences._channelCidTable(db), + referencedColumn: $$DraftMessagesTableReferences._channelCidTable(db).cid, + ) + as T; + } + + return state; + }, getPrefetchedDataCallback: (items) async { return []; }, ); }, - )); + ), + ); } -typedef $$DraftMessagesTableProcessedTableManager = ProcessedTableManager< - _$DriftChatDatabase, - $DraftMessagesTable, - DraftMessageEntity, - $$DraftMessagesTableFilterComposer, - $$DraftMessagesTableOrderingComposer, - $$DraftMessagesTableAnnotationComposer, - $$DraftMessagesTableCreateCompanionBuilder, - $$DraftMessagesTableUpdateCompanionBuilder, - (DraftMessageEntity, $$DraftMessagesTableReferences), - DraftMessageEntity, - PrefetchHooks Function({bool parentId, bool channelCid})>; -typedef $$LocationsTableCreateCompanionBuilder = LocationsCompanion Function({ - Value channelCid, - Value messageId, - Value userId, - required double latitude, - required double longitude, - Value createdByDeviceId, - Value endAt, - Value createdAt, - Value updatedAt, - Value rowid, -}); -typedef $$LocationsTableUpdateCompanionBuilder = LocationsCompanion Function({ - Value channelCid, - Value messageId, - Value userId, - Value latitude, - Value longitude, - Value createdByDeviceId, - Value endAt, - Value createdAt, - Value updatedAt, - Value rowid, -}); - -final class $$LocationsTableReferences extends BaseReferences< - _$DriftChatDatabase, $LocationsTable, LocationEntity> { +typedef $$DraftMessagesTableProcessedTableManager = + ProcessedTableManager< + _$DriftChatDatabase, + $DraftMessagesTable, + DraftMessageEntity, + $$DraftMessagesTableFilterComposer, + $$DraftMessagesTableOrderingComposer, + $$DraftMessagesTableAnnotationComposer, + $$DraftMessagesTableCreateCompanionBuilder, + $$DraftMessagesTableUpdateCompanionBuilder, + (DraftMessageEntity, $$DraftMessagesTableReferences), + DraftMessageEntity, + PrefetchHooks Function({bool parentId, bool channelCid}) + >; +typedef $$LocationsTableCreateCompanionBuilder = + LocationsCompanion Function({ + Value channelCid, + Value messageId, + Value userId, + required double latitude, + required double longitude, + Value createdByDeviceId, + Value endAt, + Value createdAt, + Value updatedAt, + Value rowid, + }); +typedef $$LocationsTableUpdateCompanionBuilder = + LocationsCompanion Function({ + Value channelCid, + Value messageId, + Value userId, + Value latitude, + Value longitude, + Value createdByDeviceId, + Value endAt, + Value createdAt, + Value updatedAt, + Value rowid, + }); + +final class $$LocationsTableReferences extends BaseReferences<_$DriftChatDatabase, $LocationsTable, LocationEntity> { $$LocationsTableReferences(super.$_db, super.$_table, super.$_typedResult); static $ChannelsTable _channelCidTable(_$DriftChatDatabase db) => - db.channels.createAlias( - $_aliasNameGenerator(db.locations.channelCid, db.channels.cid)); + db.channels.createAlias($_aliasNameGenerator(db.locations.channelCid, db.channels.cid)); $$ChannelsTableProcessedTableManager? get channelCid { final $_column = $_itemColumn('channel_cid'); if ($_column == null) return null; - final manager = $$ChannelsTableTableManager($_db, $_db.channels) - .filter((f) => f.cid.sqlEquals($_column)); + final manager = $$ChannelsTableTableManager($_db, $_db.channels).filter((f) => f.cid.sqlEquals($_column)); final item = $_typedResult.readTableOrNull(_channelCidTable($_db)); if (item == null) return manager; - return ProcessedTableManager( - manager.$state.copyWith(prefetchedData: [item])); + return ProcessedTableManager(manager.$state.copyWith(prefetchedData: [item])); } static $MessagesTable _messageIdTable(_$DriftChatDatabase db) => - db.messages.createAlias( - $_aliasNameGenerator(db.locations.messageId, db.messages.id)); + db.messages.createAlias($_aliasNameGenerator(db.locations.messageId, db.messages.id)); $$MessagesTableProcessedTableManager? get messageId { final $_column = $_itemColumn('message_id'); if ($_column == null) return null; - final manager = $$MessagesTableTableManager($_db, $_db.messages) - .filter((f) => f.id.sqlEquals($_column)); + final manager = $$MessagesTableTableManager($_db, $_db.messages).filter((f) => f.id.sqlEquals($_column)); final item = $_typedResult.readTableOrNull(_messageIdTable($_db)); if (item == null) return manager; - return ProcessedTableManager( - manager.$state.copyWith(prefetchedData: [item])); + return ProcessedTableManager(manager.$state.copyWith(prefetchedData: [item])); } } -class $$LocationsTableFilterComposer - extends Composer<_$DriftChatDatabase, $LocationsTable> { +class $$LocationsTableFilterComposer extends Composer<_$DriftChatDatabase, $LocationsTable> { $$LocationsTableFilterComposer({ required super.$db, required super.$table, @@ -11728,71 +11388,65 @@ class $$LocationsTableFilterComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnFilters get userId => $composableBuilder( - column: $table.userId, builder: (column) => ColumnFilters(column)); + ColumnFilters get userId => + $composableBuilder(column: $table.userId, builder: (column) => ColumnFilters(column)); - ColumnFilters get latitude => $composableBuilder( - column: $table.latitude, builder: (column) => ColumnFilters(column)); + ColumnFilters get latitude => + $composableBuilder(column: $table.latitude, builder: (column) => ColumnFilters(column)); - ColumnFilters get longitude => $composableBuilder( - column: $table.longitude, builder: (column) => ColumnFilters(column)); + ColumnFilters get longitude => + $composableBuilder(column: $table.longitude, builder: (column) => ColumnFilters(column)); - ColumnFilters get createdByDeviceId => $composableBuilder( - column: $table.createdByDeviceId, - builder: (column) => ColumnFilters(column)); + ColumnFilters get createdByDeviceId => + $composableBuilder(column: $table.createdByDeviceId, builder: (column) => ColumnFilters(column)); - ColumnFilters get endAt => $composableBuilder( - column: $table.endAt, builder: (column) => ColumnFilters(column)); + ColumnFilters get endAt => + $composableBuilder(column: $table.endAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get createdAt => $composableBuilder( - column: $table.createdAt, builder: (column) => ColumnFilters(column)); + ColumnFilters get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get updatedAt => $composableBuilder( - column: $table.updatedAt, builder: (column) => ColumnFilters(column)); + ColumnFilters get updatedAt => + $composableBuilder(column: $table.updatedAt, builder: (column) => ColumnFilters(column)); $$ChannelsTableFilterComposer get channelCid { final $$ChannelsTableFilterComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.channelCid, - referencedTable: $db.channels, - getReferencedColumn: (t) => t.cid, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$ChannelsTableFilterComposer( - $db: $db, - $table: $db.channels, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.channelCid, + referencedTable: $db.channels, + getReferencedColumn: (t) => t.cid, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$ChannelsTableFilterComposer( + $db: $db, + $table: $db.channels, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return composer; } $$MessagesTableFilterComposer get messageId { final $$MessagesTableFilterComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.messageId, - referencedTable: $db.messages, - getReferencedColumn: (t) => t.id, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$MessagesTableFilterComposer( - $db: $db, - $table: $db.messages, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.messageId, + referencedTable: $db.messages, + getReferencedColumn: (t) => t.id, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$MessagesTableFilterComposer( + $db: $db, + $table: $db.messages, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return composer; } } -class $$LocationsTableOrderingComposer - extends Composer<_$DriftChatDatabase, $LocationsTable> { +class $$LocationsTableOrderingComposer extends Composer<_$DriftChatDatabase, $LocationsTable> { $$LocationsTableOrderingComposer({ required super.$db, required super.$table, @@ -11800,71 +11454,65 @@ class $$LocationsTableOrderingComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnOrderings get userId => $composableBuilder( - column: $table.userId, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get userId => + $composableBuilder(column: $table.userId, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get latitude => $composableBuilder( - column: $table.latitude, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get latitude => + $composableBuilder(column: $table.latitude, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get longitude => $composableBuilder( - column: $table.longitude, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get longitude => + $composableBuilder(column: $table.longitude, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get createdByDeviceId => $composableBuilder( - column: $table.createdByDeviceId, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get createdByDeviceId => + $composableBuilder(column: $table.createdByDeviceId, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get endAt => $composableBuilder( - column: $table.endAt, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get endAt => + $composableBuilder(column: $table.endAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get createdAt => $composableBuilder( - column: $table.createdAt, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get updatedAt => $composableBuilder( - column: $table.updatedAt, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get updatedAt => + $composableBuilder(column: $table.updatedAt, builder: (column) => ColumnOrderings(column)); $$ChannelsTableOrderingComposer get channelCid { final $$ChannelsTableOrderingComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.channelCid, - referencedTable: $db.channels, - getReferencedColumn: (t) => t.cid, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$ChannelsTableOrderingComposer( - $db: $db, - $table: $db.channels, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.channelCid, + referencedTable: $db.channels, + getReferencedColumn: (t) => t.cid, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$ChannelsTableOrderingComposer( + $db: $db, + $table: $db.channels, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return composer; } $$MessagesTableOrderingComposer get messageId { final $$MessagesTableOrderingComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.messageId, - referencedTable: $db.messages, - getReferencedColumn: (t) => t.id, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$MessagesTableOrderingComposer( - $db: $db, - $table: $db.messages, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.messageId, + referencedTable: $db.messages, + getReferencedColumn: (t) => t.id, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$MessagesTableOrderingComposer( + $db: $db, + $table: $db.messages, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return composer; } } -class $$LocationsTableAnnotationComposer - extends Composer<_$DriftChatDatabase, $LocationsTable> { +class $$LocationsTableAnnotationComposer extends Composer<_$DriftChatDatabase, $LocationsTable> { $$LocationsTableAnnotationComposer({ required super.$db, required super.$table, @@ -11872,150 +11520,138 @@ class $$LocationsTableAnnotationComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - GeneratedColumn get userId => - $composableBuilder(column: $table.userId, builder: (column) => column); + GeneratedColumn get userId => $composableBuilder(column: $table.userId, builder: (column) => column); - GeneratedColumn get latitude => - $composableBuilder(column: $table.latitude, builder: (column) => column); + GeneratedColumn get latitude => $composableBuilder(column: $table.latitude, builder: (column) => column); - GeneratedColumn get longitude => - $composableBuilder(column: $table.longitude, builder: (column) => column); + GeneratedColumn get longitude => $composableBuilder(column: $table.longitude, builder: (column) => column); - GeneratedColumn get createdByDeviceId => $composableBuilder( - column: $table.createdByDeviceId, builder: (column) => column); + GeneratedColumn get createdByDeviceId => + $composableBuilder(column: $table.createdByDeviceId, builder: (column) => column); - GeneratedColumn get endAt => - $composableBuilder(column: $table.endAt, builder: (column) => column); + GeneratedColumn get endAt => $composableBuilder(column: $table.endAt, builder: (column) => column); - GeneratedColumn get createdAt => - $composableBuilder(column: $table.createdAt, builder: (column) => column); + GeneratedColumn get createdAt => $composableBuilder(column: $table.createdAt, builder: (column) => column); - GeneratedColumn get updatedAt => - $composableBuilder(column: $table.updatedAt, builder: (column) => column); + GeneratedColumn get updatedAt => $composableBuilder(column: $table.updatedAt, builder: (column) => column); $$ChannelsTableAnnotationComposer get channelCid { final $$ChannelsTableAnnotationComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.channelCid, - referencedTable: $db.channels, - getReferencedColumn: (t) => t.cid, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$ChannelsTableAnnotationComposer( - $db: $db, - $table: $db.channels, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.channelCid, + referencedTable: $db.channels, + getReferencedColumn: (t) => t.cid, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$ChannelsTableAnnotationComposer( + $db: $db, + $table: $db.channels, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return composer; } $$MessagesTableAnnotationComposer get messageId { final $$MessagesTableAnnotationComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.messageId, - referencedTable: $db.messages, - getReferencedColumn: (t) => t.id, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$MessagesTableAnnotationComposer( - $db: $db, - $table: $db.messages, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.messageId, + referencedTable: $db.messages, + getReferencedColumn: (t) => t.id, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$MessagesTableAnnotationComposer( + $db: $db, + $table: $db.messages, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return composer; } } -class $$LocationsTableTableManager extends RootTableManager< - _$DriftChatDatabase, - $LocationsTable, - LocationEntity, - $$LocationsTableFilterComposer, - $$LocationsTableOrderingComposer, - $$LocationsTableAnnotationComposer, - $$LocationsTableCreateCompanionBuilder, - $$LocationsTableUpdateCompanionBuilder, - (LocationEntity, $$LocationsTableReferences), - LocationEntity, - PrefetchHooks Function({bool channelCid, bool messageId})> { +class $$LocationsTableTableManager + extends + RootTableManager< + _$DriftChatDatabase, + $LocationsTable, + LocationEntity, + $$LocationsTableFilterComposer, + $$LocationsTableOrderingComposer, + $$LocationsTableAnnotationComposer, + $$LocationsTableCreateCompanionBuilder, + $$LocationsTableUpdateCompanionBuilder, + (LocationEntity, $$LocationsTableReferences), + LocationEntity, + PrefetchHooks Function({bool channelCid, bool messageId}) + > { $$LocationsTableTableManager(_$DriftChatDatabase db, $LocationsTable table) - : super(TableManagerState( + : super( + TableManagerState( db: db, table: table, - createFilteringComposer: () => - $$LocationsTableFilterComposer($db: db, $table: table), - createOrderingComposer: () => - $$LocationsTableOrderingComposer($db: db, $table: table), - createComputedFieldComposer: () => - $$LocationsTableAnnotationComposer($db: db, $table: table), - updateCompanionCallback: ({ - Value channelCid = const Value.absent(), - Value messageId = const Value.absent(), - Value userId = const Value.absent(), - Value latitude = const Value.absent(), - Value longitude = const Value.absent(), - Value createdByDeviceId = const Value.absent(), - Value endAt = const Value.absent(), - Value createdAt = const Value.absent(), - Value updatedAt = const Value.absent(), - Value rowid = const Value.absent(), - }) => - LocationsCompanion( - channelCid: channelCid, - messageId: messageId, - userId: userId, - latitude: latitude, - longitude: longitude, - createdByDeviceId: createdByDeviceId, - endAt: endAt, - createdAt: createdAt, - updatedAt: updatedAt, - rowid: rowid, - ), - createCompanionCallback: ({ - Value channelCid = const Value.absent(), - Value messageId = const Value.absent(), - Value userId = const Value.absent(), - required double latitude, - required double longitude, - Value createdByDeviceId = const Value.absent(), - Value endAt = const Value.absent(), - Value createdAt = const Value.absent(), - Value updatedAt = const Value.absent(), - Value rowid = const Value.absent(), - }) => - LocationsCompanion.insert( - channelCid: channelCid, - messageId: messageId, - userId: userId, - latitude: latitude, - longitude: longitude, - createdByDeviceId: createdByDeviceId, - endAt: endAt, - createdAt: createdAt, - updatedAt: updatedAt, - rowid: rowid, - ), - withReferenceMapper: (p0) => p0 - .map((e) => ( - e.readTable(table), - $$LocationsTableReferences(db, table, e) - )) - .toList(), + createFilteringComposer: () => $$LocationsTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => $$LocationsTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => $$LocationsTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value channelCid = const Value.absent(), + Value messageId = const Value.absent(), + Value userId = const Value.absent(), + Value latitude = const Value.absent(), + Value longitude = const Value.absent(), + Value createdByDeviceId = const Value.absent(), + Value endAt = const Value.absent(), + Value createdAt = const Value.absent(), + Value updatedAt = const Value.absent(), + Value rowid = const Value.absent(), + }) => LocationsCompanion( + channelCid: channelCid, + messageId: messageId, + userId: userId, + latitude: latitude, + longitude: longitude, + createdByDeviceId: createdByDeviceId, + endAt: endAt, + createdAt: createdAt, + updatedAt: updatedAt, + rowid: rowid, + ), + createCompanionCallback: + ({ + Value channelCid = const Value.absent(), + Value messageId = const Value.absent(), + Value userId = const Value.absent(), + required double latitude, + required double longitude, + Value createdByDeviceId = const Value.absent(), + Value endAt = const Value.absent(), + Value createdAt = const Value.absent(), + Value updatedAt = const Value.absent(), + Value rowid = const Value.absent(), + }) => LocationsCompanion.insert( + channelCid: channelCid, + messageId: messageId, + userId: userId, + latitude: latitude, + longitude: longitude, + createdByDeviceId: createdByDeviceId, + endAt: endAt, + createdAt: createdAt, + updatedAt: updatedAt, + rowid: rowid, + ), + withReferenceMapper: (p0) => + p0.map((e) => (e.readTable(table), $$LocationsTableReferences(db, table, e))).toList(), prefetchHooksCallback: ({channelCid = false, messageId = false}) { return PrefetchHooks( db: db, explicitlyWatchedTables: [], - addJoins: < - T extends TableManagerState< + addJoins: + < + T extends TableManagerState< dynamic, dynamic, dynamic, @@ -12026,150 +11662,150 @@ class $$LocationsTableTableManager extends RootTableManager< dynamic, dynamic, dynamic, - dynamic>>(state) { - if (channelCid) { - state = state.withJoin( - currentTable: table, - currentColumn: table.channelCid, - referencedTable: - $$LocationsTableReferences._channelCidTable(db), - referencedColumn: - $$LocationsTableReferences._channelCidTable(db).cid, - ) as T; - } - if (messageId) { - state = state.withJoin( - currentTable: table, - currentColumn: table.messageId, - referencedTable: - $$LocationsTableReferences._messageIdTable(db), - referencedColumn: - $$LocationsTableReferences._messageIdTable(db).id, - ) as T; - } - - return state; - }, + dynamic + > + >(state) { + if (channelCid) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.channelCid, + referencedTable: $$LocationsTableReferences._channelCidTable(db), + referencedColumn: $$LocationsTableReferences._channelCidTable(db).cid, + ) + as T; + } + if (messageId) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.messageId, + referencedTable: $$LocationsTableReferences._messageIdTable(db), + referencedColumn: $$LocationsTableReferences._messageIdTable(db).id, + ) + as T; + } + + return state; + }, getPrefetchedDataCallback: (items) async { return []; }, ); }, - )); + ), + ); } -typedef $$LocationsTableProcessedTableManager = ProcessedTableManager< - _$DriftChatDatabase, - $LocationsTable, - LocationEntity, - $$LocationsTableFilterComposer, - $$LocationsTableOrderingComposer, - $$LocationsTableAnnotationComposer, - $$LocationsTableCreateCompanionBuilder, - $$LocationsTableUpdateCompanionBuilder, - (LocationEntity, $$LocationsTableReferences), - LocationEntity, - PrefetchHooks Function({bool channelCid, bool messageId})>; -typedef $$PinnedMessagesTableCreateCompanionBuilder = PinnedMessagesCompanion - Function({ - required String id, - Value messageText, - required List attachments, - required String state, - Value type, - required List mentionedUsers, - Value?> reactionGroups, - Value parentId, - Value quotedMessageId, - Value pollId, - Value replyCount, - Value showInChannel, - Value shadowed, - Value command, - Value localCreatedAt, - Value remoteCreatedAt, - Value localUpdatedAt, - Value remoteUpdatedAt, - Value localDeletedAt, - Value remoteDeletedAt, - Value deletedForMe, - Value messageTextUpdatedAt, - Value userId, - Value channelRole, - Value pinned, - Value pinnedAt, - Value pinExpires, - Value pinnedByUserId, - required String channelCid, - Value?> i18n, - Value?> restrictedVisibility, - Value?> extraData, - Value rowid, -}); -typedef $$PinnedMessagesTableUpdateCompanionBuilder = PinnedMessagesCompanion - Function({ - Value id, - Value messageText, - Value> attachments, - Value state, - Value type, - Value> mentionedUsers, - Value?> reactionGroups, - Value parentId, - Value quotedMessageId, - Value pollId, - Value replyCount, - Value showInChannel, - Value shadowed, - Value command, - Value localCreatedAt, - Value remoteCreatedAt, - Value localUpdatedAt, - Value remoteUpdatedAt, - Value localDeletedAt, - Value remoteDeletedAt, - Value deletedForMe, - Value messageTextUpdatedAt, - Value userId, - Value channelRole, - Value pinned, - Value pinnedAt, - Value pinExpires, - Value pinnedByUserId, - Value channelCid, - Value?> i18n, - Value?> restrictedVisibility, - Value?> extraData, - Value rowid, -}); - -final class $$PinnedMessagesTableReferences extends BaseReferences< - _$DriftChatDatabase, $PinnedMessagesTable, PinnedMessageEntity> { - $$PinnedMessagesTableReferences( - super.$_db, super.$_table, super.$_typedResult); - - static MultiTypedResultKey<$PinnedMessageReactionsTable, - List> _pinnedMessageReactionsRefsTable( - _$DriftChatDatabase db) => - MultiTypedResultKey.fromTable(db.pinnedMessageReactions, - aliasName: $_aliasNameGenerator( - db.pinnedMessages.id, db.pinnedMessageReactions.messageId)); - - $$PinnedMessageReactionsTableProcessedTableManager - get pinnedMessageReactionsRefs { +typedef $$LocationsTableProcessedTableManager = + ProcessedTableManager< + _$DriftChatDatabase, + $LocationsTable, + LocationEntity, + $$LocationsTableFilterComposer, + $$LocationsTableOrderingComposer, + $$LocationsTableAnnotationComposer, + $$LocationsTableCreateCompanionBuilder, + $$LocationsTableUpdateCompanionBuilder, + (LocationEntity, $$LocationsTableReferences), + LocationEntity, + PrefetchHooks Function({bool channelCid, bool messageId}) + >; +typedef $$PinnedMessagesTableCreateCompanionBuilder = + PinnedMessagesCompanion Function({ + required String id, + Value messageText, + required List attachments, + required String state, + Value type, + required List mentionedUsers, + Value?> reactionGroups, + Value parentId, + Value quotedMessageId, + Value pollId, + Value replyCount, + Value showInChannel, + Value shadowed, + Value command, + Value localCreatedAt, + Value remoteCreatedAt, + Value localUpdatedAt, + Value remoteUpdatedAt, + Value localDeletedAt, + Value remoteDeletedAt, + Value deletedForMe, + Value messageTextUpdatedAt, + Value userId, + Value channelRole, + Value pinned, + Value pinnedAt, + Value pinExpires, + Value pinnedByUserId, + required String channelCid, + Value?> i18n, + Value?> restrictedVisibility, + Value?> extraData, + Value rowid, + }); +typedef $$PinnedMessagesTableUpdateCompanionBuilder = + PinnedMessagesCompanion Function({ + Value id, + Value messageText, + Value> attachments, + Value state, + Value type, + Value> mentionedUsers, + Value?> reactionGroups, + Value parentId, + Value quotedMessageId, + Value pollId, + Value replyCount, + Value showInChannel, + Value shadowed, + Value command, + Value localCreatedAt, + Value remoteCreatedAt, + Value localUpdatedAt, + Value remoteUpdatedAt, + Value localDeletedAt, + Value remoteDeletedAt, + Value deletedForMe, + Value messageTextUpdatedAt, + Value userId, + Value channelRole, + Value pinned, + Value pinnedAt, + Value pinExpires, + Value pinnedByUserId, + Value channelCid, + Value?> i18n, + Value?> restrictedVisibility, + Value?> extraData, + Value rowid, + }); + +final class $$PinnedMessagesTableReferences + extends BaseReferences<_$DriftChatDatabase, $PinnedMessagesTable, PinnedMessageEntity> { + $$PinnedMessagesTableReferences(super.$_db, super.$_table, super.$_typedResult); + + static MultiTypedResultKey<$PinnedMessageReactionsTable, List> + _pinnedMessageReactionsRefsTable(_$DriftChatDatabase db) => MultiTypedResultKey.fromTable( + db.pinnedMessageReactions, + aliasName: $_aliasNameGenerator(db.pinnedMessages.id, db.pinnedMessageReactions.messageId), + ); + + $$PinnedMessageReactionsTableProcessedTableManager get pinnedMessageReactionsRefs { final manager = $$PinnedMessageReactionsTableTableManager( - $_db, $_db.pinnedMessageReactions) - .filter((f) => f.messageId.id.sqlEquals($_itemColumn('id')!)); + $_db, + $_db.pinnedMessageReactions, + ).filter((f) => f.messageId.id.sqlEquals($_itemColumn('id')!)); - final cache = - $_typedResult.readTableOrNull(_pinnedMessageReactionsRefsTable($_db)); - return ProcessedTableManager( - manager.$state.copyWith(prefetchedData: cache)); + final cache = $_typedResult.readTableOrNull(_pinnedMessageReactionsRefsTable($_db)); + return ProcessedTableManager(manager.$state.copyWith(prefetchedData: cache)); } } -class $$PinnedMessagesTableFilterComposer - extends Composer<_$DriftChatDatabase, $PinnedMessagesTable> { +class $$PinnedMessagesTableFilterComposer extends Composer<_$DriftChatDatabase, $PinnedMessagesTable> { $$PinnedMessagesTableFilterComposer({ required super.$db, required super.$table, @@ -12177,152 +11813,124 @@ class $$PinnedMessagesTableFilterComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnFilters get id => $composableBuilder( - column: $table.id, builder: (column) => ColumnFilters(column)); + ColumnFilters get id => $composableBuilder(column: $table.id, builder: (column) => ColumnFilters(column)); - ColumnFilters get messageText => $composableBuilder( - column: $table.messageText, builder: (column) => ColumnFilters(column)); + ColumnFilters get messageText => + $composableBuilder(column: $table.messageText, builder: (column) => ColumnFilters(column)); - ColumnWithTypeConverterFilters, List, String> - get attachments => $composableBuilder( - column: $table.attachments, - builder: (column) => ColumnWithTypeConverterFilters(column)); + ColumnWithTypeConverterFilters, List, String> get attachments => + $composableBuilder(column: $table.attachments, builder: (column) => ColumnWithTypeConverterFilters(column)); - ColumnFilters get state => $composableBuilder( - column: $table.state, builder: (column) => ColumnFilters(column)); + ColumnFilters get state => + $composableBuilder(column: $table.state, builder: (column) => ColumnFilters(column)); - ColumnFilters get type => $composableBuilder( - column: $table.type, builder: (column) => ColumnFilters(column)); + ColumnFilters get type => $composableBuilder(column: $table.type, builder: (column) => ColumnFilters(column)); - ColumnWithTypeConverterFilters, List, String> - get mentionedUsers => $composableBuilder( - column: $table.mentionedUsers, - builder: (column) => ColumnWithTypeConverterFilters(column)); + ColumnWithTypeConverterFilters, List, String> get mentionedUsers => + $composableBuilder(column: $table.mentionedUsers, builder: (column) => ColumnWithTypeConverterFilters(column)); - ColumnWithTypeConverterFilters?, - Map, String> - get reactionGroups => $composableBuilder( - column: $table.reactionGroups, - builder: (column) => ColumnWithTypeConverterFilters(column)); + ColumnWithTypeConverterFilters?, Map, String> get reactionGroups => + $composableBuilder(column: $table.reactionGroups, builder: (column) => ColumnWithTypeConverterFilters(column)); - ColumnFilters get parentId => $composableBuilder( - column: $table.parentId, builder: (column) => ColumnFilters(column)); + ColumnFilters get parentId => + $composableBuilder(column: $table.parentId, builder: (column) => ColumnFilters(column)); - ColumnFilters get quotedMessageId => $composableBuilder( - column: $table.quotedMessageId, - builder: (column) => ColumnFilters(column)); + ColumnFilters get quotedMessageId => + $composableBuilder(column: $table.quotedMessageId, builder: (column) => ColumnFilters(column)); - ColumnFilters get pollId => $composableBuilder( - column: $table.pollId, builder: (column) => ColumnFilters(column)); + ColumnFilters get pollId => + $composableBuilder(column: $table.pollId, builder: (column) => ColumnFilters(column)); - ColumnFilters get replyCount => $composableBuilder( - column: $table.replyCount, builder: (column) => ColumnFilters(column)); + ColumnFilters get replyCount => + $composableBuilder(column: $table.replyCount, builder: (column) => ColumnFilters(column)); - ColumnFilters get showInChannel => $composableBuilder( - column: $table.showInChannel, builder: (column) => ColumnFilters(column)); + ColumnFilters get showInChannel => + $composableBuilder(column: $table.showInChannel, builder: (column) => ColumnFilters(column)); - ColumnFilters get shadowed => $composableBuilder( - column: $table.shadowed, builder: (column) => ColumnFilters(column)); + ColumnFilters get shadowed => + $composableBuilder(column: $table.shadowed, builder: (column) => ColumnFilters(column)); - ColumnFilters get command => $composableBuilder( - column: $table.command, builder: (column) => ColumnFilters(column)); + ColumnFilters get command => + $composableBuilder(column: $table.command, builder: (column) => ColumnFilters(column)); - ColumnFilters get localCreatedAt => $composableBuilder( - column: $table.localCreatedAt, - builder: (column) => ColumnFilters(column)); + ColumnFilters get localCreatedAt => + $composableBuilder(column: $table.localCreatedAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get remoteCreatedAt => $composableBuilder( - column: $table.remoteCreatedAt, - builder: (column) => ColumnFilters(column)); + ColumnFilters get remoteCreatedAt => + $composableBuilder(column: $table.remoteCreatedAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get localUpdatedAt => $composableBuilder( - column: $table.localUpdatedAt, - builder: (column) => ColumnFilters(column)); + ColumnFilters get localUpdatedAt => + $composableBuilder(column: $table.localUpdatedAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get remoteUpdatedAt => $composableBuilder( - column: $table.remoteUpdatedAt, - builder: (column) => ColumnFilters(column)); + ColumnFilters get remoteUpdatedAt => + $composableBuilder(column: $table.remoteUpdatedAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get localDeletedAt => $composableBuilder( - column: $table.localDeletedAt, - builder: (column) => ColumnFilters(column)); + ColumnFilters get localDeletedAt => + $composableBuilder(column: $table.localDeletedAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get remoteDeletedAt => $composableBuilder( - column: $table.remoteDeletedAt, - builder: (column) => ColumnFilters(column)); + ColumnFilters get remoteDeletedAt => + $composableBuilder(column: $table.remoteDeletedAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get deletedForMe => $composableBuilder( - column: $table.deletedForMe, builder: (column) => ColumnFilters(column)); + ColumnFilters get deletedForMe => + $composableBuilder(column: $table.deletedForMe, builder: (column) => ColumnFilters(column)); - ColumnFilters get messageTextUpdatedAt => $composableBuilder( - column: $table.messageTextUpdatedAt, - builder: (column) => ColumnFilters(column)); + ColumnFilters get messageTextUpdatedAt => + $composableBuilder(column: $table.messageTextUpdatedAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get userId => $composableBuilder( - column: $table.userId, builder: (column) => ColumnFilters(column)); + ColumnFilters get userId => + $composableBuilder(column: $table.userId, builder: (column) => ColumnFilters(column)); - ColumnFilters get channelRole => $composableBuilder( - column: $table.channelRole, builder: (column) => ColumnFilters(column)); + ColumnFilters get channelRole => + $composableBuilder(column: $table.channelRole, builder: (column) => ColumnFilters(column)); - ColumnFilters get pinned => $composableBuilder( - column: $table.pinned, builder: (column) => ColumnFilters(column)); + ColumnFilters get pinned => + $composableBuilder(column: $table.pinned, builder: (column) => ColumnFilters(column)); - ColumnFilters get pinnedAt => $composableBuilder( - column: $table.pinnedAt, builder: (column) => ColumnFilters(column)); + ColumnFilters get pinnedAt => + $composableBuilder(column: $table.pinnedAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get pinExpires => $composableBuilder( - column: $table.pinExpires, builder: (column) => ColumnFilters(column)); + ColumnFilters get pinExpires => + $composableBuilder(column: $table.pinExpires, builder: (column) => ColumnFilters(column)); - ColumnFilters get pinnedByUserId => $composableBuilder( - column: $table.pinnedByUserId, - builder: (column) => ColumnFilters(column)); + ColumnFilters get pinnedByUserId => + $composableBuilder(column: $table.pinnedByUserId, builder: (column) => ColumnFilters(column)); - ColumnFilters get channelCid => $composableBuilder( - column: $table.channelCid, builder: (column) => ColumnFilters(column)); + ColumnFilters get channelCid => + $composableBuilder(column: $table.channelCid, builder: (column) => ColumnFilters(column)); - ColumnWithTypeConverterFilters?, Map, - String> - get i18n => $composableBuilder( - column: $table.i18n, - builder: (column) => ColumnWithTypeConverterFilters(column)); + ColumnWithTypeConverterFilters?, Map, String> get i18n => + $composableBuilder(column: $table.i18n, builder: (column) => ColumnWithTypeConverterFilters(column)); - ColumnWithTypeConverterFilters?, List, String> - get restrictedVisibility => $composableBuilder( - column: $table.restrictedVisibility, - builder: (column) => ColumnWithTypeConverterFilters(column)); + ColumnWithTypeConverterFilters?, List, String> get restrictedVisibility => $composableBuilder( + column: $table.restrictedVisibility, + builder: (column) => ColumnWithTypeConverterFilters(column), + ); - ColumnWithTypeConverterFilters?, Map, - String> - get extraData => $composableBuilder( - column: $table.extraData, - builder: (column) => ColumnWithTypeConverterFilters(column)); + ColumnWithTypeConverterFilters?, Map, String> get extraData => + $composableBuilder(column: $table.extraData, builder: (column) => ColumnWithTypeConverterFilters(column)); Expression pinnedMessageReactionsRefs( - Expression Function($$PinnedMessageReactionsTableFilterComposer f) - f) { - final $$PinnedMessageReactionsTableFilterComposer composer = - $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.id, - referencedTable: $db.pinnedMessageReactions, - getReferencedColumn: (t) => t.messageId, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$PinnedMessageReactionsTableFilterComposer( - $db: $db, - $table: $db.pinnedMessageReactions, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + Expression Function($$PinnedMessageReactionsTableFilterComposer f) f, + ) { + final $$PinnedMessageReactionsTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.pinnedMessageReactions, + getReferencedColumn: (t) => t.messageId, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$PinnedMessageReactionsTableFilterComposer( + $db: $db, + $table: $db.pinnedMessageReactions, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return f(composer); } } -class $$PinnedMessagesTableOrderingComposer - extends Composer<_$DriftChatDatabase, $PinnedMessagesTable> { +class $$PinnedMessagesTableOrderingComposer extends Composer<_$DriftChatDatabase, $PinnedMessagesTable> { $$PinnedMessagesTableOrderingComposer({ required super.$db, required super.$table, @@ -12330,119 +11938,103 @@ class $$PinnedMessagesTableOrderingComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnOrderings get id => $composableBuilder( - column: $table.id, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get id => $composableBuilder(column: $table.id, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get messageText => $composableBuilder( - column: $table.messageText, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get messageText => + $composableBuilder(column: $table.messageText, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get attachments => $composableBuilder( - column: $table.attachments, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get attachments => + $composableBuilder(column: $table.attachments, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get state => $composableBuilder( - column: $table.state, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get state => + $composableBuilder(column: $table.state, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get type => $composableBuilder( - column: $table.type, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get type => + $composableBuilder(column: $table.type, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get mentionedUsers => $composableBuilder( - column: $table.mentionedUsers, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get mentionedUsers => + $composableBuilder(column: $table.mentionedUsers, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get reactionGroups => $composableBuilder( - column: $table.reactionGroups, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get reactionGroups => + $composableBuilder(column: $table.reactionGroups, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get parentId => $composableBuilder( - column: $table.parentId, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get parentId => + $composableBuilder(column: $table.parentId, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get quotedMessageId => $composableBuilder( - column: $table.quotedMessageId, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get quotedMessageId => + $composableBuilder(column: $table.quotedMessageId, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get pollId => $composableBuilder( - column: $table.pollId, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get pollId => + $composableBuilder(column: $table.pollId, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get replyCount => $composableBuilder( - column: $table.replyCount, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get replyCount => + $composableBuilder(column: $table.replyCount, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get showInChannel => $composableBuilder( - column: $table.showInChannel, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get showInChannel => + $composableBuilder(column: $table.showInChannel, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get shadowed => $composableBuilder( - column: $table.shadowed, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get shadowed => + $composableBuilder(column: $table.shadowed, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get command => $composableBuilder( - column: $table.command, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get command => + $composableBuilder(column: $table.command, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get localCreatedAt => $composableBuilder( - column: $table.localCreatedAt, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get localCreatedAt => + $composableBuilder(column: $table.localCreatedAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get remoteCreatedAt => $composableBuilder( - column: $table.remoteCreatedAt, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get remoteCreatedAt => + $composableBuilder(column: $table.remoteCreatedAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get localUpdatedAt => $composableBuilder( - column: $table.localUpdatedAt, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get localUpdatedAt => + $composableBuilder(column: $table.localUpdatedAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get remoteUpdatedAt => $composableBuilder( - column: $table.remoteUpdatedAt, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get remoteUpdatedAt => + $composableBuilder(column: $table.remoteUpdatedAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get localDeletedAt => $composableBuilder( - column: $table.localDeletedAt, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get localDeletedAt => + $composableBuilder(column: $table.localDeletedAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get remoteDeletedAt => $composableBuilder( - column: $table.remoteDeletedAt, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get remoteDeletedAt => + $composableBuilder(column: $table.remoteDeletedAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get deletedForMe => $composableBuilder( - column: $table.deletedForMe, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get deletedForMe => + $composableBuilder(column: $table.deletedForMe, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get messageTextUpdatedAt => $composableBuilder( - column: $table.messageTextUpdatedAt, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get messageTextUpdatedAt => + $composableBuilder(column: $table.messageTextUpdatedAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get userId => $composableBuilder( - column: $table.userId, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get userId => + $composableBuilder(column: $table.userId, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get channelRole => $composableBuilder( - column: $table.channelRole, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get channelRole => + $composableBuilder(column: $table.channelRole, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get pinned => $composableBuilder( - column: $table.pinned, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get pinned => + $composableBuilder(column: $table.pinned, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get pinnedAt => $composableBuilder( - column: $table.pinnedAt, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get pinnedAt => + $composableBuilder(column: $table.pinnedAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get pinExpires => $composableBuilder( - column: $table.pinExpires, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get pinExpires => + $composableBuilder(column: $table.pinExpires, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get pinnedByUserId => $composableBuilder( - column: $table.pinnedByUserId, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get pinnedByUserId => + $composableBuilder(column: $table.pinnedByUserId, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get channelCid => $composableBuilder( - column: $table.channelCid, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get channelCid => + $composableBuilder(column: $table.channelCid, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get i18n => $composableBuilder( - column: $table.i18n, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get i18n => + $composableBuilder(column: $table.i18n, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get restrictedVisibility => $composableBuilder( - column: $table.restrictedVisibility, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get restrictedVisibility => + $composableBuilder(column: $table.restrictedVisibility, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get extraData => $composableBuilder( - column: $table.extraData, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get extraData => + $composableBuilder(column: $table.extraData, builder: (column) => ColumnOrderings(column)); } -class $$PinnedMessagesTableAnnotationComposer - extends Composer<_$DriftChatDatabase, $PinnedMessagesTable> { +class $$PinnedMessagesTableAnnotationComposer extends Composer<_$DriftChatDatabase, $PinnedMessagesTable> { $$PinnedMessagesTableAnnotationComposer({ required super.$db, required super.$table, @@ -12450,406 +12042,376 @@ class $$PinnedMessagesTableAnnotationComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - GeneratedColumn get id => - $composableBuilder(column: $table.id, builder: (column) => column); + GeneratedColumn get id => $composableBuilder(column: $table.id, builder: (column) => column); - GeneratedColumn get messageText => $composableBuilder( - column: $table.messageText, builder: (column) => column); + GeneratedColumn get messageText => + $composableBuilder(column: $table.messageText, builder: (column) => column); GeneratedColumnWithTypeConverter, String> get attachments => - $composableBuilder( - column: $table.attachments, builder: (column) => column); + $composableBuilder(column: $table.attachments, builder: (column) => column); - GeneratedColumn get state => - $composableBuilder(column: $table.state, builder: (column) => column); + GeneratedColumn get state => $composableBuilder(column: $table.state, builder: (column) => column); - GeneratedColumn get type => - $composableBuilder(column: $table.type, builder: (column) => column); + GeneratedColumn get type => $composableBuilder(column: $table.type, builder: (column) => column); GeneratedColumnWithTypeConverter, String> get mentionedUsers => - $composableBuilder( - column: $table.mentionedUsers, builder: (column) => column); + $composableBuilder(column: $table.mentionedUsers, builder: (column) => column); - GeneratedColumnWithTypeConverter?, String> - get reactionGroups => $composableBuilder( - column: $table.reactionGroups, builder: (column) => column); + GeneratedColumnWithTypeConverter?, String> get reactionGroups => + $composableBuilder(column: $table.reactionGroups, builder: (column) => column); - GeneratedColumn get parentId => - $composableBuilder(column: $table.parentId, builder: (column) => column); + GeneratedColumn get parentId => $composableBuilder(column: $table.parentId, builder: (column) => column); - GeneratedColumn get quotedMessageId => $composableBuilder( - column: $table.quotedMessageId, builder: (column) => column); + GeneratedColumn get quotedMessageId => + $composableBuilder(column: $table.quotedMessageId, builder: (column) => column); - GeneratedColumn get pollId => - $composableBuilder(column: $table.pollId, builder: (column) => column); + GeneratedColumn get pollId => $composableBuilder(column: $table.pollId, builder: (column) => column); - GeneratedColumn get replyCount => $composableBuilder( - column: $table.replyCount, builder: (column) => column); + GeneratedColumn get replyCount => $composableBuilder(column: $table.replyCount, builder: (column) => column); - GeneratedColumn get showInChannel => $composableBuilder( - column: $table.showInChannel, builder: (column) => column); + GeneratedColumn get showInChannel => + $composableBuilder(column: $table.showInChannel, builder: (column) => column); - GeneratedColumn get shadowed => - $composableBuilder(column: $table.shadowed, builder: (column) => column); + GeneratedColumn get shadowed => $composableBuilder(column: $table.shadowed, builder: (column) => column); - GeneratedColumn get command => - $composableBuilder(column: $table.command, builder: (column) => column); + GeneratedColumn get command => $composableBuilder(column: $table.command, builder: (column) => column); - GeneratedColumn get localCreatedAt => $composableBuilder( - column: $table.localCreatedAt, builder: (column) => column); + GeneratedColumn get localCreatedAt => + $composableBuilder(column: $table.localCreatedAt, builder: (column) => column); - GeneratedColumn get remoteCreatedAt => $composableBuilder( - column: $table.remoteCreatedAt, builder: (column) => column); + GeneratedColumn get remoteCreatedAt => + $composableBuilder(column: $table.remoteCreatedAt, builder: (column) => column); - GeneratedColumn get localUpdatedAt => $composableBuilder( - column: $table.localUpdatedAt, builder: (column) => column); + GeneratedColumn get localUpdatedAt => + $composableBuilder(column: $table.localUpdatedAt, builder: (column) => column); - GeneratedColumn get remoteUpdatedAt => $composableBuilder( - column: $table.remoteUpdatedAt, builder: (column) => column); + GeneratedColumn get remoteUpdatedAt => + $composableBuilder(column: $table.remoteUpdatedAt, builder: (column) => column); - GeneratedColumn get localDeletedAt => $composableBuilder( - column: $table.localDeletedAt, builder: (column) => column); + GeneratedColumn get localDeletedAt => + $composableBuilder(column: $table.localDeletedAt, builder: (column) => column); - GeneratedColumn get remoteDeletedAt => $composableBuilder( - column: $table.remoteDeletedAt, builder: (column) => column); + GeneratedColumn get remoteDeletedAt => + $composableBuilder(column: $table.remoteDeletedAt, builder: (column) => column); - GeneratedColumn get deletedForMe => $composableBuilder( - column: $table.deletedForMe, builder: (column) => column); + GeneratedColumn get deletedForMe => + $composableBuilder(column: $table.deletedForMe, builder: (column) => column); - GeneratedColumn get messageTextUpdatedAt => $composableBuilder( - column: $table.messageTextUpdatedAt, builder: (column) => column); + GeneratedColumn get messageTextUpdatedAt => + $composableBuilder(column: $table.messageTextUpdatedAt, builder: (column) => column); - GeneratedColumn get userId => - $composableBuilder(column: $table.userId, builder: (column) => column); + GeneratedColumn get userId => $composableBuilder(column: $table.userId, builder: (column) => column); - GeneratedColumn get channelRole => $composableBuilder( - column: $table.channelRole, builder: (column) => column); + GeneratedColumn get channelRole => + $composableBuilder(column: $table.channelRole, builder: (column) => column); - GeneratedColumn get pinned => - $composableBuilder(column: $table.pinned, builder: (column) => column); + GeneratedColumn get pinned => $composableBuilder(column: $table.pinned, builder: (column) => column); - GeneratedColumn get pinnedAt => - $composableBuilder(column: $table.pinnedAt, builder: (column) => column); + GeneratedColumn get pinnedAt => $composableBuilder(column: $table.pinnedAt, builder: (column) => column); - GeneratedColumn get pinExpires => $composableBuilder( - column: $table.pinExpires, builder: (column) => column); + GeneratedColumn get pinExpires => + $composableBuilder(column: $table.pinExpires, builder: (column) => column); - GeneratedColumn get pinnedByUserId => $composableBuilder( - column: $table.pinnedByUserId, builder: (column) => column); + GeneratedColumn get pinnedByUserId => + $composableBuilder(column: $table.pinnedByUserId, builder: (column) => column); - GeneratedColumn get channelCid => $composableBuilder( - column: $table.channelCid, builder: (column) => column); + GeneratedColumn get channelCid => $composableBuilder(column: $table.channelCid, builder: (column) => column); GeneratedColumnWithTypeConverter?, String> get i18n => $composableBuilder(column: $table.i18n, builder: (column) => column); - GeneratedColumnWithTypeConverter?, String> - get restrictedVisibility => $composableBuilder( - column: $table.restrictedVisibility, builder: (column) => column); + GeneratedColumnWithTypeConverter?, String> get restrictedVisibility => + $composableBuilder(column: $table.restrictedVisibility, builder: (column) => column); - GeneratedColumnWithTypeConverter?, String> - get extraData => $composableBuilder( - column: $table.extraData, builder: (column) => column); + GeneratedColumnWithTypeConverter?, String> get extraData => + $composableBuilder(column: $table.extraData, builder: (column) => column); Expression pinnedMessageReactionsRefs( - Expression Function($$PinnedMessageReactionsTableAnnotationComposer a) - f) { - final $$PinnedMessageReactionsTableAnnotationComposer composer = - $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.id, - referencedTable: $db.pinnedMessageReactions, - getReferencedColumn: (t) => t.messageId, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$PinnedMessageReactionsTableAnnotationComposer( - $db: $db, - $table: $db.pinnedMessageReactions, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + Expression Function($$PinnedMessageReactionsTableAnnotationComposer a) f, + ) { + final $$PinnedMessageReactionsTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.pinnedMessageReactions, + getReferencedColumn: (t) => t.messageId, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$PinnedMessageReactionsTableAnnotationComposer( + $db: $db, + $table: $db.pinnedMessageReactions, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return f(composer); } } -class $$PinnedMessagesTableTableManager extends RootTableManager< - _$DriftChatDatabase, - $PinnedMessagesTable, - PinnedMessageEntity, - $$PinnedMessagesTableFilterComposer, - $$PinnedMessagesTableOrderingComposer, - $$PinnedMessagesTableAnnotationComposer, - $$PinnedMessagesTableCreateCompanionBuilder, - $$PinnedMessagesTableUpdateCompanionBuilder, - (PinnedMessageEntity, $$PinnedMessagesTableReferences), - PinnedMessageEntity, - PrefetchHooks Function({bool pinnedMessageReactionsRefs})> { - $$PinnedMessagesTableTableManager( - _$DriftChatDatabase db, $PinnedMessagesTable table) - : super(TableManagerState( +class $$PinnedMessagesTableTableManager + extends + RootTableManager< + _$DriftChatDatabase, + $PinnedMessagesTable, + PinnedMessageEntity, + $$PinnedMessagesTableFilterComposer, + $$PinnedMessagesTableOrderingComposer, + $$PinnedMessagesTableAnnotationComposer, + $$PinnedMessagesTableCreateCompanionBuilder, + $$PinnedMessagesTableUpdateCompanionBuilder, + (PinnedMessageEntity, $$PinnedMessagesTableReferences), + PinnedMessageEntity, + PrefetchHooks Function({bool pinnedMessageReactionsRefs}) + > { + $$PinnedMessagesTableTableManager(_$DriftChatDatabase db, $PinnedMessagesTable table) + : super( + TableManagerState( db: db, table: table, - createFilteringComposer: () => - $$PinnedMessagesTableFilterComposer($db: db, $table: table), - createOrderingComposer: () => - $$PinnedMessagesTableOrderingComposer($db: db, $table: table), - createComputedFieldComposer: () => - $$PinnedMessagesTableAnnotationComposer($db: db, $table: table), - updateCompanionCallback: ({ - Value id = const Value.absent(), - Value messageText = const Value.absent(), - Value> attachments = const Value.absent(), - Value state = const Value.absent(), - Value type = const Value.absent(), - Value> mentionedUsers = const Value.absent(), - Value?> reactionGroups = - const Value.absent(), - Value parentId = const Value.absent(), - Value quotedMessageId = const Value.absent(), - Value pollId = const Value.absent(), - Value replyCount = const Value.absent(), - Value showInChannel = const Value.absent(), - Value shadowed = const Value.absent(), - Value command = const Value.absent(), - Value localCreatedAt = const Value.absent(), - Value remoteCreatedAt = const Value.absent(), - Value localUpdatedAt = const Value.absent(), - Value remoteUpdatedAt = const Value.absent(), - Value localDeletedAt = const Value.absent(), - Value remoteDeletedAt = const Value.absent(), - Value deletedForMe = const Value.absent(), - Value messageTextUpdatedAt = const Value.absent(), - Value userId = const Value.absent(), - Value channelRole = const Value.absent(), - Value pinned = const Value.absent(), - Value pinnedAt = const Value.absent(), - Value pinExpires = const Value.absent(), - Value pinnedByUserId = const Value.absent(), - Value channelCid = const Value.absent(), - Value?> i18n = const Value.absent(), - Value?> restrictedVisibility = const Value.absent(), - Value?> extraData = const Value.absent(), - Value rowid = const Value.absent(), - }) => - PinnedMessagesCompanion( - id: id, - messageText: messageText, - attachments: attachments, - state: state, - type: type, - mentionedUsers: mentionedUsers, - reactionGroups: reactionGroups, - parentId: parentId, - quotedMessageId: quotedMessageId, - pollId: pollId, - replyCount: replyCount, - showInChannel: showInChannel, - shadowed: shadowed, - command: command, - localCreatedAt: localCreatedAt, - remoteCreatedAt: remoteCreatedAt, - localUpdatedAt: localUpdatedAt, - remoteUpdatedAt: remoteUpdatedAt, - localDeletedAt: localDeletedAt, - remoteDeletedAt: remoteDeletedAt, - deletedForMe: deletedForMe, - messageTextUpdatedAt: messageTextUpdatedAt, - userId: userId, - channelRole: channelRole, - pinned: pinned, - pinnedAt: pinnedAt, - pinExpires: pinExpires, - pinnedByUserId: pinnedByUserId, - channelCid: channelCid, - i18n: i18n, - restrictedVisibility: restrictedVisibility, - extraData: extraData, - rowid: rowid, - ), - createCompanionCallback: ({ - required String id, - Value messageText = const Value.absent(), - required List attachments, - required String state, - Value type = const Value.absent(), - required List mentionedUsers, - Value?> reactionGroups = - const Value.absent(), - Value parentId = const Value.absent(), - Value quotedMessageId = const Value.absent(), - Value pollId = const Value.absent(), - Value replyCount = const Value.absent(), - Value showInChannel = const Value.absent(), - Value shadowed = const Value.absent(), - Value command = const Value.absent(), - Value localCreatedAt = const Value.absent(), - Value remoteCreatedAt = const Value.absent(), - Value localUpdatedAt = const Value.absent(), - Value remoteUpdatedAt = const Value.absent(), - Value localDeletedAt = const Value.absent(), - Value remoteDeletedAt = const Value.absent(), - Value deletedForMe = const Value.absent(), - Value messageTextUpdatedAt = const Value.absent(), - Value userId = const Value.absent(), - Value channelRole = const Value.absent(), - Value pinned = const Value.absent(), - Value pinnedAt = const Value.absent(), - Value pinExpires = const Value.absent(), - Value pinnedByUserId = const Value.absent(), - required String channelCid, - Value?> i18n = const Value.absent(), - Value?> restrictedVisibility = const Value.absent(), - Value?> extraData = const Value.absent(), - Value rowid = const Value.absent(), - }) => - PinnedMessagesCompanion.insert( - id: id, - messageText: messageText, - attachments: attachments, - state: state, - type: type, - mentionedUsers: mentionedUsers, - reactionGroups: reactionGroups, - parentId: parentId, - quotedMessageId: quotedMessageId, - pollId: pollId, - replyCount: replyCount, - showInChannel: showInChannel, - shadowed: shadowed, - command: command, - localCreatedAt: localCreatedAt, - remoteCreatedAt: remoteCreatedAt, - localUpdatedAt: localUpdatedAt, - remoteUpdatedAt: remoteUpdatedAt, - localDeletedAt: localDeletedAt, - remoteDeletedAt: remoteDeletedAt, - deletedForMe: deletedForMe, - messageTextUpdatedAt: messageTextUpdatedAt, - userId: userId, - channelRole: channelRole, - pinned: pinned, - pinnedAt: pinnedAt, - pinExpires: pinExpires, - pinnedByUserId: pinnedByUserId, - channelCid: channelCid, - i18n: i18n, - restrictedVisibility: restrictedVisibility, - extraData: extraData, - rowid: rowid, - ), - withReferenceMapper: (p0) => p0 - .map((e) => ( - e.readTable(table), - $$PinnedMessagesTableReferences(db, table, e) - )) - .toList(), + createFilteringComposer: () => $$PinnedMessagesTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => $$PinnedMessagesTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => $$PinnedMessagesTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value id = const Value.absent(), + Value messageText = const Value.absent(), + Value> attachments = const Value.absent(), + Value state = const Value.absent(), + Value type = const Value.absent(), + Value> mentionedUsers = const Value.absent(), + Value?> reactionGroups = const Value.absent(), + Value parentId = const Value.absent(), + Value quotedMessageId = const Value.absent(), + Value pollId = const Value.absent(), + Value replyCount = const Value.absent(), + Value showInChannel = const Value.absent(), + Value shadowed = const Value.absent(), + Value command = const Value.absent(), + Value localCreatedAt = const Value.absent(), + Value remoteCreatedAt = const Value.absent(), + Value localUpdatedAt = const Value.absent(), + Value remoteUpdatedAt = const Value.absent(), + Value localDeletedAt = const Value.absent(), + Value remoteDeletedAt = const Value.absent(), + Value deletedForMe = const Value.absent(), + Value messageTextUpdatedAt = const Value.absent(), + Value userId = const Value.absent(), + Value channelRole = const Value.absent(), + Value pinned = const Value.absent(), + Value pinnedAt = const Value.absent(), + Value pinExpires = const Value.absent(), + Value pinnedByUserId = const Value.absent(), + Value channelCid = const Value.absent(), + Value?> i18n = const Value.absent(), + Value?> restrictedVisibility = const Value.absent(), + Value?> extraData = const Value.absent(), + Value rowid = const Value.absent(), + }) => PinnedMessagesCompanion( + id: id, + messageText: messageText, + attachments: attachments, + state: state, + type: type, + mentionedUsers: mentionedUsers, + reactionGroups: reactionGroups, + parentId: parentId, + quotedMessageId: quotedMessageId, + pollId: pollId, + replyCount: replyCount, + showInChannel: showInChannel, + shadowed: shadowed, + command: command, + localCreatedAt: localCreatedAt, + remoteCreatedAt: remoteCreatedAt, + localUpdatedAt: localUpdatedAt, + remoteUpdatedAt: remoteUpdatedAt, + localDeletedAt: localDeletedAt, + remoteDeletedAt: remoteDeletedAt, + deletedForMe: deletedForMe, + messageTextUpdatedAt: messageTextUpdatedAt, + userId: userId, + channelRole: channelRole, + pinned: pinned, + pinnedAt: pinnedAt, + pinExpires: pinExpires, + pinnedByUserId: pinnedByUserId, + channelCid: channelCid, + i18n: i18n, + restrictedVisibility: restrictedVisibility, + extraData: extraData, + rowid: rowid, + ), + createCompanionCallback: + ({ + required String id, + Value messageText = const Value.absent(), + required List attachments, + required String state, + Value type = const Value.absent(), + required List mentionedUsers, + Value?> reactionGroups = const Value.absent(), + Value parentId = const Value.absent(), + Value quotedMessageId = const Value.absent(), + Value pollId = const Value.absent(), + Value replyCount = const Value.absent(), + Value showInChannel = const Value.absent(), + Value shadowed = const Value.absent(), + Value command = const Value.absent(), + Value localCreatedAt = const Value.absent(), + Value remoteCreatedAt = const Value.absent(), + Value localUpdatedAt = const Value.absent(), + Value remoteUpdatedAt = const Value.absent(), + Value localDeletedAt = const Value.absent(), + Value remoteDeletedAt = const Value.absent(), + Value deletedForMe = const Value.absent(), + Value messageTextUpdatedAt = const Value.absent(), + Value userId = const Value.absent(), + Value channelRole = const Value.absent(), + Value pinned = const Value.absent(), + Value pinnedAt = const Value.absent(), + Value pinExpires = const Value.absent(), + Value pinnedByUserId = const Value.absent(), + required String channelCid, + Value?> i18n = const Value.absent(), + Value?> restrictedVisibility = const Value.absent(), + Value?> extraData = const Value.absent(), + Value rowid = const Value.absent(), + }) => PinnedMessagesCompanion.insert( + id: id, + messageText: messageText, + attachments: attachments, + state: state, + type: type, + mentionedUsers: mentionedUsers, + reactionGroups: reactionGroups, + parentId: parentId, + quotedMessageId: quotedMessageId, + pollId: pollId, + replyCount: replyCount, + showInChannel: showInChannel, + shadowed: shadowed, + command: command, + localCreatedAt: localCreatedAt, + remoteCreatedAt: remoteCreatedAt, + localUpdatedAt: localUpdatedAt, + remoteUpdatedAt: remoteUpdatedAt, + localDeletedAt: localDeletedAt, + remoteDeletedAt: remoteDeletedAt, + deletedForMe: deletedForMe, + messageTextUpdatedAt: messageTextUpdatedAt, + userId: userId, + channelRole: channelRole, + pinned: pinned, + pinnedAt: pinnedAt, + pinExpires: pinExpires, + pinnedByUserId: pinnedByUserId, + channelCid: channelCid, + i18n: i18n, + restrictedVisibility: restrictedVisibility, + extraData: extraData, + rowid: rowid, + ), + withReferenceMapper: (p0) => + p0.map((e) => (e.readTable(table), $$PinnedMessagesTableReferences(db, table, e))).toList(), prefetchHooksCallback: ({pinnedMessageReactionsRefs = false}) { return PrefetchHooks( db: db, - explicitlyWatchedTables: [ - if (pinnedMessageReactionsRefs) db.pinnedMessageReactions - ], + explicitlyWatchedTables: [if (pinnedMessageReactionsRefs) db.pinnedMessageReactions], addJoins: null, getPrefetchedDataCallback: (items) async { return [ if (pinnedMessageReactionsRefs) - await $_getPrefetchedData( - currentTable: table, - referencedTable: $$PinnedMessagesTableReferences - ._pinnedMessageReactionsRefsTable(db), - managerFromTypedResult: (p0) => - $$PinnedMessagesTableReferences(db, table, p0) - .pinnedMessageReactionsRefs, - referencedItemsForCurrentItem: - (item, referencedItems) => referencedItems - .where((e) => e.messageId == item.id), - typedResults: items) + await $_getPrefetchedData( + currentTable: table, + referencedTable: $$PinnedMessagesTableReferences._pinnedMessageReactionsRefsTable(db), + managerFromTypedResult: (p0) => + $$PinnedMessagesTableReferences(db, table, p0).pinnedMessageReactionsRefs, + referencedItemsForCurrentItem: (item, referencedItems) => + referencedItems.where((e) => e.messageId == item.id), + typedResults: items, + ), ]; }, ); }, - )); + ), + ); } -typedef $$PinnedMessagesTableProcessedTableManager = ProcessedTableManager< - _$DriftChatDatabase, - $PinnedMessagesTable, - PinnedMessageEntity, - $$PinnedMessagesTableFilterComposer, - $$PinnedMessagesTableOrderingComposer, - $$PinnedMessagesTableAnnotationComposer, - $$PinnedMessagesTableCreateCompanionBuilder, - $$PinnedMessagesTableUpdateCompanionBuilder, - (PinnedMessageEntity, $$PinnedMessagesTableReferences), - PinnedMessageEntity, - PrefetchHooks Function({bool pinnedMessageReactionsRefs})>; -typedef $$PollsTableCreateCompanionBuilder = PollsCompanion Function({ - required String id, - required String name, - Value description, - required List options, - Value votingVisibility, - Value enforceUniqueVote, - Value maxVotesAllowed, - Value allowUserSuggestedOptions, - Value allowAnswers, - Value isClosed, - Value answersCount, - required Map voteCountsByOption, - Value voteCount, - Value createdById, - Value createdAt, - Value updatedAt, - Value?> extraData, - Value rowid, -}); -typedef $$PollsTableUpdateCompanionBuilder = PollsCompanion Function({ - Value id, - Value name, - Value description, - Value> options, - Value votingVisibility, - Value enforceUniqueVote, - Value maxVotesAllowed, - Value allowUserSuggestedOptions, - Value allowAnswers, - Value isClosed, - Value answersCount, - Value> voteCountsByOption, - Value voteCount, - Value createdById, - Value createdAt, - Value updatedAt, - Value?> extraData, - Value rowid, -}); - -final class $$PollsTableReferences - extends BaseReferences<_$DriftChatDatabase, $PollsTable, PollEntity> { +typedef $$PinnedMessagesTableProcessedTableManager = + ProcessedTableManager< + _$DriftChatDatabase, + $PinnedMessagesTable, + PinnedMessageEntity, + $$PinnedMessagesTableFilterComposer, + $$PinnedMessagesTableOrderingComposer, + $$PinnedMessagesTableAnnotationComposer, + $$PinnedMessagesTableCreateCompanionBuilder, + $$PinnedMessagesTableUpdateCompanionBuilder, + (PinnedMessageEntity, $$PinnedMessagesTableReferences), + PinnedMessageEntity, + PrefetchHooks Function({bool pinnedMessageReactionsRefs}) + >; +typedef $$PollsTableCreateCompanionBuilder = + PollsCompanion Function({ + required String id, + required String name, + Value description, + required List options, + Value votingVisibility, + Value enforceUniqueVote, + Value maxVotesAllowed, + Value allowUserSuggestedOptions, + Value allowAnswers, + Value isClosed, + Value answersCount, + required Map voteCountsByOption, + Value voteCount, + Value createdById, + Value createdAt, + Value updatedAt, + Value?> extraData, + Value rowid, + }); +typedef $$PollsTableUpdateCompanionBuilder = + PollsCompanion Function({ + Value id, + Value name, + Value description, + Value> options, + Value votingVisibility, + Value enforceUniqueVote, + Value maxVotesAllowed, + Value allowUserSuggestedOptions, + Value allowAnswers, + Value isClosed, + Value answersCount, + Value> voteCountsByOption, + Value voteCount, + Value createdById, + Value createdAt, + Value updatedAt, + Value?> extraData, + Value rowid, + }); + +final class $$PollsTableReferences extends BaseReferences<_$DriftChatDatabase, $PollsTable, PollEntity> { $$PollsTableReferences(super.$_db, super.$_table, super.$_typedResult); - static MultiTypedResultKey<$PollVotesTable, List> - _pollVotesRefsTable(_$DriftChatDatabase db) => - MultiTypedResultKey.fromTable(db.pollVotes, - aliasName: - $_aliasNameGenerator(db.polls.id, db.pollVotes.pollId)); + static MultiTypedResultKey<$PollVotesTable, List> _pollVotesRefsTable(_$DriftChatDatabase db) => + MultiTypedResultKey.fromTable(db.pollVotes, aliasName: $_aliasNameGenerator(db.polls.id, db.pollVotes.pollId)); $$PollVotesTableProcessedTableManager get pollVotesRefs { - final manager = $$PollVotesTableTableManager($_db, $_db.pollVotes) - .filter((f) => f.pollId.id.sqlEquals($_itemColumn('id')!)); + final manager = $$PollVotesTableTableManager( + $_db, + $_db.pollVotes, + ).filter((f) => f.pollId.id.sqlEquals($_itemColumn('id')!)); final cache = $_typedResult.readTableOrNull(_pollVotesRefsTable($_db)); - return ProcessedTableManager( - manager.$state.copyWith(prefetchedData: cache)); + return ProcessedTableManager(manager.$state.copyWith(prefetchedData: cache)); } } -class $$PollsTableFilterComposer - extends Composer<_$DriftChatDatabase, $PollsTable> { +class $$PollsTableFilterComposer extends Composer<_$DriftChatDatabase, $PollsTable> { $$PollsTableFilterComposer({ required super.$db, required super.$table, @@ -12857,93 +12419,78 @@ class $$PollsTableFilterComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnFilters get id => $composableBuilder( - column: $table.id, builder: (column) => ColumnFilters(column)); + ColumnFilters get id => $composableBuilder(column: $table.id, builder: (column) => ColumnFilters(column)); - ColumnFilters get name => $composableBuilder( - column: $table.name, builder: (column) => ColumnFilters(column)); + ColumnFilters get name => $composableBuilder(column: $table.name, builder: (column) => ColumnFilters(column)); - ColumnFilters get description => $composableBuilder( - column: $table.description, builder: (column) => ColumnFilters(column)); + ColumnFilters get description => + $composableBuilder(column: $table.description, builder: (column) => ColumnFilters(column)); - ColumnWithTypeConverterFilters, List, String> - get options => $composableBuilder( - column: $table.options, - builder: (column) => ColumnWithTypeConverterFilters(column)); + ColumnWithTypeConverterFilters, List, String> get options => + $composableBuilder(column: $table.options, builder: (column) => ColumnWithTypeConverterFilters(column)); - ColumnWithTypeConverterFilters - get votingVisibility => $composableBuilder( - column: $table.votingVisibility, - builder: (column) => ColumnWithTypeConverterFilters(column)); + ColumnWithTypeConverterFilters get votingVisibility => + $composableBuilder(column: $table.votingVisibility, builder: (column) => ColumnWithTypeConverterFilters(column)); - ColumnFilters get enforceUniqueVote => $composableBuilder( - column: $table.enforceUniqueVote, - builder: (column) => ColumnFilters(column)); + ColumnFilters get enforceUniqueVote => + $composableBuilder(column: $table.enforceUniqueVote, builder: (column) => ColumnFilters(column)); - ColumnFilters get maxVotesAllowed => $composableBuilder( - column: $table.maxVotesAllowed, - builder: (column) => ColumnFilters(column)); + ColumnFilters get maxVotesAllowed => + $composableBuilder(column: $table.maxVotesAllowed, builder: (column) => ColumnFilters(column)); - ColumnFilters get allowUserSuggestedOptions => $composableBuilder( - column: $table.allowUserSuggestedOptions, - builder: (column) => ColumnFilters(column)); + ColumnFilters get allowUserSuggestedOptions => + $composableBuilder(column: $table.allowUserSuggestedOptions, builder: (column) => ColumnFilters(column)); - ColumnFilters get allowAnswers => $composableBuilder( - column: $table.allowAnswers, builder: (column) => ColumnFilters(column)); + ColumnFilters get allowAnswers => + $composableBuilder(column: $table.allowAnswers, builder: (column) => ColumnFilters(column)); - ColumnFilters get isClosed => $composableBuilder( - column: $table.isClosed, builder: (column) => ColumnFilters(column)); + ColumnFilters get isClosed => + $composableBuilder(column: $table.isClosed, builder: (column) => ColumnFilters(column)); - ColumnFilters get answersCount => $composableBuilder( - column: $table.answersCount, builder: (column) => ColumnFilters(column)); + ColumnFilters get answersCount => + $composableBuilder(column: $table.answersCount, builder: (column) => ColumnFilters(column)); - ColumnWithTypeConverterFilters, Map, String> - get voteCountsByOption => $composableBuilder( - column: $table.voteCountsByOption, - builder: (column) => ColumnWithTypeConverterFilters(column)); + ColumnWithTypeConverterFilters, Map, String> get voteCountsByOption => + $composableBuilder( + column: $table.voteCountsByOption, + builder: (column) => ColumnWithTypeConverterFilters(column), + ); - ColumnFilters get voteCount => $composableBuilder( - column: $table.voteCount, builder: (column) => ColumnFilters(column)); + ColumnFilters get voteCount => + $composableBuilder(column: $table.voteCount, builder: (column) => ColumnFilters(column)); - ColumnFilters get createdById => $composableBuilder( - column: $table.createdById, builder: (column) => ColumnFilters(column)); + ColumnFilters get createdById => + $composableBuilder(column: $table.createdById, builder: (column) => ColumnFilters(column)); - ColumnFilters get createdAt => $composableBuilder( - column: $table.createdAt, builder: (column) => ColumnFilters(column)); + ColumnFilters get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get updatedAt => $composableBuilder( - column: $table.updatedAt, builder: (column) => ColumnFilters(column)); + ColumnFilters get updatedAt => + $composableBuilder(column: $table.updatedAt, builder: (column) => ColumnFilters(column)); - ColumnWithTypeConverterFilters?, Map, - String> - get extraData => $composableBuilder( - column: $table.extraData, - builder: (column) => ColumnWithTypeConverterFilters(column)); + ColumnWithTypeConverterFilters?, Map, String> get extraData => + $composableBuilder(column: $table.extraData, builder: (column) => ColumnWithTypeConverterFilters(column)); - Expression pollVotesRefs( - Expression Function($$PollVotesTableFilterComposer f) f) { + Expression pollVotesRefs(Expression Function($$PollVotesTableFilterComposer f) f) { final $$PollVotesTableFilterComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.id, - referencedTable: $db.pollVotes, - getReferencedColumn: (t) => t.pollId, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$PollVotesTableFilterComposer( - $db: $db, - $table: $db.pollVotes, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.pollVotes, + getReferencedColumn: (t) => t.pollId, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$PollVotesTableFilterComposer( + $db: $db, + $table: $db.pollVotes, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return f(composer); } } -class $$PollsTableOrderingComposer - extends Composer<_$DriftChatDatabase, $PollsTable> { +class $$PollsTableOrderingComposer extends Composer<_$DriftChatDatabase, $PollsTable> { $$PollsTableOrderingComposer({ required super.$db, required super.$table, @@ -12951,67 +12498,58 @@ class $$PollsTableOrderingComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnOrderings get id => $composableBuilder( - column: $table.id, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get id => $composableBuilder(column: $table.id, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get name => $composableBuilder( - column: $table.name, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get name => + $composableBuilder(column: $table.name, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get description => $composableBuilder( - column: $table.description, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get description => + $composableBuilder(column: $table.description, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get options => $composableBuilder( - column: $table.options, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get options => + $composableBuilder(column: $table.options, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get votingVisibility => $composableBuilder( - column: $table.votingVisibility, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get votingVisibility => + $composableBuilder(column: $table.votingVisibility, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get enforceUniqueVote => $composableBuilder( - column: $table.enforceUniqueVote, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get enforceUniqueVote => + $composableBuilder(column: $table.enforceUniqueVote, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get maxVotesAllowed => $composableBuilder( - column: $table.maxVotesAllowed, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get maxVotesAllowed => + $composableBuilder(column: $table.maxVotesAllowed, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get allowUserSuggestedOptions => $composableBuilder( - column: $table.allowUserSuggestedOptions, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get allowUserSuggestedOptions => + $composableBuilder(column: $table.allowUserSuggestedOptions, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get allowAnswers => $composableBuilder( - column: $table.allowAnswers, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get allowAnswers => + $composableBuilder(column: $table.allowAnswers, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get isClosed => $composableBuilder( - column: $table.isClosed, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get isClosed => + $composableBuilder(column: $table.isClosed, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get answersCount => $composableBuilder( - column: $table.answersCount, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get answersCount => + $composableBuilder(column: $table.answersCount, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get voteCountsByOption => $composableBuilder( - column: $table.voteCountsByOption, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get voteCountsByOption => + $composableBuilder(column: $table.voteCountsByOption, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get voteCount => $composableBuilder( - column: $table.voteCount, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get voteCount => + $composableBuilder(column: $table.voteCount, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get createdById => $composableBuilder( - column: $table.createdById, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get createdById => + $composableBuilder(column: $table.createdById, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get createdAt => $composableBuilder( - column: $table.createdAt, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get updatedAt => $composableBuilder( - column: $table.updatedAt, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get updatedAt => + $composableBuilder(column: $table.updatedAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get extraData => $composableBuilder( - column: $table.extraData, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get extraData => + $composableBuilder(column: $table.extraData, builder: (column) => ColumnOrderings(column)); } -class $$PollsTableAnnotationComposer - extends Composer<_$DriftChatDatabase, $PollsTable> { +class $$PollsTableAnnotationComposer extends Composer<_$DriftChatDatabase, $PollsTable> { $$PollsTableAnnotationComposer({ required super.$db, required super.$table, @@ -13019,188 +12557,174 @@ class $$PollsTableAnnotationComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - GeneratedColumn get id => - $composableBuilder(column: $table.id, builder: (column) => column); + GeneratedColumn get id => $composableBuilder(column: $table.id, builder: (column) => column); - GeneratedColumn get name => - $composableBuilder(column: $table.name, builder: (column) => column); + GeneratedColumn get name => $composableBuilder(column: $table.name, builder: (column) => column); - GeneratedColumn get description => $composableBuilder( - column: $table.description, builder: (column) => column); + GeneratedColumn get description => + $composableBuilder(column: $table.description, builder: (column) => column); GeneratedColumnWithTypeConverter, String> get options => $composableBuilder(column: $table.options, builder: (column) => column); - GeneratedColumnWithTypeConverter - get votingVisibility => $composableBuilder( - column: $table.votingVisibility, builder: (column) => column); + GeneratedColumnWithTypeConverter get votingVisibility => + $composableBuilder(column: $table.votingVisibility, builder: (column) => column); - GeneratedColumn get enforceUniqueVote => $composableBuilder( - column: $table.enforceUniqueVote, builder: (column) => column); + GeneratedColumn get enforceUniqueVote => + $composableBuilder(column: $table.enforceUniqueVote, builder: (column) => column); - GeneratedColumn get maxVotesAllowed => $composableBuilder( - column: $table.maxVotesAllowed, builder: (column) => column); + GeneratedColumn get maxVotesAllowed => + $composableBuilder(column: $table.maxVotesAllowed, builder: (column) => column); - GeneratedColumn get allowUserSuggestedOptions => $composableBuilder( - column: $table.allowUserSuggestedOptions, builder: (column) => column); + GeneratedColumn get allowUserSuggestedOptions => + $composableBuilder(column: $table.allowUserSuggestedOptions, builder: (column) => column); - GeneratedColumn get allowAnswers => $composableBuilder( - column: $table.allowAnswers, builder: (column) => column); + GeneratedColumn get allowAnswers => + $composableBuilder(column: $table.allowAnswers, builder: (column) => column); - GeneratedColumn get isClosed => - $composableBuilder(column: $table.isClosed, builder: (column) => column); + GeneratedColumn get isClosed => $composableBuilder(column: $table.isClosed, builder: (column) => column); - GeneratedColumn get answersCount => $composableBuilder( - column: $table.answersCount, builder: (column) => column); + GeneratedColumn get answersCount => $composableBuilder(column: $table.answersCount, builder: (column) => column); - GeneratedColumnWithTypeConverter, String> - get voteCountsByOption => $composableBuilder( - column: $table.voteCountsByOption, builder: (column) => column); + GeneratedColumnWithTypeConverter, String> get voteCountsByOption => + $composableBuilder(column: $table.voteCountsByOption, builder: (column) => column); - GeneratedColumn get voteCount => - $composableBuilder(column: $table.voteCount, builder: (column) => column); + GeneratedColumn get voteCount => $composableBuilder(column: $table.voteCount, builder: (column) => column); - GeneratedColumn get createdById => $composableBuilder( - column: $table.createdById, builder: (column) => column); + GeneratedColumn get createdById => + $composableBuilder(column: $table.createdById, builder: (column) => column); - GeneratedColumn get createdAt => - $composableBuilder(column: $table.createdAt, builder: (column) => column); + GeneratedColumn get createdAt => $composableBuilder(column: $table.createdAt, builder: (column) => column); - GeneratedColumn get updatedAt => - $composableBuilder(column: $table.updatedAt, builder: (column) => column); + GeneratedColumn get updatedAt => $composableBuilder(column: $table.updatedAt, builder: (column) => column); - GeneratedColumnWithTypeConverter?, String> - get extraData => $composableBuilder( - column: $table.extraData, builder: (column) => column); + GeneratedColumnWithTypeConverter?, String> get extraData => + $composableBuilder(column: $table.extraData, builder: (column) => column); - Expression pollVotesRefs( - Expression Function($$PollVotesTableAnnotationComposer a) f) { + Expression pollVotesRefs(Expression Function($$PollVotesTableAnnotationComposer a) f) { final $$PollVotesTableAnnotationComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.id, - referencedTable: $db.pollVotes, - getReferencedColumn: (t) => t.pollId, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$PollVotesTableAnnotationComposer( - $db: $db, - $table: $db.pollVotes, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.pollVotes, + getReferencedColumn: (t) => t.pollId, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$PollVotesTableAnnotationComposer( + $db: $db, + $table: $db.pollVotes, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return f(composer); } } -class $$PollsTableTableManager extends RootTableManager< - _$DriftChatDatabase, - $PollsTable, - PollEntity, - $$PollsTableFilterComposer, - $$PollsTableOrderingComposer, - $$PollsTableAnnotationComposer, - $$PollsTableCreateCompanionBuilder, - $$PollsTableUpdateCompanionBuilder, - (PollEntity, $$PollsTableReferences), - PollEntity, - PrefetchHooks Function({bool pollVotesRefs})> { +class $$PollsTableTableManager + extends + RootTableManager< + _$DriftChatDatabase, + $PollsTable, + PollEntity, + $$PollsTableFilterComposer, + $$PollsTableOrderingComposer, + $$PollsTableAnnotationComposer, + $$PollsTableCreateCompanionBuilder, + $$PollsTableUpdateCompanionBuilder, + (PollEntity, $$PollsTableReferences), + PollEntity, + PrefetchHooks Function({bool pollVotesRefs}) + > { $$PollsTableTableManager(_$DriftChatDatabase db, $PollsTable table) - : super(TableManagerState( + : super( + TableManagerState( db: db, table: table, - createFilteringComposer: () => - $$PollsTableFilterComposer($db: db, $table: table), - createOrderingComposer: () => - $$PollsTableOrderingComposer($db: db, $table: table), - createComputedFieldComposer: () => - $$PollsTableAnnotationComposer($db: db, $table: table), - updateCompanionCallback: ({ - Value id = const Value.absent(), - Value name = const Value.absent(), - Value description = const Value.absent(), - Value> options = const Value.absent(), - Value votingVisibility = const Value.absent(), - Value enforceUniqueVote = const Value.absent(), - Value maxVotesAllowed = const Value.absent(), - Value allowUserSuggestedOptions = const Value.absent(), - Value allowAnswers = const Value.absent(), - Value isClosed = const Value.absent(), - Value answersCount = const Value.absent(), - Value> voteCountsByOption = const Value.absent(), - Value voteCount = const Value.absent(), - Value createdById = const Value.absent(), - Value createdAt = const Value.absent(), - Value updatedAt = const Value.absent(), - Value?> extraData = const Value.absent(), - Value rowid = const Value.absent(), - }) => - PollsCompanion( - id: id, - name: name, - description: description, - options: options, - votingVisibility: votingVisibility, - enforceUniqueVote: enforceUniqueVote, - maxVotesAllowed: maxVotesAllowed, - allowUserSuggestedOptions: allowUserSuggestedOptions, - allowAnswers: allowAnswers, - isClosed: isClosed, - answersCount: answersCount, - voteCountsByOption: voteCountsByOption, - voteCount: voteCount, - createdById: createdById, - createdAt: createdAt, - updatedAt: updatedAt, - extraData: extraData, - rowid: rowid, - ), - createCompanionCallback: ({ - required String id, - required String name, - Value description = const Value.absent(), - required List options, - Value votingVisibility = const Value.absent(), - Value enforceUniqueVote = const Value.absent(), - Value maxVotesAllowed = const Value.absent(), - Value allowUserSuggestedOptions = const Value.absent(), - Value allowAnswers = const Value.absent(), - Value isClosed = const Value.absent(), - Value answersCount = const Value.absent(), - required Map voteCountsByOption, - Value voteCount = const Value.absent(), - Value createdById = const Value.absent(), - Value createdAt = const Value.absent(), - Value updatedAt = const Value.absent(), - Value?> extraData = const Value.absent(), - Value rowid = const Value.absent(), - }) => - PollsCompanion.insert( - id: id, - name: name, - description: description, - options: options, - votingVisibility: votingVisibility, - enforceUniqueVote: enforceUniqueVote, - maxVotesAllowed: maxVotesAllowed, - allowUserSuggestedOptions: allowUserSuggestedOptions, - allowAnswers: allowAnswers, - isClosed: isClosed, - answersCount: answersCount, - voteCountsByOption: voteCountsByOption, - voteCount: voteCount, - createdById: createdById, - createdAt: createdAt, - updatedAt: updatedAt, - extraData: extraData, - rowid: rowid, - ), - withReferenceMapper: (p0) => p0 - .map((e) => - (e.readTable(table), $$PollsTableReferences(db, table, e))) - .toList(), + createFilteringComposer: () => $$PollsTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => $$PollsTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => $$PollsTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value id = const Value.absent(), + Value name = const Value.absent(), + Value description = const Value.absent(), + Value> options = const Value.absent(), + Value votingVisibility = const Value.absent(), + Value enforceUniqueVote = const Value.absent(), + Value maxVotesAllowed = const Value.absent(), + Value allowUserSuggestedOptions = const Value.absent(), + Value allowAnswers = const Value.absent(), + Value isClosed = const Value.absent(), + Value answersCount = const Value.absent(), + Value> voteCountsByOption = const Value.absent(), + Value voteCount = const Value.absent(), + Value createdById = const Value.absent(), + Value createdAt = const Value.absent(), + Value updatedAt = const Value.absent(), + Value?> extraData = const Value.absent(), + Value rowid = const Value.absent(), + }) => PollsCompanion( + id: id, + name: name, + description: description, + options: options, + votingVisibility: votingVisibility, + enforceUniqueVote: enforceUniqueVote, + maxVotesAllowed: maxVotesAllowed, + allowUserSuggestedOptions: allowUserSuggestedOptions, + allowAnswers: allowAnswers, + isClosed: isClosed, + answersCount: answersCount, + voteCountsByOption: voteCountsByOption, + voteCount: voteCount, + createdById: createdById, + createdAt: createdAt, + updatedAt: updatedAt, + extraData: extraData, + rowid: rowid, + ), + createCompanionCallback: + ({ + required String id, + required String name, + Value description = const Value.absent(), + required List options, + Value votingVisibility = const Value.absent(), + Value enforceUniqueVote = const Value.absent(), + Value maxVotesAllowed = const Value.absent(), + Value allowUserSuggestedOptions = const Value.absent(), + Value allowAnswers = const Value.absent(), + Value isClosed = const Value.absent(), + Value answersCount = const Value.absent(), + required Map voteCountsByOption, + Value voteCount = const Value.absent(), + Value createdById = const Value.absent(), + Value createdAt = const Value.absent(), + Value updatedAt = const Value.absent(), + Value?> extraData = const Value.absent(), + Value rowid = const Value.absent(), + }) => PollsCompanion.insert( + id: id, + name: name, + description: description, + options: options, + votingVisibility: votingVisibility, + enforceUniqueVote: enforceUniqueVote, + maxVotesAllowed: maxVotesAllowed, + allowUserSuggestedOptions: allowUserSuggestedOptions, + allowAnswers: allowAnswers, + isClosed: isClosed, + answersCount: answersCount, + voteCountsByOption: voteCountsByOption, + voteCount: voteCount, + createdById: createdById, + createdAt: createdAt, + updatedAt: updatedAt, + extraData: extraData, + rowid: rowid, + ), + withReferenceMapper: (p0) => + p0.map((e) => (e.readTable(table), $$PollsTableReferences(db, table, e))).toList(), prefetchHooksCallback: ({pollVotesRefs = false}) { return PrefetchHooks( db: db, @@ -13209,78 +12733,76 @@ class $$PollsTableTableManager extends RootTableManager< getPrefetchedDataCallback: (items) async { return [ if (pollVotesRefs) - await $_getPrefetchedData( - currentTable: table, - referencedTable: - $$PollsTableReferences._pollVotesRefsTable(db), - managerFromTypedResult: (p0) => - $$PollsTableReferences(db, table, p0).pollVotesRefs, - referencedItemsForCurrentItem: (item, - referencedItems) => - referencedItems.where((e) => e.pollId == item.id), - typedResults: items) + await $_getPrefetchedData( + currentTable: table, + referencedTable: $$PollsTableReferences._pollVotesRefsTable(db), + managerFromTypedResult: (p0) => $$PollsTableReferences(db, table, p0).pollVotesRefs, + referencedItemsForCurrentItem: (item, referencedItems) => + referencedItems.where((e) => e.pollId == item.id), + typedResults: items, + ), ]; }, ); }, - )); + ), + ); } -typedef $$PollsTableProcessedTableManager = ProcessedTableManager< - _$DriftChatDatabase, - $PollsTable, - PollEntity, - $$PollsTableFilterComposer, - $$PollsTableOrderingComposer, - $$PollsTableAnnotationComposer, - $$PollsTableCreateCompanionBuilder, - $$PollsTableUpdateCompanionBuilder, - (PollEntity, $$PollsTableReferences), - PollEntity, - PrefetchHooks Function({bool pollVotesRefs})>; -typedef $$PollVotesTableCreateCompanionBuilder = PollVotesCompanion Function({ - Value id, - Value pollId, - Value optionId, - Value answerText, - Value createdAt, - Value updatedAt, - Value userId, - Value rowid, -}); -typedef $$PollVotesTableUpdateCompanionBuilder = PollVotesCompanion Function({ - Value id, - Value pollId, - Value optionId, - Value answerText, - Value createdAt, - Value updatedAt, - Value userId, - Value rowid, -}); - -final class $$PollVotesTableReferences extends BaseReferences< - _$DriftChatDatabase, $PollVotesTable, PollVoteEntity> { +typedef $$PollsTableProcessedTableManager = + ProcessedTableManager< + _$DriftChatDatabase, + $PollsTable, + PollEntity, + $$PollsTableFilterComposer, + $$PollsTableOrderingComposer, + $$PollsTableAnnotationComposer, + $$PollsTableCreateCompanionBuilder, + $$PollsTableUpdateCompanionBuilder, + (PollEntity, $$PollsTableReferences), + PollEntity, + PrefetchHooks Function({bool pollVotesRefs}) + >; +typedef $$PollVotesTableCreateCompanionBuilder = + PollVotesCompanion Function({ + Value id, + Value pollId, + Value optionId, + Value answerText, + Value createdAt, + Value updatedAt, + Value userId, + Value rowid, + }); +typedef $$PollVotesTableUpdateCompanionBuilder = + PollVotesCompanion Function({ + Value id, + Value pollId, + Value optionId, + Value answerText, + Value createdAt, + Value updatedAt, + Value userId, + Value rowid, + }); + +final class $$PollVotesTableReferences extends BaseReferences<_$DriftChatDatabase, $PollVotesTable, PollVoteEntity> { $$PollVotesTableReferences(super.$_db, super.$_table, super.$_typedResult); - static $PollsTable _pollIdTable(_$DriftChatDatabase db) => db.polls - .createAlias($_aliasNameGenerator(db.pollVotes.pollId, db.polls.id)); + static $PollsTable _pollIdTable(_$DriftChatDatabase db) => + db.polls.createAlias($_aliasNameGenerator(db.pollVotes.pollId, db.polls.id)); $$PollsTableProcessedTableManager? get pollId { final $_column = $_itemColumn('poll_id'); if ($_column == null) return null; - final manager = $$PollsTableTableManager($_db, $_db.polls) - .filter((f) => f.id.sqlEquals($_column)); + final manager = $$PollsTableTableManager($_db, $_db.polls).filter((f) => f.id.sqlEquals($_column)); final item = $_typedResult.readTableOrNull(_pollIdTable($_db)); if (item == null) return manager; - return ProcessedTableManager( - manager.$state.copyWith(prefetchedData: [item])); + return ProcessedTableManager(manager.$state.copyWith(prefetchedData: [item])); } } -class $$PollVotesTableFilterComposer - extends Composer<_$DriftChatDatabase, $PollVotesTable> { +class $$PollVotesTableFilterComposer extends Composer<_$DriftChatDatabase, $PollVotesTable> { $$PollVotesTableFilterComposer({ required super.$db, required super.$table, @@ -13288,47 +12810,43 @@ class $$PollVotesTableFilterComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnFilters get id => $composableBuilder( - column: $table.id, builder: (column) => ColumnFilters(column)); + ColumnFilters get id => $composableBuilder(column: $table.id, builder: (column) => ColumnFilters(column)); - ColumnFilters get optionId => $composableBuilder( - column: $table.optionId, builder: (column) => ColumnFilters(column)); + ColumnFilters get optionId => + $composableBuilder(column: $table.optionId, builder: (column) => ColumnFilters(column)); - ColumnFilters get answerText => $composableBuilder( - column: $table.answerText, builder: (column) => ColumnFilters(column)); + ColumnFilters get answerText => + $composableBuilder(column: $table.answerText, builder: (column) => ColumnFilters(column)); - ColumnFilters get createdAt => $composableBuilder( - column: $table.createdAt, builder: (column) => ColumnFilters(column)); + ColumnFilters get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get updatedAt => $composableBuilder( - column: $table.updatedAt, builder: (column) => ColumnFilters(column)); + ColumnFilters get updatedAt => + $composableBuilder(column: $table.updatedAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get userId => $composableBuilder( - column: $table.userId, builder: (column) => ColumnFilters(column)); + ColumnFilters get userId => + $composableBuilder(column: $table.userId, builder: (column) => ColumnFilters(column)); $$PollsTableFilterComposer get pollId { final $$PollsTableFilterComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.pollId, - referencedTable: $db.polls, - getReferencedColumn: (t) => t.id, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$PollsTableFilterComposer( - $db: $db, - $table: $db.polls, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.pollId, + referencedTable: $db.polls, + getReferencedColumn: (t) => t.id, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$PollsTableFilterComposer( + $db: $db, + $table: $db.polls, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return composer; } } -class $$PollVotesTableOrderingComposer - extends Composer<_$DriftChatDatabase, $PollVotesTable> { +class $$PollVotesTableOrderingComposer extends Composer<_$DriftChatDatabase, $PollVotesTable> { $$PollVotesTableOrderingComposer({ required super.$db, required super.$table, @@ -13336,47 +12854,43 @@ class $$PollVotesTableOrderingComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnOrderings get id => $composableBuilder( - column: $table.id, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get id => $composableBuilder(column: $table.id, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get optionId => $composableBuilder( - column: $table.optionId, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get optionId => + $composableBuilder(column: $table.optionId, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get answerText => $composableBuilder( - column: $table.answerText, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get answerText => + $composableBuilder(column: $table.answerText, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get createdAt => $composableBuilder( - column: $table.createdAt, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get updatedAt => $composableBuilder( - column: $table.updatedAt, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get updatedAt => + $composableBuilder(column: $table.updatedAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get userId => $composableBuilder( - column: $table.userId, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get userId => + $composableBuilder(column: $table.userId, builder: (column) => ColumnOrderings(column)); $$PollsTableOrderingComposer get pollId { final $$PollsTableOrderingComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.pollId, - referencedTable: $db.polls, - getReferencedColumn: (t) => t.id, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$PollsTableOrderingComposer( - $db: $db, - $table: $db.polls, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.pollId, + referencedTable: $db.polls, + getReferencedColumn: (t) => t.id, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$PollsTableOrderingComposer( + $db: $db, + $table: $db.polls, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return composer; } } -class $$PollVotesTableAnnotationComposer - extends Composer<_$DriftChatDatabase, $PollVotesTable> { +class $$PollVotesTableAnnotationComposer extends Composer<_$DriftChatDatabase, $PollVotesTable> { $$PollVotesTableAnnotationComposer({ required super.$db, required super.$table, @@ -13384,119 +12898,109 @@ class $$PollVotesTableAnnotationComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - GeneratedColumn get id => - $composableBuilder(column: $table.id, builder: (column) => column); + GeneratedColumn get id => $composableBuilder(column: $table.id, builder: (column) => column); - GeneratedColumn get optionId => - $composableBuilder(column: $table.optionId, builder: (column) => column); + GeneratedColumn get optionId => $composableBuilder(column: $table.optionId, builder: (column) => column); - GeneratedColumn get answerText => $composableBuilder( - column: $table.answerText, builder: (column) => column); + GeneratedColumn get answerText => $composableBuilder(column: $table.answerText, builder: (column) => column); - GeneratedColumn get createdAt => - $composableBuilder(column: $table.createdAt, builder: (column) => column); + GeneratedColumn get createdAt => $composableBuilder(column: $table.createdAt, builder: (column) => column); - GeneratedColumn get updatedAt => - $composableBuilder(column: $table.updatedAt, builder: (column) => column); + GeneratedColumn get updatedAt => $composableBuilder(column: $table.updatedAt, builder: (column) => column); - GeneratedColumn get userId => - $composableBuilder(column: $table.userId, builder: (column) => column); + GeneratedColumn get userId => $composableBuilder(column: $table.userId, builder: (column) => column); $$PollsTableAnnotationComposer get pollId { final $$PollsTableAnnotationComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.pollId, - referencedTable: $db.polls, - getReferencedColumn: (t) => t.id, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$PollsTableAnnotationComposer( - $db: $db, - $table: $db.polls, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.pollId, + referencedTable: $db.polls, + getReferencedColumn: (t) => t.id, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$PollsTableAnnotationComposer( + $db: $db, + $table: $db.polls, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return composer; } } -class $$PollVotesTableTableManager extends RootTableManager< - _$DriftChatDatabase, - $PollVotesTable, - PollVoteEntity, - $$PollVotesTableFilterComposer, - $$PollVotesTableOrderingComposer, - $$PollVotesTableAnnotationComposer, - $$PollVotesTableCreateCompanionBuilder, - $$PollVotesTableUpdateCompanionBuilder, - (PollVoteEntity, $$PollVotesTableReferences), - PollVoteEntity, - PrefetchHooks Function({bool pollId})> { +class $$PollVotesTableTableManager + extends + RootTableManager< + _$DriftChatDatabase, + $PollVotesTable, + PollVoteEntity, + $$PollVotesTableFilterComposer, + $$PollVotesTableOrderingComposer, + $$PollVotesTableAnnotationComposer, + $$PollVotesTableCreateCompanionBuilder, + $$PollVotesTableUpdateCompanionBuilder, + (PollVoteEntity, $$PollVotesTableReferences), + PollVoteEntity, + PrefetchHooks Function({bool pollId}) + > { $$PollVotesTableTableManager(_$DriftChatDatabase db, $PollVotesTable table) - : super(TableManagerState( + : super( + TableManagerState( db: db, table: table, - createFilteringComposer: () => - $$PollVotesTableFilterComposer($db: db, $table: table), - createOrderingComposer: () => - $$PollVotesTableOrderingComposer($db: db, $table: table), - createComputedFieldComposer: () => - $$PollVotesTableAnnotationComposer($db: db, $table: table), - updateCompanionCallback: ({ - Value id = const Value.absent(), - Value pollId = const Value.absent(), - Value optionId = const Value.absent(), - Value answerText = const Value.absent(), - Value createdAt = const Value.absent(), - Value updatedAt = const Value.absent(), - Value userId = const Value.absent(), - Value rowid = const Value.absent(), - }) => - PollVotesCompanion( - id: id, - pollId: pollId, - optionId: optionId, - answerText: answerText, - createdAt: createdAt, - updatedAt: updatedAt, - userId: userId, - rowid: rowid, - ), - createCompanionCallback: ({ - Value id = const Value.absent(), - Value pollId = const Value.absent(), - Value optionId = const Value.absent(), - Value answerText = const Value.absent(), - Value createdAt = const Value.absent(), - Value updatedAt = const Value.absent(), - Value userId = const Value.absent(), - Value rowid = const Value.absent(), - }) => - PollVotesCompanion.insert( - id: id, - pollId: pollId, - optionId: optionId, - answerText: answerText, - createdAt: createdAt, - updatedAt: updatedAt, - userId: userId, - rowid: rowid, - ), - withReferenceMapper: (p0) => p0 - .map((e) => ( - e.readTable(table), - $$PollVotesTableReferences(db, table, e) - )) - .toList(), + createFilteringComposer: () => $$PollVotesTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => $$PollVotesTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => $$PollVotesTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value id = const Value.absent(), + Value pollId = const Value.absent(), + Value optionId = const Value.absent(), + Value answerText = const Value.absent(), + Value createdAt = const Value.absent(), + Value updatedAt = const Value.absent(), + Value userId = const Value.absent(), + Value rowid = const Value.absent(), + }) => PollVotesCompanion( + id: id, + pollId: pollId, + optionId: optionId, + answerText: answerText, + createdAt: createdAt, + updatedAt: updatedAt, + userId: userId, + rowid: rowid, + ), + createCompanionCallback: + ({ + Value id = const Value.absent(), + Value pollId = const Value.absent(), + Value optionId = const Value.absent(), + Value answerText = const Value.absent(), + Value createdAt = const Value.absent(), + Value updatedAt = const Value.absent(), + Value userId = const Value.absent(), + Value rowid = const Value.absent(), + }) => PollVotesCompanion.insert( + id: id, + pollId: pollId, + optionId: optionId, + answerText: answerText, + createdAt: createdAt, + updatedAt: updatedAt, + userId: userId, + rowid: rowid, + ), + withReferenceMapper: (p0) => + p0.map((e) => (e.readTable(table), $$PollVotesTableReferences(db, table, e))).toList(), prefetchHooksCallback: ({pollId = false}) { return PrefetchHooks( db: db, explicitlyWatchedTables: [], - addJoins: < - T extends TableManagerState< + addJoins: + < + T extends TableManagerState< dynamic, dynamic, dynamic, @@ -13507,90 +13011,91 @@ class $$PollVotesTableTableManager extends RootTableManager< dynamic, dynamic, dynamic, - dynamic>>(state) { - if (pollId) { - state = state.withJoin( - currentTable: table, - currentColumn: table.pollId, - referencedTable: - $$PollVotesTableReferences._pollIdTable(db), - referencedColumn: - $$PollVotesTableReferences._pollIdTable(db).id, - ) as T; - } - - return state; - }, + dynamic + > + >(state) { + if (pollId) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.pollId, + referencedTable: $$PollVotesTableReferences._pollIdTable(db), + referencedColumn: $$PollVotesTableReferences._pollIdTable(db).id, + ) + as T; + } + + return state; + }, getPrefetchedDataCallback: (items) async { return []; }, ); }, - )); + ), + ); } -typedef $$PollVotesTableProcessedTableManager = ProcessedTableManager< - _$DriftChatDatabase, - $PollVotesTable, - PollVoteEntity, - $$PollVotesTableFilterComposer, - $$PollVotesTableOrderingComposer, - $$PollVotesTableAnnotationComposer, - $$PollVotesTableCreateCompanionBuilder, - $$PollVotesTableUpdateCompanionBuilder, - (PollVoteEntity, $$PollVotesTableReferences), - PollVoteEntity, - PrefetchHooks Function({bool pollId})>; -typedef $$PinnedMessageReactionsTableCreateCompanionBuilder - = PinnedMessageReactionsCompanion Function({ - Value userId, - Value messageId, - required String type, - Value emojiCode, - Value createdAt, - Value updatedAt, - Value score, - Value?> extraData, - Value rowid, -}); -typedef $$PinnedMessageReactionsTableUpdateCompanionBuilder - = PinnedMessageReactionsCompanion Function({ - Value userId, - Value messageId, - Value type, - Value emojiCode, - Value createdAt, - Value updatedAt, - Value score, - Value?> extraData, - Value rowid, -}); - -final class $$PinnedMessageReactionsTableReferences extends BaseReferences< - _$DriftChatDatabase, - $PinnedMessageReactionsTable, - PinnedMessageReactionEntity> { - $$PinnedMessageReactionsTableReferences( - super.$_db, super.$_table, super.$_typedResult); +typedef $$PollVotesTableProcessedTableManager = + ProcessedTableManager< + _$DriftChatDatabase, + $PollVotesTable, + PollVoteEntity, + $$PollVotesTableFilterComposer, + $$PollVotesTableOrderingComposer, + $$PollVotesTableAnnotationComposer, + $$PollVotesTableCreateCompanionBuilder, + $$PollVotesTableUpdateCompanionBuilder, + (PollVoteEntity, $$PollVotesTableReferences), + PollVoteEntity, + PrefetchHooks Function({bool pollId}) + >; +typedef $$PinnedMessageReactionsTableCreateCompanionBuilder = + PinnedMessageReactionsCompanion Function({ + Value userId, + Value messageId, + required String type, + Value emojiCode, + Value createdAt, + Value updatedAt, + Value score, + Value?> extraData, + Value rowid, + }); +typedef $$PinnedMessageReactionsTableUpdateCompanionBuilder = + PinnedMessageReactionsCompanion Function({ + Value userId, + Value messageId, + Value type, + Value emojiCode, + Value createdAt, + Value updatedAt, + Value score, + Value?> extraData, + Value rowid, + }); + +final class $$PinnedMessageReactionsTableReferences + extends BaseReferences<_$DriftChatDatabase, $PinnedMessageReactionsTable, PinnedMessageReactionEntity> { + $$PinnedMessageReactionsTableReferences(super.$_db, super.$_table, super.$_typedResult); static $PinnedMessagesTable _messageIdTable(_$DriftChatDatabase db) => - db.pinnedMessages.createAlias($_aliasNameGenerator( - db.pinnedMessageReactions.messageId, db.pinnedMessages.id)); + db.pinnedMessages.createAlias($_aliasNameGenerator(db.pinnedMessageReactions.messageId, db.pinnedMessages.id)); $$PinnedMessagesTableProcessedTableManager? get messageId { final $_column = $_itemColumn('message_id'); if ($_column == null) return null; - final manager = $$PinnedMessagesTableTableManager($_db, $_db.pinnedMessages) - .filter((f) => f.id.sqlEquals($_column)); + final manager = $$PinnedMessagesTableTableManager( + $_db, + $_db.pinnedMessages, + ).filter((f) => f.id.sqlEquals($_column)); final item = $_typedResult.readTableOrNull(_messageIdTable($_db)); if (item == null) return manager; - return ProcessedTableManager( - manager.$state.copyWith(prefetchedData: [item])); + return ProcessedTableManager(manager.$state.copyWith(prefetchedData: [item])); } } -class $$PinnedMessageReactionsTableFilterComposer - extends Composer<_$DriftChatDatabase, $PinnedMessageReactionsTable> { +class $$PinnedMessageReactionsTableFilterComposer extends Composer<_$DriftChatDatabase, $PinnedMessageReactionsTable> { $$PinnedMessageReactionsTableFilterComposer({ required super.$db, required super.$table, @@ -13598,47 +13103,40 @@ class $$PinnedMessageReactionsTableFilterComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnFilters get userId => $composableBuilder( - column: $table.userId, builder: (column) => ColumnFilters(column)); + ColumnFilters get userId => + $composableBuilder(column: $table.userId, builder: (column) => ColumnFilters(column)); - ColumnFilters get type => $composableBuilder( - column: $table.type, builder: (column) => ColumnFilters(column)); + ColumnFilters get type => $composableBuilder(column: $table.type, builder: (column) => ColumnFilters(column)); - ColumnFilters get emojiCode => $composableBuilder( - column: $table.emojiCode, builder: (column) => ColumnFilters(column)); + ColumnFilters get emojiCode => + $composableBuilder(column: $table.emojiCode, builder: (column) => ColumnFilters(column)); - ColumnFilters get createdAt => $composableBuilder( - column: $table.createdAt, builder: (column) => ColumnFilters(column)); + ColumnFilters get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get updatedAt => $composableBuilder( - column: $table.updatedAt, builder: (column) => ColumnFilters(column)); + ColumnFilters get updatedAt => + $composableBuilder(column: $table.updatedAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get score => $composableBuilder( - column: $table.score, builder: (column) => ColumnFilters(column)); + ColumnFilters get score => $composableBuilder(column: $table.score, builder: (column) => ColumnFilters(column)); - ColumnWithTypeConverterFilters?, Map, - String> - get extraData => $composableBuilder( - column: $table.extraData, - builder: (column) => ColumnWithTypeConverterFilters(column)); + ColumnWithTypeConverterFilters?, Map, String> get extraData => + $composableBuilder(column: $table.extraData, builder: (column) => ColumnWithTypeConverterFilters(column)); $$PinnedMessagesTableFilterComposer get messageId { final $$PinnedMessagesTableFilterComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.messageId, - referencedTable: $db.pinnedMessages, - getReferencedColumn: (t) => t.id, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$PinnedMessagesTableFilterComposer( - $db: $db, - $table: $db.pinnedMessages, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.messageId, + referencedTable: $db.pinnedMessages, + getReferencedColumn: (t) => t.id, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$PinnedMessagesTableFilterComposer( + $db: $db, + $table: $db.pinnedMessages, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return composer; } } @@ -13652,44 +13150,42 @@ class $$PinnedMessageReactionsTableOrderingComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnOrderings get userId => $composableBuilder( - column: $table.userId, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get userId => + $composableBuilder(column: $table.userId, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get type => $composableBuilder( - column: $table.type, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get type => + $composableBuilder(column: $table.type, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get emojiCode => $composableBuilder( - column: $table.emojiCode, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get emojiCode => + $composableBuilder(column: $table.emojiCode, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get createdAt => $composableBuilder( - column: $table.createdAt, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get updatedAt => $composableBuilder( - column: $table.updatedAt, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get updatedAt => + $composableBuilder(column: $table.updatedAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get score => $composableBuilder( - column: $table.score, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get score => + $composableBuilder(column: $table.score, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get extraData => $composableBuilder( - column: $table.extraData, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get extraData => + $composableBuilder(column: $table.extraData, builder: (column) => ColumnOrderings(column)); $$PinnedMessagesTableOrderingComposer get messageId { final $$PinnedMessagesTableOrderingComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.messageId, - referencedTable: $db.pinnedMessages, - getReferencedColumn: (t) => t.id, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$PinnedMessagesTableOrderingComposer( - $db: $db, - $table: $db.pinnedMessages, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.messageId, + referencedTable: $db.pinnedMessages, + getReferencedColumn: (t) => t.id, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$PinnedMessagesTableOrderingComposer( + $db: $db, + $table: $db.pinnedMessages, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return composer; } } @@ -13703,131 +13199,116 @@ class $$PinnedMessageReactionsTableAnnotationComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - GeneratedColumn get userId => - $composableBuilder(column: $table.userId, builder: (column) => column); + GeneratedColumn get userId => $composableBuilder(column: $table.userId, builder: (column) => column); - GeneratedColumn get type => - $composableBuilder(column: $table.type, builder: (column) => column); + GeneratedColumn get type => $composableBuilder(column: $table.type, builder: (column) => column); - GeneratedColumn get emojiCode => - $composableBuilder(column: $table.emojiCode, builder: (column) => column); + GeneratedColumn get emojiCode => $composableBuilder(column: $table.emojiCode, builder: (column) => column); - GeneratedColumn get createdAt => - $composableBuilder(column: $table.createdAt, builder: (column) => column); + GeneratedColumn get createdAt => $composableBuilder(column: $table.createdAt, builder: (column) => column); - GeneratedColumn get updatedAt => - $composableBuilder(column: $table.updatedAt, builder: (column) => column); + GeneratedColumn get updatedAt => $composableBuilder(column: $table.updatedAt, builder: (column) => column); - GeneratedColumn get score => - $composableBuilder(column: $table.score, builder: (column) => column); + GeneratedColumn get score => $composableBuilder(column: $table.score, builder: (column) => column); - GeneratedColumnWithTypeConverter?, String> - get extraData => $composableBuilder( - column: $table.extraData, builder: (column) => column); + GeneratedColumnWithTypeConverter?, String> get extraData => + $composableBuilder(column: $table.extraData, builder: (column) => column); $$PinnedMessagesTableAnnotationComposer get messageId { final $$PinnedMessagesTableAnnotationComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.messageId, - referencedTable: $db.pinnedMessages, - getReferencedColumn: (t) => t.id, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$PinnedMessagesTableAnnotationComposer( - $db: $db, - $table: $db.pinnedMessages, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.messageId, + referencedTable: $db.pinnedMessages, + getReferencedColumn: (t) => t.id, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$PinnedMessagesTableAnnotationComposer( + $db: $db, + $table: $db.pinnedMessages, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return composer; } } -class $$PinnedMessageReactionsTableTableManager extends RootTableManager< - _$DriftChatDatabase, - $PinnedMessageReactionsTable, - PinnedMessageReactionEntity, - $$PinnedMessageReactionsTableFilterComposer, - $$PinnedMessageReactionsTableOrderingComposer, - $$PinnedMessageReactionsTableAnnotationComposer, - $$PinnedMessageReactionsTableCreateCompanionBuilder, - $$PinnedMessageReactionsTableUpdateCompanionBuilder, - (PinnedMessageReactionEntity, $$PinnedMessageReactionsTableReferences), - PinnedMessageReactionEntity, - PrefetchHooks Function({bool messageId})> { - $$PinnedMessageReactionsTableTableManager( - _$DriftChatDatabase db, $PinnedMessageReactionsTable table) - : super(TableManagerState( +class $$PinnedMessageReactionsTableTableManager + extends + RootTableManager< + _$DriftChatDatabase, + $PinnedMessageReactionsTable, + PinnedMessageReactionEntity, + $$PinnedMessageReactionsTableFilterComposer, + $$PinnedMessageReactionsTableOrderingComposer, + $$PinnedMessageReactionsTableAnnotationComposer, + $$PinnedMessageReactionsTableCreateCompanionBuilder, + $$PinnedMessageReactionsTableUpdateCompanionBuilder, + (PinnedMessageReactionEntity, $$PinnedMessageReactionsTableReferences), + PinnedMessageReactionEntity, + PrefetchHooks Function({bool messageId}) + > { + $$PinnedMessageReactionsTableTableManager(_$DriftChatDatabase db, $PinnedMessageReactionsTable table) + : super( + TableManagerState( db: db, table: table, - createFilteringComposer: () => - $$PinnedMessageReactionsTableFilterComposer( - $db: db, $table: table), - createOrderingComposer: () => - $$PinnedMessageReactionsTableOrderingComposer( - $db: db, $table: table), - createComputedFieldComposer: () => - $$PinnedMessageReactionsTableAnnotationComposer( - $db: db, $table: table), - updateCompanionCallback: ({ - Value userId = const Value.absent(), - Value messageId = const Value.absent(), - Value type = const Value.absent(), - Value emojiCode = const Value.absent(), - Value createdAt = const Value.absent(), - Value updatedAt = const Value.absent(), - Value score = const Value.absent(), - Value?> extraData = const Value.absent(), - Value rowid = const Value.absent(), - }) => - PinnedMessageReactionsCompanion( - userId: userId, - messageId: messageId, - type: type, - emojiCode: emojiCode, - createdAt: createdAt, - updatedAt: updatedAt, - score: score, - extraData: extraData, - rowid: rowid, - ), - createCompanionCallback: ({ - Value userId = const Value.absent(), - Value messageId = const Value.absent(), - required String type, - Value emojiCode = const Value.absent(), - Value createdAt = const Value.absent(), - Value updatedAt = const Value.absent(), - Value score = const Value.absent(), - Value?> extraData = const Value.absent(), - Value rowid = const Value.absent(), - }) => - PinnedMessageReactionsCompanion.insert( - userId: userId, - messageId: messageId, - type: type, - emojiCode: emojiCode, - createdAt: createdAt, - updatedAt: updatedAt, - score: score, - extraData: extraData, - rowid: rowid, - ), - withReferenceMapper: (p0) => p0 - .map((e) => ( - e.readTable(table), - $$PinnedMessageReactionsTableReferences(db, table, e) - )) - .toList(), + createFilteringComposer: () => $$PinnedMessageReactionsTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => $$PinnedMessageReactionsTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => $$PinnedMessageReactionsTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value userId = const Value.absent(), + Value messageId = const Value.absent(), + Value type = const Value.absent(), + Value emojiCode = const Value.absent(), + Value createdAt = const Value.absent(), + Value updatedAt = const Value.absent(), + Value score = const Value.absent(), + Value?> extraData = const Value.absent(), + Value rowid = const Value.absent(), + }) => PinnedMessageReactionsCompanion( + userId: userId, + messageId: messageId, + type: type, + emojiCode: emojiCode, + createdAt: createdAt, + updatedAt: updatedAt, + score: score, + extraData: extraData, + rowid: rowid, + ), + createCompanionCallback: + ({ + Value userId = const Value.absent(), + Value messageId = const Value.absent(), + required String type, + Value emojiCode = const Value.absent(), + Value createdAt = const Value.absent(), + Value updatedAt = const Value.absent(), + Value score = const Value.absent(), + Value?> extraData = const Value.absent(), + Value rowid = const Value.absent(), + }) => PinnedMessageReactionsCompanion.insert( + userId: userId, + messageId: messageId, + type: type, + emojiCode: emojiCode, + createdAt: createdAt, + updatedAt: updatedAt, + score: score, + extraData: extraData, + rowid: rowid, + ), + withReferenceMapper: (p0) => + p0.map((e) => (e.readTable(table), $$PinnedMessageReactionsTableReferences(db, table, e))).toList(), prefetchHooksCallback: ({messageId = false}) { return PrefetchHooks( db: db, explicitlyWatchedTables: [], - addJoins: < - T extends TableManagerState< + addJoins: + < + T extends TableManagerState< dynamic, dynamic, dynamic, @@ -13838,87 +13319,87 @@ class $$PinnedMessageReactionsTableTableManager extends RootTableManager< dynamic, dynamic, dynamic, - dynamic>>(state) { - if (messageId) { - state = state.withJoin( - currentTable: table, - currentColumn: table.messageId, - referencedTable: $$PinnedMessageReactionsTableReferences - ._messageIdTable(db), - referencedColumn: $$PinnedMessageReactionsTableReferences - ._messageIdTable(db) - .id, - ) as T; - } - - return state; - }, + dynamic + > + >(state) { + if (messageId) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.messageId, + referencedTable: $$PinnedMessageReactionsTableReferences._messageIdTable(db), + referencedColumn: $$PinnedMessageReactionsTableReferences._messageIdTable(db).id, + ) + as T; + } + + return state; + }, getPrefetchedDataCallback: (items) async { return []; }, ); }, - )); + ), + ); } -typedef $$PinnedMessageReactionsTableProcessedTableManager - = ProcessedTableManager< - _$DriftChatDatabase, - $PinnedMessageReactionsTable, - PinnedMessageReactionEntity, - $$PinnedMessageReactionsTableFilterComposer, - $$PinnedMessageReactionsTableOrderingComposer, - $$PinnedMessageReactionsTableAnnotationComposer, - $$PinnedMessageReactionsTableCreateCompanionBuilder, - $$PinnedMessageReactionsTableUpdateCompanionBuilder, - (PinnedMessageReactionEntity, $$PinnedMessageReactionsTableReferences), - PinnedMessageReactionEntity, - PrefetchHooks Function({bool messageId})>; -typedef $$ReactionsTableCreateCompanionBuilder = ReactionsCompanion Function({ - Value userId, - Value messageId, - required String type, - Value emojiCode, - Value createdAt, - Value updatedAt, - Value score, - Value?> extraData, - Value rowid, -}); -typedef $$ReactionsTableUpdateCompanionBuilder = ReactionsCompanion Function({ - Value userId, - Value messageId, - Value type, - Value emojiCode, - Value createdAt, - Value updatedAt, - Value score, - Value?> extraData, - Value rowid, -}); - -final class $$ReactionsTableReferences extends BaseReferences< - _$DriftChatDatabase, $ReactionsTable, ReactionEntity> { +typedef $$PinnedMessageReactionsTableProcessedTableManager = + ProcessedTableManager< + _$DriftChatDatabase, + $PinnedMessageReactionsTable, + PinnedMessageReactionEntity, + $$PinnedMessageReactionsTableFilterComposer, + $$PinnedMessageReactionsTableOrderingComposer, + $$PinnedMessageReactionsTableAnnotationComposer, + $$PinnedMessageReactionsTableCreateCompanionBuilder, + $$PinnedMessageReactionsTableUpdateCompanionBuilder, + (PinnedMessageReactionEntity, $$PinnedMessageReactionsTableReferences), + PinnedMessageReactionEntity, + PrefetchHooks Function({bool messageId}) + >; +typedef $$ReactionsTableCreateCompanionBuilder = + ReactionsCompanion Function({ + Value userId, + Value messageId, + required String type, + Value emojiCode, + Value createdAt, + Value updatedAt, + Value score, + Value?> extraData, + Value rowid, + }); +typedef $$ReactionsTableUpdateCompanionBuilder = + ReactionsCompanion Function({ + Value userId, + Value messageId, + Value type, + Value emojiCode, + Value createdAt, + Value updatedAt, + Value score, + Value?> extraData, + Value rowid, + }); + +final class $$ReactionsTableReferences extends BaseReferences<_$DriftChatDatabase, $ReactionsTable, ReactionEntity> { $$ReactionsTableReferences(super.$_db, super.$_table, super.$_typedResult); static $MessagesTable _messageIdTable(_$DriftChatDatabase db) => - db.messages.createAlias( - $_aliasNameGenerator(db.reactions.messageId, db.messages.id)); + db.messages.createAlias($_aliasNameGenerator(db.reactions.messageId, db.messages.id)); $$MessagesTableProcessedTableManager? get messageId { final $_column = $_itemColumn('message_id'); if ($_column == null) return null; - final manager = $$MessagesTableTableManager($_db, $_db.messages) - .filter((f) => f.id.sqlEquals($_column)); + final manager = $$MessagesTableTableManager($_db, $_db.messages).filter((f) => f.id.sqlEquals($_column)); final item = $_typedResult.readTableOrNull(_messageIdTable($_db)); if (item == null) return manager; - return ProcessedTableManager( - manager.$state.copyWith(prefetchedData: [item])); + return ProcessedTableManager(manager.$state.copyWith(prefetchedData: [item])); } } -class $$ReactionsTableFilterComposer - extends Composer<_$DriftChatDatabase, $ReactionsTable> { +class $$ReactionsTableFilterComposer extends Composer<_$DriftChatDatabase, $ReactionsTable> { $$ReactionsTableFilterComposer({ required super.$db, required super.$table, @@ -13926,53 +13407,45 @@ class $$ReactionsTableFilterComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnFilters get userId => $composableBuilder( - column: $table.userId, builder: (column) => ColumnFilters(column)); + ColumnFilters get userId => + $composableBuilder(column: $table.userId, builder: (column) => ColumnFilters(column)); - ColumnFilters get type => $composableBuilder( - column: $table.type, builder: (column) => ColumnFilters(column)); + ColumnFilters get type => $composableBuilder(column: $table.type, builder: (column) => ColumnFilters(column)); - ColumnFilters get emojiCode => $composableBuilder( - column: $table.emojiCode, builder: (column) => ColumnFilters(column)); + ColumnFilters get emojiCode => + $composableBuilder(column: $table.emojiCode, builder: (column) => ColumnFilters(column)); - ColumnFilters get createdAt => $composableBuilder( - column: $table.createdAt, builder: (column) => ColumnFilters(column)); + ColumnFilters get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get updatedAt => $composableBuilder( - column: $table.updatedAt, builder: (column) => ColumnFilters(column)); + ColumnFilters get updatedAt => + $composableBuilder(column: $table.updatedAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get score => $composableBuilder( - column: $table.score, builder: (column) => ColumnFilters(column)); + ColumnFilters get score => $composableBuilder(column: $table.score, builder: (column) => ColumnFilters(column)); - ColumnWithTypeConverterFilters?, Map, - String> - get extraData => $composableBuilder( - column: $table.extraData, - builder: (column) => ColumnWithTypeConverterFilters(column)); + ColumnWithTypeConverterFilters?, Map, String> get extraData => + $composableBuilder(column: $table.extraData, builder: (column) => ColumnWithTypeConverterFilters(column)); $$MessagesTableFilterComposer get messageId { final $$MessagesTableFilterComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.messageId, - referencedTable: $db.messages, - getReferencedColumn: (t) => t.id, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$MessagesTableFilterComposer( - $db: $db, - $table: $db.messages, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.messageId, + referencedTable: $db.messages, + getReferencedColumn: (t) => t.id, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$MessagesTableFilterComposer( + $db: $db, + $table: $db.messages, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return composer; } } -class $$ReactionsTableOrderingComposer - extends Composer<_$DriftChatDatabase, $ReactionsTable> { +class $$ReactionsTableOrderingComposer extends Composer<_$DriftChatDatabase, $ReactionsTable> { $$ReactionsTableOrderingComposer({ required super.$db, required super.$table, @@ -13980,50 +13453,47 @@ class $$ReactionsTableOrderingComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnOrderings get userId => $composableBuilder( - column: $table.userId, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get userId => + $composableBuilder(column: $table.userId, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get type => $composableBuilder( - column: $table.type, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get type => + $composableBuilder(column: $table.type, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get emojiCode => $composableBuilder( - column: $table.emojiCode, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get emojiCode => + $composableBuilder(column: $table.emojiCode, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get createdAt => $composableBuilder( - column: $table.createdAt, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get updatedAt => $composableBuilder( - column: $table.updatedAt, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get updatedAt => + $composableBuilder(column: $table.updatedAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get score => $composableBuilder( - column: $table.score, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get score => + $composableBuilder(column: $table.score, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get extraData => $composableBuilder( - column: $table.extraData, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get extraData => + $composableBuilder(column: $table.extraData, builder: (column) => ColumnOrderings(column)); $$MessagesTableOrderingComposer get messageId { final $$MessagesTableOrderingComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.messageId, - referencedTable: $db.messages, - getReferencedColumn: (t) => t.id, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$MessagesTableOrderingComposer( - $db: $db, - $table: $db.messages, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.messageId, + referencedTable: $db.messages, + getReferencedColumn: (t) => t.id, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$MessagesTableOrderingComposer( + $db: $db, + $table: $db.messages, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return composer; } } -class $$ReactionsTableAnnotationComposer - extends Composer<_$DriftChatDatabase, $ReactionsTable> { +class $$ReactionsTableAnnotationComposer extends Composer<_$DriftChatDatabase, $ReactionsTable> { $$ReactionsTableAnnotationComposer({ required super.$db, required super.$table, @@ -14031,127 +13501,116 @@ class $$ReactionsTableAnnotationComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - GeneratedColumn get userId => - $composableBuilder(column: $table.userId, builder: (column) => column); + GeneratedColumn get userId => $composableBuilder(column: $table.userId, builder: (column) => column); - GeneratedColumn get type => - $composableBuilder(column: $table.type, builder: (column) => column); + GeneratedColumn get type => $composableBuilder(column: $table.type, builder: (column) => column); - GeneratedColumn get emojiCode => - $composableBuilder(column: $table.emojiCode, builder: (column) => column); + GeneratedColumn get emojiCode => $composableBuilder(column: $table.emojiCode, builder: (column) => column); - GeneratedColumn get createdAt => - $composableBuilder(column: $table.createdAt, builder: (column) => column); + GeneratedColumn get createdAt => $composableBuilder(column: $table.createdAt, builder: (column) => column); - GeneratedColumn get updatedAt => - $composableBuilder(column: $table.updatedAt, builder: (column) => column); + GeneratedColumn get updatedAt => $composableBuilder(column: $table.updatedAt, builder: (column) => column); - GeneratedColumn get score => - $composableBuilder(column: $table.score, builder: (column) => column); + GeneratedColumn get score => $composableBuilder(column: $table.score, builder: (column) => column); - GeneratedColumnWithTypeConverter?, String> - get extraData => $composableBuilder( - column: $table.extraData, builder: (column) => column); + GeneratedColumnWithTypeConverter?, String> get extraData => + $composableBuilder(column: $table.extraData, builder: (column) => column); $$MessagesTableAnnotationComposer get messageId { final $$MessagesTableAnnotationComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.messageId, - referencedTable: $db.messages, - getReferencedColumn: (t) => t.id, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$MessagesTableAnnotationComposer( - $db: $db, - $table: $db.messages, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.messageId, + referencedTable: $db.messages, + getReferencedColumn: (t) => t.id, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$MessagesTableAnnotationComposer( + $db: $db, + $table: $db.messages, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return composer; } } -class $$ReactionsTableTableManager extends RootTableManager< - _$DriftChatDatabase, - $ReactionsTable, - ReactionEntity, - $$ReactionsTableFilterComposer, - $$ReactionsTableOrderingComposer, - $$ReactionsTableAnnotationComposer, - $$ReactionsTableCreateCompanionBuilder, - $$ReactionsTableUpdateCompanionBuilder, - (ReactionEntity, $$ReactionsTableReferences), - ReactionEntity, - PrefetchHooks Function({bool messageId})> { +class $$ReactionsTableTableManager + extends + RootTableManager< + _$DriftChatDatabase, + $ReactionsTable, + ReactionEntity, + $$ReactionsTableFilterComposer, + $$ReactionsTableOrderingComposer, + $$ReactionsTableAnnotationComposer, + $$ReactionsTableCreateCompanionBuilder, + $$ReactionsTableUpdateCompanionBuilder, + (ReactionEntity, $$ReactionsTableReferences), + ReactionEntity, + PrefetchHooks Function({bool messageId}) + > { $$ReactionsTableTableManager(_$DriftChatDatabase db, $ReactionsTable table) - : super(TableManagerState( + : super( + TableManagerState( db: db, table: table, - createFilteringComposer: () => - $$ReactionsTableFilterComposer($db: db, $table: table), - createOrderingComposer: () => - $$ReactionsTableOrderingComposer($db: db, $table: table), - createComputedFieldComposer: () => - $$ReactionsTableAnnotationComposer($db: db, $table: table), - updateCompanionCallback: ({ - Value userId = const Value.absent(), - Value messageId = const Value.absent(), - Value type = const Value.absent(), - Value emojiCode = const Value.absent(), - Value createdAt = const Value.absent(), - Value updatedAt = const Value.absent(), - Value score = const Value.absent(), - Value?> extraData = const Value.absent(), - Value rowid = const Value.absent(), - }) => - ReactionsCompanion( - userId: userId, - messageId: messageId, - type: type, - emojiCode: emojiCode, - createdAt: createdAt, - updatedAt: updatedAt, - score: score, - extraData: extraData, - rowid: rowid, - ), - createCompanionCallback: ({ - Value userId = const Value.absent(), - Value messageId = const Value.absent(), - required String type, - Value emojiCode = const Value.absent(), - Value createdAt = const Value.absent(), - Value updatedAt = const Value.absent(), - Value score = const Value.absent(), - Value?> extraData = const Value.absent(), - Value rowid = const Value.absent(), - }) => - ReactionsCompanion.insert( - userId: userId, - messageId: messageId, - type: type, - emojiCode: emojiCode, - createdAt: createdAt, - updatedAt: updatedAt, - score: score, - extraData: extraData, - rowid: rowid, - ), - withReferenceMapper: (p0) => p0 - .map((e) => ( - e.readTable(table), - $$ReactionsTableReferences(db, table, e) - )) - .toList(), + createFilteringComposer: () => $$ReactionsTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => $$ReactionsTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => $$ReactionsTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value userId = const Value.absent(), + Value messageId = const Value.absent(), + Value type = const Value.absent(), + Value emojiCode = const Value.absent(), + Value createdAt = const Value.absent(), + Value updatedAt = const Value.absent(), + Value score = const Value.absent(), + Value?> extraData = const Value.absent(), + Value rowid = const Value.absent(), + }) => ReactionsCompanion( + userId: userId, + messageId: messageId, + type: type, + emojiCode: emojiCode, + createdAt: createdAt, + updatedAt: updatedAt, + score: score, + extraData: extraData, + rowid: rowid, + ), + createCompanionCallback: + ({ + Value userId = const Value.absent(), + Value messageId = const Value.absent(), + required String type, + Value emojiCode = const Value.absent(), + Value createdAt = const Value.absent(), + Value updatedAt = const Value.absent(), + Value score = const Value.absent(), + Value?> extraData = const Value.absent(), + Value rowid = const Value.absent(), + }) => ReactionsCompanion.insert( + userId: userId, + messageId: messageId, + type: type, + emojiCode: emojiCode, + createdAt: createdAt, + updatedAt: updatedAt, + score: score, + extraData: extraData, + rowid: rowid, + ), + withReferenceMapper: (p0) => + p0.map((e) => (e.readTable(table), $$ReactionsTableReferences(db, table, e))).toList(), prefetchHooksCallback: ({messageId = false}) { return PrefetchHooks( db: db, explicitlyWatchedTables: [], - addJoins: < - T extends TableManagerState< + addJoins: + < + T extends TableManagerState< dynamic, dynamic, dynamic, @@ -14162,71 +13621,77 @@ class $$ReactionsTableTableManager extends RootTableManager< dynamic, dynamic, dynamic, - dynamic>>(state) { - if (messageId) { - state = state.withJoin( - currentTable: table, - currentColumn: table.messageId, - referencedTable: - $$ReactionsTableReferences._messageIdTable(db), - referencedColumn: - $$ReactionsTableReferences._messageIdTable(db).id, - ) as T; - } - - return state; - }, + dynamic + > + >(state) { + if (messageId) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.messageId, + referencedTable: $$ReactionsTableReferences._messageIdTable(db), + referencedColumn: $$ReactionsTableReferences._messageIdTable(db).id, + ) + as T; + } + + return state; + }, getPrefetchedDataCallback: (items) async { return []; }, ); }, - )); + ), + ); } -typedef $$ReactionsTableProcessedTableManager = ProcessedTableManager< - _$DriftChatDatabase, - $ReactionsTable, - ReactionEntity, - $$ReactionsTableFilterComposer, - $$ReactionsTableOrderingComposer, - $$ReactionsTableAnnotationComposer, - $$ReactionsTableCreateCompanionBuilder, - $$ReactionsTableUpdateCompanionBuilder, - (ReactionEntity, $$ReactionsTableReferences), - ReactionEntity, - PrefetchHooks Function({bool messageId})>; -typedef $$UsersTableCreateCompanionBuilder = UsersCompanion Function({ - required String id, - Value role, - Value language, - Value createdAt, - Value updatedAt, - Value lastActive, - Value online, - Value banned, - Value?> teamsRole, - Value avgResponseTime, - required Map extraData, - Value rowid, -}); -typedef $$UsersTableUpdateCompanionBuilder = UsersCompanion Function({ - Value id, - Value role, - Value language, - Value createdAt, - Value updatedAt, - Value lastActive, - Value online, - Value banned, - Value?> teamsRole, - Value avgResponseTime, - Value> extraData, - Value rowid, -}); - -class $$UsersTableFilterComposer - extends Composer<_$DriftChatDatabase, $UsersTable> { +typedef $$ReactionsTableProcessedTableManager = + ProcessedTableManager< + _$DriftChatDatabase, + $ReactionsTable, + ReactionEntity, + $$ReactionsTableFilterComposer, + $$ReactionsTableOrderingComposer, + $$ReactionsTableAnnotationComposer, + $$ReactionsTableCreateCompanionBuilder, + $$ReactionsTableUpdateCompanionBuilder, + (ReactionEntity, $$ReactionsTableReferences), + ReactionEntity, + PrefetchHooks Function({bool messageId}) + >; +typedef $$UsersTableCreateCompanionBuilder = + UsersCompanion Function({ + required String id, + Value role, + Value language, + Value createdAt, + Value updatedAt, + Value lastActive, + Value online, + Value banned, + Value?> teamsRole, + Value avgResponseTime, + required Map extraData, + Value rowid, + }); +typedef $$UsersTableUpdateCompanionBuilder = + UsersCompanion Function({ + Value id, + Value role, + Value language, + Value createdAt, + Value updatedAt, + Value lastActive, + Value online, + Value banned, + Value?> teamsRole, + Value avgResponseTime, + Value> extraData, + Value rowid, + }); + +class $$UsersTableFilterComposer extends Composer<_$DriftChatDatabase, $UsersTable> { $$UsersTableFilterComposer({ required super.$db, required super.$table, @@ -14234,49 +13699,39 @@ class $$UsersTableFilterComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnFilters get id => $composableBuilder( - column: $table.id, builder: (column) => ColumnFilters(column)); + ColumnFilters get id => $composableBuilder(column: $table.id, builder: (column) => ColumnFilters(column)); - ColumnFilters get role => $composableBuilder( - column: $table.role, builder: (column) => ColumnFilters(column)); + ColumnFilters get role => $composableBuilder(column: $table.role, builder: (column) => ColumnFilters(column)); - ColumnFilters get language => $composableBuilder( - column: $table.language, builder: (column) => ColumnFilters(column)); + ColumnFilters get language => + $composableBuilder(column: $table.language, builder: (column) => ColumnFilters(column)); - ColumnFilters get createdAt => $composableBuilder( - column: $table.createdAt, builder: (column) => ColumnFilters(column)); + ColumnFilters get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get updatedAt => $composableBuilder( - column: $table.updatedAt, builder: (column) => ColumnFilters(column)); + ColumnFilters get updatedAt => + $composableBuilder(column: $table.updatedAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get lastActive => $composableBuilder( - column: $table.lastActive, builder: (column) => ColumnFilters(column)); + ColumnFilters get lastActive => + $composableBuilder(column: $table.lastActive, builder: (column) => ColumnFilters(column)); - ColumnFilters get online => $composableBuilder( - column: $table.online, builder: (column) => ColumnFilters(column)); + ColumnFilters get online => + $composableBuilder(column: $table.online, builder: (column) => ColumnFilters(column)); - ColumnFilters get banned => $composableBuilder( - column: $table.banned, builder: (column) => ColumnFilters(column)); + ColumnFilters get banned => + $composableBuilder(column: $table.banned, builder: (column) => ColumnFilters(column)); - ColumnWithTypeConverterFilters?, Map, - String> - get teamsRole => $composableBuilder( - column: $table.teamsRole, - builder: (column) => ColumnWithTypeConverterFilters(column)); + ColumnWithTypeConverterFilters?, Map, String> get teamsRole => + $composableBuilder(column: $table.teamsRole, builder: (column) => ColumnWithTypeConverterFilters(column)); - ColumnFilters get avgResponseTime => $composableBuilder( - column: $table.avgResponseTime, - builder: (column) => ColumnFilters(column)); + ColumnFilters get avgResponseTime => + $composableBuilder(column: $table.avgResponseTime, builder: (column) => ColumnFilters(column)); - ColumnWithTypeConverterFilters, Map, - String> - get extraData => $composableBuilder( - column: $table.extraData, - builder: (column) => ColumnWithTypeConverterFilters(column)); + ColumnWithTypeConverterFilters, Map, String> get extraData => + $composableBuilder(column: $table.extraData, builder: (column) => ColumnWithTypeConverterFilters(column)); } -class $$UsersTableOrderingComposer - extends Composer<_$DriftChatDatabase, $UsersTable> { +class $$UsersTableOrderingComposer extends Composer<_$DriftChatDatabase, $UsersTable> { $$UsersTableOrderingComposer({ required super.$db, required super.$table, @@ -14284,43 +13739,40 @@ class $$UsersTableOrderingComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnOrderings get id => $composableBuilder( - column: $table.id, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get id => $composableBuilder(column: $table.id, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get role => $composableBuilder( - column: $table.role, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get role => + $composableBuilder(column: $table.role, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get language => $composableBuilder( - column: $table.language, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get language => + $composableBuilder(column: $table.language, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get createdAt => $composableBuilder( - column: $table.createdAt, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get updatedAt => $composableBuilder( - column: $table.updatedAt, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get updatedAt => + $composableBuilder(column: $table.updatedAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get lastActive => $composableBuilder( - column: $table.lastActive, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get lastActive => + $composableBuilder(column: $table.lastActive, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get online => $composableBuilder( - column: $table.online, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get online => + $composableBuilder(column: $table.online, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get banned => $composableBuilder( - column: $table.banned, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get banned => + $composableBuilder(column: $table.banned, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get teamsRole => $composableBuilder( - column: $table.teamsRole, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get teamsRole => + $composableBuilder(column: $table.teamsRole, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get avgResponseTime => $composableBuilder( - column: $table.avgResponseTime, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get avgResponseTime => + $composableBuilder(column: $table.avgResponseTime, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get extraData => $composableBuilder( - column: $table.extraData, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get extraData => + $composableBuilder(column: $table.extraData, builder: (column) => ColumnOrderings(column)); } -class $$UsersTableAnnotationComposer - extends Composer<_$DriftChatDatabase, $UsersTable> { +class $$UsersTableAnnotationComposer extends Composer<_$DriftChatDatabase, $UsersTable> { $$UsersTableAnnotationComposer({ required super.$db, required super.$table, @@ -14328,198 +13780,188 @@ class $$UsersTableAnnotationComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - GeneratedColumn get id => - $composableBuilder(column: $table.id, builder: (column) => column); + GeneratedColumn get id => $composableBuilder(column: $table.id, builder: (column) => column); - GeneratedColumn get role => - $composableBuilder(column: $table.role, builder: (column) => column); + GeneratedColumn get role => $composableBuilder(column: $table.role, builder: (column) => column); - GeneratedColumn get language => - $composableBuilder(column: $table.language, builder: (column) => column); + GeneratedColumn get language => $composableBuilder(column: $table.language, builder: (column) => column); - GeneratedColumn get createdAt => - $composableBuilder(column: $table.createdAt, builder: (column) => column); + GeneratedColumn get createdAt => $composableBuilder(column: $table.createdAt, builder: (column) => column); - GeneratedColumn get updatedAt => - $composableBuilder(column: $table.updatedAt, builder: (column) => column); + GeneratedColumn get updatedAt => $composableBuilder(column: $table.updatedAt, builder: (column) => column); - GeneratedColumn get lastActive => $composableBuilder( - column: $table.lastActive, builder: (column) => column); + GeneratedColumn get lastActive => + $composableBuilder(column: $table.lastActive, builder: (column) => column); - GeneratedColumn get online => - $composableBuilder(column: $table.online, builder: (column) => column); + GeneratedColumn get online => $composableBuilder(column: $table.online, builder: (column) => column); - GeneratedColumn get banned => - $composableBuilder(column: $table.banned, builder: (column) => column); + GeneratedColumn get banned => $composableBuilder(column: $table.banned, builder: (column) => column); - GeneratedColumnWithTypeConverter?, String> - get teamsRole => $composableBuilder( - column: $table.teamsRole, builder: (column) => column); + GeneratedColumnWithTypeConverter?, String> get teamsRole => + $composableBuilder(column: $table.teamsRole, builder: (column) => column); - GeneratedColumn get avgResponseTime => $composableBuilder( - column: $table.avgResponseTime, builder: (column) => column); + GeneratedColumn get avgResponseTime => + $composableBuilder(column: $table.avgResponseTime, builder: (column) => column); - GeneratedColumnWithTypeConverter, String> - get extraData => $composableBuilder( - column: $table.extraData, builder: (column) => column); + GeneratedColumnWithTypeConverter, String> get extraData => + $composableBuilder(column: $table.extraData, builder: (column) => column); } -class $$UsersTableTableManager extends RootTableManager< - _$DriftChatDatabase, - $UsersTable, - UserEntity, - $$UsersTableFilterComposer, - $$UsersTableOrderingComposer, - $$UsersTableAnnotationComposer, - $$UsersTableCreateCompanionBuilder, - $$UsersTableUpdateCompanionBuilder, - (UserEntity, BaseReferences<_$DriftChatDatabase, $UsersTable, UserEntity>), - UserEntity, - PrefetchHooks Function()> { +class $$UsersTableTableManager + extends + RootTableManager< + _$DriftChatDatabase, + $UsersTable, + UserEntity, + $$UsersTableFilterComposer, + $$UsersTableOrderingComposer, + $$UsersTableAnnotationComposer, + $$UsersTableCreateCompanionBuilder, + $$UsersTableUpdateCompanionBuilder, + (UserEntity, BaseReferences<_$DriftChatDatabase, $UsersTable, UserEntity>), + UserEntity, + PrefetchHooks Function() + > { $$UsersTableTableManager(_$DriftChatDatabase db, $UsersTable table) - : super(TableManagerState( + : super( + TableManagerState( db: db, table: table, - createFilteringComposer: () => - $$UsersTableFilterComposer($db: db, $table: table), - createOrderingComposer: () => - $$UsersTableOrderingComposer($db: db, $table: table), - createComputedFieldComposer: () => - $$UsersTableAnnotationComposer($db: db, $table: table), - updateCompanionCallback: ({ - Value id = const Value.absent(), - Value role = const Value.absent(), - Value language = const Value.absent(), - Value createdAt = const Value.absent(), - Value updatedAt = const Value.absent(), - Value lastActive = const Value.absent(), - Value online = const Value.absent(), - Value banned = const Value.absent(), - Value?> teamsRole = const Value.absent(), - Value avgResponseTime = const Value.absent(), - Value> extraData = const Value.absent(), - Value rowid = const Value.absent(), - }) => - UsersCompanion( - id: id, - role: role, - language: language, - createdAt: createdAt, - updatedAt: updatedAt, - lastActive: lastActive, - online: online, - banned: banned, - teamsRole: teamsRole, - avgResponseTime: avgResponseTime, - extraData: extraData, - rowid: rowid, - ), - createCompanionCallback: ({ - required String id, - Value role = const Value.absent(), - Value language = const Value.absent(), - Value createdAt = const Value.absent(), - Value updatedAt = const Value.absent(), - Value lastActive = const Value.absent(), - Value online = const Value.absent(), - Value banned = const Value.absent(), - Value?> teamsRole = const Value.absent(), - Value avgResponseTime = const Value.absent(), - required Map extraData, - Value rowid = const Value.absent(), - }) => - UsersCompanion.insert( - id: id, - role: role, - language: language, - createdAt: createdAt, - updatedAt: updatedAt, - lastActive: lastActive, - online: online, - banned: banned, - teamsRole: teamsRole, - avgResponseTime: avgResponseTime, - extraData: extraData, - rowid: rowid, - ), - withReferenceMapper: (p0) => p0 - .map((e) => (e.readTable(table), BaseReferences(db, table, e))) - .toList(), + createFilteringComposer: () => $$UsersTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => $$UsersTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => $$UsersTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value id = const Value.absent(), + Value role = const Value.absent(), + Value language = const Value.absent(), + Value createdAt = const Value.absent(), + Value updatedAt = const Value.absent(), + Value lastActive = const Value.absent(), + Value online = const Value.absent(), + Value banned = const Value.absent(), + Value?> teamsRole = const Value.absent(), + Value avgResponseTime = const Value.absent(), + Value> extraData = const Value.absent(), + Value rowid = const Value.absent(), + }) => UsersCompanion( + id: id, + role: role, + language: language, + createdAt: createdAt, + updatedAt: updatedAt, + lastActive: lastActive, + online: online, + banned: banned, + teamsRole: teamsRole, + avgResponseTime: avgResponseTime, + extraData: extraData, + rowid: rowid, + ), + createCompanionCallback: + ({ + required String id, + Value role = const Value.absent(), + Value language = const Value.absent(), + Value createdAt = const Value.absent(), + Value updatedAt = const Value.absent(), + Value lastActive = const Value.absent(), + Value online = const Value.absent(), + Value banned = const Value.absent(), + Value?> teamsRole = const Value.absent(), + Value avgResponseTime = const Value.absent(), + required Map extraData, + Value rowid = const Value.absent(), + }) => UsersCompanion.insert( + id: id, + role: role, + language: language, + createdAt: createdAt, + updatedAt: updatedAt, + lastActive: lastActive, + online: online, + banned: banned, + teamsRole: teamsRole, + avgResponseTime: avgResponseTime, + extraData: extraData, + rowid: rowid, + ), + withReferenceMapper: (p0) => p0.map((e) => (e.readTable(table), BaseReferences(db, table, e))).toList(), prefetchHooksCallback: null, - )); + ), + ); } -typedef $$UsersTableProcessedTableManager = ProcessedTableManager< - _$DriftChatDatabase, - $UsersTable, - UserEntity, - $$UsersTableFilterComposer, - $$UsersTableOrderingComposer, - $$UsersTableAnnotationComposer, - $$UsersTableCreateCompanionBuilder, - $$UsersTableUpdateCompanionBuilder, - (UserEntity, BaseReferences<_$DriftChatDatabase, $UsersTable, UserEntity>), - UserEntity, - PrefetchHooks Function()>; -typedef $$MembersTableCreateCompanionBuilder = MembersCompanion Function({ - required String userId, - required String channelCid, - Value channelRole, - Value inviteAcceptedAt, - Value inviteRejectedAt, - Value invited, - Value banned, - Value shadowBanned, - Value pinnedAt, - Value archivedAt, - Value isModerator, - Value?> extraData, - Value createdAt, - Value updatedAt, - required List deletedMessages, - Value rowid, -}); -typedef $$MembersTableUpdateCompanionBuilder = MembersCompanion Function({ - Value userId, - Value channelCid, - Value channelRole, - Value inviteAcceptedAt, - Value inviteRejectedAt, - Value invited, - Value banned, - Value shadowBanned, - Value pinnedAt, - Value archivedAt, - Value isModerator, - Value?> extraData, - Value createdAt, - Value updatedAt, - Value> deletedMessages, - Value rowid, -}); - -final class $$MembersTableReferences - extends BaseReferences<_$DriftChatDatabase, $MembersTable, MemberEntity> { +typedef $$UsersTableProcessedTableManager = + ProcessedTableManager< + _$DriftChatDatabase, + $UsersTable, + UserEntity, + $$UsersTableFilterComposer, + $$UsersTableOrderingComposer, + $$UsersTableAnnotationComposer, + $$UsersTableCreateCompanionBuilder, + $$UsersTableUpdateCompanionBuilder, + (UserEntity, BaseReferences<_$DriftChatDatabase, $UsersTable, UserEntity>), + UserEntity, + PrefetchHooks Function() + >; +typedef $$MembersTableCreateCompanionBuilder = + MembersCompanion Function({ + required String userId, + required String channelCid, + Value channelRole, + Value inviteAcceptedAt, + Value inviteRejectedAt, + Value invited, + Value banned, + Value shadowBanned, + Value pinnedAt, + Value archivedAt, + Value isModerator, + Value?> extraData, + Value createdAt, + Value updatedAt, + required List deletedMessages, + Value rowid, + }); +typedef $$MembersTableUpdateCompanionBuilder = + MembersCompanion Function({ + Value userId, + Value channelCid, + Value channelRole, + Value inviteAcceptedAt, + Value inviteRejectedAt, + Value invited, + Value banned, + Value shadowBanned, + Value pinnedAt, + Value archivedAt, + Value isModerator, + Value?> extraData, + Value createdAt, + Value updatedAt, + Value> deletedMessages, + Value rowid, + }); + +final class $$MembersTableReferences extends BaseReferences<_$DriftChatDatabase, $MembersTable, MemberEntity> { $$MembersTableReferences(super.$_db, super.$_table, super.$_typedResult); static $ChannelsTable _channelCidTable(_$DriftChatDatabase db) => - db.channels.createAlias( - $_aliasNameGenerator(db.members.channelCid, db.channels.cid)); + db.channels.createAlias($_aliasNameGenerator(db.members.channelCid, db.channels.cid)); $$ChannelsTableProcessedTableManager get channelCid { final $_column = $_itemColumn('channel_cid')!; - final manager = $$ChannelsTableTableManager($_db, $_db.channels) - .filter((f) => f.cid.sqlEquals($_column)); + final manager = $$ChannelsTableTableManager($_db, $_db.channels).filter((f) => f.cid.sqlEquals($_column)); final item = $_typedResult.readTableOrNull(_channelCidTable($_db)); if (item == null) return manager; - return ProcessedTableManager( - manager.$state.copyWith(prefetchedData: [item])); + return ProcessedTableManager(manager.$state.copyWith(prefetchedData: [item])); } } -class $$MembersTableFilterComposer - extends Composer<_$DriftChatDatabase, $MembersTable> { +class $$MembersTableFilterComposer extends Composer<_$DriftChatDatabase, $MembersTable> { $$MembersTableFilterComposer({ required super.$db, required super.$table, @@ -14527,78 +13969,68 @@ class $$MembersTableFilterComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnFilters get userId => $composableBuilder( - column: $table.userId, builder: (column) => ColumnFilters(column)); + ColumnFilters get userId => + $composableBuilder(column: $table.userId, builder: (column) => ColumnFilters(column)); - ColumnFilters get channelRole => $composableBuilder( - column: $table.channelRole, builder: (column) => ColumnFilters(column)); + ColumnFilters get channelRole => + $composableBuilder(column: $table.channelRole, builder: (column) => ColumnFilters(column)); - ColumnFilters get inviteAcceptedAt => $composableBuilder( - column: $table.inviteAcceptedAt, - builder: (column) => ColumnFilters(column)); + ColumnFilters get inviteAcceptedAt => + $composableBuilder(column: $table.inviteAcceptedAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get inviteRejectedAt => $composableBuilder( - column: $table.inviteRejectedAt, - builder: (column) => ColumnFilters(column)); + ColumnFilters get inviteRejectedAt => + $composableBuilder(column: $table.inviteRejectedAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get invited => $composableBuilder( - column: $table.invited, builder: (column) => ColumnFilters(column)); + ColumnFilters get invited => + $composableBuilder(column: $table.invited, builder: (column) => ColumnFilters(column)); - ColumnFilters get banned => $composableBuilder( - column: $table.banned, builder: (column) => ColumnFilters(column)); + ColumnFilters get banned => + $composableBuilder(column: $table.banned, builder: (column) => ColumnFilters(column)); - ColumnFilters get shadowBanned => $composableBuilder( - column: $table.shadowBanned, builder: (column) => ColumnFilters(column)); + ColumnFilters get shadowBanned => + $composableBuilder(column: $table.shadowBanned, builder: (column) => ColumnFilters(column)); - ColumnFilters get pinnedAt => $composableBuilder( - column: $table.pinnedAt, builder: (column) => ColumnFilters(column)); + ColumnFilters get pinnedAt => + $composableBuilder(column: $table.pinnedAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get archivedAt => $composableBuilder( - column: $table.archivedAt, builder: (column) => ColumnFilters(column)); + ColumnFilters get archivedAt => + $composableBuilder(column: $table.archivedAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get isModerator => $composableBuilder( - column: $table.isModerator, builder: (column) => ColumnFilters(column)); + ColumnFilters get isModerator => + $composableBuilder(column: $table.isModerator, builder: (column) => ColumnFilters(column)); - ColumnWithTypeConverterFilters?, Map, - String> - get extraData => $composableBuilder( - column: $table.extraData, - builder: (column) => ColumnWithTypeConverterFilters(column)); + ColumnWithTypeConverterFilters?, Map, String> get extraData => + $composableBuilder(column: $table.extraData, builder: (column) => ColumnWithTypeConverterFilters(column)); - ColumnFilters get createdAt => $composableBuilder( - column: $table.createdAt, builder: (column) => ColumnFilters(column)); + ColumnFilters get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get updatedAt => $composableBuilder( - column: $table.updatedAt, builder: (column) => ColumnFilters(column)); + ColumnFilters get updatedAt => + $composableBuilder(column: $table.updatedAt, builder: (column) => ColumnFilters(column)); - ColumnWithTypeConverterFilters, List, String> - get deletedMessages => $composableBuilder( - column: $table.deletedMessages, - builder: (column) => ColumnWithTypeConverterFilters(column)); + ColumnWithTypeConverterFilters, List, String> get deletedMessages => + $composableBuilder(column: $table.deletedMessages, builder: (column) => ColumnWithTypeConverterFilters(column)); $$ChannelsTableFilterComposer get channelCid { final $$ChannelsTableFilterComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.channelCid, - referencedTable: $db.channels, - getReferencedColumn: (t) => t.cid, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$ChannelsTableFilterComposer( - $db: $db, - $table: $db.channels, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.channelCid, + referencedTable: $db.channels, + getReferencedColumn: (t) => t.cid, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$ChannelsTableFilterComposer( + $db: $db, + $table: $db.channels, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return composer; } } -class $$MembersTableOrderingComposer - extends Composer<_$DriftChatDatabase, $MembersTable> { +class $$MembersTableOrderingComposer extends Composer<_$DriftChatDatabase, $MembersTable> { $$MembersTableOrderingComposer({ required super.$db, required super.$table, @@ -14606,75 +14038,68 @@ class $$MembersTableOrderingComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnOrderings get userId => $composableBuilder( - column: $table.userId, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get userId => + $composableBuilder(column: $table.userId, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get channelRole => $composableBuilder( - column: $table.channelRole, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get channelRole => + $composableBuilder(column: $table.channelRole, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get inviteAcceptedAt => $composableBuilder( - column: $table.inviteAcceptedAt, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get inviteAcceptedAt => + $composableBuilder(column: $table.inviteAcceptedAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get inviteRejectedAt => $composableBuilder( - column: $table.inviteRejectedAt, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get inviteRejectedAt => + $composableBuilder(column: $table.inviteRejectedAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get invited => $composableBuilder( - column: $table.invited, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get invited => + $composableBuilder(column: $table.invited, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get banned => $composableBuilder( - column: $table.banned, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get banned => + $composableBuilder(column: $table.banned, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get shadowBanned => $composableBuilder( - column: $table.shadowBanned, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get shadowBanned => + $composableBuilder(column: $table.shadowBanned, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get pinnedAt => $composableBuilder( - column: $table.pinnedAt, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get pinnedAt => + $composableBuilder(column: $table.pinnedAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get archivedAt => $composableBuilder( - column: $table.archivedAt, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get archivedAt => + $composableBuilder(column: $table.archivedAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get isModerator => $composableBuilder( - column: $table.isModerator, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get isModerator => + $composableBuilder(column: $table.isModerator, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get extraData => $composableBuilder( - column: $table.extraData, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get extraData => + $composableBuilder(column: $table.extraData, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get createdAt => $composableBuilder( - column: $table.createdAt, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get updatedAt => $composableBuilder( - column: $table.updatedAt, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get updatedAt => + $composableBuilder(column: $table.updatedAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get deletedMessages => $composableBuilder( - column: $table.deletedMessages, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get deletedMessages => + $composableBuilder(column: $table.deletedMessages, builder: (column) => ColumnOrderings(column)); $$ChannelsTableOrderingComposer get channelCid { final $$ChannelsTableOrderingComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.channelCid, - referencedTable: $db.channels, - getReferencedColumn: (t) => t.cid, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$ChannelsTableOrderingComposer( - $db: $db, - $table: $db.channels, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.channelCid, + referencedTable: $db.channels, + getReferencedColumn: (t) => t.cid, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$ChannelsTableOrderingComposer( + $db: $db, + $table: $db.channels, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return composer; } } -class $$MembersTableAnnotationComposer - extends Composer<_$DriftChatDatabase, $MembersTable> { +class $$MembersTableAnnotationComposer extends Composer<_$DriftChatDatabase, $MembersTable> { $$MembersTableAnnotationComposer({ required super.$db, required super.$table, @@ -14682,175 +14107,164 @@ class $$MembersTableAnnotationComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - GeneratedColumn get userId => - $composableBuilder(column: $table.userId, builder: (column) => column); + GeneratedColumn get userId => $composableBuilder(column: $table.userId, builder: (column) => column); - GeneratedColumn get channelRole => $composableBuilder( - column: $table.channelRole, builder: (column) => column); + GeneratedColumn get channelRole => + $composableBuilder(column: $table.channelRole, builder: (column) => column); - GeneratedColumn get inviteAcceptedAt => $composableBuilder( - column: $table.inviteAcceptedAt, builder: (column) => column); + GeneratedColumn get inviteAcceptedAt => + $composableBuilder(column: $table.inviteAcceptedAt, builder: (column) => column); - GeneratedColumn get inviteRejectedAt => $composableBuilder( - column: $table.inviteRejectedAt, builder: (column) => column); + GeneratedColumn get inviteRejectedAt => + $composableBuilder(column: $table.inviteRejectedAt, builder: (column) => column); - GeneratedColumn get invited => - $composableBuilder(column: $table.invited, builder: (column) => column); + GeneratedColumn get invited => $composableBuilder(column: $table.invited, builder: (column) => column); - GeneratedColumn get banned => - $composableBuilder(column: $table.banned, builder: (column) => column); + GeneratedColumn get banned => $composableBuilder(column: $table.banned, builder: (column) => column); - GeneratedColumn get shadowBanned => $composableBuilder( - column: $table.shadowBanned, builder: (column) => column); + GeneratedColumn get shadowBanned => + $composableBuilder(column: $table.shadowBanned, builder: (column) => column); - GeneratedColumn get pinnedAt => - $composableBuilder(column: $table.pinnedAt, builder: (column) => column); + GeneratedColumn get pinnedAt => $composableBuilder(column: $table.pinnedAt, builder: (column) => column); - GeneratedColumn get archivedAt => $composableBuilder( - column: $table.archivedAt, builder: (column) => column); + GeneratedColumn get archivedAt => + $composableBuilder(column: $table.archivedAt, builder: (column) => column); - GeneratedColumn get isModerator => $composableBuilder( - column: $table.isModerator, builder: (column) => column); + GeneratedColumn get isModerator => $composableBuilder(column: $table.isModerator, builder: (column) => column); - GeneratedColumnWithTypeConverter?, String> - get extraData => $composableBuilder( - column: $table.extraData, builder: (column) => column); + GeneratedColumnWithTypeConverter?, String> get extraData => + $composableBuilder(column: $table.extraData, builder: (column) => column); - GeneratedColumn get createdAt => - $composableBuilder(column: $table.createdAt, builder: (column) => column); + GeneratedColumn get createdAt => $composableBuilder(column: $table.createdAt, builder: (column) => column); - GeneratedColumn get updatedAt => - $composableBuilder(column: $table.updatedAt, builder: (column) => column); + GeneratedColumn get updatedAt => $composableBuilder(column: $table.updatedAt, builder: (column) => column); GeneratedColumnWithTypeConverter, String> get deletedMessages => - $composableBuilder( - column: $table.deletedMessages, builder: (column) => column); + $composableBuilder(column: $table.deletedMessages, builder: (column) => column); $$ChannelsTableAnnotationComposer get channelCid { final $$ChannelsTableAnnotationComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.channelCid, - referencedTable: $db.channels, - getReferencedColumn: (t) => t.cid, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$ChannelsTableAnnotationComposer( - $db: $db, - $table: $db.channels, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.channelCid, + referencedTable: $db.channels, + getReferencedColumn: (t) => t.cid, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$ChannelsTableAnnotationComposer( + $db: $db, + $table: $db.channels, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return composer; } } -class $$MembersTableTableManager extends RootTableManager< - _$DriftChatDatabase, - $MembersTable, - MemberEntity, - $$MembersTableFilterComposer, - $$MembersTableOrderingComposer, - $$MembersTableAnnotationComposer, - $$MembersTableCreateCompanionBuilder, - $$MembersTableUpdateCompanionBuilder, - (MemberEntity, $$MembersTableReferences), - MemberEntity, - PrefetchHooks Function({bool channelCid})> { +class $$MembersTableTableManager + extends + RootTableManager< + _$DriftChatDatabase, + $MembersTable, + MemberEntity, + $$MembersTableFilterComposer, + $$MembersTableOrderingComposer, + $$MembersTableAnnotationComposer, + $$MembersTableCreateCompanionBuilder, + $$MembersTableUpdateCompanionBuilder, + (MemberEntity, $$MembersTableReferences), + MemberEntity, + PrefetchHooks Function({bool channelCid}) + > { $$MembersTableTableManager(_$DriftChatDatabase db, $MembersTable table) - : super(TableManagerState( + : super( + TableManagerState( db: db, table: table, - createFilteringComposer: () => - $$MembersTableFilterComposer($db: db, $table: table), - createOrderingComposer: () => - $$MembersTableOrderingComposer($db: db, $table: table), - createComputedFieldComposer: () => - $$MembersTableAnnotationComposer($db: db, $table: table), - updateCompanionCallback: ({ - Value userId = const Value.absent(), - Value channelCid = const Value.absent(), - Value channelRole = const Value.absent(), - Value inviteAcceptedAt = const Value.absent(), - Value inviteRejectedAt = const Value.absent(), - Value invited = const Value.absent(), - Value banned = const Value.absent(), - Value shadowBanned = const Value.absent(), - Value pinnedAt = const Value.absent(), - Value archivedAt = const Value.absent(), - Value isModerator = const Value.absent(), - Value?> extraData = const Value.absent(), - Value createdAt = const Value.absent(), - Value updatedAt = const Value.absent(), - Value> deletedMessages = const Value.absent(), - Value rowid = const Value.absent(), - }) => - MembersCompanion( - userId: userId, - channelCid: channelCid, - channelRole: channelRole, - inviteAcceptedAt: inviteAcceptedAt, - inviteRejectedAt: inviteRejectedAt, - invited: invited, - banned: banned, - shadowBanned: shadowBanned, - pinnedAt: pinnedAt, - archivedAt: archivedAt, - isModerator: isModerator, - extraData: extraData, - createdAt: createdAt, - updatedAt: updatedAt, - deletedMessages: deletedMessages, - rowid: rowid, - ), - createCompanionCallback: ({ - required String userId, - required String channelCid, - Value channelRole = const Value.absent(), - Value inviteAcceptedAt = const Value.absent(), - Value inviteRejectedAt = const Value.absent(), - Value invited = const Value.absent(), - Value banned = const Value.absent(), - Value shadowBanned = const Value.absent(), - Value pinnedAt = const Value.absent(), - Value archivedAt = const Value.absent(), - Value isModerator = const Value.absent(), - Value?> extraData = const Value.absent(), - Value createdAt = const Value.absent(), - Value updatedAt = const Value.absent(), - required List deletedMessages, - Value rowid = const Value.absent(), - }) => - MembersCompanion.insert( - userId: userId, - channelCid: channelCid, - channelRole: channelRole, - inviteAcceptedAt: inviteAcceptedAt, - inviteRejectedAt: inviteRejectedAt, - invited: invited, - banned: banned, - shadowBanned: shadowBanned, - pinnedAt: pinnedAt, - archivedAt: archivedAt, - isModerator: isModerator, - extraData: extraData, - createdAt: createdAt, - updatedAt: updatedAt, - deletedMessages: deletedMessages, - rowid: rowid, - ), - withReferenceMapper: (p0) => p0 - .map((e) => - (e.readTable(table), $$MembersTableReferences(db, table, e))) - .toList(), + createFilteringComposer: () => $$MembersTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => $$MembersTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => $$MembersTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value userId = const Value.absent(), + Value channelCid = const Value.absent(), + Value channelRole = const Value.absent(), + Value inviteAcceptedAt = const Value.absent(), + Value inviteRejectedAt = const Value.absent(), + Value invited = const Value.absent(), + Value banned = const Value.absent(), + Value shadowBanned = const Value.absent(), + Value pinnedAt = const Value.absent(), + Value archivedAt = const Value.absent(), + Value isModerator = const Value.absent(), + Value?> extraData = const Value.absent(), + Value createdAt = const Value.absent(), + Value updatedAt = const Value.absent(), + Value> deletedMessages = const Value.absent(), + Value rowid = const Value.absent(), + }) => MembersCompanion( + userId: userId, + channelCid: channelCid, + channelRole: channelRole, + inviteAcceptedAt: inviteAcceptedAt, + inviteRejectedAt: inviteRejectedAt, + invited: invited, + banned: banned, + shadowBanned: shadowBanned, + pinnedAt: pinnedAt, + archivedAt: archivedAt, + isModerator: isModerator, + extraData: extraData, + createdAt: createdAt, + updatedAt: updatedAt, + deletedMessages: deletedMessages, + rowid: rowid, + ), + createCompanionCallback: + ({ + required String userId, + required String channelCid, + Value channelRole = const Value.absent(), + Value inviteAcceptedAt = const Value.absent(), + Value inviteRejectedAt = const Value.absent(), + Value invited = const Value.absent(), + Value banned = const Value.absent(), + Value shadowBanned = const Value.absent(), + Value pinnedAt = const Value.absent(), + Value archivedAt = const Value.absent(), + Value isModerator = const Value.absent(), + Value?> extraData = const Value.absent(), + Value createdAt = const Value.absent(), + Value updatedAt = const Value.absent(), + required List deletedMessages, + Value rowid = const Value.absent(), + }) => MembersCompanion.insert( + userId: userId, + channelCid: channelCid, + channelRole: channelRole, + inviteAcceptedAt: inviteAcceptedAt, + inviteRejectedAt: inviteRejectedAt, + invited: invited, + banned: banned, + shadowBanned: shadowBanned, + pinnedAt: pinnedAt, + archivedAt: archivedAt, + isModerator: isModerator, + extraData: extraData, + createdAt: createdAt, + updatedAt: updatedAt, + deletedMessages: deletedMessages, + rowid: rowid, + ), + withReferenceMapper: (p0) => + p0.map((e) => (e.readTable(table), $$MembersTableReferences(db, table, e))).toList(), prefetchHooksCallback: ({channelCid = false}) { return PrefetchHooks( db: db, explicitlyWatchedTables: [], - addJoins: < - T extends TableManagerState< + addJoins: + < + T extends TableManagerState< dynamic, dynamic, dynamic, @@ -14861,82 +14275,85 @@ class $$MembersTableTableManager extends RootTableManager< dynamic, dynamic, dynamic, - dynamic>>(state) { - if (channelCid) { - state = state.withJoin( - currentTable: table, - currentColumn: table.channelCid, - referencedTable: - $$MembersTableReferences._channelCidTable(db), - referencedColumn: - $$MembersTableReferences._channelCidTable(db).cid, - ) as T; - } - - return state; - }, + dynamic + > + >(state) { + if (channelCid) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.channelCid, + referencedTable: $$MembersTableReferences._channelCidTable(db), + referencedColumn: $$MembersTableReferences._channelCidTable(db).cid, + ) + as T; + } + + return state; + }, getPrefetchedDataCallback: (items) async { return []; }, ); }, - )); + ), + ); } -typedef $$MembersTableProcessedTableManager = ProcessedTableManager< - _$DriftChatDatabase, - $MembersTable, - MemberEntity, - $$MembersTableFilterComposer, - $$MembersTableOrderingComposer, - $$MembersTableAnnotationComposer, - $$MembersTableCreateCompanionBuilder, - $$MembersTableUpdateCompanionBuilder, - (MemberEntity, $$MembersTableReferences), - MemberEntity, - PrefetchHooks Function({bool channelCid})>; -typedef $$ReadsTableCreateCompanionBuilder = ReadsCompanion Function({ - required DateTime lastRead, - required String userId, - required String channelCid, - Value unreadMessages, - Value lastReadMessageId, - Value lastDeliveredAt, - Value lastDeliveredMessageId, - Value rowid, -}); -typedef $$ReadsTableUpdateCompanionBuilder = ReadsCompanion Function({ - Value lastRead, - Value userId, - Value channelCid, - Value unreadMessages, - Value lastReadMessageId, - Value lastDeliveredAt, - Value lastDeliveredMessageId, - Value rowid, -}); - -final class $$ReadsTableReferences - extends BaseReferences<_$DriftChatDatabase, $ReadsTable, ReadEntity> { +typedef $$MembersTableProcessedTableManager = + ProcessedTableManager< + _$DriftChatDatabase, + $MembersTable, + MemberEntity, + $$MembersTableFilterComposer, + $$MembersTableOrderingComposer, + $$MembersTableAnnotationComposer, + $$MembersTableCreateCompanionBuilder, + $$MembersTableUpdateCompanionBuilder, + (MemberEntity, $$MembersTableReferences), + MemberEntity, + PrefetchHooks Function({bool channelCid}) + >; +typedef $$ReadsTableCreateCompanionBuilder = + ReadsCompanion Function({ + required DateTime lastRead, + required String userId, + required String channelCid, + Value unreadMessages, + Value lastReadMessageId, + Value lastDeliveredAt, + Value lastDeliveredMessageId, + Value rowid, + }); +typedef $$ReadsTableUpdateCompanionBuilder = + ReadsCompanion Function({ + Value lastRead, + Value userId, + Value channelCid, + Value unreadMessages, + Value lastReadMessageId, + Value lastDeliveredAt, + Value lastDeliveredMessageId, + Value rowid, + }); + +final class $$ReadsTableReferences extends BaseReferences<_$DriftChatDatabase, $ReadsTable, ReadEntity> { $$ReadsTableReferences(super.$_db, super.$_table, super.$_typedResult); - static $ChannelsTable _channelCidTable(_$DriftChatDatabase db) => db.channels - .createAlias($_aliasNameGenerator(db.reads.channelCid, db.channels.cid)); + static $ChannelsTable _channelCidTable(_$DriftChatDatabase db) => + db.channels.createAlias($_aliasNameGenerator(db.reads.channelCid, db.channels.cid)); $$ChannelsTableProcessedTableManager get channelCid { final $_column = $_itemColumn('channel_cid')!; - final manager = $$ChannelsTableTableManager($_db, $_db.channels) - .filter((f) => f.cid.sqlEquals($_column)); + final manager = $$ChannelsTableTableManager($_db, $_db.channels).filter((f) => f.cid.sqlEquals($_column)); final item = $_typedResult.readTableOrNull(_channelCidTable($_db)); if (item == null) return manager; - return ProcessedTableManager( - manager.$state.copyWith(prefetchedData: [item])); + return ProcessedTableManager(manager.$state.copyWith(prefetchedData: [item])); } } -class $$ReadsTableFilterComposer - extends Composer<_$DriftChatDatabase, $ReadsTable> { +class $$ReadsTableFilterComposer extends Composer<_$DriftChatDatabase, $ReadsTable> { $$ReadsTableFilterComposer({ required super.$db, required super.$table, @@ -14944,51 +14361,44 @@ class $$ReadsTableFilterComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnFilters get lastRead => $composableBuilder( - column: $table.lastRead, builder: (column) => ColumnFilters(column)); + ColumnFilters get lastRead => + $composableBuilder(column: $table.lastRead, builder: (column) => ColumnFilters(column)); - ColumnFilters get userId => $composableBuilder( - column: $table.userId, builder: (column) => ColumnFilters(column)); + ColumnFilters get userId => + $composableBuilder(column: $table.userId, builder: (column) => ColumnFilters(column)); - ColumnFilters get unreadMessages => $composableBuilder( - column: $table.unreadMessages, - builder: (column) => ColumnFilters(column)); + ColumnFilters get unreadMessages => + $composableBuilder(column: $table.unreadMessages, builder: (column) => ColumnFilters(column)); - ColumnFilters get lastReadMessageId => $composableBuilder( - column: $table.lastReadMessageId, - builder: (column) => ColumnFilters(column)); + ColumnFilters get lastReadMessageId => + $composableBuilder(column: $table.lastReadMessageId, builder: (column) => ColumnFilters(column)); - ColumnFilters get lastDeliveredAt => $composableBuilder( - column: $table.lastDeliveredAt, - builder: (column) => ColumnFilters(column)); + ColumnFilters get lastDeliveredAt => + $composableBuilder(column: $table.lastDeliveredAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get lastDeliveredMessageId => $composableBuilder( - column: $table.lastDeliveredMessageId, - builder: (column) => ColumnFilters(column)); + ColumnFilters get lastDeliveredMessageId => + $composableBuilder(column: $table.lastDeliveredMessageId, builder: (column) => ColumnFilters(column)); $$ChannelsTableFilterComposer get channelCid { final $$ChannelsTableFilterComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.channelCid, - referencedTable: $db.channels, - getReferencedColumn: (t) => t.cid, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$ChannelsTableFilterComposer( - $db: $db, - $table: $db.channels, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.channelCid, + referencedTable: $db.channels, + getReferencedColumn: (t) => t.cid, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$ChannelsTableFilterComposer( + $db: $db, + $table: $db.channels, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return composer; } } -class $$ReadsTableOrderingComposer - extends Composer<_$DriftChatDatabase, $ReadsTable> { +class $$ReadsTableOrderingComposer extends Composer<_$DriftChatDatabase, $ReadsTable> { $$ReadsTableOrderingComposer({ required super.$db, required super.$table, @@ -14996,51 +14406,44 @@ class $$ReadsTableOrderingComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnOrderings get lastRead => $composableBuilder( - column: $table.lastRead, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get lastRead => + $composableBuilder(column: $table.lastRead, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get userId => $composableBuilder( - column: $table.userId, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get userId => + $composableBuilder(column: $table.userId, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get unreadMessages => $composableBuilder( - column: $table.unreadMessages, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get unreadMessages => + $composableBuilder(column: $table.unreadMessages, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get lastReadMessageId => $composableBuilder( - column: $table.lastReadMessageId, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get lastReadMessageId => + $composableBuilder(column: $table.lastReadMessageId, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get lastDeliveredAt => $composableBuilder( - column: $table.lastDeliveredAt, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get lastDeliveredAt => + $composableBuilder(column: $table.lastDeliveredAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get lastDeliveredMessageId => $composableBuilder( - column: $table.lastDeliveredMessageId, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get lastDeliveredMessageId => + $composableBuilder(column: $table.lastDeliveredMessageId, builder: (column) => ColumnOrderings(column)); $$ChannelsTableOrderingComposer get channelCid { final $$ChannelsTableOrderingComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.channelCid, - referencedTable: $db.channels, - getReferencedColumn: (t) => t.cid, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$ChannelsTableOrderingComposer( - $db: $db, - $table: $db.channels, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.channelCid, + referencedTable: $db.channels, + getReferencedColumn: (t) => t.cid, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$ChannelsTableOrderingComposer( + $db: $db, + $table: $db.channels, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return composer; } } -class $$ReadsTableAnnotationComposer - extends Composer<_$DriftChatDatabase, $ReadsTable> { +class $$ReadsTableAnnotationComposer extends Composer<_$DriftChatDatabase, $ReadsTable> { $$ReadsTableAnnotationComposer({ required super.$db, required super.$table, @@ -15048,117 +14451,113 @@ class $$ReadsTableAnnotationComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - GeneratedColumn get lastRead => - $composableBuilder(column: $table.lastRead, builder: (column) => column); + GeneratedColumn get lastRead => $composableBuilder(column: $table.lastRead, builder: (column) => column); - GeneratedColumn get userId => - $composableBuilder(column: $table.userId, builder: (column) => column); + GeneratedColumn get userId => $composableBuilder(column: $table.userId, builder: (column) => column); - GeneratedColumn get unreadMessages => $composableBuilder( - column: $table.unreadMessages, builder: (column) => column); + GeneratedColumn get unreadMessages => + $composableBuilder(column: $table.unreadMessages, builder: (column) => column); - GeneratedColumn get lastReadMessageId => $composableBuilder( - column: $table.lastReadMessageId, builder: (column) => column); + GeneratedColumn get lastReadMessageId => + $composableBuilder(column: $table.lastReadMessageId, builder: (column) => column); - GeneratedColumn get lastDeliveredAt => $composableBuilder( - column: $table.lastDeliveredAt, builder: (column) => column); + GeneratedColumn get lastDeliveredAt => + $composableBuilder(column: $table.lastDeliveredAt, builder: (column) => column); - GeneratedColumn get lastDeliveredMessageId => $composableBuilder( - column: $table.lastDeliveredMessageId, builder: (column) => column); + GeneratedColumn get lastDeliveredMessageId => + $composableBuilder(column: $table.lastDeliveredMessageId, builder: (column) => column); $$ChannelsTableAnnotationComposer get channelCid { final $$ChannelsTableAnnotationComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.channelCid, - referencedTable: $db.channels, - getReferencedColumn: (t) => t.cid, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$ChannelsTableAnnotationComposer( - $db: $db, - $table: $db.channels, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); + composer: this, + getCurrentColumn: (t) => t.channelCid, + referencedTable: $db.channels, + getReferencedColumn: (t) => t.cid, + builder: (joinBuilder, {$addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer}) => + $$ChannelsTableAnnotationComposer( + $db: $db, + $table: $db.channels, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: $removeJoinBuilderFromRootComposer, + ), + ); return composer; } } -class $$ReadsTableTableManager extends RootTableManager< - _$DriftChatDatabase, - $ReadsTable, - ReadEntity, - $$ReadsTableFilterComposer, - $$ReadsTableOrderingComposer, - $$ReadsTableAnnotationComposer, - $$ReadsTableCreateCompanionBuilder, - $$ReadsTableUpdateCompanionBuilder, - (ReadEntity, $$ReadsTableReferences), - ReadEntity, - PrefetchHooks Function({bool channelCid})> { +class $$ReadsTableTableManager + extends + RootTableManager< + _$DriftChatDatabase, + $ReadsTable, + ReadEntity, + $$ReadsTableFilterComposer, + $$ReadsTableOrderingComposer, + $$ReadsTableAnnotationComposer, + $$ReadsTableCreateCompanionBuilder, + $$ReadsTableUpdateCompanionBuilder, + (ReadEntity, $$ReadsTableReferences), + ReadEntity, + PrefetchHooks Function({bool channelCid}) + > { $$ReadsTableTableManager(_$DriftChatDatabase db, $ReadsTable table) - : super(TableManagerState( + : super( + TableManagerState( db: db, table: table, - createFilteringComposer: () => - $$ReadsTableFilterComposer($db: db, $table: table), - createOrderingComposer: () => - $$ReadsTableOrderingComposer($db: db, $table: table), - createComputedFieldComposer: () => - $$ReadsTableAnnotationComposer($db: db, $table: table), - updateCompanionCallback: ({ - Value lastRead = const Value.absent(), - Value userId = const Value.absent(), - Value channelCid = const Value.absent(), - Value unreadMessages = const Value.absent(), - Value lastReadMessageId = const Value.absent(), - Value lastDeliveredAt = const Value.absent(), - Value lastDeliveredMessageId = const Value.absent(), - Value rowid = const Value.absent(), - }) => - ReadsCompanion( - lastRead: lastRead, - userId: userId, - channelCid: channelCid, - unreadMessages: unreadMessages, - lastReadMessageId: lastReadMessageId, - lastDeliveredAt: lastDeliveredAt, - lastDeliveredMessageId: lastDeliveredMessageId, - rowid: rowid, - ), - createCompanionCallback: ({ - required DateTime lastRead, - required String userId, - required String channelCid, - Value unreadMessages = const Value.absent(), - Value lastReadMessageId = const Value.absent(), - Value lastDeliveredAt = const Value.absent(), - Value lastDeliveredMessageId = const Value.absent(), - Value rowid = const Value.absent(), - }) => - ReadsCompanion.insert( - lastRead: lastRead, - userId: userId, - channelCid: channelCid, - unreadMessages: unreadMessages, - lastReadMessageId: lastReadMessageId, - lastDeliveredAt: lastDeliveredAt, - lastDeliveredMessageId: lastDeliveredMessageId, - rowid: rowid, - ), - withReferenceMapper: (p0) => p0 - .map((e) => - (e.readTable(table), $$ReadsTableReferences(db, table, e))) - .toList(), + createFilteringComposer: () => $$ReadsTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => $$ReadsTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => $$ReadsTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value lastRead = const Value.absent(), + Value userId = const Value.absent(), + Value channelCid = const Value.absent(), + Value unreadMessages = const Value.absent(), + Value lastReadMessageId = const Value.absent(), + Value lastDeliveredAt = const Value.absent(), + Value lastDeliveredMessageId = const Value.absent(), + Value rowid = const Value.absent(), + }) => ReadsCompanion( + lastRead: lastRead, + userId: userId, + channelCid: channelCid, + unreadMessages: unreadMessages, + lastReadMessageId: lastReadMessageId, + lastDeliveredAt: lastDeliveredAt, + lastDeliveredMessageId: lastDeliveredMessageId, + rowid: rowid, + ), + createCompanionCallback: + ({ + required DateTime lastRead, + required String userId, + required String channelCid, + Value unreadMessages = const Value.absent(), + Value lastReadMessageId = const Value.absent(), + Value lastDeliveredAt = const Value.absent(), + Value lastDeliveredMessageId = const Value.absent(), + Value rowid = const Value.absent(), + }) => ReadsCompanion.insert( + lastRead: lastRead, + userId: userId, + channelCid: channelCid, + unreadMessages: unreadMessages, + lastReadMessageId: lastReadMessageId, + lastDeliveredAt: lastDeliveredAt, + lastDeliveredMessageId: lastDeliveredMessageId, + rowid: rowid, + ), + withReferenceMapper: (p0) => + p0.map((e) => (e.readTable(table), $$ReadsTableReferences(db, table, e))).toList(), prefetchHooksCallback: ({channelCid = false}) { return PrefetchHooks( db: db, explicitlyWatchedTables: [], - addJoins: < - T extends TableManagerState< + addJoins: + < + T extends TableManagerState< dynamic, dynamic, dynamic, @@ -15169,55 +14568,59 @@ class $$ReadsTableTableManager extends RootTableManager< dynamic, dynamic, dynamic, - dynamic>>(state) { - if (channelCid) { - state = state.withJoin( - currentTable: table, - currentColumn: table.channelCid, - referencedTable: - $$ReadsTableReferences._channelCidTable(db), - referencedColumn: - $$ReadsTableReferences._channelCidTable(db).cid, - ) as T; - } - - return state; - }, + dynamic + > + >(state) { + if (channelCid) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.channelCid, + referencedTable: $$ReadsTableReferences._channelCidTable(db), + referencedColumn: $$ReadsTableReferences._channelCidTable(db).cid, + ) + as T; + } + + return state; + }, getPrefetchedDataCallback: (items) async { return []; }, ); }, - )); + ), + ); } -typedef $$ReadsTableProcessedTableManager = ProcessedTableManager< - _$DriftChatDatabase, - $ReadsTable, - ReadEntity, - $$ReadsTableFilterComposer, - $$ReadsTableOrderingComposer, - $$ReadsTableAnnotationComposer, - $$ReadsTableCreateCompanionBuilder, - $$ReadsTableUpdateCompanionBuilder, - (ReadEntity, $$ReadsTableReferences), - ReadEntity, - PrefetchHooks Function({bool channelCid})>; -typedef $$ChannelQueriesTableCreateCompanionBuilder = ChannelQueriesCompanion - Function({ - required String queryHash, - required String channelCid, - Value rowid, -}); -typedef $$ChannelQueriesTableUpdateCompanionBuilder = ChannelQueriesCompanion - Function({ - Value queryHash, - Value channelCid, - Value rowid, -}); - -class $$ChannelQueriesTableFilterComposer - extends Composer<_$DriftChatDatabase, $ChannelQueriesTable> { +typedef $$ReadsTableProcessedTableManager = + ProcessedTableManager< + _$DriftChatDatabase, + $ReadsTable, + ReadEntity, + $$ReadsTableFilterComposer, + $$ReadsTableOrderingComposer, + $$ReadsTableAnnotationComposer, + $$ReadsTableCreateCompanionBuilder, + $$ReadsTableUpdateCompanionBuilder, + (ReadEntity, $$ReadsTableReferences), + ReadEntity, + PrefetchHooks Function({bool channelCid}) + >; +typedef $$ChannelQueriesTableCreateCompanionBuilder = + ChannelQueriesCompanion Function({ + required String queryHash, + required String channelCid, + Value rowid, + }); +typedef $$ChannelQueriesTableUpdateCompanionBuilder = + ChannelQueriesCompanion Function({ + Value queryHash, + Value channelCid, + Value rowid, + }); + +class $$ChannelQueriesTableFilterComposer extends Composer<_$DriftChatDatabase, $ChannelQueriesTable> { $$ChannelQueriesTableFilterComposer({ required super.$db, required super.$table, @@ -15225,15 +14628,14 @@ class $$ChannelQueriesTableFilterComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnFilters get queryHash => $composableBuilder( - column: $table.queryHash, builder: (column) => ColumnFilters(column)); + ColumnFilters get queryHash => + $composableBuilder(column: $table.queryHash, builder: (column) => ColumnFilters(column)); - ColumnFilters get channelCid => $composableBuilder( - column: $table.channelCid, builder: (column) => ColumnFilters(column)); + ColumnFilters get channelCid => + $composableBuilder(column: $table.channelCid, builder: (column) => ColumnFilters(column)); } -class $$ChannelQueriesTableOrderingComposer - extends Composer<_$DriftChatDatabase, $ChannelQueriesTable> { +class $$ChannelQueriesTableOrderingComposer extends Composer<_$DriftChatDatabase, $ChannelQueriesTable> { $$ChannelQueriesTableOrderingComposer({ required super.$db, required super.$table, @@ -15241,15 +14643,14 @@ class $$ChannelQueriesTableOrderingComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnOrderings get queryHash => $composableBuilder( - column: $table.queryHash, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get queryHash => + $composableBuilder(column: $table.queryHash, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get channelCid => $composableBuilder( - column: $table.channelCid, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get channelCid => + $composableBuilder(column: $table.channelCid, builder: (column) => ColumnOrderings(column)); } -class $$ChannelQueriesTableAnnotationComposer - extends Composer<_$DriftChatDatabase, $ChannelQueriesTable> { +class $$ChannelQueriesTableAnnotationComposer extends Composer<_$DriftChatDatabase, $ChannelQueriesTable> { $$ChannelQueriesTableAnnotationComposer({ required super.$db, required super.$table, @@ -15257,106 +14658,96 @@ class $$ChannelQueriesTableAnnotationComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - GeneratedColumn get queryHash => - $composableBuilder(column: $table.queryHash, builder: (column) => column); + GeneratedColumn get queryHash => $composableBuilder(column: $table.queryHash, builder: (column) => column); - GeneratedColumn get channelCid => $composableBuilder( - column: $table.channelCid, builder: (column) => column); + GeneratedColumn get channelCid => $composableBuilder(column: $table.channelCid, builder: (column) => column); } -class $$ChannelQueriesTableTableManager extends RootTableManager< - _$DriftChatDatabase, - $ChannelQueriesTable, - ChannelQueryEntity, - $$ChannelQueriesTableFilterComposer, - $$ChannelQueriesTableOrderingComposer, - $$ChannelQueriesTableAnnotationComposer, - $$ChannelQueriesTableCreateCompanionBuilder, - $$ChannelQueriesTableUpdateCompanionBuilder, - ( - ChannelQueryEntity, - BaseReferences<_$DriftChatDatabase, $ChannelQueriesTable, - ChannelQueryEntity> - ), - ChannelQueryEntity, - PrefetchHooks Function()> { - $$ChannelQueriesTableTableManager( - _$DriftChatDatabase db, $ChannelQueriesTable table) - : super(TableManagerState( +class $$ChannelQueriesTableTableManager + extends + RootTableManager< + _$DriftChatDatabase, + $ChannelQueriesTable, + ChannelQueryEntity, + $$ChannelQueriesTableFilterComposer, + $$ChannelQueriesTableOrderingComposer, + $$ChannelQueriesTableAnnotationComposer, + $$ChannelQueriesTableCreateCompanionBuilder, + $$ChannelQueriesTableUpdateCompanionBuilder, + (ChannelQueryEntity, BaseReferences<_$DriftChatDatabase, $ChannelQueriesTable, ChannelQueryEntity>), + ChannelQueryEntity, + PrefetchHooks Function() + > { + $$ChannelQueriesTableTableManager(_$DriftChatDatabase db, $ChannelQueriesTable table) + : super( + TableManagerState( db: db, table: table, - createFilteringComposer: () => - $$ChannelQueriesTableFilterComposer($db: db, $table: table), - createOrderingComposer: () => - $$ChannelQueriesTableOrderingComposer($db: db, $table: table), - createComputedFieldComposer: () => - $$ChannelQueriesTableAnnotationComposer($db: db, $table: table), - updateCompanionCallback: ({ - Value queryHash = const Value.absent(), - Value channelCid = const Value.absent(), - Value rowid = const Value.absent(), - }) => - ChannelQueriesCompanion( - queryHash: queryHash, - channelCid: channelCid, - rowid: rowid, - ), - createCompanionCallback: ({ - required String queryHash, - required String channelCid, - Value rowid = const Value.absent(), - }) => - ChannelQueriesCompanion.insert( - queryHash: queryHash, - channelCid: channelCid, - rowid: rowid, - ), - withReferenceMapper: (p0) => p0 - .map((e) => (e.readTable(table), BaseReferences(db, table, e))) - .toList(), + createFilteringComposer: () => $$ChannelQueriesTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => $$ChannelQueriesTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => $$ChannelQueriesTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value queryHash = const Value.absent(), + Value channelCid = const Value.absent(), + Value rowid = const Value.absent(), + }) => ChannelQueriesCompanion( + queryHash: queryHash, + channelCid: channelCid, + rowid: rowid, + ), + createCompanionCallback: + ({ + required String queryHash, + required String channelCid, + Value rowid = const Value.absent(), + }) => ChannelQueriesCompanion.insert( + queryHash: queryHash, + channelCid: channelCid, + rowid: rowid, + ), + withReferenceMapper: (p0) => p0.map((e) => (e.readTable(table), BaseReferences(db, table, e))).toList(), prefetchHooksCallback: null, - )); + ), + ); } -typedef $$ChannelQueriesTableProcessedTableManager = ProcessedTableManager< - _$DriftChatDatabase, - $ChannelQueriesTable, - ChannelQueryEntity, - $$ChannelQueriesTableFilterComposer, - $$ChannelQueriesTableOrderingComposer, - $$ChannelQueriesTableAnnotationComposer, - $$ChannelQueriesTableCreateCompanionBuilder, - $$ChannelQueriesTableUpdateCompanionBuilder, - ( +typedef $$ChannelQueriesTableProcessedTableManager = + ProcessedTableManager< + _$DriftChatDatabase, + $ChannelQueriesTable, ChannelQueryEntity, - BaseReferences<_$DriftChatDatabase, $ChannelQueriesTable, - ChannelQueryEntity> - ), - ChannelQueryEntity, - PrefetchHooks Function()>; -typedef $$ConnectionEventsTableCreateCompanionBuilder - = ConnectionEventsCompanion Function({ - Value id, - required String type, - Value?> ownUser, - Value totalUnreadCount, - Value unreadChannels, - Value lastEventAt, - Value lastSyncAt, -}); -typedef $$ConnectionEventsTableUpdateCompanionBuilder - = ConnectionEventsCompanion Function({ - Value id, - Value type, - Value?> ownUser, - Value totalUnreadCount, - Value unreadChannels, - Value lastEventAt, - Value lastSyncAt, -}); - -class $$ConnectionEventsTableFilterComposer - extends Composer<_$DriftChatDatabase, $ConnectionEventsTable> { + $$ChannelQueriesTableFilterComposer, + $$ChannelQueriesTableOrderingComposer, + $$ChannelQueriesTableAnnotationComposer, + $$ChannelQueriesTableCreateCompanionBuilder, + $$ChannelQueriesTableUpdateCompanionBuilder, + (ChannelQueryEntity, BaseReferences<_$DriftChatDatabase, $ChannelQueriesTable, ChannelQueryEntity>), + ChannelQueryEntity, + PrefetchHooks Function() + >; +typedef $$ConnectionEventsTableCreateCompanionBuilder = + ConnectionEventsCompanion Function({ + Value id, + required String type, + Value?> ownUser, + Value totalUnreadCount, + Value unreadChannels, + Value lastEventAt, + Value lastSyncAt, + }); +typedef $$ConnectionEventsTableUpdateCompanionBuilder = + ConnectionEventsCompanion Function({ + Value id, + Value type, + Value?> ownUser, + Value totalUnreadCount, + Value unreadChannels, + Value lastEventAt, + Value lastSyncAt, + }); + +class $$ConnectionEventsTableFilterComposer extends Composer<_$DriftChatDatabase, $ConnectionEventsTable> { $$ConnectionEventsTableFilterComposer({ required super.$db, required super.$table, @@ -15364,35 +14755,27 @@ class $$ConnectionEventsTableFilterComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnFilters get id => $composableBuilder( - column: $table.id, builder: (column) => ColumnFilters(column)); + ColumnFilters get id => $composableBuilder(column: $table.id, builder: (column) => ColumnFilters(column)); - ColumnFilters get type => $composableBuilder( - column: $table.type, builder: (column) => ColumnFilters(column)); + ColumnFilters get type => $composableBuilder(column: $table.type, builder: (column) => ColumnFilters(column)); - ColumnWithTypeConverterFilters?, Map, - String> - get ownUser => $composableBuilder( - column: $table.ownUser, - builder: (column) => ColumnWithTypeConverterFilters(column)); + ColumnWithTypeConverterFilters?, Map, String> get ownUser => + $composableBuilder(column: $table.ownUser, builder: (column) => ColumnWithTypeConverterFilters(column)); - ColumnFilters get totalUnreadCount => $composableBuilder( - column: $table.totalUnreadCount, - builder: (column) => ColumnFilters(column)); + ColumnFilters get totalUnreadCount => + $composableBuilder(column: $table.totalUnreadCount, builder: (column) => ColumnFilters(column)); - ColumnFilters get unreadChannels => $composableBuilder( - column: $table.unreadChannels, - builder: (column) => ColumnFilters(column)); + ColumnFilters get unreadChannels => + $composableBuilder(column: $table.unreadChannels, builder: (column) => ColumnFilters(column)); - ColumnFilters get lastEventAt => $composableBuilder( - column: $table.lastEventAt, builder: (column) => ColumnFilters(column)); + ColumnFilters get lastEventAt => + $composableBuilder(column: $table.lastEventAt, builder: (column) => ColumnFilters(column)); - ColumnFilters get lastSyncAt => $composableBuilder( - column: $table.lastSyncAt, builder: (column) => ColumnFilters(column)); + ColumnFilters get lastSyncAt => + $composableBuilder(column: $table.lastSyncAt, builder: (column) => ColumnFilters(column)); } -class $$ConnectionEventsTableOrderingComposer - extends Composer<_$DriftChatDatabase, $ConnectionEventsTable> { +class $$ConnectionEventsTableOrderingComposer extends Composer<_$DriftChatDatabase, $ConnectionEventsTable> { $$ConnectionEventsTableOrderingComposer({ required super.$db, required super.$table, @@ -15400,32 +14783,28 @@ class $$ConnectionEventsTableOrderingComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnOrderings get id => $composableBuilder( - column: $table.id, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get id => $composableBuilder(column: $table.id, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get type => $composableBuilder( - column: $table.type, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get type => + $composableBuilder(column: $table.type, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get ownUser => $composableBuilder( - column: $table.ownUser, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get ownUser => + $composableBuilder(column: $table.ownUser, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get totalUnreadCount => $composableBuilder( - column: $table.totalUnreadCount, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get totalUnreadCount => + $composableBuilder(column: $table.totalUnreadCount, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get unreadChannels => $composableBuilder( - column: $table.unreadChannels, - builder: (column) => ColumnOrderings(column)); + ColumnOrderings get unreadChannels => + $composableBuilder(column: $table.unreadChannels, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get lastEventAt => $composableBuilder( - column: $table.lastEventAt, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get lastEventAt => + $composableBuilder(column: $table.lastEventAt, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get lastSyncAt => $composableBuilder( - column: $table.lastSyncAt, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get lastSyncAt => + $composableBuilder(column: $table.lastSyncAt, builder: (column) => ColumnOrderings(column)); } -class $$ConnectionEventsTableAnnotationComposer - extends Composer<_$DriftChatDatabase, $ConnectionEventsTable> { +class $$ConnectionEventsTableAnnotationComposer extends Composer<_$DriftChatDatabase, $ConnectionEventsTable> { $$ConnectionEventsTableAnnotationComposer({ required super.$db, required super.$table, @@ -15433,145 +14812,123 @@ class $$ConnectionEventsTableAnnotationComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - GeneratedColumn get id => - $composableBuilder(column: $table.id, builder: (column) => column); + GeneratedColumn get id => $composableBuilder(column: $table.id, builder: (column) => column); - GeneratedColumn get type => - $composableBuilder(column: $table.type, builder: (column) => column); + GeneratedColumn get type => $composableBuilder(column: $table.type, builder: (column) => column); GeneratedColumnWithTypeConverter?, String> get ownUser => $composableBuilder(column: $table.ownUser, builder: (column) => column); - GeneratedColumn get totalUnreadCount => $composableBuilder( - column: $table.totalUnreadCount, builder: (column) => column); + GeneratedColumn get totalUnreadCount => + $composableBuilder(column: $table.totalUnreadCount, builder: (column) => column); - GeneratedColumn get unreadChannels => $composableBuilder( - column: $table.unreadChannels, builder: (column) => column); + GeneratedColumn get unreadChannels => + $composableBuilder(column: $table.unreadChannels, builder: (column) => column); - GeneratedColumn get lastEventAt => $composableBuilder( - column: $table.lastEventAt, builder: (column) => column); + GeneratedColumn get lastEventAt => + $composableBuilder(column: $table.lastEventAt, builder: (column) => column); - GeneratedColumn get lastSyncAt => $composableBuilder( - column: $table.lastSyncAt, builder: (column) => column); + GeneratedColumn get lastSyncAt => + $composableBuilder(column: $table.lastSyncAt, builder: (column) => column); } -class $$ConnectionEventsTableTableManager extends RootTableManager< - _$DriftChatDatabase, - $ConnectionEventsTable, - ConnectionEventEntity, - $$ConnectionEventsTableFilterComposer, - $$ConnectionEventsTableOrderingComposer, - $$ConnectionEventsTableAnnotationComposer, - $$ConnectionEventsTableCreateCompanionBuilder, - $$ConnectionEventsTableUpdateCompanionBuilder, - ( - ConnectionEventEntity, - BaseReferences<_$DriftChatDatabase, $ConnectionEventsTable, - ConnectionEventEntity> - ), - ConnectionEventEntity, - PrefetchHooks Function()> { - $$ConnectionEventsTableTableManager( - _$DriftChatDatabase db, $ConnectionEventsTable table) - : super(TableManagerState( +class $$ConnectionEventsTableTableManager + extends + RootTableManager< + _$DriftChatDatabase, + $ConnectionEventsTable, + ConnectionEventEntity, + $$ConnectionEventsTableFilterComposer, + $$ConnectionEventsTableOrderingComposer, + $$ConnectionEventsTableAnnotationComposer, + $$ConnectionEventsTableCreateCompanionBuilder, + $$ConnectionEventsTableUpdateCompanionBuilder, + (ConnectionEventEntity, BaseReferences<_$DriftChatDatabase, $ConnectionEventsTable, ConnectionEventEntity>), + ConnectionEventEntity, + PrefetchHooks Function() + > { + $$ConnectionEventsTableTableManager(_$DriftChatDatabase db, $ConnectionEventsTable table) + : super( + TableManagerState( db: db, table: table, - createFilteringComposer: () => - $$ConnectionEventsTableFilterComposer($db: db, $table: table), - createOrderingComposer: () => - $$ConnectionEventsTableOrderingComposer($db: db, $table: table), - createComputedFieldComposer: () => - $$ConnectionEventsTableAnnotationComposer($db: db, $table: table), - updateCompanionCallback: ({ - Value id = const Value.absent(), - Value type = const Value.absent(), - Value?> ownUser = const Value.absent(), - Value totalUnreadCount = const Value.absent(), - Value unreadChannels = const Value.absent(), - Value lastEventAt = const Value.absent(), - Value lastSyncAt = const Value.absent(), - }) => - ConnectionEventsCompanion( - id: id, - type: type, - ownUser: ownUser, - totalUnreadCount: totalUnreadCount, - unreadChannels: unreadChannels, - lastEventAt: lastEventAt, - lastSyncAt: lastSyncAt, - ), - createCompanionCallback: ({ - Value id = const Value.absent(), - required String type, - Value?> ownUser = const Value.absent(), - Value totalUnreadCount = const Value.absent(), - Value unreadChannels = const Value.absent(), - Value lastEventAt = const Value.absent(), - Value lastSyncAt = const Value.absent(), - }) => - ConnectionEventsCompanion.insert( - id: id, - type: type, - ownUser: ownUser, - totalUnreadCount: totalUnreadCount, - unreadChannels: unreadChannels, - lastEventAt: lastEventAt, - lastSyncAt: lastSyncAt, - ), - withReferenceMapper: (p0) => p0 - .map((e) => (e.readTable(table), BaseReferences(db, table, e))) - .toList(), + createFilteringComposer: () => $$ConnectionEventsTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => $$ConnectionEventsTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => $$ConnectionEventsTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value id = const Value.absent(), + Value type = const Value.absent(), + Value?> ownUser = const Value.absent(), + Value totalUnreadCount = const Value.absent(), + Value unreadChannels = const Value.absent(), + Value lastEventAt = const Value.absent(), + Value lastSyncAt = const Value.absent(), + }) => ConnectionEventsCompanion( + id: id, + type: type, + ownUser: ownUser, + totalUnreadCount: totalUnreadCount, + unreadChannels: unreadChannels, + lastEventAt: lastEventAt, + lastSyncAt: lastSyncAt, + ), + createCompanionCallback: + ({ + Value id = const Value.absent(), + required String type, + Value?> ownUser = const Value.absent(), + Value totalUnreadCount = const Value.absent(), + Value unreadChannels = const Value.absent(), + Value lastEventAt = const Value.absent(), + Value lastSyncAt = const Value.absent(), + }) => ConnectionEventsCompanion.insert( + id: id, + type: type, + ownUser: ownUser, + totalUnreadCount: totalUnreadCount, + unreadChannels: unreadChannels, + lastEventAt: lastEventAt, + lastSyncAt: lastSyncAt, + ), + withReferenceMapper: (p0) => p0.map((e) => (e.readTable(table), BaseReferences(db, table, e))).toList(), prefetchHooksCallback: null, - )); + ), + ); } -typedef $$ConnectionEventsTableProcessedTableManager = ProcessedTableManager< - _$DriftChatDatabase, - $ConnectionEventsTable, - ConnectionEventEntity, - $$ConnectionEventsTableFilterComposer, - $$ConnectionEventsTableOrderingComposer, - $$ConnectionEventsTableAnnotationComposer, - $$ConnectionEventsTableCreateCompanionBuilder, - $$ConnectionEventsTableUpdateCompanionBuilder, - ( +typedef $$ConnectionEventsTableProcessedTableManager = + ProcessedTableManager< + _$DriftChatDatabase, + $ConnectionEventsTable, + ConnectionEventEntity, + $$ConnectionEventsTableFilterComposer, + $$ConnectionEventsTableOrderingComposer, + $$ConnectionEventsTableAnnotationComposer, + $$ConnectionEventsTableCreateCompanionBuilder, + $$ConnectionEventsTableUpdateCompanionBuilder, + (ConnectionEventEntity, BaseReferences<_$DriftChatDatabase, $ConnectionEventsTable, ConnectionEventEntity>), ConnectionEventEntity, - BaseReferences<_$DriftChatDatabase, $ConnectionEventsTable, - ConnectionEventEntity> - ), - ConnectionEventEntity, - PrefetchHooks Function()>; + PrefetchHooks Function() + >; class $DriftChatDatabaseManager { final _$DriftChatDatabase _db; $DriftChatDatabaseManager(this._db); - $$ChannelsTableTableManager get channels => - $$ChannelsTableTableManager(_db, _db.channels); - $$MessagesTableTableManager get messages => - $$MessagesTableTableManager(_db, _db.messages); - $$DraftMessagesTableTableManager get draftMessages => - $$DraftMessagesTableTableManager(_db, _db.draftMessages); - $$LocationsTableTableManager get locations => - $$LocationsTableTableManager(_db, _db.locations); - $$PinnedMessagesTableTableManager get pinnedMessages => - $$PinnedMessagesTableTableManager(_db, _db.pinnedMessages); - $$PollsTableTableManager get polls => - $$PollsTableTableManager(_db, _db.polls); - $$PollVotesTableTableManager get pollVotes => - $$PollVotesTableTableManager(_db, _db.pollVotes); + $$ChannelsTableTableManager get channels => $$ChannelsTableTableManager(_db, _db.channels); + $$MessagesTableTableManager get messages => $$MessagesTableTableManager(_db, _db.messages); + $$DraftMessagesTableTableManager get draftMessages => $$DraftMessagesTableTableManager(_db, _db.draftMessages); + $$LocationsTableTableManager get locations => $$LocationsTableTableManager(_db, _db.locations); + $$PinnedMessagesTableTableManager get pinnedMessages => $$PinnedMessagesTableTableManager(_db, _db.pinnedMessages); + $$PollsTableTableManager get polls => $$PollsTableTableManager(_db, _db.polls); + $$PollVotesTableTableManager get pollVotes => $$PollVotesTableTableManager(_db, _db.pollVotes); $$PinnedMessageReactionsTableTableManager get pinnedMessageReactions => - $$PinnedMessageReactionsTableTableManager( - _db, _db.pinnedMessageReactions); - $$ReactionsTableTableManager get reactions => - $$ReactionsTableTableManager(_db, _db.reactions); - $$UsersTableTableManager get users => - $$UsersTableTableManager(_db, _db.users); - $$MembersTableTableManager get members => - $$MembersTableTableManager(_db, _db.members); - $$ReadsTableTableManager get reads => - $$ReadsTableTableManager(_db, _db.reads); - $$ChannelQueriesTableTableManager get channelQueries => - $$ChannelQueriesTableTableManager(_db, _db.channelQueries); + $$PinnedMessageReactionsTableTableManager(_db, _db.pinnedMessageReactions); + $$ReactionsTableTableManager get reactions => $$ReactionsTableTableManager(_db, _db.reactions); + $$UsersTableTableManager get users => $$UsersTableTableManager(_db, _db.users); + $$MembersTableTableManager get members => $$MembersTableTableManager(_db, _db.members); + $$ReadsTableTableManager get reads => $$ReadsTableTableManager(_db, _db.reads); + $$ChannelQueriesTableTableManager get channelQueries => $$ChannelQueriesTableTableManager(_db, _db.channelQueries); $$ConnectionEventsTableTableManager get connectionEvents => $$ConnectionEventsTableTableManager(_db, _db.connectionEvents); } diff --git a/packages/stream_chat_persistence/lib/src/db/shared/native_db.dart b/packages/stream_chat_persistence/lib/src/db/shared/native_db.dart index ca31c3bb2d..0af7d3a543 100644 --- a/packages/stream_chat_persistence/lib/src/db/shared/native_db.dart +++ b/packages/stream_chat_persistence/lib/src/db/shared/native_db.dart @@ -25,13 +25,15 @@ class SharedDB { if (connectionMode == ConnectionMode.background) { return DriftChatDatabase( userId, - DatabaseConnection.delayed(Future(() async { - final isolate = await _createMoorIsolate( - dbName, - logStatements: logStatements, - ); - return isolate.connect(); - })), + DatabaseConnection.delayed( + Future(() async { + final isolate = await _createMoorIsolate( + dbName, + logStatements: logStatements, + ); + return isolate.connect(); + }), + ), ); } @@ -64,10 +66,12 @@ class SharedDB { } static void _startBackground(_IsolateStartRequest request) { - final executor = LazyDatabase(() async => NativeDatabase( - File(request.targetPath), - logStatements: request.logStatements, - )); + final executor = LazyDatabase( + () async => NativeDatabase( + File(request.targetPath), + logStatements: request.logStatements, + ), + ); final moorIsolate = DriftIsolate.inCurrent( () => DatabaseConnection(executor), ); diff --git a/packages/stream_chat_persistence/lib/src/entity/channel_queries.dart b/packages/stream_chat_persistence/lib/src/entity/channel_queries.dart index a9d3dc6cc0..44829c0fa0 100644 --- a/packages/stream_chat_persistence/lib/src/entity/channel_queries.dart +++ b/packages/stream_chat_persistence/lib/src/entity/channel_queries.dart @@ -12,7 +12,7 @@ class ChannelQueries extends Table { @override Set get primaryKey => { - queryHash, - channelCid, - }; + queryHash, + channelCid, + }; } diff --git a/packages/stream_chat_persistence/lib/src/entity/channels.dart b/packages/stream_chat_persistence/lib/src/entity/channels.dart index d7e6cc4112..1e417e3ab0 100644 --- a/packages/stream_chat_persistence/lib/src/entity/channels.dart +++ b/packages/stream_chat_persistence/lib/src/entity/channels.dart @@ -15,8 +15,7 @@ class Channels extends Table { TextColumn get cid => text()(); /// List of user permissions on this channel - TextColumn get ownCapabilities => - text().nullable().map(ListConverter())(); + TextColumn get ownCapabilities => text().nullable().map(ListConverter())(); /// The channel configuration data TextColumn get config => text().map(MapConverter())(); diff --git a/packages/stream_chat_persistence/lib/src/entity/draft_messages.dart b/packages/stream_chat_persistence/lib/src/entity/draft_messages.dart index 0105aec3a1..6a9054eec1 100644 --- a/packages/stream_chat_persistence/lib/src/entity/draft_messages.dart +++ b/packages/stream_chat_persistence/lib/src/entity/draft_messages.dart @@ -24,9 +24,7 @@ class DraftMessages extends Table { TextColumn get mentionedUsers => text().map(ListConverter())(); /// The ID of the parent message, if the message is a thread reply. - TextColumn get parentId => text() - .nullable() - .references(Messages, #id, onDelete: KeyAction.cascade)(); + TextColumn get parentId => text().nullable().references(Messages, #id, onDelete: KeyAction.cascade)(); /// The ID of the quoted message, if the message is a quoted reply. TextColumn get quotedMessageId => text().nullable()(); @@ -47,8 +45,7 @@ class DraftMessages extends Table { DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)(); /// The channel cid of which this message is part of - TextColumn get channelCid => - text().references(Channels, #cid, onDelete: KeyAction.cascade)(); + TextColumn get channelCid => text().references(Channels, #cid, onDelete: KeyAction.cascade)(); /// Message custom extraData TextColumn get extraData => text().nullable().map(MapConverter())(); diff --git a/packages/stream_chat_persistence/lib/src/entity/locations.dart b/packages/stream_chat_persistence/lib/src/entity/locations.dart index b12f448b8b..93b0678ec9 100644 --- a/packages/stream_chat_persistence/lib/src/entity/locations.dart +++ b/packages/stream_chat_persistence/lib/src/entity/locations.dart @@ -7,14 +7,10 @@ import 'package:stream_chat_persistence/src/entity/messages.dart'; @DataClassName('LocationEntity') class Locations extends Table { /// The channel CID where the location is shared - TextColumn get channelCid => text() - .nullable() - .references(Channels, #cid, onDelete: KeyAction.cascade)(); + TextColumn get channelCid => text().nullable().references(Channels, #cid, onDelete: KeyAction.cascade)(); /// The ID of the message that contains this shared location - TextColumn get messageId => text() - .nullable() - .references(Messages, #id, onDelete: KeyAction.cascade)(); + TextColumn get messageId => text().nullable().references(Messages, #id, onDelete: KeyAction.cascade)(); /// The ID of the user who shared the location TextColumn get userId => text().nullable()(); diff --git a/packages/stream_chat_persistence/lib/src/entity/members.dart b/packages/stream_chat_persistence/lib/src/entity/members.dart index 9e3734b38c..801c44c594 100644 --- a/packages/stream_chat_persistence/lib/src/entity/members.dart +++ b/packages/stream_chat_persistence/lib/src/entity/members.dart @@ -10,8 +10,7 @@ class Members extends Table { TextColumn get userId => text()(); /// The channel cid of which this user is part of - TextColumn get channelCid => - text().references(Channels, #cid, onDelete: KeyAction.cascade)(); + TextColumn get channelCid => text().references(Channels, #cid, onDelete: KeyAction.cascade)(); /// The role of the user in the channel TextColumn get channelRole => text().nullable()(); @@ -32,12 +31,10 @@ class Members extends Table { BoolColumn get shadowBanned => boolean().withDefault(const Constant(false))(); /// The date at which the channel was pinned by the member - DateTimeColumn get pinnedAt => - dateTime().nullable().withDefault(const Constant(null))(); + DateTimeColumn get pinnedAt => dateTime().nullable().withDefault(const Constant(null))(); /// The date at which the channel was archived by the member - DateTimeColumn get archivedAt => - dateTime().nullable().withDefault(const Constant(null))(); + DateTimeColumn get archivedAt => dateTime().nullable().withDefault(const Constant(null))(); /// True if the user is a moderator of the channel BoolColumn get isModerator => boolean().withDefault(const Constant(false))(); diff --git a/packages/stream_chat_persistence/lib/src/entity/messages.dart b/packages/stream_chat_persistence/lib/src/entity/messages.dart index aea14217f8..6ede2f9f12 100644 --- a/packages/stream_chat_persistence/lib/src/entity/messages.dart +++ b/packages/stream_chat_persistence/lib/src/entity/messages.dart @@ -28,8 +28,7 @@ class Messages extends Table { TextColumn get mentionedUsers => text().map(ListConverter())(); /// A map describing the reaction group for every reaction - TextColumn get reactionGroups => - text().map(ReactionGroupsConverter()).nullable()(); + TextColumn get reactionGroups => text().map(ReactionGroupsConverter()).nullable()(); /// The ID of the parent message, if the message is a thread reply. TextColumn get parentId => text().nullable()(); @@ -124,16 +123,13 @@ class Messages extends Table { TextColumn get pinnedByUserId => text().nullable()(); /// The channel cid of which this message is part of - TextColumn get channelCid => - text().references(Channels, #cid, onDelete: KeyAction.cascade)(); + TextColumn get channelCid => text().references(Channels, #cid, onDelete: KeyAction.cascade)(); /// A Map of [messageText] translations. - TextColumn get i18n => - text().nullable().map(NullableMapConverter())(); + TextColumn get i18n => text().nullable().map(NullableMapConverter())(); /// The list of user ids that should be able to see the message. - TextColumn get restrictedVisibility => - text().nullable().map(ListConverter())(); + TextColumn get restrictedVisibility => text().nullable().map(ListConverter())(); /// Message custom extraData TextColumn get extraData => text().nullable().map(MapConverter())(); diff --git a/packages/stream_chat_persistence/lib/src/entity/pinned_message_reactions.dart b/packages/stream_chat_persistence/lib/src/entity/pinned_message_reactions.dart index 6e490cf8fc..97fa438f95 100644 --- a/packages/stream_chat_persistence/lib/src/entity/pinned_message_reactions.dart +++ b/packages/stream_chat_persistence/lib/src/entity/pinned_message_reactions.dart @@ -8,7 +8,5 @@ import 'package:stream_chat_persistence/src/entity/reactions.dart'; class PinnedMessageReactions extends Reactions { /// The messageId to which the reaction belongs @override - TextColumn get messageId => text() - .nullable() - .references(PinnedMessages, #id, onDelete: KeyAction.cascade)(); + TextColumn get messageId => text().nullable().references(PinnedMessages, #id, onDelete: KeyAction.cascade)(); } diff --git a/packages/stream_chat_persistence/lib/src/entity/poll_votes.dart b/packages/stream_chat_persistence/lib/src/entity/poll_votes.dart index 5087b5a0df..f6a069d8d0 100644 --- a/packages/stream_chat_persistence/lib/src/entity/poll_votes.dart +++ b/packages/stream_chat_persistence/lib/src/entity/poll_votes.dart @@ -9,8 +9,7 @@ class PollVotes extends Table { TextColumn get id => text().nullable()(); /// The unique identifier of the poll the vote belongs to. - TextColumn get pollId => - text().nullable().references(Polls, #id, onDelete: KeyAction.cascade)(); + TextColumn get pollId => text().nullable().references(Polls, #id, onDelete: KeyAction.cascade)(); /// The unique identifier of the option selected in the poll. /// diff --git a/packages/stream_chat_persistence/lib/src/entity/polls.dart b/packages/stream_chat_persistence/lib/src/entity/polls.dart index f377301dba..ee4d99e85c 100644 --- a/packages/stream_chat_persistence/lib/src/entity/polls.dart +++ b/packages/stream_chat_persistence/lib/src/entity/polls.dart @@ -22,15 +22,13 @@ class Polls extends Table { /// Represents the visibility of the voting process. /// /// Defaults to 'public'. - TextColumn get votingVisibility => text() - .map(const VotingVisibilityConverter()) - .withDefault(const Constant('public'))(); + TextColumn get votingVisibility => + text().map(const VotingVisibilityConverter()).withDefault(const Constant('public'))(); /// If true, only unique votes are allowed. /// /// Defaults to false. - BoolColumn get enforceUniqueVote => - boolean().withDefault(const Constant(false))(); + BoolColumn get enforceUniqueVote => boolean().withDefault(const Constant(false))(); /// The maximum number of votes allowed per user. IntColumn get maxVotesAllowed => integer().nullable()(); @@ -38,8 +36,7 @@ class Polls extends Table { /// If true, users can suggest their own options. /// /// Defaults to false. - BoolColumn get allowUserSuggestedOptions => - boolean().withDefault(const Constant(false))(); + BoolColumn get allowUserSuggestedOptions => boolean().withDefault(const Constant(false))(); /// If true, users can provide their own answers/comments. /// diff --git a/packages/stream_chat_persistence/lib/src/entity/reactions.dart b/packages/stream_chat_persistence/lib/src/entity/reactions.dart index 64d23bf979..b43044e772 100644 --- a/packages/stream_chat_persistence/lib/src/entity/reactions.dart +++ b/packages/stream_chat_persistence/lib/src/entity/reactions.dart @@ -10,9 +10,7 @@ class Reactions extends Table { TextColumn get userId => text().nullable()(); /// The messageId to which the reaction belongs - TextColumn get messageId => text() - .nullable() - .references(Messages, #id, onDelete: KeyAction.cascade)(); + TextColumn get messageId => text().nullable().references(Messages, #id, onDelete: KeyAction.cascade)(); /// The type of the reaction TextColumn get type => text()(); @@ -34,8 +32,8 @@ class Reactions extends Table { @override Set get primaryKey => { - messageId, - type, - userId, - }; + messageId, + type, + userId, + }; } diff --git a/packages/stream_chat_persistence/lib/src/entity/reads.dart b/packages/stream_chat_persistence/lib/src/entity/reads.dart index 2a842d3d69..aadac4c69c 100644 --- a/packages/stream_chat_persistence/lib/src/entity/reads.dart +++ b/packages/stream_chat_persistence/lib/src/entity/reads.dart @@ -12,8 +12,7 @@ class Reads extends Table { TextColumn get userId => text()(); /// The channel cid of which this read belongs - TextColumn get channelCid => - text().references(Channels, #cid, onDelete: KeyAction.cascade)(); + TextColumn get channelCid => text().references(Channels, #cid, onDelete: KeyAction.cascade)(); /// Number of unread messages IntColumn get unreadMessages => integer().withDefault(const Constant(0))(); @@ -29,7 +28,7 @@ class Reads extends Table { @override Set get primaryKey => { - userId, - channelCid, - }; + userId, + channelCid, + }; } diff --git a/packages/stream_chat_persistence/lib/src/mapper/channel_mapper.dart b/packages/stream_chat_persistence/lib/src/mapper/channel_mapper.dart index 60c0910fba..35b3bc1d84 100644 --- a/packages/stream_chat_persistence/lib/src/mapper/channel_mapper.dart +++ b/packages/stream_chat_persistence/lib/src/mapper/channel_mapper.dart @@ -32,34 +32,33 @@ extension ChannelEntityX on ChannelEntity { List reads = const [], List messages = const [], List pinnedMessages = const [], - }) => - ChannelState( - members: members, - read: reads, - messages: messages, - pinnedMessages: pinnedMessages, - channel: toChannelModel(createdBy: createdBy), - ); + }) => ChannelState( + members: members, + read: reads, + messages: messages, + pinnedMessages: pinnedMessages, + channel: toChannelModel(createdBy: createdBy), + ); } /// Useful mapping functions for [ChannelModel] extension ChannelModelX on ChannelModel { /// Maps a [ChannelModel] into [ChannelEntity] ChannelEntity toEntity() => ChannelEntity( - id: id, - type: type, - cid: cid, - ownCapabilities: ownCapabilities, - config: config.toJson(), - frozen: frozen, - lastMessageAt: lastMessageAt, - createdAt: createdAt, - updatedAt: updatedAt, - deletedAt: deletedAt, - memberCount: memberCount, - messageCount: messageCount, - createdById: createdBy?.id, - filterTags: filterTags, - extraData: extraData, - ); + id: id, + type: type, + cid: cid, + ownCapabilities: ownCapabilities, + config: config.toJson(), + frozen: frozen, + lastMessageAt: lastMessageAt, + createdAt: createdAt, + updatedAt: updatedAt, + deletedAt: deletedAt, + memberCount: memberCount, + messageCount: messageCount, + createdById: createdBy?.id, + filterTags: filterTags, + extraData: extraData, + ); } diff --git a/packages/stream_chat_persistence/lib/src/mapper/draft_message_mapper.dart b/packages/stream_chat_persistence/lib/src/mapper/draft_message_mapper.dart index f4ae5b31ce..9dd08a0d00 100644 --- a/packages/stream_chat_persistence/lib/src/mapper/draft_message_mapper.dart +++ b/packages/stream_chat_persistence/lib/src/mapper/draft_message_mapper.dart @@ -47,23 +47,23 @@ extension DraftMessageEntityX on DraftMessageEntity { extension DraftMessageX on Draft { /// Maps a [DraftMessage] into [DraftMessageEntity] DraftMessageEntity toEntity() => DraftMessageEntity( - id: message.id, - channelCid: channelCid, - messageText: message.text, - type: message.type, - createdAt: createdAt, - attachments: message.attachments.map((it) { - return jsonEncode(it.toData()); - }).toList(), - parentId: parentId, - showInChannel: message.showInChannel, - mentionedUsers: message.mentionedUsers.map((e) { - return jsonEncode(e.toJson()); - }).toList(), - quotedMessageId: message.quotedMessageId, - silent: message.silent, - command: message.command, - pollId: message.pollId, - extraData: message.extraData, - ); + id: message.id, + channelCid: channelCid, + messageText: message.text, + type: message.type, + createdAt: createdAt, + attachments: message.attachments.map((it) { + return jsonEncode(it.toData()); + }).toList(), + parentId: parentId, + showInChannel: message.showInChannel, + mentionedUsers: message.mentionedUsers.map((e) { + return jsonEncode(e.toJson()); + }).toList(), + quotedMessageId: message.quotedMessageId, + silent: message.silent, + command: message.command, + pollId: message.pollId, + extraData: message.extraData, + ); } diff --git a/packages/stream_chat_persistence/lib/src/mapper/event_mapper.dart b/packages/stream_chat_persistence/lib/src/mapper/event_mapper.dart index 0c3adb7b07..43a420b060 100644 --- a/packages/stream_chat_persistence/lib/src/mapper/event_mapper.dart +++ b/packages/stream_chat_persistence/lib/src/mapper/event_mapper.dart @@ -5,10 +5,10 @@ import 'package:stream_chat_persistence/src/db/drift_chat_database.dart'; extension ConnectionEventX on ConnectionEventEntity { /// Maps a [ConnectionEventEntity] into [Event] Event toEvent() => Event( - type: type, - createdAt: lastEventAt, - me: ownUser != null ? OwnUser.fromJson(ownUser!) : null, - totalUnreadCount: totalUnreadCount, - unreadChannels: unreadChannels, - ); + type: type, + createdAt: lastEventAt, + me: ownUser != null ? OwnUser.fromJson(ownUser!) : null, + totalUnreadCount: totalUnreadCount, + unreadChannels: unreadChannels, + ); } diff --git a/packages/stream_chat_persistence/lib/src/mapper/location_mapper.dart b/packages/stream_chat_persistence/lib/src/mapper/location_mapper.dart index bcbc18be1b..35985f9132 100644 --- a/packages/stream_chat_persistence/lib/src/mapper/location_mapper.dart +++ b/packages/stream_chat_persistence/lib/src/mapper/location_mapper.dart @@ -7,34 +7,33 @@ extension LocationEntityX on LocationEntity { Location toLocation({ ChannelModel? channel, Message? message, - }) => - Location( - channelCid: channelCid, - channel: channel, - messageId: messageId, - message: message, - userId: userId, - latitude: latitude, - longitude: longitude, - createdByDeviceId: createdByDeviceId, - endAt: endAt, - createdAt: createdAt, - updatedAt: updatedAt, - ); + }) => Location( + channelCid: channelCid, + channel: channel, + messageId: messageId, + message: message, + userId: userId, + latitude: latitude, + longitude: longitude, + createdByDeviceId: createdByDeviceId, + endAt: endAt, + createdAt: createdAt, + updatedAt: updatedAt, + ); } /// Useful mapping functions for [Location] extension LocationX on Location { /// Maps a [Location] into [LocationEntity] LocationEntity toEntity() => LocationEntity( - channelCid: channelCid, - messageId: messageId, - userId: userId, - latitude: latitude, - longitude: longitude, - createdByDeviceId: createdByDeviceId, - endAt: endAt, - createdAt: createdAt, - updatedAt: updatedAt, - ); + channelCid: channelCid, + messageId: messageId, + userId: userId, + latitude: latitude, + longitude: longitude, + createdByDeviceId: createdByDeviceId, + endAt: endAt, + createdAt: createdAt, + updatedAt: updatedAt, + ); } diff --git a/packages/stream_chat_persistence/lib/src/mapper/member_mapper.dart b/packages/stream_chat_persistence/lib/src/mapper/member_mapper.dart index f37aaf1bd4..26f7df3015 100644 --- a/packages/stream_chat_persistence/lib/src/mapper/member_mapper.dart +++ b/packages/stream_chat_persistence/lib/src/mapper/member_mapper.dart @@ -5,42 +5,42 @@ import 'package:stream_chat_persistence/src/db/drift_chat_database.dart'; extension MemberEntityX on MemberEntity { /// Maps a [MemberEntity] into [Member] Member toMember({User? user}) => Member( - user: user, - userId: userId, - banned: banned, - shadowBanned: shadowBanned, - updatedAt: updatedAt, - createdAt: createdAt, - channelRole: channelRole, - inviteAcceptedAt: inviteAcceptedAt, - invited: invited, - inviteRejectedAt: inviteRejectedAt, - pinnedAt: pinnedAt, - archivedAt: archivedAt, - isModerator: isModerator, - deletedMessages: deletedMessages, - extraData: extraData ?? {}, - ); + user: user, + userId: userId, + banned: banned, + shadowBanned: shadowBanned, + updatedAt: updatedAt, + createdAt: createdAt, + channelRole: channelRole, + inviteAcceptedAt: inviteAcceptedAt, + invited: invited, + inviteRejectedAt: inviteRejectedAt, + pinnedAt: pinnedAt, + archivedAt: archivedAt, + isModerator: isModerator, + deletedMessages: deletedMessages, + extraData: extraData ?? {}, + ); } /// Useful mapping functions for [Member] extension MemberX on Member { /// Maps a [Member] into [MemberEntity] MemberEntity toEntity({required String cid}) => MemberEntity( - userId: user!.id, - banned: banned, - shadowBanned: shadowBanned, - channelCid: cid, - createdAt: createdAt, - isModerator: isModerator, - inviteRejectedAt: inviteRejectedAt, - invited: invited, - inviteAcceptedAt: inviteAcceptedAt, - pinnedAt: pinnedAt, - archivedAt: archivedAt, - channelRole: channelRole, - updatedAt: updatedAt, - deletedMessages: deletedMessages, - extraData: extraData, - ); + userId: user!.id, + banned: banned, + shadowBanned: shadowBanned, + channelCid: cid, + createdAt: createdAt, + isModerator: isModerator, + inviteRejectedAt: inviteRejectedAt, + invited: invited, + inviteAcceptedAt: inviteAcceptedAt, + pinnedAt: pinnedAt, + archivedAt: archivedAt, + channelRole: channelRole, + updatedAt: updatedAt, + deletedMessages: deletedMessages, + extraData: extraData, + ); } diff --git a/packages/stream_chat_persistence/lib/src/mapper/message_mapper.dart b/packages/stream_chat_persistence/lib/src/mapper/message_mapper.dart index e6d3402278..584ac6f222 100644 --- a/packages/stream_chat_persistence/lib/src/mapper/message_mapper.dart +++ b/packages/stream_chat_persistence/lib/src/mapper/message_mapper.dart @@ -15,87 +15,85 @@ extension MessageEntityX on MessageEntity { Poll? poll, Draft? draft, Location? sharedLocation, - }) => - Message( - shadowed: shadowed, - latestReactions: latestReactions, - ownReactions: ownReactions, - attachments: attachments.map((it) { - final json = jsonDecode(it); - return Attachment.fromData(json); - }).toList(), - extraData: extraData ?? {}, - createdAt: remoteCreatedAt, - localCreatedAt: localCreatedAt, - updatedAt: remoteUpdatedAt, - localUpdatedAt: localUpdatedAt, - deletedAt: remoteDeletedAt, - localDeletedAt: localDeletedAt, - deletedForMe: deletedForMe, - messageTextUpdatedAt: messageTextUpdatedAt, - id: id, - type: type, - state: MessageState.fromJson(jsonDecode(state)), - command: command, - parentId: parentId, - quotedMessageId: quotedMessageId, - quotedMessage: quotedMessage, - pollId: pollId, - poll: poll, - reactionGroups: reactionGroups, - replyCount: replyCount, - showInChannel: showInChannel, - text: messageText, - user: user, - channelRole: channelRole, - pinned: pinned, - pinnedAt: pinnedAt, - pinExpires: pinExpires, - pinnedBy: pinnedBy, - mentionedUsers: - mentionedUsers.map((e) => User.fromJson(jsonDecode(e))).toList(), - i18n: i18n, - restrictedVisibility: restrictedVisibility, - draft: draft, - sharedLocation: sharedLocation, - ); + }) => Message( + shadowed: shadowed, + latestReactions: latestReactions, + ownReactions: ownReactions, + attachments: attachments.map((it) { + final json = jsonDecode(it); + return Attachment.fromData(json); + }).toList(), + extraData: extraData ?? {}, + createdAt: remoteCreatedAt, + localCreatedAt: localCreatedAt, + updatedAt: remoteUpdatedAt, + localUpdatedAt: localUpdatedAt, + deletedAt: remoteDeletedAt, + localDeletedAt: localDeletedAt, + deletedForMe: deletedForMe, + messageTextUpdatedAt: messageTextUpdatedAt, + id: id, + type: type, + state: MessageState.fromJson(jsonDecode(state)), + command: command, + parentId: parentId, + quotedMessageId: quotedMessageId, + quotedMessage: quotedMessage, + pollId: pollId, + poll: poll, + reactionGroups: reactionGroups, + replyCount: replyCount, + showInChannel: showInChannel, + text: messageText, + user: user, + channelRole: channelRole, + pinned: pinned, + pinnedAt: pinnedAt, + pinExpires: pinExpires, + pinnedBy: pinnedBy, + mentionedUsers: mentionedUsers.map((e) => User.fromJson(jsonDecode(e))).toList(), + i18n: i18n, + restrictedVisibility: restrictedVisibility, + draft: draft, + sharedLocation: sharedLocation, + ); } /// Useful mapping functions for [Message] extension MessageX on Message { /// Maps a [Message] into [MessageEntity] MessageEntity toEntity({required String cid}) => MessageEntity( - id: id, - attachments: attachments.map((it) => jsonEncode(it.toData())).toList(), - channelCid: cid, - type: type, - parentId: parentId, - quotedMessageId: quotedMessageId, - pollId: pollId, - command: command, - remoteCreatedAt: remoteCreatedAt, - localCreatedAt: localCreatedAt, - shadowed: shadowed, - showInChannel: showInChannel, - replyCount: replyCount, - reactionGroups: reactionGroups, - mentionedUsers: mentionedUsers.map(jsonEncode).toList(), - state: jsonEncode(state.toJson()), - remoteUpdatedAt: remoteUpdatedAt, - localUpdatedAt: localUpdatedAt, - extraData: extraData, - userId: user?.id, - channelRole: channelRole, - remoteDeletedAt: remoteDeletedAt, - localDeletedAt: localDeletedAt, - deletedForMe: deletedForMe, - messageTextUpdatedAt: messageTextUpdatedAt, - messageText: text, - pinned: pinned, - pinnedAt: pinnedAt, - pinExpires: pinExpires, - pinnedByUserId: pinnedBy?.id, - i18n: i18n, - restrictedVisibility: restrictedVisibility, - ); + id: id, + attachments: attachments.map((it) => jsonEncode(it.toData())).toList(), + channelCid: cid, + type: type, + parentId: parentId, + quotedMessageId: quotedMessageId, + pollId: pollId, + command: command, + remoteCreatedAt: remoteCreatedAt, + localCreatedAt: localCreatedAt, + shadowed: shadowed, + showInChannel: showInChannel, + replyCount: replyCount, + reactionGroups: reactionGroups, + mentionedUsers: mentionedUsers.map(jsonEncode).toList(), + state: jsonEncode(state.toJson()), + remoteUpdatedAt: remoteUpdatedAt, + localUpdatedAt: localUpdatedAt, + extraData: extraData, + userId: user?.id, + channelRole: channelRole, + remoteDeletedAt: remoteDeletedAt, + localDeletedAt: localDeletedAt, + deletedForMe: deletedForMe, + messageTextUpdatedAt: messageTextUpdatedAt, + messageText: text, + pinned: pinned, + pinnedAt: pinnedAt, + pinExpires: pinExpires, + pinnedByUserId: pinnedBy?.id, + i18n: i18n, + restrictedVisibility: restrictedVisibility, + ); } diff --git a/packages/stream_chat_persistence/lib/src/mapper/pinned_message_mapper.dart b/packages/stream_chat_persistence/lib/src/mapper/pinned_message_mapper.dart index 2cb11d3422..90a06b5f55 100644 --- a/packages/stream_chat_persistence/lib/src/mapper/pinned_message_mapper.dart +++ b/packages/stream_chat_persistence/lib/src/mapper/pinned_message_mapper.dart @@ -15,88 +15,85 @@ extension PinnedMessageEntityX on PinnedMessageEntity { Poll? poll, Draft? draft, Location? sharedLocation, - }) => - Message( - shadowed: shadowed, - latestReactions: latestReactions, - ownReactions: ownReactions, - attachments: attachments.map((it) { - final json = jsonDecode(it); - return Attachment.fromData(json); - }).toList(), - extraData: extraData ?? {}, - createdAt: remoteCreatedAt, - localCreatedAt: localCreatedAt, - updatedAt: remoteUpdatedAt, - localUpdatedAt: localUpdatedAt, - deletedAt: remoteDeletedAt, - localDeletedAt: localDeletedAt, - deletedForMe: deletedForMe, - messageTextUpdatedAt: messageTextUpdatedAt, - id: id, - type: type, - state: MessageState.fromJson(jsonDecode(state)), - command: command, - parentId: parentId, - quotedMessageId: quotedMessageId, - quotedMessage: quotedMessage, - pollId: pollId, - poll: poll, - reactionGroups: reactionGroups, - replyCount: replyCount, - showInChannel: showInChannel, - text: messageText, - user: user, - channelRole: channelRole, - pinned: pinned, - pinnedAt: pinnedAt, - pinExpires: pinExpires, - pinnedBy: pinnedBy, - mentionedUsers: - mentionedUsers.map((e) => User.fromJson(jsonDecode(e))).toList(), - i18n: i18n, - restrictedVisibility: restrictedVisibility, - draft: draft, - sharedLocation: sharedLocation, - ); + }) => Message( + shadowed: shadowed, + latestReactions: latestReactions, + ownReactions: ownReactions, + attachments: attachments.map((it) { + final json = jsonDecode(it); + return Attachment.fromData(json); + }).toList(), + extraData: extraData ?? {}, + createdAt: remoteCreatedAt, + localCreatedAt: localCreatedAt, + updatedAt: remoteUpdatedAt, + localUpdatedAt: localUpdatedAt, + deletedAt: remoteDeletedAt, + localDeletedAt: localDeletedAt, + deletedForMe: deletedForMe, + messageTextUpdatedAt: messageTextUpdatedAt, + id: id, + type: type, + state: MessageState.fromJson(jsonDecode(state)), + command: command, + parentId: parentId, + quotedMessageId: quotedMessageId, + quotedMessage: quotedMessage, + pollId: pollId, + poll: poll, + reactionGroups: reactionGroups, + replyCount: replyCount, + showInChannel: showInChannel, + text: messageText, + user: user, + channelRole: channelRole, + pinned: pinned, + pinnedAt: pinnedAt, + pinExpires: pinExpires, + pinnedBy: pinnedBy, + mentionedUsers: mentionedUsers.map((e) => User.fromJson(jsonDecode(e))).toList(), + i18n: i18n, + restrictedVisibility: restrictedVisibility, + draft: draft, + sharedLocation: sharedLocation, + ); } /// Useful mapping functions for [Message] extension PMessageX on Message { /// Maps a [Message] into [PinnedMessageEntity] - PinnedMessageEntity toPinnedEntity({required String cid}) => - PinnedMessageEntity( - id: id, - attachments: attachments.map((it) => jsonEncode(it.toData())).toList(), - channelCid: cid, - type: type, - parentId: parentId, - quotedMessageId: quotedMessageId, - pollId: pollId, - command: command, - remoteCreatedAt: remoteCreatedAt, - localCreatedAt: localCreatedAt, - shadowed: shadowed, - showInChannel: showInChannel, - replyCount: replyCount, - reactionGroups: reactionGroups, - mentionedUsers: mentionedUsers.map(jsonEncode).toList(), - state: jsonEncode(state.toJson()), - remoteUpdatedAt: remoteUpdatedAt, - localUpdatedAt: localUpdatedAt, - extraData: extraData, - userId: user?.id, - channelRole: channelRole, - remoteDeletedAt: remoteDeletedAt, - localDeletedAt: localDeletedAt, - deletedForMe: deletedForMe, - messageTextUpdatedAt: messageTextUpdatedAt, - messageText: text, - pinned: pinned, - pinnedAt: pinnedAt, - pinExpires: pinExpires, - pinnedByUserId: pinnedBy?.id, - i18n: i18n, - restrictedVisibility: restrictedVisibility, - ); + PinnedMessageEntity toPinnedEntity({required String cid}) => PinnedMessageEntity( + id: id, + attachments: attachments.map((it) => jsonEncode(it.toData())).toList(), + channelCid: cid, + type: type, + parentId: parentId, + quotedMessageId: quotedMessageId, + pollId: pollId, + command: command, + remoteCreatedAt: remoteCreatedAt, + localCreatedAt: localCreatedAt, + shadowed: shadowed, + showInChannel: showInChannel, + replyCount: replyCount, + reactionGroups: reactionGroups, + mentionedUsers: mentionedUsers.map(jsonEncode).toList(), + state: jsonEncode(state.toJson()), + remoteUpdatedAt: remoteUpdatedAt, + localUpdatedAt: localUpdatedAt, + extraData: extraData, + userId: user?.id, + channelRole: channelRole, + remoteDeletedAt: remoteDeletedAt, + localDeletedAt: localDeletedAt, + deletedForMe: deletedForMe, + messageTextUpdatedAt: messageTextUpdatedAt, + messageText: text, + pinned: pinned, + pinnedAt: pinnedAt, + pinExpires: pinExpires, + pinnedByUserId: pinnedBy?.id, + i18n: i18n, + restrictedVisibility: restrictedVisibility, + ); } diff --git a/packages/stream_chat_persistence/lib/src/mapper/pinned_message_reaction_mapper.dart b/packages/stream_chat_persistence/lib/src/mapper/pinned_message_reaction_mapper.dart index 00deda65d2..1802280fd5 100644 --- a/packages/stream_chat_persistence/lib/src/mapper/pinned_message_reaction_mapper.dart +++ b/packages/stream_chat_persistence/lib/src/mapper/pinned_message_reaction_mapper.dart @@ -5,29 +5,29 @@ import 'package:stream_chat_persistence/src/db/drift_chat_database.dart'; extension PinnedMessageReactionEntityX on PinnedMessageReactionEntity { /// Maps a [PinnedMessageReactionEntity] into [Reaction] Reaction toReaction({User? user}) => Reaction( - type: type, - userId: userId, - user: user, - messageId: messageId, - score: score, - emojiCode: emojiCode, - createdAt: createdAt, - updatedAt: updatedAt, - extraData: extraData ?? {}, - ); + type: type, + userId: userId, + user: user, + messageId: messageId, + score: score, + emojiCode: emojiCode, + createdAt: createdAt, + updatedAt: updatedAt, + extraData: extraData ?? {}, + ); } /// Useful mapping functions for [Reaction] extension PReactionX on Reaction { /// Maps a [Reaction] into [ReactionEntity] PinnedMessageReactionEntity toPinnedEntity() => PinnedMessageReactionEntity( - type: type, - userId: userId ?? user?.id, - messageId: messageId, - score: score, - emojiCode: emojiCode, - createdAt: createdAt, - updatedAt: updatedAt, - extraData: extraData, - ); + type: type, + userId: userId ?? user?.id, + messageId: messageId, + score: score, + emojiCode: emojiCode, + createdAt: createdAt, + updatedAt: updatedAt, + extraData: extraData, + ); } diff --git a/packages/stream_chat_persistence/lib/src/mapper/poll_mapper.dart b/packages/stream_chat_persistence/lib/src/mapper/poll_mapper.dart index 451b5e853c..2b9a388ce9 100644 --- a/packages/stream_chat_persistence/lib/src/mapper/poll_mapper.dart +++ b/packages/stream_chat_persistence/lib/src/mapper/poll_mapper.dart @@ -45,22 +45,22 @@ extension PollEntityX on PollEntity { extension PollX on Poll { /// Maps a [Poll] into [PollEntity] PollEntity toEntity() => PollEntity( - id: id, - name: name, - description: description, - options: options.map(jsonEncode).toList(), - votingVisibility: votingVisibility, - enforceUniqueVote: enforceUniqueVote, - maxVotesAllowed: maxVotesAllowed, - allowAnswers: allowAnswers, - answersCount: answersCount, - allowUserSuggestedOptions: allowUserSuggestedOptions, - isClosed: isClosed, - createdAt: createdAt, - updatedAt: updatedAt, - voteCountsByOption: voteCountsByOption, - voteCount: voteCount, - createdById: createdById, - extraData: extraData, - ); + id: id, + name: name, + description: description, + options: options.map(jsonEncode).toList(), + votingVisibility: votingVisibility, + enforceUniqueVote: enforceUniqueVote, + maxVotesAllowed: maxVotesAllowed, + allowAnswers: allowAnswers, + answersCount: answersCount, + allowUserSuggestedOptions: allowUserSuggestedOptions, + isClosed: isClosed, + createdAt: createdAt, + updatedAt: updatedAt, + voteCountsByOption: voteCountsByOption, + voteCount: voteCount, + createdById: createdById, + extraData: extraData, + ); } diff --git a/packages/stream_chat_persistence/lib/src/mapper/poll_vote_mapper.dart b/packages/stream_chat_persistence/lib/src/mapper/poll_vote_mapper.dart index 25fa23ab35..a9cac39709 100644 --- a/packages/stream_chat_persistence/lib/src/mapper/poll_vote_mapper.dart +++ b/packages/stream_chat_persistence/lib/src/mapper/poll_vote_mapper.dart @@ -6,29 +6,28 @@ extension PollVoteEntityX on PollVoteEntity { /// Maps a [PollVoteEntity] into [PollVote] PollVote toPollVote({ User? user, - }) => - PollVote( - id: id, - pollId: pollId, - optionId: optionId, - answerText: answerText, - createdAt: createdAt, - updatedAt: updatedAt, - userId: userId, - user: user, - ); + }) => PollVote( + id: id, + pollId: pollId, + optionId: optionId, + answerText: answerText, + createdAt: createdAt, + updatedAt: updatedAt, + userId: userId, + user: user, + ); } /// Useful mapping functions for [PollVote] extension PollVoteX on PollVote { /// Maps a [PollVote] into [PollVoteEntity] PollVoteEntity toEntity() => PollVoteEntity( - id: id, - pollId: pollId, - optionId: optionId, - answerText: answerText, - createdAt: createdAt, - updatedAt: updatedAt, - userId: userId, - ); + id: id, + pollId: pollId, + optionId: optionId, + answerText: answerText, + createdAt: createdAt, + updatedAt: updatedAt, + userId: userId, + ); } diff --git a/packages/stream_chat_persistence/lib/src/mapper/reaction_mapper.dart b/packages/stream_chat_persistence/lib/src/mapper/reaction_mapper.dart index 81b2bd4419..cc62e59db7 100644 --- a/packages/stream_chat_persistence/lib/src/mapper/reaction_mapper.dart +++ b/packages/stream_chat_persistence/lib/src/mapper/reaction_mapper.dart @@ -5,29 +5,29 @@ import 'package:stream_chat_persistence/src/db/drift_chat_database.dart'; extension ReactionEntityX on ReactionEntity { /// Maps a [ReactionEntity] into [Reaction] Reaction toReaction({User? user}) => Reaction( - type: type, - userId: userId, - user: user, - messageId: messageId, - score: score, - emojiCode: emojiCode, - createdAt: createdAt, - updatedAt: updatedAt, - extraData: extraData ?? {}, - ); + type: type, + userId: userId, + user: user, + messageId: messageId, + score: score, + emojiCode: emojiCode, + createdAt: createdAt, + updatedAt: updatedAt, + extraData: extraData ?? {}, + ); } /// Useful mapping functions for [Reaction] extension ReactionX on Reaction { /// Maps a [Reaction] into [ReactionEntity] ReactionEntity toEntity() => ReactionEntity( - type: type, - userId: userId ?? user?.id, - messageId: messageId, - score: score, - emojiCode: emojiCode, - createdAt: createdAt, - updatedAt: updatedAt, - extraData: extraData, - ); + type: type, + userId: userId ?? user?.id, + messageId: messageId, + score: score, + emojiCode: emojiCode, + createdAt: createdAt, + updatedAt: updatedAt, + extraData: extraData, + ); } diff --git a/packages/stream_chat_persistence/lib/src/mapper/read_mapper.dart b/packages/stream_chat_persistence/lib/src/mapper/read_mapper.dart index bdcaefc70e..d26e8bf80a 100644 --- a/packages/stream_chat_persistence/lib/src/mapper/read_mapper.dart +++ b/packages/stream_chat_persistence/lib/src/mapper/read_mapper.dart @@ -5,25 +5,25 @@ import 'package:stream_chat_persistence/src/db/drift_chat_database.dart'; extension ReadEntityX on ReadEntity { /// Maps a [ReadEntity] into [Read] Read toRead({required User user}) => Read( - user: user, - lastRead: lastRead, - unreadMessages: unreadMessages, - lastReadMessageId: lastReadMessageId, - lastDeliveredAt: lastDeliveredAt, - lastDeliveredMessageId: lastDeliveredMessageId, - ); + user: user, + lastRead: lastRead, + unreadMessages: unreadMessages, + lastReadMessageId: lastReadMessageId, + lastDeliveredAt: lastDeliveredAt, + lastDeliveredMessageId: lastDeliveredMessageId, + ); } /// Useful mapping functions for [Read] extension ReadX on Read { /// Maps a [Read] into [ReadEntity] ReadEntity toEntity({required String cid}) => ReadEntity( - lastRead: lastRead, - userId: user.id, - channelCid: cid, - unreadMessages: unreadMessages, - lastReadMessageId: lastReadMessageId, - lastDeliveredAt: lastDeliveredAt, - lastDeliveredMessageId: lastDeliveredMessageId, - ); + lastRead: lastRead, + userId: user.id, + channelCid: cid, + unreadMessages: unreadMessages, + lastReadMessageId: lastReadMessageId, + lastDeliveredAt: lastDeliveredAt, + lastDeliveredMessageId: lastDeliveredMessageId, + ); } diff --git a/packages/stream_chat_persistence/lib/src/mapper/user_mapper.dart b/packages/stream_chat_persistence/lib/src/mapper/user_mapper.dart index 4e894e81c6..678f4cc3a9 100644 --- a/packages/stream_chat_persistence/lib/src/mapper/user_mapper.dart +++ b/packages/stream_chat_persistence/lib/src/mapper/user_mapper.dart @@ -5,34 +5,34 @@ import 'package:stream_chat_persistence/src/db/drift_chat_database.dart'; extension UserEntityX on UserEntity { /// Maps a [UserEntity] into [User] User toUser() => User( - id: id, - updatedAt: updatedAt, - language: language, - role: role, - online: online, - lastActive: lastActive, - extraData: extraData, - banned: banned, - createdAt: createdAt, - teamsRole: teamsRole, - avgResponseTime: avgResponseTime, - ); + id: id, + updatedAt: updatedAt, + language: language, + role: role, + online: online, + lastActive: lastActive, + extraData: extraData, + banned: banned, + createdAt: createdAt, + teamsRole: teamsRole, + avgResponseTime: avgResponseTime, + ); } /// Useful mapping functions for [User] extension UserX on User { /// Maps a [User] into [UserEntity] UserEntity toEntity() => UserEntity( - id: id, - role: role, - language: language, - createdAt: createdAt, - updatedAt: updatedAt, - lastActive: lastActive, - online: online, - banned: banned, - teamsRole: teamsRole, - avgResponseTime: avgResponseTime, - extraData: extraData, - ); + id: id, + role: role, + language: language, + createdAt: createdAt, + updatedAt: updatedAt, + lastActive: lastActive, + online: online, + banned: banned, + teamsRole: teamsRole, + avgResponseTime: avgResponseTime, + extraData: extraData, + ); } diff --git a/packages/stream_chat_persistence/lib/src/stream_chat_persistence_client.dart b/packages/stream_chat_persistence/lib/src/stream_chat_persistence_client.dart index c0a15cdb95..3db937a51a 100644 --- a/packages/stream_chat_persistence/lib/src/stream_chat_persistence_client.dart +++ b/packages/stream_chat_persistence/lib/src/stream_chat_persistence_client.dart @@ -33,9 +33,9 @@ class StreamChatPersistenceClient extends ChatPersistenceClient { /// Otherwise, falls back to the local storage based implementation. bool webUseExperimentalIndexedDb = false, LogHandlerFunction? logHandlerFunction, - }) : _connectionMode = connectionMode, - _webUseIndexedDbIfSupported = webUseExperimentalIndexedDb, - _logger = Logger.detached('💽')..level = logLevel { + }) : _connectionMode = connectionMode, + _webUseIndexedDbIfSupported = webUseExperimentalIndexedDb, + _logger = Logger.detached('💽')..level = logLevel { _logger.onRecord.listen(logHandlerFunction ?? _defaultLogHandler); } @@ -72,12 +72,11 @@ class StreamChatPersistenceClient extends ChatPersistenceClient { Future _defaultDatabaseProvider( String userId, ConnectionMode mode, - ) => - SharedDB.constructDatabase( - userId, - connectionMode: mode, - webUseIndexedDbIfSupported: _webUseIndexedDbIfSupported, - ); + ) => SharedDB.constructDatabase( + userId, + connectionMode: mode, + webUseIndexedDbIfSupported: _webUseIndexedDbIfSupported, + ); @override bool get isConnected => db != null; @@ -97,8 +96,7 @@ class StreamChatPersistenceClient extends ChatPersistenceClient { ); } _logger.info('connect'); - db = databaseProvider?.call(userId, _connectionMode) ?? - await _defaultDatabaseProvider(userId, _connectionMode); + db = databaseProvider?.call(userId, _connectionMode) ?? await _defaultDatabaseProvider(userId, _connectionMode); } @override @@ -222,17 +220,19 @@ class StreamChatPersistenceClient extends ChatPersistenceClient { _logger.info('deleteMessagesFromUser'); // Delete from both messages and pinned_messages tables - await Future.wait([ - db!.messageDao.deleteMessagesByUser, - db!.pinnedMessageDao.deleteMessagesByUser, - ].map( - (f) => f.call( - cid: cid, - userId: userId, - hardDelete: hardDelete, - deletedAt: deletedAt, + await Future.wait( + [ + db!.messageDao.deleteMessagesByUser, + db!.pinnedMessageDao.deleteMessagesByUser, + ].map( + (f) => f.call( + cid: cid, + userId: userId, + hardDelete: hardDelete, + deletedAt: deletedAt, + ), ), - )); + ); } @override diff --git a/packages/stream_chat_persistence/pubspec.yaml b/packages/stream_chat_persistence/pubspec.yaml index b14d4d3dbb..0c789f426f 100644 --- a/packages/stream_chat_persistence/pubspec.yaml +++ b/packages/stream_chat_persistence/pubspec.yaml @@ -18,8 +18,8 @@ issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues # 2. Add it to the melos.yaml file for future updates. environment: - sdk: ^3.6.2 - flutter: ">=3.27.4" + sdk: ^3.10.0 + flutter: ">=3.38.1" dependencies: drift: ^2.28.0 diff --git a/packages/stream_chat_persistence/test/mock_chat_database.dart b/packages/stream_chat_persistence/test/mock_chat_database.dart index cb0f06a541..fe4facfd75 100644 --- a/packages/stream_chat_persistence/test/mock_chat_database.dart +++ b/packages/stream_chat_persistence/test/mock_chat_database.dart @@ -19,8 +19,7 @@ class MockChatDatabase extends Mock implements DriftChatDatabase { MessageDao? _messageDao; @override - PinnedMessageDao get pinnedMessageDao => - _pinnedMessageDao ??= MockPinnedMessageDao(); + PinnedMessageDao get pinnedMessageDao => _pinnedMessageDao ??= MockPinnedMessageDao(); PinnedMessageDao? _pinnedMessageDao; @override @@ -32,8 +31,7 @@ class MockChatDatabase extends Mock implements DriftChatDatabase { ReactionDao? _reactionDao; @override - PinnedMessageReactionDao get pinnedMessageReactionDao => - _pinnedMessageReactionDao ??= MockPinnedMessageReactionDao(); + PinnedMessageReactionDao get pinnedMessageReactionDao => _pinnedMessageReactionDao ??= MockPinnedMessageReactionDao(); PinnedMessageReactionDao? _pinnedMessageReactionDao; @override @@ -41,13 +39,11 @@ class MockChatDatabase extends Mock implements DriftChatDatabase { ReadDao? _readDao; @override - ChannelQueryDao get channelQueryDao => - _channelQueryDao ??= MockChannelQueryDao(); + ChannelQueryDao get channelQueryDao => _channelQueryDao ??= MockChannelQueryDao(); ChannelQueryDao? _channelQueryDao; @override - ConnectionEventDao get connectionEventDao => - _connectionEventDao ??= MockConnectionEventDao(); + ConnectionEventDao get connectionEventDao => _connectionEventDao ??= MockConnectionEventDao(); ConnectionEventDao? _connectionEventDao; @override @@ -59,8 +55,7 @@ class MockChatDatabase extends Mock implements DriftChatDatabase { PollVoteDao? _pollVoteDao; @override - DraftMessageDao get draftMessageDao => - _draftMessageDao ??= MockDraftMessageDao(); + DraftMessageDao get draftMessageDao => _draftMessageDao ??= MockDraftMessageDao(); DraftMessageDao? _draftMessageDao; @override @@ -86,8 +81,7 @@ class MockMemberDao extends Mock implements MemberDao {} class MockReactionDao extends Mock implements ReactionDao {} -class MockPinnedMessageReactionDao extends Mock - implements PinnedMessageReactionDao {} +class MockPinnedMessageReactionDao extends Mock implements PinnedMessageReactionDao {} class MockReadDao extends Mock implements ReadDao {} diff --git a/packages/stream_chat_persistence/test/src/dao/channel_dao_test.dart b/packages/stream_chat_persistence/test/src/dao/channel_dao_test.dart index da974fa31c..44dde6b1f6 100644 --- a/packages/stream_chat_persistence/test/src/dao/channel_dao_test.dart +++ b/packages/stream_chat_persistence/test/src/dao/channel_dao_test.dart @@ -99,13 +99,11 @@ void main() { expect(updatedReads.first.user, dummyUser); // Saving a dummy reaction - final dummyReaction = - Reaction(type: 'type', messageId: messageId, userId: userId); + final dummyReaction = Reaction(type: 'type', messageId: messageId, userId: userId); await database.reactionDao.updateReactions([dummyReaction]); // Should match the dummy reaction - final updatedReactions = - await database.reactionDao.getReactionsByUserId(messageId, userId); + final updatedReactions = await database.reactionDao.getReactionsByUserId(messageId, userId); expect(updatedReactions.length, 1); expect(updatedReactions.first.messageId, messageId); @@ -129,8 +127,7 @@ void main() { expect(reads, isEmpty); // Fetched readtions for passed message id and user id should be empty - final reactions = - await database.reactionDao.getReactionsByUserId(messageId, userId); + final reactions = await database.reactionDao.getReactionsByUserId(messageId, userId); expect(reactions, isEmpty); }); diff --git a/packages/stream_chat_persistence/test/src/dao/draft_message_dao_test.dart b/packages/stream_chat_persistence/test/src/dao/draft_message_dao_test.dart index 15c8e48b2f..0a020da477 100644 --- a/packages/stream_chat_persistence/test/src/dao/draft_message_dao_test.dart +++ b/packages/stream_chat_persistence/test/src/dao/draft_message_dao_test.dart @@ -62,8 +62,7 @@ void main() { (index) { // When count is 1, use the exact cid provided // Otherwise, create unique cids for each draft to avoid conflicts - final draftChannelCid = - count == 1 ? cid : (withParentMessage ? cid : '$cid$index'); + final draftChannelCid = count == 1 ? cid : (withParentMessage ? cid : '$cid$index'); final draftMessage = DraftMessage( id: 'testDraftId$cid$index', @@ -236,8 +235,7 @@ void main() { await draftMessageDao.updateDraftMessages([firstDraft]); // Verify first draft exists - final firstFetchedDraft = - await draftMessageDao.getDraftMessageByCid(cid); + final firstFetchedDraft = await draftMessageDao.getDraftMessageByCid(cid); expect(firstFetchedDraft, isNotNull); expect(firstFetchedDraft!.message.text, 'First channel draft'); @@ -254,16 +252,13 @@ void main() { await draftMessageDao.updateDraftMessages([secondDraft]); // Verify only the second draft exists - final secondFetchedDraft = - await draftMessageDao.getDraftMessageByCid(cid); + final secondFetchedDraft = await draftMessageDao.getDraftMessageByCid(cid); expect(secondFetchedDraft, isNotNull); expect(secondFetchedDraft!.message.text, 'Second channel draft'); // Verify the first draft no longer exists - final firstDraftAfterUpdate = - await draftMessageDao.getDraftMessageByCid(firstDraft.channelCid); - expect( - firstDraftAfterUpdate!.message.text, isNot('First channel draft')); + final firstDraftAfterUpdate = await draftMessageDao.getDraftMessageByCid(firstDraft.channelCid); + expect(firstDraftAfterUpdate!.message.text, isNot('First channel draft')); // Verify there's only one draft message for this channel final channelDraft = await draftMessageDao.getDraftMessageByCid(cid); @@ -303,8 +298,7 @@ void main() { await draftMessageDao.updateDraftMessages([firstDraft]); // Verify first thread draft exists - final firstFetchedDraft = await draftMessageDao - .getDraftMessageByCid(cid, parentId: firstDraft.parentId); + final firstFetchedDraft = await draftMessageDao.getDraftMessageByCid(cid, parentId: firstDraft.parentId); expect(firstFetchedDraft, isNotNull); expect(firstFetchedDraft!.message.text, 'First thread draft'); @@ -323,20 +317,16 @@ void main() { await draftMessageDao.updateDraftMessages([secondDraft]); // Verify only the second draft exists - final secondFetchedDraft = await draftMessageDao - .getDraftMessageByCid(cid, parentId: secondDraft.parentId); + final secondFetchedDraft = await draftMessageDao.getDraftMessageByCid(cid, parentId: secondDraft.parentId); expect(secondFetchedDraft, isNotNull); expect(secondFetchedDraft!.message.text, 'Second thread draft'); // Verify the first draft no longer exists - final firstDraftAfterUpdate = await draftMessageDao - .getDraftMessageByCid(cid, parentId: firstDraft.parentId); - expect( - firstDraftAfterUpdate!.message.text, isNot('First thread draft')); + final firstDraftAfterUpdate = await draftMessageDao.getDraftMessageByCid(cid, parentId: firstDraft.parentId); + expect(firstDraftAfterUpdate!.message.text, isNot('First thread draft')); // Verify there's only one draft message for this thread - final threadDraft = await draftMessageDao.getDraftMessageByCid(cid, - parentId: parentMessage.id); + final threadDraft = await draftMessageDao.getDraftMessageByCid(cid, parentId: parentMessage.id); expect(threadDraft, isNotNull); expect(threadDraft!.message.text, 'Second thread draft'); }, @@ -387,16 +377,14 @@ void main() { await _prepareTestData(cid, count: 1); // Verify draft exists - final draftBeforeChannelDelete = - await draftMessageDao.getDraftMessageByCid(cid); + final draftBeforeChannelDelete = await draftMessageDao.getDraftMessageByCid(cid); expect(draftBeforeChannelDelete, isNotNull); // Delete the channel await database.channelDao.deleteChannelByCids([cid]); // Verify draft has been deleted (cascade) - final draftAfterChannelDelete = - await draftMessageDao.getDraftMessageByCid(cid); + final draftAfterChannelDelete = await draftMessageDao.getDraftMessageByCid(cid); expect(draftAfterChannelDelete, isNull); }, ); @@ -454,14 +442,12 @@ void main() { ); // Verify drafts exist before channel deletion - final channelDraftBeforeDelete = - await draftMessageDao.getDraftMessageByCid(cid); + final channelDraftBeforeDelete = await draftMessageDao.getDraftMessageByCid(cid); expect(channelDraftBeforeDelete, isNotNull); expect(channelDraftBeforeDelete!.parentId, isNull); for (var i = 0; i < threadDrafts.length; i++) { - final threadDraft = await draftMessageDao.getDraftMessageByCid(cid, - parentId: threadDrafts[i].parentId); + final threadDraft = await draftMessageDao.getDraftMessageByCid(cid, parentId: threadDrafts[i].parentId); expect(threadDraft, isNotNull); expect(threadDraft!.parentId, messages[i].id); } @@ -470,13 +456,11 @@ void main() { await database.channelDao.deleteChannelByCids([cid]); // Verify all drafts have been deleted (cascade) - final channelDraftAfterDelete = - await draftMessageDao.getDraftMessageByCid(cid); + final channelDraftAfterDelete = await draftMessageDao.getDraftMessageByCid(cid); expect(channelDraftAfterDelete, isNull); for (final threadDraft in threadDrafts) { - final draft = await draftMessageDao.getDraftMessageByCid(cid, - parentId: threadDraft.parentId); + final draft = await draftMessageDao.getDraftMessageByCid(cid, parentId: threadDraft.parentId); expect(draft, isNull); } }, @@ -486,21 +470,18 @@ void main() { 'should delete draft messages when referenced parent message is deleted', () async { const cid = 'test:parentRefCascade'; - final testDrafts = - await _prepareTestData(cid, withParentMessage: true, count: 1); + final testDrafts = await _prepareTestData(cid, withParentMessage: true, count: 1); final parentId = testDrafts.first.parentId!; // Verify draft with parent exists - final draftBeforeMessageDelete = - await draftMessageDao.getDraftMessageByCid(cid, parentId: parentId); + final draftBeforeMessageDelete = await draftMessageDao.getDraftMessageByCid(cid, parentId: parentId); expect(draftBeforeMessageDelete, isNotNull); // Delete the parent message await database.messageDao.deleteMessageByIds([parentId]); // Verify draft has been deleted (cascade) - final draftAfterMessageDelete = - await draftMessageDao.getDraftMessageByCid(cid, parentId: parentId); + final draftAfterMessageDelete = await draftMessageDao.getDraftMessageByCid(cid, parentId: parentId); expect(draftAfterMessageDelete, isNull); }, ); diff --git a/packages/stream_chat_persistence/test/src/dao/location_dao_test.dart b/packages/stream_chat_persistence/test/src/dao/location_dao_test.dart index 2311ac8d2d..a521d2836a 100644 --- a/packages/stream_chat_persistence/test/src/dao/location_dao_test.dart +++ b/packages/stream_chat_persistence/test/src/dao/location_dao_test.dart @@ -42,9 +42,7 @@ void main() { latitude: 37.7749 + index * 0.001, // San Francisco area longitude: -122.4194 + index * 0.001, createdByDeviceId: 'testDevice$index', - endAt: index.isEven - ? DateTime.now().add(const Duration(hours: 1)) - : null, // Some live, some static + endAt: index.isEven ? DateTime.now().add(const Duration(hours: 1)) : null, // Some live, some static createdAt: DateTime.now(), updatedAt: DateTime.now(), ), @@ -112,10 +110,12 @@ void main() { final fetchedLocations = await locationDao.getLocationsByCid(cid); expect(fetchedLocations.length, insertedLocations.length + 1); expect( - fetchedLocations.any((it) => - it.messageId == newLocation.messageId && - it.latitude == newLocation.latitude && - it.longitude == newLocation.longitude), + fetchedLocations.any( + (it) => + it.messageId == newLocation.messageId && + it.latitude == newLocation.latitude && + it.longitude == newLocation.longitude, + ), isTrue, ); }); @@ -128,8 +128,7 @@ void main() { // Fetched location should not be null final locationToFetch = insertedLocations.first; - final fetchedLocation = - await locationDao.getLocationByMessageId(locationToFetch.messageId!); + final fetchedLocation = await locationDao.getLocationByMessageId(locationToFetch.messageId!); expect(fetchedLocation, isNotNull); expect(fetchedLocation!.messageId, locationToFetch.messageId); expect(fetchedLocation.latitude, locationToFetch.latitude); @@ -140,8 +139,7 @@ void main() { 'getLocationByMessageId should return null for non-existent messageId', () async { // Should return null for non-existent messageId - final fetchedLocation = - await locationDao.getLocationByMessageId('nonExistentMessageId'); + final fetchedLocation = await locationDao.getLocationByMessageId('nonExistentMessageId'); expect(fetchedLocation, isNull); }, ); @@ -171,14 +169,12 @@ void main() { final insertedLocations = await _prepareLocationData(cid: cid); // Deleting the first two locations by their message IDs - final messageIdsToDelete = - insertedLocations.take(2).map((it) => it.messageId!).toList(); + final messageIdsToDelete = insertedLocations.take(2).map((it) => it.messageId!).toList(); await locationDao.deleteLocationsByMessageIds(messageIdsToDelete); // Fetched location list should be one less than inserted locations final fetchedLocations = await locationDao.getLocationsByCid(cid); - expect(fetchedLocations.length, - insertedLocations.length - messageIdsToDelete.length); + expect(fetchedLocations.length, insertedLocations.length - messageIdsToDelete.length); // Deleted locations should not exist in fetched locations expect( @@ -193,10 +189,8 @@ void main() { const cid2 = 'test:Cid2'; // Preparing test data for two channels - final insertedLocations1 = - await _prepareLocationData(cid: cid1, count: 2); - final insertedLocations2 = - await _prepareLocationData(cid: cid2, count: 2); + final insertedLocations1 = await _prepareLocationData(cid: cid1, count: 2); + final insertedLocations2 = await _prepareLocationData(cid: cid2, count: 2); // Verify all locations exist final locations1 = await locationDao.getLocationsByCid(cid1); @@ -205,8 +199,7 @@ void main() { expect(locations2.length, insertedLocations2.length); // Delete only locations from the first channel - final messageIdsToDelete = - insertedLocations1.map((it) => it.messageId!).toList(); + final messageIdsToDelete = insertedLocations1.map((it) => it.messageId!).toList(); await locationDao.deleteLocationsByMessageIds(messageIdsToDelete); // Only locations from cid1 should be deleted @@ -246,32 +239,27 @@ void main() { const cid = 'test:messageRefCascade'; // Prepare test data - final insertedLocations = - await _prepareLocationData(cid: cid, count: 3); + final insertedLocations = await _prepareLocationData(cid: cid, count: 3); final messageToDelete = insertedLocations.first.messageId!; // Verify location exists before message deletion - final locationBeforeDelete = - await locationDao.getLocationByMessageId(messageToDelete); + final locationBeforeDelete = await locationDao.getLocationByMessageId(messageToDelete); expect(locationBeforeDelete, isNotNull); expect(locationBeforeDelete!.messageId, messageToDelete); // Verify all locations exist - final allLocationsBeforeDelete = - await locationDao.getLocationsByCid(cid); + final allLocationsBeforeDelete = await locationDao.getLocationsByCid(cid); expect(allLocationsBeforeDelete.length, 3); // Delete the message await database.messageDao.deleteMessageByIds([messageToDelete]); // Verify the specific location has been deleted (cascade) - final locationAfterDelete = - await locationDao.getLocationByMessageId(messageToDelete); + final locationAfterDelete = await locationDao.getLocationByMessageId(messageToDelete); expect(locationAfterDelete, isNull); // Verify other locations still exist - final allLocationsAfterDelete = - await locationDao.getLocationsByCid(cid); + final allLocationsAfterDelete = await locationDao.getLocationsByCid(cid); expect(allLocationsAfterDelete.length, 2); expect( allLocationsAfterDelete.any((it) => it.messageId == messageToDelete), @@ -286,26 +274,21 @@ void main() { const cid = 'test:multipleMessageRefCascade'; // Prepare test data - final insertedLocations = - await _prepareLocationData(cid: cid, count: 3); - final messageIdsToDelete = - insertedLocations.take(2).map((it) => it.messageId!).toList(); + final insertedLocations = await _prepareLocationData(cid: cid, count: 3); + final messageIdsToDelete = insertedLocations.take(2).map((it) => it.messageId!).toList(); // Verify locations exist before message deletion - final allLocationsBeforeDelete = - await locationDao.getLocationsByCid(cid); + final allLocationsBeforeDelete = await locationDao.getLocationsByCid(cid); expect(allLocationsBeforeDelete.length, 3); // Delete multiple messages await database.messageDao.deleteMessageByIds(messageIdsToDelete); // Verify corresponding locations have been deleted (cascade) - final allLocationsAfterDelete = - await locationDao.getLocationsByCid(cid); + final allLocationsAfterDelete = await locationDao.getLocationsByCid(cid); expect(allLocationsAfterDelete.length, 1); expect( - allLocationsAfterDelete - .any((it) => messageIdsToDelete.contains(it.messageId)), + allLocationsAfterDelete.any((it) => messageIdsToDelete.contains(it.messageId)), isFalse, ); }, diff --git a/packages/stream_chat_persistence/test/src/dao/member_dao_test.dart b/packages/stream_chat_persistence/test/src/dao/member_dao_test.dart index 936097fe8f..88f034a793 100644 --- a/packages/stream_chat_persistence/test/src/dao/member_dao_test.dart +++ b/packages/stream_chat_persistence/test/src/dao/member_dao_test.dart @@ -129,15 +129,11 @@ void main() { final newFetchedMembers = await memberDao.getMembersByCid(cid); expect(newFetchedMembers.length, fetchedMembers.length + 1); expect( - newFetchedMembers - .firstWhere((it) => it.user!.id == copyMember.user!.id) - .banned, + newFetchedMembers.firstWhere((it) => it.user!.id == copyMember.user!.id).banned, true, ); expect( - newFetchedMembers - .where((it) => it.user!.id == newMember.user!.id) - .isNotEmpty, + newFetchedMembers.where((it) => it.user!.id == newMember.user!.id).isNotEmpty, true, ); }); diff --git a/packages/stream_chat_persistence/test/src/dao/message_dao_test.dart b/packages/stream_chat_persistence/test/src/dao/message_dao_test.dart index d1b201f720..80472a1a78 100644 --- a/packages/stream_chat_persistence/test/src/dao/message_dao_test.dart +++ b/packages/stream_chat_persistence/test/src/dao/message_dao_test.dart @@ -85,8 +85,7 @@ void main() { type: 'testType', user: users[index], channelRole: 'channel_member', - parentId: - mapAllThreadToFirstMessage ? messages[0].id : messages[index].id, + parentId: mapAllThreadToFirstMessage ? messages[0].id : messages[index].id, createdAt: DateTime.now(), shadowed: math.Random().nextBool(), replyCount: index, @@ -103,11 +102,7 @@ void main() { }, ), ); - final allMessages = [ - ...messages, - if (quoted) ...quotedMessages, - if (threads) ...threadMessages - ]; + final allMessages = [...messages, if (quoted) ...quotedMessages, if (threads) ...threadMessages]; final reaction = Reaction( type: 'type', messageId: allMessages.first.id, @@ -147,8 +142,7 @@ void main() { expect(newMessages.length, messages.length - 2); // Reaction for the first message should be deleted too - final newReactions = - await database.reactionDao.getReactions(firstMessageId); + final newReactions = await database.reactionDao.getReactions(firstMessageId); expect(newReactions, isEmpty); }); @@ -171,8 +165,7 @@ void main() { // Fetched reactions list should have one reaction for given message id final cid1firstMessageId = cid1Messages.first.id; - final cid1Reactions = - await database.reactionDao.getReactions(cid1firstMessageId); + final cid1Reactions = await database.reactionDao.getReactions(cid1firstMessageId); expect(cid1Reactions.length, 1); // Deleting all the messages of cid1 @@ -185,8 +178,7 @@ void main() { expect(cid2FetchedMessages, isNotEmpty); // Reaction for the first message should be deleted too - final cid1FetchedReactions = - await database.reactionDao.getReactions(cid1firstMessageId); + final cid1FetchedReactions = await database.reactionDao.getReactions(cid1firstMessageId); expect(cid1FetchedReactions, isEmpty); }, ); @@ -206,12 +198,10 @@ void main() { // Fetched reactions list should have one reaction for given message id final cid1FirstMessageId = cid1Messages.first.id; - final cid1Reactions = - await database.reactionDao.getReactions(cid1FirstMessageId); + final cid1Reactions = await database.reactionDao.getReactions(cid1FirstMessageId); expect(cid1Reactions.length, 1); final cid2FirstMessageId = cid2Messages.first.id; - final cid2Reactions = - await database.reactionDao.getReactions(cid2FirstMessageId); + final cid2Reactions = await database.reactionDao.getReactions(cid2FirstMessageId); expect(cid2Reactions.length, 1); // Deleting all the messages of cid1 @@ -224,11 +214,9 @@ void main() { expect(cid2FetchedMessages, isEmpty); // Reaction for the first message should be deleted too - final cid1FetchedReactions = - await database.reactionDao.getReactions(cid1FirstMessageId); + final cid1FetchedReactions = await database.reactionDao.getReactions(cid1FirstMessageId); expect(cid1FetchedReactions, isEmpty); - final cid2FetchedReactions = - await database.reactionDao.getReactions(cid2FirstMessageId); + final cid2FetchedReactions = await database.reactionDao.getReactions(cid2FirstMessageId); expect(cid2FetchedReactions, isEmpty); }, ); @@ -284,8 +272,7 @@ void main() { expect(insertedMessages, isNotEmpty); // Should fetch all the thread messages of parentId - final threadMessages = - await messageDao.getThreadMessagesByParentId(parentId); + final threadMessages = await messageDao.getThreadMessagesByParentId(parentId); expect(threadMessages.length, 1); expect(threadMessages.first.parentId, parentId); }); @@ -454,8 +441,7 @@ void main() { expect(cid2Messages, isNotEmpty); // Count messages from the specific user in cid1 - final cid1UserMessages = - cid1Messages.where((m) => m.user?.id == userId).length; + final cid1UserMessages = cid1Messages.where((m) => m.user?.id == userId).length; expect(cid1UserMessages, greaterThan(0)); // Hard delete messages from user in cid1 only @@ -467,8 +453,7 @@ void main() { // Verify user's messages are deleted from cid1 final cid1MessagesAfter = await messageDao.getMessagesByCid(cid1); - final cid1UserMessagesAfter = - cid1MessagesAfter.where((m) => m.user?.id == userId).length; + final cid1UserMessagesAfter = cid1MessagesAfter.where((m) => m.user?.id == userId).length; expect(cid1UserMessagesAfter, 0); // Verify other users' messages in cid1 are not affected @@ -484,8 +469,7 @@ void main() { await _prepareTestData(cid1); final cid1Messages = await messageDao.getMessagesByCid(cid1); - final cid1UserMessages = - cid1Messages.where((m) => m.user?.id == userId).toList(); + final cid1UserMessages = cid1Messages.where((m) => m.user?.id == userId).toList(); expect(cid1UserMessages, isNotEmpty); // Verify messages are not deleted initially @@ -505,8 +489,7 @@ void main() { // Verify messages are marked as deleted final cid1MessagesAfter = await messageDao.getMessagesByCid(cid1); - final cid1UserMessagesAfter = - cid1MessagesAfter.where((m) => m.user?.id == userId).toList(); + final cid1UserMessagesAfter = cid1MessagesAfter.where((m) => m.user?.id == userId).toList(); // Messages should still exist in DB expect(cid1UserMessagesAfter.length, cid1UserMessages.length); @@ -518,15 +501,13 @@ void main() { } // Other users' messages should not be affected - final otherUserMessages = - cid1MessagesAfter.where((m) => m.user?.id != userId).toList(); + final otherUserMessages = cid1MessagesAfter.where((m) => m.user?.id != userId).toList(); for (final message in otherUserMessages) { expect(message.type, isNot('deleted')); } }); - test('hard deletes user messages across all channels when cid is null', - () async { + test('hard deletes user messages across all channels when cid is null', () async { // Preparing test data for multiple channels await _prepareTestData(cid1); await _prepareTestData(cid2); @@ -534,10 +515,8 @@ void main() { final cid1Messages = await messageDao.getMessagesByCid(cid1); final cid2Messages = await messageDao.getMessagesByCid(cid2); - final cid1UserMessages = - cid1Messages.where((m) => m.user?.id == userId).length; - final cid2UserMessages = - cid2Messages.where((m) => m.user?.id == userId).length; + final cid1UserMessages = cid1Messages.where((m) => m.user?.id == userId).length; + final cid2UserMessages = cid2Messages.where((m) => m.user?.id == userId).length; expect(cid1UserMessages, greaterThan(0)); expect(cid2UserMessages, greaterThan(0)); @@ -566,8 +545,7 @@ void main() { ); }); - test('soft deletes user messages across all channels when cid is null', - () async { + test('soft deletes user messages across all channels when cid is null', () async { // Preparing test data for multiple channels await _prepareTestData(cid1); await _prepareTestData(cid2); @@ -575,10 +553,8 @@ void main() { final cid1Messages = await messageDao.getMessagesByCid(cid1); final cid2Messages = await messageDao.getMessagesByCid(cid2); - final cid1UserMessages = - cid1Messages.where((m) => m.user?.id == userId).length; - final cid2UserMessages = - cid2Messages.where((m) => m.user?.id == userId).length; + final cid1UserMessages = cid1Messages.where((m) => m.user?.id == userId).length; + final cid2UserMessages = cid2Messages.where((m) => m.user?.id == userId).length; // Soft delete all messages from user across all channels await messageDao.deleteMessagesByUser( @@ -590,20 +566,15 @@ void main() { final cid1MessagesAfter = await messageDao.getMessagesByCid(cid1); final cid2MessagesAfter = await messageDao.getMessagesByCid(cid2); - final cid1UserMessagesAfter = - cid1MessagesAfter.where((m) => m.user?.id == userId).toList(); - final cid2UserMessagesAfter = - cid2MessagesAfter.where((m) => m.user?.id == userId).toList(); + final cid1UserMessagesAfter = cid1MessagesAfter.where((m) => m.user?.id == userId).toList(); + final cid2UserMessagesAfter = cid2MessagesAfter.where((m) => m.user?.id == userId).toList(); // Messages should still exist expect(cid1UserMessagesAfter.length, cid1UserMessages); expect(cid2UserMessagesAfter.length, cid2UserMessages); // All user messages should be marked as deleted - for (final message in [ - ...cid1UserMessagesAfter, - ...cid2UserMessagesAfter - ]) { + for (final message in [...cid1UserMessagesAfter, ...cid2UserMessagesAfter]) { expect(message.type, 'deleted'); expect(message.deletedAt, isNotNull); } @@ -615,8 +586,7 @@ void main() { final cid1ThreadMessages = await messageDao.getThreadMessages(cid1); - final userThreadMessages = - cid1ThreadMessages.where((m) => m.user?.id == userId).length; + final userThreadMessages = cid1ThreadMessages.where((m) => m.user?.id == userId).length; expect(userThreadMessages, greaterThan(0)); // Hard delete all messages from user @@ -628,8 +598,7 @@ void main() { // Verify thread messages from user are also deleted final cid1ThreadMessagesAfter = await messageDao.getThreadMessages(cid1); - final userThreadMessagesAfter = - cid1ThreadMessagesAfter.where((m) => m.user?.id == userId).length; + final userThreadMessagesAfter = cid1ThreadMessagesAfter.where((m) => m.user?.id == userId).length; expect(userThreadMessagesAfter, 0); }); }); diff --git a/packages/stream_chat_persistence/test/src/dao/pinned_message_dao_test.dart b/packages/stream_chat_persistence/test/src/dao/pinned_message_dao_test.dart index 3a76a5d953..ca7567b1de 100644 --- a/packages/stream_chat_persistence/test/src/dao/pinned_message_dao_test.dart +++ b/packages/stream_chat_persistence/test/src/dao/pinned_message_dao_test.dart @@ -75,8 +75,7 @@ void main() { type: 'testType', user: users[index], channelRole: 'channel_member', - parentId: - mapAllThreadToFirstMessage ? messages[0].id : messages[index].id, + parentId: mapAllThreadToFirstMessage ? messages[0].id : messages[index].id, createdAt: DateTime.now(), shadowed: math.Random().nextBool(), replyCount: index, @@ -88,11 +87,7 @@ void main() { pinnedBy: User(id: 'testUserId$index'), ), ); - final allMessages = [ - ...messages, - if (quoted) ...quotedMessages, - if (threads) ...threadMessages - ]; + final allMessages = [...messages, if (quoted) ...quotedMessages, if (threads) ...threadMessages]; final reaction = Reaction( type: 'type', messageId: allMessages.first.id, @@ -118,8 +113,7 @@ void main() { final firstMessageId = messages.first.id; // Fetched reactions list should have one reaction for given message id - final reactions = - await database.pinnedMessageReactionDao.getReactions(firstMessageId); + final reactions = await database.pinnedMessageReactionDao.getReactions(firstMessageId); expect(reactions.length, 1); // Deleting 2 messages from DB @@ -133,8 +127,7 @@ void main() { expect(newMessages.length, messages.length - 2); // Reaction for the first message should be deleted too - final newReactions = - await database.pinnedMessageReactionDao.getReactions(firstMessageId); + final newReactions = await database.pinnedMessageReactionDao.getReactions(firstMessageId); expect(newReactions, isEmpty); }); @@ -157,24 +150,20 @@ void main() { // Fetched reactions list should have one reaction for given message id final cid1firstMessageId = cid1Messages.first.id; - final cid1Reactions = await database.pinnedMessageReactionDao - .getReactions(cid1firstMessageId); + final cid1Reactions = await database.pinnedMessageReactionDao.getReactions(cid1firstMessageId); expect(cid1Reactions.length, 1); // Deleting all the messages of cid1 await pinnedMessageDao.deleteMessageByCids([cid1]); // Fetched messages length of only cid1 should be empty - final cid1FetchedMessages = - await pinnedMessageDao.getMessagesByCid(cid1); - final cid2FetchedMessages = - await pinnedMessageDao.getMessagesByCid(cid2); + final cid1FetchedMessages = await pinnedMessageDao.getMessagesByCid(cid1); + final cid2FetchedMessages = await pinnedMessageDao.getMessagesByCid(cid2); expect(cid1FetchedMessages, isEmpty); expect(cid2FetchedMessages, isNotEmpty); // Reaction for the first message should be deleted too - final cid1FetchedReactions = await database.pinnedMessageReactionDao - .getReactions(cid1firstMessageId); + final cid1FetchedReactions = await database.pinnedMessageReactionDao.getReactions(cid1firstMessageId); expect(cid1FetchedReactions, isEmpty); }, ); @@ -194,31 +183,25 @@ void main() { // Fetched reactions list should have one reaction for given message id final cid1FirstMessageId = cid1Messages.first.id; - final cid1Reactions = await database.pinnedMessageReactionDao - .getReactions(cid1FirstMessageId); + final cid1Reactions = await database.pinnedMessageReactionDao.getReactions(cid1FirstMessageId); expect(cid1Reactions.length, 1); final cid2FirstMessageId = cid2Messages.first.id; - final cid2Reactions = await database.pinnedMessageReactionDao - .getReactions(cid2FirstMessageId); + final cid2Reactions = await database.pinnedMessageReactionDao.getReactions(cid2FirstMessageId); expect(cid2Reactions.length, 1); // Deleting all the messages of cid1 await pinnedMessageDao.deleteMessageByCids([cid1, cid2]); // Fetched messages length of both cid1 and cid2 should be empty - final cid1FetchedMessages = - await pinnedMessageDao.getMessagesByCid(cid1); - final cid2FetchedMessages = - await pinnedMessageDao.getMessagesByCid(cid2); + final cid1FetchedMessages = await pinnedMessageDao.getMessagesByCid(cid1); + final cid2FetchedMessages = await pinnedMessageDao.getMessagesByCid(cid2); expect(cid1FetchedMessages, isEmpty); expect(cid2FetchedMessages, isEmpty); // Reaction for the first message should be deleted too - final cid1FetchedReactions = await database.pinnedMessageReactionDao - .getReactions(cid1FirstMessageId); + final cid1FetchedReactions = await database.pinnedMessageReactionDao.getReactions(cid1FirstMessageId); expect(cid1FetchedReactions, isEmpty); - final cid2FetchedReactions = await database.pinnedMessageReactionDao - .getReactions(cid2FirstMessageId); + final cid2FetchedReactions = await database.pinnedMessageReactionDao.getReactions(cid2FirstMessageId); expect(cid2FetchedReactions, isEmpty); }, ); @@ -266,8 +249,7 @@ void main() { const parentId = 'testMessageId${cid}0'; // Messages should be empty initially - final messages = - await pinnedMessageDao.getThreadMessagesByParentId(parentId); + final messages = await pinnedMessageDao.getThreadMessagesByParentId(parentId); expect(messages, isEmpty); // Preparing test data @@ -275,8 +257,7 @@ void main() { expect(insertedMessages, isNotEmpty); // Should fetch all the thread messages of parentId - final threadMessages = - await pinnedMessageDao.getThreadMessagesByParentId(parentId); + final threadMessages = await pinnedMessageDao.getThreadMessagesByParentId(parentId); expect(threadMessages.length, 1); expect(threadMessages.first.parentId, parentId); }); @@ -445,8 +426,7 @@ void main() { expect(cid2Messages, isNotEmpty); // Count messages from the specific user in cid1 - final cid1UserMessages = - cid1Messages.where((m) => m.user?.id == userId).length; + final cid1UserMessages = cid1Messages.where((m) => m.user?.id == userId).length; expect(cid1UserMessages, greaterThan(0)); // Hard delete messages from user in cid1 only @@ -458,8 +438,7 @@ void main() { // Verify user's messages are deleted from cid1 final cid1MessagesAfter = await pinnedMessageDao.getMessagesByCid(cid1); - final cid1UserMessagesAfter = - cid1MessagesAfter.where((m) => m.user?.id == userId).length; + final cid1UserMessagesAfter = cid1MessagesAfter.where((m) => m.user?.id == userId).length; expect(cid1UserMessagesAfter, 0); // Verify other users' messages in cid1 are not affected @@ -475,8 +454,7 @@ void main() { await _prepareTestData(cid1); final cid1Messages = await pinnedMessageDao.getMessagesByCid(cid1); - final cid1UserMessages = - cid1Messages.where((m) => m.user?.id == userId).toList(); + final cid1UserMessages = cid1Messages.where((m) => m.user?.id == userId).toList(); expect(cid1UserMessages, isNotEmpty); // Verify messages are not deleted initially @@ -496,8 +474,7 @@ void main() { // Verify messages are marked as deleted final cid1MessagesAfter = await pinnedMessageDao.getMessagesByCid(cid1); - final cid1UserMessagesAfter = - cid1MessagesAfter.where((m) => m.user?.id == userId).toList(); + final cid1UserMessagesAfter = cid1MessagesAfter.where((m) => m.user?.id == userId).toList(); // Messages should still exist in DB expect(cid1UserMessagesAfter.length, cid1UserMessages.length); @@ -509,15 +486,13 @@ void main() { } // Other users' messages should not be affected - final otherUserMessages = - cid1MessagesAfter.where((m) => m.user?.id != userId).toList(); + final otherUserMessages = cid1MessagesAfter.where((m) => m.user?.id != userId).toList(); for (final message in otherUserMessages) { expect(message.type, isNot('deleted')); } }); - test('hard deletes user pinned messages across all channels when cid null', - () async { + test('hard deletes user pinned messages across all channels when cid null', () async { // Preparing test data for multiple channels await _prepareTestData(cid1); await _prepareTestData(cid2); @@ -525,10 +500,8 @@ void main() { final cid1Messages = await pinnedMessageDao.getMessagesByCid(cid1); final cid2Messages = await pinnedMessageDao.getMessagesByCid(cid2); - final cid1UserMessages = - cid1Messages.where((m) => m.user?.id == userId).length; - final cid2UserMessages = - cid2Messages.where((m) => m.user?.id == userId).length; + final cid1UserMessages = cid1Messages.where((m) => m.user?.id == userId).length; + final cid2UserMessages = cid2Messages.where((m) => m.user?.id == userId).length; expect(cid1UserMessages, greaterThan(0)); expect(cid2UserMessages, greaterThan(0)); @@ -563,8 +536,7 @@ void main() { ); }); - test('soft deletes user pinned messages across all channels when cid null', - () async { + test('soft deletes user pinned messages across all channels when cid null', () async { // Preparing test data for multiple channels await _prepareTestData(cid1); await _prepareTestData(cid2); @@ -572,10 +544,8 @@ void main() { final cid1Messages = await pinnedMessageDao.getMessagesByCid(cid1); final cid2Messages = await pinnedMessageDao.getMessagesByCid(cid2); - final cid1UserMessages = - cid1Messages.where((m) => m.user?.id == userId).length; - final cid2UserMessages = - cid2Messages.where((m) => m.user?.id == userId).length; + final cid1UserMessages = cid1Messages.where((m) => m.user?.id == userId).length; + final cid2UserMessages = cid2Messages.where((m) => m.user?.id == userId).length; // Soft delete all messages from user across all channels await pinnedMessageDao.deleteMessagesByUser( @@ -587,20 +557,15 @@ void main() { final cid1MessagesAfter = await pinnedMessageDao.getMessagesByCid(cid1); final cid2MessagesAfter = await pinnedMessageDao.getMessagesByCid(cid2); - final cid1UserMessagesAfter = - cid1MessagesAfter.where((m) => m.user?.id == userId).toList(); - final cid2UserMessagesAfter = - cid2MessagesAfter.where((m) => m.user?.id == userId).toList(); + final cid1UserMessagesAfter = cid1MessagesAfter.where((m) => m.user?.id == userId).toList(); + final cid2UserMessagesAfter = cid2MessagesAfter.where((m) => m.user?.id == userId).toList(); // Messages should still exist expect(cid1UserMessagesAfter.length, cid1UserMessages); expect(cid2UserMessagesAfter.length, cid2UserMessages); // All user messages should be marked as deleted - for (final message in [ - ...cid1UserMessagesAfter, - ...cid2UserMessagesAfter - ]) { + for (final message in [...cid1UserMessagesAfter, ...cid2UserMessagesAfter]) { expect(message.type, 'deleted'); expect(message.deletedAt, isNotNull); } diff --git a/packages/stream_chat_persistence/test/src/dao/pinned_message_reaction_dao_test.dart b/packages/stream_chat_persistence/test/src/dao/pinned_message_reaction_dao_test.dart index 57482c4317..4b76fe095c 100644 --- a/packages/stream_chat_persistence/test/src/dao/pinned_message_reaction_dao_test.dart +++ b/packages/stream_chat_persistence/test/src/dao/pinned_message_reaction_dao_test.dart @@ -80,8 +80,7 @@ void main() { // Fetched reaction length should match inserted reactions length. // Every reaction messageId should match the provided messageId. - final fetchedReactions = - await pinnedMessageReactionDao.getReactions(messageId); + final fetchedReactions = await pinnedMessageReactionDao.getReactions(messageId); expect(fetchedReactions.length, insertedReactions.length); expect(fetchedReactions.every((it) => it.messageId == messageId), true); @@ -99,20 +98,17 @@ void main() { const userId = 'testUserId'; // Should be empty initially - final reactions = - await pinnedMessageReactionDao.getReactionsByUserId(messageId, userId); + final reactions = await pinnedMessageReactionDao.getReactionsByUserId(messageId, userId); expect(reactions, isEmpty); // Adding sample reactions - final insertedReactions = - await _prepareReactionData(messageId, userId: userId); + final insertedReactions = await _prepareReactionData(messageId, userId: userId); expect(insertedReactions, isNotEmpty); // Fetched reaction length should match inserted reactions length. // Every reaction messageId should match the provided messageId. // Every reaction userId should match the provided userId. - final fetchedReactions = - await pinnedMessageReactionDao.getReactionsByUserId(messageId, userId); + final fetchedReactions = await pinnedMessageReactionDao.getReactionsByUserId(messageId, userId); expect(fetchedReactions.length, insertedReactions.length); expect(fetchedReactions.every((it) => it.messageId == messageId), true); expect(fetchedReactions.every((it) => it.userId == userId), true); @@ -155,8 +151,7 @@ void main() { // Fetched reaction length should be one more than inserted reactions. // copyReaction modified fields should match // Fetched reactions should contain the newReaction. - final fetchedReactions = - await pinnedMessageReactionDao.getReactions(messageId); + final fetchedReactions = await pinnedMessageReactionDao.getReactions(messageId); expect(fetchedReactions.length, reactions.length + 1); final fetchedCopyReaction = fetchedReactions.firstWhere( @@ -186,10 +181,8 @@ void main() { // Fetched reaction list length should match // the inserted reactions list length - final reactions1 = - await pinnedMessageReactionDao.getReactions(messageId1); - final reactions2 = - await pinnedMessageReactionDao.getReactions(messageId2); + final reactions1 = await pinnedMessageReactionDao.getReactions(messageId1); + final reactions2 = await pinnedMessageReactionDao.getReactions(messageId2); expect(reactions1.length, insertedReactions1.length); expect(reactions2.length, insertedReactions2.length); @@ -197,10 +190,8 @@ void main() { await pinnedMessageReactionDao.deleteReactionsByMessageIds([messageId1]); // Fetched reactions length of only messageId1 should be empty - final fetchedReactions1 = - await pinnedMessageReactionDao.getReactions(messageId1); - final fetchedReactions2 = - await pinnedMessageReactionDao.getReactions(messageId2); + final fetchedReactions1 = await pinnedMessageReactionDao.getReactions(messageId1); + final fetchedReactions2 = await pinnedMessageReactionDao.getReactions(messageId2); expect(fetchedReactions1, isEmpty); expect(fetchedReactions2, isNotEmpty); }); @@ -212,22 +203,17 @@ void main() { // Fetched reaction list length should match // the inserted reactions list length - final reactions1 = - await pinnedMessageReactionDao.getReactions(messageId1); - final reactions2 = - await pinnedMessageReactionDao.getReactions(messageId2); + final reactions1 = await pinnedMessageReactionDao.getReactions(messageId1); + final reactions2 = await pinnedMessageReactionDao.getReactions(messageId2); expect(reactions1.length, insertedReactions1.length); expect(reactions2.length, insertedReactions2.length); // Deleting all the reactions of messageId1 and messageId2 - await pinnedMessageReactionDao - .deleteReactionsByMessageIds([messageId1, messageId2]); + await pinnedMessageReactionDao.deleteReactionsByMessageIds([messageId1, messageId2]); // Fetched reactions length of both messages should be empty - final fetchedReactions1 = - await pinnedMessageReactionDao.getReactions(messageId1); - final fetchedReactions2 = - await pinnedMessageReactionDao.getReactions(messageId2); + final fetchedReactions1 = await pinnedMessageReactionDao.getReactions(messageId1); + final fetchedReactions2 = await pinnedMessageReactionDao.getReactions(messageId2); expect(fetchedReactions1, isEmpty); expect(fetchedReactions2, isEmpty); }); diff --git a/packages/stream_chat_persistence/test/src/dao/poll_vote_dao_test.dart b/packages/stream_chat_persistence/test/src/dao/poll_vote_dao_test.dart index 093aac84bf..2c13ee1cdb 100644 --- a/packages/stream_chat_persistence/test/src/dao/poll_vote_dao_test.dart +++ b/packages/stream_chat_persistence/test/src/dao/poll_vote_dao_test.dart @@ -44,9 +44,7 @@ void main() { (key, value) => MapEntry(key, value.length), ); - final users = latestVotesByOption.values - .expand((it) => it.map((it) => it.user!)) - .toList(); + final users = latestVotesByOption.values.expand((it) => it.map((it) => it.user!)).toList(); final poll = Poll( id: pollId, @@ -114,11 +112,13 @@ void main() { final fetchedPollVotes = await pollVoteDao.getPollVotes(pollId); expect(fetchedPollVotes.length, pollVotes.length + 1); expect( - fetchedPollVotes.any((it) => - it.id == newPollVote.id && - it.pollId == newPollVote.pollId && - it.optionId == newPollVote.optionId && - it.answerText == newPollVote.answerText), + fetchedPollVotes.any( + (it) => + it.id == newPollVote.id && + it.pollId == newPollVote.pollId && + it.optionId == newPollVote.optionId && + it.answerText == newPollVote.answerText, + ), true, ); }); diff --git a/packages/stream_chat_persistence/test/src/dao/reaction_dao_test.dart b/packages/stream_chat_persistence/test/src/dao/reaction_dao_test.dart index ddd01f09ff..2252641bc1 100644 --- a/packages/stream_chat_persistence/test/src/dao/reaction_dao_test.dart +++ b/packages/stream_chat_persistence/test/src/dao/reaction_dao_test.dart @@ -102,15 +102,13 @@ void main() { expect(reactions, isEmpty); // Adding sample reactions - final insertedReactions = - await _prepareReactionData(messageId, userId: userId); + final insertedReactions = await _prepareReactionData(messageId, userId: userId); expect(insertedReactions, isNotEmpty); // Fetched reaction length should match inserted reactions length. // Every reaction messageId should match the provided messageId. // Every reaction userId should match the provided userId. - final fetchedReactions = - await reactionDao.getReactionsByUserId(messageId, userId); + final fetchedReactions = await reactionDao.getReactionsByUserId(messageId, userId); expect(fetchedReactions.length, insertedReactions.length); expect(fetchedReactions.every((it) => it.messageId == messageId), true); expect(fetchedReactions.every((it) => it.userId == userId), true); diff --git a/packages/stream_chat_persistence/test/src/dao/read_dao_test.dart b/packages/stream_chat_persistence/test/src/dao/read_dao_test.dart index 142daf4155..473e2de82f 100644 --- a/packages/stream_chat_persistence/test/src/dao/read_dao_test.dart +++ b/packages/stream_chat_persistence/test/src/dao/read_dao_test.dart @@ -56,10 +56,8 @@ void main() { expect(fetchedRead.user.id, insertedRead.user.id); expect(fetchedRead.lastRead, isSameDateAs(insertedRead.lastRead)); expect(fetchedRead.unreadMessages, insertedRead.unreadMessages); - expect(fetchedRead.lastDeliveredAt, - isSameDateAs(insertedRead.lastDeliveredAt)); - expect(fetchedRead.lastDeliveredMessageId, - insertedRead.lastDeliveredMessageId); + expect(fetchedRead.lastDeliveredAt, isSameDateAs(insertedRead.lastDeliveredAt)); + expect(fetchedRead.lastDeliveredMessageId, insertedRead.lastDeliveredMessageId); } }); @@ -89,16 +87,12 @@ void main() { final fetchedReads = await readDao.getReadsByCid(cid); expect(fetchedReads.length, insertedReads.length + 1); expect( - fetchedReads - .firstWhere((it) => it.user.id == copyRead.user.id) - .unreadMessages, + fetchedReads.firstWhere((it) => it.user.id == copyRead.user.id).unreadMessages, 33, ); expect( fetchedReads - .where((it) => - it.user.id == newRead.user.id && - it.unreadMessages == newRead.unreadMessages) + .where((it) => it.user.id == newRead.user.id && it.unreadMessages == newRead.unreadMessages) .isNotEmpty, true, ); diff --git a/packages/stream_chat_persistence/test/src/db/drift_chat_database_test.dart b/packages/stream_chat_persistence/test/src/db/drift_chat_database_test.dart index 996c152fb1..dd14acf8c1 100644 --- a/packages/stream_chat_persistence/test/src/db/drift_chat_database_test.dart +++ b/packages/stream_chat_persistence/test/src/db/drift_chat_database_test.dart @@ -4,8 +4,7 @@ import 'package:drift/native.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:stream_chat_persistence/src/db/drift_chat_database.dart'; -DatabaseConnection _backgroundConnection() => - DatabaseConnection(NativeDatabase.memory()); +DatabaseConnection _backgroundConnection() => DatabaseConnection(NativeDatabase.memory()); void main() { test( diff --git a/packages/stream_chat_persistence/test/src/mapper/location_mapper_test.dart b/packages/stream_chat_persistence/test/src/mapper/location_mapper_test.dart index 2d87c6cfd9..ba728659d2 100644 --- a/packages/stream_chat_persistence/test/src/mapper/location_mapper_test.dart +++ b/packages/stream_chat_persistence/test/src/mapper/location_mapper_test.dart @@ -92,8 +92,7 @@ void main() { expect(convertedLocation.messageId, originalLocation.messageId); expect(convertedLocation.latitude, originalLocation.latitude); expect(convertedLocation.longitude, originalLocation.longitude); - expect(convertedLocation.createdByDeviceId, - originalLocation.createdByDeviceId); + expect(convertedLocation.createdByDeviceId, originalLocation.createdByDeviceId); expect(convertedLocation.endAt, originalLocation.endAt); expect(convertedLocation.createdAt, originalLocation.createdAt); expect(convertedLocation.updatedAt, originalLocation.updatedAt); diff --git a/packages/stream_chat_persistence/test/src/mapper/message_mapper_test.dart b/packages/stream_chat_persistence/test/src/mapper/message_mapper_test.dart index c4682b5e9e..be7cc74e9b 100644 --- a/packages/stream_chat_persistence/test/src/mapper/message_mapper_test.dart +++ b/packages/stream_chat_persistence/test/src/mapper/message_mapper_test.dart @@ -115,8 +115,7 @@ void main() { expect(message.shadowed, entity.shadowed); expect(message.showInChannel, entity.showInChannel); for (var i = 0; i < message.mentionedUsers.length; i++) { - final entityMentionedUser = - User.fromJson(jsonDecode(entity.mentionedUsers[i])); + final entityMentionedUser = User.fromJson(jsonDecode(entity.mentionedUsers[i])); expect(message.mentionedUsers[i].id, entityMentionedUser.id); } expect(message.replyCount, entity.replyCount); @@ -249,8 +248,7 @@ void main() { expect(entity.shadowed, message.shadowed); expect(entity.showInChannel, message.showInChannel); expect(entity.replyCount, message.replyCount); - expect( - entity.mentionedUsers, message.mentionedUsers.map(jsonEncode).toList()); + expect(entity.mentionedUsers, message.mentionedUsers.map(jsonEncode).toList()); expect(entity.state, jsonEncode(message.state)); expect(entity.localUpdatedAt, isSameDateAs(message.localUpdatedAt)); expect(entity.remoteUpdatedAt, isSameDateAs(message.remoteUpdatedAt)); diff --git a/packages/stream_chat_persistence/test/src/mapper/pinned_message_mapper_test.dart b/packages/stream_chat_persistence/test/src/mapper/pinned_message_mapper_test.dart index 9a9171887e..170026665c 100644 --- a/packages/stream_chat_persistence/test/src/mapper/pinned_message_mapper_test.dart +++ b/packages/stream_chat_persistence/test/src/mapper/pinned_message_mapper_test.dart @@ -115,8 +115,7 @@ void main() { expect(message.shadowed, entity.shadowed); expect(message.showInChannel, entity.showInChannel); for (var i = 0; i < message.mentionedUsers.length; i++) { - final entityMentionedUser = - User.fromJson(jsonDecode(entity.mentionedUsers[i])); + final entityMentionedUser = User.fromJson(jsonDecode(entity.mentionedUsers[i])); expect(message.mentionedUsers[i].id, entityMentionedUser.id); } expect(message.replyCount, entity.replyCount); @@ -249,8 +248,7 @@ void main() { expect(entity.shadowed, message.shadowed); expect(entity.showInChannel, message.showInChannel); expect(entity.replyCount, message.replyCount); - expect( - entity.mentionedUsers, message.mentionedUsers.map(jsonEncode).toList()); + expect(entity.mentionedUsers, message.mentionedUsers.map(jsonEncode).toList()); expect(entity.state, jsonEncode(message.state)); expect(entity.localUpdatedAt, isSameDateAs(message.localUpdatedAt)); expect(entity.remoteUpdatedAt, isSameDateAs(message.remoteUpdatedAt)); diff --git a/packages/stream_chat_persistence/test/src/utils/date_matcher.dart b/packages/stream_chat_persistence/test/src/utils/date_matcher.dart index d19d25f2a6..1d49e4a719 100644 --- a/packages/stream_chat_persistence/test/src/utils/date_matcher.dart +++ b/packages/stream_chat_persistence/test/src/utils/date_matcher.dart @@ -1,7 +1,6 @@ import 'package:flutter_test/flutter_test.dart'; -Matcher isSameDateAs(DateTime? targetDate) => - _IsSameDateAs(targetDate: targetDate); +Matcher isSameDateAs(DateTime? targetDate) => _IsSameDateAs(targetDate: targetDate); class _IsSameDateAs extends Matcher { const _IsSameDateAs({required this.targetDate}); @@ -19,6 +18,5 @@ class _IsSameDateAs extends Matcher { } @override - Description describe(Description description) => - description.add('is same date as $targetDate'); + Description describe(Description description) => description.add('is same date as $targetDate'); } diff --git a/packages/stream_chat_persistence/test/stream_chat_persistence_client_test.dart b/packages/stream_chat_persistence/test/stream_chat_persistence_client_test.dart index cf25e4c68b..3fe63bef88 100644 --- a/packages/stream_chat_persistence/test/stream_chat_persistence_client_test.dart +++ b/packages/stream_chat_persistence/test/stream_chat_persistence_client_test.dart @@ -115,20 +115,16 @@ void main() { const parentId = 'testParentId'; final replies = List.generate(3, (index) => Message(id: 'testId$index')); - when(() => mockDatabase.messageDao.getThreadMessagesByParentId(parentId)) - .thenAnswer((_) async => replies); + when(() => mockDatabase.messageDao.getThreadMessagesByParentId(parentId)).thenAnswer((_) async => replies); final fetchedReplies = await client.getReplies(parentId); expect(fetchedReplies.length, replies.length); - verify(() => - mockDatabase.messageDao.getThreadMessagesByParentId(parentId)) - .called(1); + verify(() => mockDatabase.messageDao.getThreadMessagesByParentId(parentId)).called(1); }); test('getConnectionInfo', () async { final event = Event(); - when(() => mockDatabase.connectionEventDao.connectionEvent) - .thenAnswer((_) async => event); + when(() => mockDatabase.connectionEventDao.connectionEvent).thenAnswer((_) async => event); final fetchedEvent = await client.getConnectionInfo(); expect(fetchedEvent, isNotNull); @@ -138,8 +134,7 @@ void main() { test('getLastSyncAt', () async { final lastSync = DateTime.now(); - when(() => mockDatabase.connectionEventDao.lastSyncAt) - .thenAnswer((_) async => lastSync); + when(() => mockDatabase.connectionEventDao.lastSyncAt).thenAnswer((_) async => lastSync); final fetchedLastSync = await client.getLastSyncAt(); expect(fetchedLastSync, isSameDateAs(lastSync)); @@ -148,28 +143,23 @@ void main() { test('updateConnectionInfo', () async { final event = Event(); - when(() => mockDatabase.connectionEventDao.updateConnectionEvent(event)) - .thenAnswer((_) async => 1); + when(() => mockDatabase.connectionEventDao.updateConnectionEvent(event)).thenAnswer((_) async => 1); await client.updateConnectionInfo(event); - verify(() => mockDatabase.connectionEventDao.updateConnectionEvent(event)) - .called(1); + verify(() => mockDatabase.connectionEventDao.updateConnectionEvent(event)).called(1); }); test('updateLastSyncAt', () async { final lastSync = DateTime.now(); - when(() => mockDatabase.connectionEventDao.updateLastSyncAt(lastSync)) - .thenAnswer((_) async => 1); + when(() => mockDatabase.connectionEventDao.updateLastSyncAt(lastSync)).thenAnswer((_) async => 1); await client.updateLastSyncAt(lastSync); - verify(() => mockDatabase.connectionEventDao.updateLastSyncAt(lastSync)) - .called(1); + verify(() => mockDatabase.connectionEventDao.updateLastSyncAt(lastSync)).called(1); }); test('getChannelCids', () async { final channelCids = List.generate(3, (index) => 'testCid$index'); - when(() => mockDatabase.channelDao.cids) - .thenAnswer((_) async => channelCids); + when(() => mockDatabase.channelDao.cids).thenAnswer((_) async => channelCids); final fetchedChannelCids = await client.getChannelCids(); expect(fetchedChannelCids.length, channelCids.length); @@ -179,8 +169,7 @@ void main() { test('getChannelByCid', () async { const cid = 'testType:testId'; final channelModel = ChannelModel(cid: cid); - when(() => mockDatabase.channelDao.getChannelByCid(cid)) - .thenAnswer((_) async => channelModel); + when(() => mockDatabase.channelDao.getChannelByCid(cid)).thenAnswer((_) async => channelModel); final fetchedChannelModel = await client.getChannelByCid(cid); expect(fetchedChannelModel, isNotNull); @@ -191,8 +180,7 @@ void main() { test('getMembersByCid', () async { const cid = 'testCid'; final members = List.generate(3, (index) => Member()); - when(() => mockDatabase.memberDao.getMembersByCid(cid)) - .thenAnswer((_) async => members); + when(() => mockDatabase.memberDao.getMembersByCid(cid)).thenAnswer((_) async => members); final fetchedMembers = await client.getMembersByCid(cid); expect(fetchedMembers.length, members.length); @@ -209,8 +197,7 @@ void main() { lastReadMessageId: 'lastMessageId$index', ), ); - when(() => mockDatabase.readDao.getReadsByCid(cid)) - .thenAnswer((_) async => reads); + when(() => mockDatabase.readDao.getReadsByCid(cid)).thenAnswer((_) async => reads); final fetchedReads = await client.getReadsByCid(cid); expect(fetchedReads.length, reads.length); @@ -220,8 +207,7 @@ void main() { test('getMessagesByCid', () async { const cid = 'testCid'; final messages = List.generate(3, (index) => Message()); - when(() => mockDatabase.messageDao.getMessagesByCid(cid)) - .thenAnswer((_) async => messages); + when(() => mockDatabase.messageDao.getMessagesByCid(cid)).thenAnswer((_) async => messages); final fetchedMessages = await client.getMessagesByCid(cid); expect(fetchedMessages.length, messages.length); @@ -231,13 +217,11 @@ void main() { test('getPinnedMessagesByCid', () async { const cid = 'testCid'; final messages = List.generate(3, (index) => Message()); - when(() => mockDatabase.pinnedMessageDao.getMessagesByCid(cid)) - .thenAnswer((_) async => messages); + when(() => mockDatabase.pinnedMessageDao.getMessagesByCid(cid)).thenAnswer((_) async => messages); final fetchedMessages = await client.getPinnedMessagesByCid(cid); expect(fetchedMessages.length, messages.length); - verify(() => mockDatabase.pinnedMessageDao.getMessagesByCid(cid)) - .called(1); + verify(() => mockDatabase.pinnedMessageDao.getMessagesByCid(cid)).called(1); }); test('getChannelStateByCid', () async { @@ -262,21 +246,14 @@ void main() { ), ); - when(() => mockDatabase.draftMessageDao.getDraftMessageByCid(cid)) - .thenAnswer((_) async => draft); - - when(() => mockDatabase.memberDao.getMembersByCid(cid)) - .thenAnswer((_) async => members); - when(() => mockDatabase.readDao.getReadsByCid(cid)) - .thenAnswer((_) async => reads); - when(() => mockDatabase.channelDao.getChannelByCid(cid)) - .thenAnswer((_) async => channel); - when(() => mockDatabase.messageDao.getMessagesByCid(cid)) - .thenAnswer((_) async => messages); - when(() => mockDatabase.pinnedMessageDao.getMessagesByCid(cid)) - .thenAnswer((_) async => messages); - when(() => mockDatabase.draftMessageDao.getDraftMessageByCid(cid)) - .thenAnswer((_) async => draft); + when(() => mockDatabase.draftMessageDao.getDraftMessageByCid(cid)).thenAnswer((_) async => draft); + + when(() => mockDatabase.memberDao.getMembersByCid(cid)).thenAnswer((_) async => members); + when(() => mockDatabase.readDao.getReadsByCid(cid)).thenAnswer((_) async => reads); + when(() => mockDatabase.channelDao.getChannelByCid(cid)).thenAnswer((_) async => channel); + when(() => mockDatabase.messageDao.getMessagesByCid(cid)).thenAnswer((_) async => messages); + when(() => mockDatabase.pinnedMessageDao.getMessagesByCid(cid)).thenAnswer((_) async => messages); + when(() => mockDatabase.draftMessageDao.getDraftMessageByCid(cid)).thenAnswer((_) async => draft); final fetchedChannelState = await client.getChannelStateByCid(cid); expect(fetchedChannelState.messages?.length, messages.length); @@ -290,10 +267,8 @@ void main() { verify(() => mockDatabase.readDao.getReadsByCid(cid)).called(1); verify(() => mockDatabase.channelDao.getChannelByCid(cid)).called(1); verify(() => mockDatabase.messageDao.getMessagesByCid(cid)).called(1); - verify(() => mockDatabase.pinnedMessageDao.getMessagesByCid(cid)) - .called(1); - verify(() => mockDatabase.draftMessageDao.getDraftMessageByCid(cid)) - .called(1); + verify(() => mockDatabase.pinnedMessageDao.getMessagesByCid(cid)).called(1); + verify(() => mockDatabase.draftMessageDao.getDraftMessageByCid(cid)).called(1); }); group('getChannelState', () { @@ -323,21 +298,15 @@ void main() { ) .toList(growable: false); - when(() => mockDatabase.channelQueryDao.getChannels()) - .thenAnswer((_) async => channels); - when(() => mockDatabase.memberDao.getMembersByCid(cid)) - .thenAnswer((_) async => members); - when(() => mockDatabase.readDao.getReadsByCid(cid)) - .thenAnswer((_) async => reads); - when(() => mockDatabase.channelDao.getChannelByCid(cid)) - .thenAnswer((_) async => channel); - when(() => mockDatabase.messageDao.getMessagesByCid(cid, - messagePagination: any(named: 'messagePagination'))) - .thenAnswer((_) async => messages); - when(() => mockDatabase.pinnedMessageDao.getMessagesByCid(cid)) - .thenAnswer((_) async => messages); - when(() => mockDatabase.draftMessageDao.getDraftMessageByCid(cid)) - .thenAnswer((_) async => null); + when(() => mockDatabase.channelQueryDao.getChannels()).thenAnswer((_) async => channels); + when(() => mockDatabase.memberDao.getMembersByCid(cid)).thenAnswer((_) async => members); + when(() => mockDatabase.readDao.getReadsByCid(cid)).thenAnswer((_) async => reads); + when(() => mockDatabase.channelDao.getChannelByCid(cid)).thenAnswer((_) async => channel); + when( + () => mockDatabase.messageDao.getMessagesByCid(cid, messagePagination: any(named: 'messagePagination')), + ).thenAnswer((_) async => messages); + when(() => mockDatabase.pinnedMessageDao.getMessagesByCid(cid)).thenAnswer((_) async => messages); + when(() => mockDatabase.draftMessageDao.getDraftMessageByCid(cid)).thenAnswer((_) async => null); final fetchedChannelStates = await client.getChannelStates(); expect(fetchedChannelStates.length, channelStates.length); @@ -347,8 +316,7 @@ void main() { final fetched = fetchedChannelStates[i]; expect(fetched.members?.length, original.members?.length); expect(fetched.messages?.length, original.messages?.length); - expect( - fetched.pinnedMessages?.length, original.pinnedMessages?.length); + expect(fetched.pinnedMessages?.length, original.pinnedMessages?.length); expect(fetched.read?.length, original.read?.length); expect(fetched.channel!.cid, original.channel!.cid); } @@ -357,93 +325,74 @@ void main() { verify(() => mockDatabase.memberDao.getMembersByCid(cid)).called(3); verify(() => mockDatabase.readDao.getReadsByCid(cid)).called(3); verify(() => mockDatabase.channelDao.getChannelByCid(cid)).called(3); - verify(() => mockDatabase.messageDao.getMessagesByCid(cid, - messagePagination: any(named: 'messagePagination'))).called(3); - verify(() => mockDatabase.pinnedMessageDao.getMessagesByCid(cid)) - .called(3); - verify(() => mockDatabase.draftMessageDao.getDraftMessageByCid(cid)) - .called(3); + verify( + () => mockDatabase.messageDao.getMessagesByCid(cid, messagePagination: any(named: 'messagePagination')), + ).called(3); + verify(() => mockDatabase.pinnedMessageDao.getMessagesByCid(cid)).called(3); + verify(() => mockDatabase.draftMessageDao.getDraftMessageByCid(cid)).called(3); }); }); test('updateChannelQueries', () async { final filter = Filter.in_('members', const ['testUserId']); const cids = []; - when(() => - mockDatabase.channelQueryDao.updateChannelQueries(filter, cids)) - .thenAnswer((_) => Future.value()); + when(() => mockDatabase.channelQueryDao.updateChannelQueries(filter, cids)).thenAnswer((_) => Future.value()); await client.updateChannelQueries(filter, cids); - verify(() => - mockDatabase.channelQueryDao.updateChannelQueries(filter, cids)) - .called(1); + verify(() => mockDatabase.channelQueryDao.updateChannelQueries(filter, cids)).called(1); }); test('deleteMessageById', () async { const messageId = 'testMessageId'; - when(() => mockDatabase.messageDao.deleteMessageByIds([messageId])) - .thenAnswer((_) async => 1); + when(() => mockDatabase.messageDao.deleteMessageByIds([messageId])).thenAnswer((_) async => 1); await client.deleteMessageById(messageId); - verify(() => mockDatabase.messageDao.deleteMessageByIds([messageId])) - .called(1); + verify(() => mockDatabase.messageDao.deleteMessageByIds([messageId])).called(1); }); test('deletePinnedMessageById', () async { const messageId = 'testMessageId'; - when(() => mockDatabase.pinnedMessageDao.deleteMessageByIds([messageId])) - .thenAnswer((_) async => 1); + when(() => mockDatabase.pinnedMessageDao.deleteMessageByIds([messageId])).thenAnswer((_) async => 1); await client.deletePinnedMessageById(messageId); - verify(() => - mockDatabase.pinnedMessageDao.deleteMessageByIds([messageId])) - .called(1); + verify(() => mockDatabase.pinnedMessageDao.deleteMessageByIds([messageId])).called(1); }); test('deleteMessageByIds', () async { const messageIds = []; - when(() => mockDatabase.messageDao.deleteMessageByIds(messageIds)) - .thenAnswer((_) async => 1); + when(() => mockDatabase.messageDao.deleteMessageByIds(messageIds)).thenAnswer((_) async => 1); await client.deleteMessageByIds(messageIds); - verify(() => mockDatabase.messageDao.deleteMessageByIds(messageIds)) - .called(1); + verify(() => mockDatabase.messageDao.deleteMessageByIds(messageIds)).called(1); }); test('deletePinnedMessageByIds', () async { const messageIds = []; - when(() => mockDatabase.pinnedMessageDao.deleteMessageByIds(messageIds)) - .thenAnswer((_) async => 1); + when(() => mockDatabase.pinnedMessageDao.deleteMessageByIds(messageIds)).thenAnswer((_) async => 1); await client.deletePinnedMessageByIds(messageIds); - verify(() => mockDatabase.pinnedMessageDao.deleteMessageByIds(messageIds)) - .called(1); + verify(() => mockDatabase.pinnedMessageDao.deleteMessageByIds(messageIds)).called(1); }); test('deleteMessageByCid', () async { const cid = 'testCid'; - when(() => mockDatabase.messageDao.deleteMessageByCids([cid])) - .thenAnswer((_) async => 1); + when(() => mockDatabase.messageDao.deleteMessageByCids([cid])).thenAnswer((_) async => 1); await client.deleteMessageByCid(cid); - verify(() => mockDatabase.messageDao.deleteMessageByCids([cid])) - .called(1); + verify(() => mockDatabase.messageDao.deleteMessageByCids([cid])).called(1); }); test('deletePinnedMessageByCid', () async { const cid = 'testCid'; - when(() => mockDatabase.pinnedMessageDao.deleteMessageByCids([cid])) - .thenAnswer((_) async => 1); + when(() => mockDatabase.pinnedMessageDao.deleteMessageByCids([cid])).thenAnswer((_) async => 1); await client.deletePinnedMessageByCid(cid); - verify(() => mockDatabase.pinnedMessageDao.deleteMessageByCids([cid])) - .called(1); + verify(() => mockDatabase.pinnedMessageDao.deleteMessageByCids([cid])).called(1); }); test('deleteMessageByCids', () async { const cids = []; - when(() => mockDatabase.messageDao.deleteMessageByCids(cids)) - .thenAnswer((_) async => 1); + when(() => mockDatabase.messageDao.deleteMessageByCids(cids)).thenAnswer((_) async => 1); await client.deleteMessageByCids(cids); verify(() => mockDatabase.messageDao.deleteMessageByCids(cids)).called(1); @@ -451,18 +400,15 @@ void main() { test('deletePinnedMessageByCids', () async { const cids = []; - when(() => mockDatabase.pinnedMessageDao.deleteMessageByCids(cids)) - .thenAnswer((_) async => 1); + when(() => mockDatabase.pinnedMessageDao.deleteMessageByCids(cids)).thenAnswer((_) async => 1); await client.deletePinnedMessageByCids(cids); - verify(() => mockDatabase.pinnedMessageDao.deleteMessageByCids(cids)) - .called(1); + verify(() => mockDatabase.pinnedMessageDao.deleteMessageByCids(cids)).called(1); }); test('deleteChannels', () async { const cids = []; - when(() => mockDatabase.channelDao.deleteChannelByCids(cids)) - .thenAnswer((_) async => 1); + when(() => mockDatabase.channelDao.deleteChannelByCids(cids)).thenAnswer((_) async => 1); await client.deleteChannels(cids); verify(() => mockDatabase.channelDao.deleteChannelByCids(cids)).called(1); @@ -472,12 +418,10 @@ void main() { const cid = 'testCid'; final messages = List.generate(3, (index) => Message()); - when(() => mockDatabase.messageDao.bulkUpdateMessages({cid: messages})) - .thenAnswer((_) => Future.value()); + when(() => mockDatabase.messageDao.bulkUpdateMessages({cid: messages})).thenAnswer((_) => Future.value()); await client.updateMessages(cid, messages); - verify(() => mockDatabase.messageDao.bulkUpdateMessages({cid: messages})) - .called(1); + verify(() => mockDatabase.messageDao.bulkUpdateMessages({cid: messages})).called(1); }); test('updatePinnedMessages', () async { @@ -495,8 +439,7 @@ void main() { test('getChannelThreads', () async { const cid = 'testCid'; - final messages = - List.generate(3, (index) => Message(parentId: 'testParentId$index')); + final messages = List.generate(3, (index) => Message(parentId: 'testParentId$index')); final threads = messages.fold>>( {}, (prev, curr) => prev @@ -506,8 +449,7 @@ void main() { ifAbsent: () => [], ), ); - when(() => mockDatabase.messageDao.getThreadMessages(cid)) - .thenAnswer((realInvocation) async => messages); + when(() => mockDatabase.messageDao.getThreadMessages(cid)).thenAnswer((realInvocation) async => messages); final fetchedThreads = await client.getChannelThreads(cid); expect(fetchedThreads.length, threads.length); @@ -523,8 +465,7 @@ void main() { test('updateChannels', () async { const cid = 'testType:testId'; final channels = List.generate(3, (index) => ChannelModel(cid: cid)); - when(() => mockDatabase.channelDao.updateChannels(channels)) - .thenAnswer((_) => Future.value()); + when(() => mockDatabase.channelDao.updateChannels(channels)).thenAnswer((_) => Future.value()); await client.updateChannels(channels); verify(() => mockDatabase.channelDao.updateChannels(channels)).called(1); @@ -533,10 +474,8 @@ void main() { test('updatePolls', () async { const name = 'testPollName'; final options = List.generate(3, (index) => PollOption(text: '$index')); - final polls = - List.generate(3, (index) => Poll(name: name, options: options)); - when(() => mockDatabase.pollDao.updatePolls(polls)) - .thenAnswer((_) => Future.value()); + final polls = List.generate(3, (index) => Poll(name: name, options: options)); + when(() => mockDatabase.pollDao.updatePolls(polls)).thenAnswer((_) => Future.value()); await client.updatePolls(polls); verify(() => mockDatabase.pollDao.updatePolls(polls)).called(1); @@ -544,43 +483,35 @@ void main() { test('deletePollsByIds', () async { final pollIds = ['testPollId']; - when(() => mockDatabase.pollDao.deletePollsByIds(pollIds)) - .thenAnswer((_) => Future.value()); + when(() => mockDatabase.pollDao.deletePollsByIds(pollIds)).thenAnswer((_) => Future.value()); await client.deletePollsByIds(pollIds); verify(() => mockDatabase.pollDao.deletePollsByIds(pollIds)).called(1); }); test('updatePollVotes', () async { - final pollVotes = List.generate( - 3, (index) => PollVote(id: '$index', optionId: 'testOptionId$index')); - when(() => mockDatabase.pollVoteDao.updatePollVotes(pollVotes)) - .thenAnswer((_) => Future.value()); + final pollVotes = List.generate(3, (index) => PollVote(id: '$index', optionId: 'testOptionId$index')); + when(() => mockDatabase.pollVoteDao.updatePollVotes(pollVotes)).thenAnswer((_) => Future.value()); await client.updatePollVotes(pollVotes); - verify(() => mockDatabase.pollVoteDao.updatePollVotes(pollVotes)) - .called(1); + verify(() => mockDatabase.pollVoteDao.updatePollVotes(pollVotes)).called(1); }); test('deletePollVotesByPollIds', () async { final pollIds = ['testPollId']; - when(() => mockDatabase.pollVoteDao.deletePollVotesByPollIds(pollIds)) - .thenAnswer((_) => Future.value()); + when(() => mockDatabase.pollVoteDao.deletePollVotesByPollIds(pollIds)).thenAnswer((_) => Future.value()); await client.deletePollVotesByPollIds(pollIds); - verify(() => mockDatabase.pollVoteDao.deletePollVotesByPollIds(pollIds)) - .called(1); + verify(() => mockDatabase.pollVoteDao.deletePollVotesByPollIds(pollIds)).called(1); }); test('updateMembers', () async { const cid = 'testCid'; final members = List.generate(3, (index) => Member()); - when(() => mockDatabase.memberDao.bulkUpdateMembers({cid: members})) - .thenAnswer((_) => Future.value()); + when(() => mockDatabase.memberDao.bulkUpdateMembers({cid: members})).thenAnswer((_) => Future.value()); await client.updateMembers(cid, members); - verify(() => mockDatabase.memberDao.bulkUpdateMembers({cid: members})) - .called(1); + verify(() => mockDatabase.memberDao.bulkUpdateMembers({cid: members})).called(1); }); test('updateReads', () async { @@ -593,18 +524,15 @@ void main() { lastReadMessageId: 'lastMessageId$index', ), ); - when(() => mockDatabase.readDao.bulkUpdateReads({cid: reads})) - .thenAnswer((_) => Future.value()); + when(() => mockDatabase.readDao.bulkUpdateReads({cid: reads})).thenAnswer((_) => Future.value()); await client.updateReads(cid, reads); - verify(() => mockDatabase.readDao.bulkUpdateReads({cid: reads})) - .called(1); + verify(() => mockDatabase.readDao.bulkUpdateReads({cid: reads})).called(1); }); test('updateUsers', () async { final users = List.generate(3, (index) => User(id: 'testUserId$index')); - when(() => mockDatabase.userDao.updateUsers(users)) - .thenAnswer((_) => Future.value()); + when(() => mockDatabase.userDao.updateUsers(users)).thenAnswer((_) => Future.value()); await client.updateUsers(users); verify(() => mockDatabase.userDao.updateUsers(users)).called(1); @@ -615,12 +543,10 @@ void main() { 3, (index) => Reaction(type: 'testType$index'), ); - when(() => mockDatabase.reactionDao.updateReactions(reactions)) - .thenAnswer((_) => Future.value()); + when(() => mockDatabase.reactionDao.updateReactions(reactions)).thenAnswer((_) => Future.value()); await client.updateReactions(reactions); - verify(() => mockDatabase.reactionDao.updateReactions(reactions)) - .called(1); + verify(() => mockDatabase.reactionDao.updateReactions(reactions)).called(1); }); test('updatePinnedMessageReactions', () async { @@ -628,43 +554,33 @@ void main() { 3, (index) => Reaction(type: 'testType$index'), ); - when(() => - mockDatabase.pinnedMessageReactionDao.updateReactions(reactions)) - .thenAnswer((_) => Future.value()); + when(() => mockDatabase.pinnedMessageReactionDao.updateReactions(reactions)).thenAnswer((_) => Future.value()); await client.updatePinnedMessageReactions(reactions); - verify(() => - mockDatabase.pinnedMessageReactionDao.updateReactions(reactions)) - .called(1); + verify(() => mockDatabase.pinnedMessageReactionDao.updateReactions(reactions)).called(1); }); test('deleteReactionsByMessageId', () async { final messageIds = []; - when(() => - mockDatabase.reactionDao.deleteReactionsByMessageIds(messageIds)) - .thenAnswer((_) => Future.value()); + when(() => mockDatabase.reactionDao.deleteReactionsByMessageIds(messageIds)).thenAnswer((_) => Future.value()); await client.deleteReactionsByMessageId(messageIds); - verify(() => - mockDatabase.reactionDao.deleteReactionsByMessageIds(messageIds)) - .called(1); + verify(() => mockDatabase.reactionDao.deleteReactionsByMessageIds(messageIds)).called(1); }); test('deletePinnedMessageReactionsByMessageId', () async { final messageIds = []; - when(() => mockDatabase.pinnedMessageReactionDao - .deleteReactionsByMessageIds(messageIds)) - .thenAnswer((_) => Future.value()); + when( + () => mockDatabase.pinnedMessageReactionDao.deleteReactionsByMessageIds(messageIds), + ).thenAnswer((_) => Future.value()); await client.deletePinnedMessageReactionsByMessageId(messageIds); - verify(() => mockDatabase.pinnedMessageReactionDao - .deleteReactionsByMessageIds(messageIds)).called(1); + verify(() => mockDatabase.pinnedMessageReactionDao.deleteReactionsByMessageIds(messageIds)).called(1); }); test('deleteMembersByCids', () async { final cids = []; - when(() => mockDatabase.memberDao.deleteMemberByCids(cids)) - .thenAnswer((_) => Future.value()); + when(() => mockDatabase.memberDao.deleteMemberByCids(cids)).thenAnswer((_) => Future.value()); await client.deleteMembersByCids(cids); verify(() => mockDatabase.memberDao.deleteMemberByCids(cids)).called(1); @@ -672,12 +588,10 @@ void main() { test('deleteDraftMessagesByCids', () async { final cids = []; - when(() => mockDatabase.draftMessageDao.deleteDraftMessagesByCids(cids)) - .thenAnswer((_) => Future.value()); + when(() => mockDatabase.draftMessageDao.deleteDraftMessagesByCids(cids)).thenAnswer((_) => Future.value()); await client.deleteDraftMessagesByCids(cids); - verify(() => mockDatabase.draftMessageDao.deleteDraftMessagesByCids(cids)) - .called(1); + verify(() => mockDatabase.draftMessageDao.deleteDraftMessagesByCids(cids)).called(1); }); test('getDraftMessageByCid', () async { @@ -693,16 +607,14 @@ void main() { ), ); - when(() => mockDatabase.draftMessageDao.getDraftMessageByCid(cid)) - .thenAnswer((_) async => draft); + when(() => mockDatabase.draftMessageDao.getDraftMessageByCid(cid)).thenAnswer((_) async => draft); final fetchedDraft = await client.getDraftMessageByCid(cid); expect(fetchedDraft, isNotNull); expect(fetchedDraft!.channelCid, cid); expect(fetchedDraft.message.id, draft.message.id); expect(fetchedDraft.message.text, draft.message.text); - verify(() => mockDatabase.draftMessageDao.getDraftMessageByCid(cid)) - .called(1); + verify(() => mockDatabase.draftMessageDao.getDraftMessageByCid(cid)).called(1); }); test('updateDraftMessages', () async { @@ -718,24 +630,22 @@ void main() { ), ); - when(() => mockDatabase.draftMessageDao.updateDraftMessages(drafts)) - .thenAnswer((_) async {}); + when(() => mockDatabase.draftMessageDao.updateDraftMessages(drafts)).thenAnswer((_) async {}); await client.updateDraftMessages(drafts); - verify(() => mockDatabase.draftMessageDao.updateDraftMessages(drafts)) - .called(1); + verify(() => mockDatabase.draftMessageDao.updateDraftMessages(drafts)).called(1); }); test('deleteDraftMessageByCid', () async { const cid = 'testCid'; const parentId = 'testParentId'; - when(() => mockDatabase.draftMessageDao.deleteDraftMessageByCid(cid, - parentId: parentId)).thenAnswer((_) async {}); + when( + () => mockDatabase.draftMessageDao.deleteDraftMessageByCid(cid, parentId: parentId), + ).thenAnswer((_) async {}); await client.deleteDraftMessageByCid(cid, parentId: parentId); - verify(() => mockDatabase.draftMessageDao - .deleteDraftMessageByCid(cid, parentId: parentId)).called(1); + verify(() => mockDatabase.draftMessageDao.deleteDraftMessageByCid(cid, parentId: parentId)).called(1); }); test('getLocationsByCid', () async { @@ -754,8 +664,7 @@ void main() { ), ); - when(() => mockDatabase.locationDao.getLocationsByCid(cid)) - .thenAnswer((_) async => locations); + when(() => mockDatabase.locationDao.getLocationsByCid(cid)).thenAnswer((_) async => locations); final fetchedLocations = await client.getLocationsByCid(cid); expect(fetchedLocations.length, locations.length); @@ -775,14 +684,12 @@ void main() { updatedAt: DateTime.now(), ); - when(() => mockDatabase.locationDao.getLocationByMessageId(messageId)) - .thenAnswer((_) async => location); + when(() => mockDatabase.locationDao.getLocationByMessageId(messageId)).thenAnswer((_) async => location); final fetchedLocation = await client.getLocationByMessageId(messageId); expect(fetchedLocation, isNotNull); expect(fetchedLocation!.messageId, messageId); - verify(() => mockDatabase.locationDao.getLocationByMessageId(messageId)) - .called(1); + verify(() => mockDatabase.locationDao.getLocationByMessageId(messageId)).called(1); }); test('updateLocations', () async { @@ -800,22 +707,18 @@ void main() { ), ); - when(() => mockDatabase.locationDao.updateLocations(locations)) - .thenAnswer((_) async {}); + when(() => mockDatabase.locationDao.updateLocations(locations)).thenAnswer((_) async {}); await client.updateLocations(locations); - verify(() => mockDatabase.locationDao.updateLocations(locations)) - .called(1); + verify(() => mockDatabase.locationDao.updateLocations(locations)).called(1); }); test('deleteLocationsByCid', () async { const cid = 'testCid'; - when(() => mockDatabase.locationDao.deleteLocationsByCid(cid)) - .thenAnswer((_) async {}); + when(() => mockDatabase.locationDao.deleteLocationsByCid(cid)).thenAnswer((_) async {}); await client.deleteLocationsByCid(cid); - verify(() => mockDatabase.locationDao.deleteLocationsByCid(cid)) - .called(1); + verify(() => mockDatabase.locationDao.deleteLocationsByCid(cid)).called(1); }); test('deleteLocationsByMessageIds', () async { @@ -834,21 +737,24 @@ void main() { const userId = 'testUserId'; const cid = 'testCid'; - test('calls deleteMessagesByUser on both DAOs with hard delete', - () async { - when(() => mockDatabase.messageDao.deleteMessagesByUser( - cid: cid, - userId: userId, - hardDelete: true, - deletedAt: any(named: 'deletedAt'), - )).thenAnswer((_) async => 1); - - when(() => mockDatabase.pinnedMessageDao.deleteMessagesByUser( - cid: cid, - userId: userId, - hardDelete: true, - deletedAt: any(named: 'deletedAt'), - )).thenAnswer((_) async => 1); + test('calls deleteMessagesByUser on both DAOs with hard delete', () async { + when( + () => mockDatabase.messageDao.deleteMessagesByUser( + cid: cid, + userId: userId, + hardDelete: true, + deletedAt: any(named: 'deletedAt'), + ), + ).thenAnswer((_) async => 1); + + when( + () => mockDatabase.pinnedMessageDao.deleteMessagesByUser( + cid: cid, + userId: userId, + hardDelete: true, + deletedAt: any(named: 'deletedAt'), + ), + ).thenAnswer((_) async => 1); await client.deleteMessagesFromUser( cid: cid, @@ -856,38 +762,45 @@ void main() { hardDelete: true, ); - verify(() => mockDatabase.messageDao.deleteMessagesByUser( - cid: cid, - userId: userId, - hardDelete: true, - deletedAt: any(named: 'deletedAt'), - )).called(1); - - verify(() => mockDatabase.pinnedMessageDao.deleteMessagesByUser( - cid: cid, - userId: userId, - hardDelete: true, - deletedAt: any(named: 'deletedAt'), - )).called(1); + verify( + () => mockDatabase.messageDao.deleteMessagesByUser( + cid: cid, + userId: userId, + hardDelete: true, + deletedAt: any(named: 'deletedAt'), + ), + ).called(1); + + verify( + () => mockDatabase.pinnedMessageDao.deleteMessagesByUser( + cid: cid, + userId: userId, + hardDelete: true, + deletedAt: any(named: 'deletedAt'), + ), + ).called(1); }); - test('calls deleteMessagesByUser on both DAOs with soft delete', - () async { + test('calls deleteMessagesByUser on both DAOs with soft delete', () async { final deletedAt = DateTime.now(); - when(() => mockDatabase.messageDao.deleteMessagesByUser( - cid: cid, - userId: userId, - hardDelete: false, - deletedAt: deletedAt, - )).thenAnswer((_) async => 1); - - when(() => mockDatabase.pinnedMessageDao.deleteMessagesByUser( - cid: cid, - userId: userId, - hardDelete: false, - deletedAt: deletedAt, - )).thenAnswer((_) async => 1); + when( + () => mockDatabase.messageDao.deleteMessagesByUser( + cid: cid, + userId: userId, + hardDelete: false, + deletedAt: deletedAt, + ), + ).thenAnswer((_) async => 1); + + when( + () => mockDatabase.pinnedMessageDao.deleteMessagesByUser( + cid: cid, + userId: userId, + hardDelete: false, + deletedAt: deletedAt, + ), + ).thenAnswer((_) async => 1); await client.deleteMessagesFromUser( cid: cid, @@ -896,50 +809,62 @@ void main() { deletedAt: deletedAt, ); - verify(() => mockDatabase.messageDao.deleteMessagesByUser( - cid: cid, - userId: userId, - hardDelete: false, - deletedAt: deletedAt, - )).called(1); - - verify(() => mockDatabase.pinnedMessageDao.deleteMessagesByUser( - cid: cid, - userId: userId, - hardDelete: false, - deletedAt: deletedAt, - )).called(1); + verify( + () => mockDatabase.messageDao.deleteMessagesByUser( + cid: cid, + userId: userId, + hardDelete: false, + deletedAt: deletedAt, + ), + ).called(1); + + verify( + () => mockDatabase.pinnedMessageDao.deleteMessagesByUser( + cid: cid, + userId: userId, + hardDelete: false, + deletedAt: deletedAt, + ), + ).called(1); }); test('calls deleteMessagesByUser without cid when cid is null', () async { - when(() => mockDatabase.messageDao.deleteMessagesByUser( - userId: userId, - hardDelete: true, - deletedAt: any(named: 'deletedAt'), - )).thenAnswer((_) async => 1); - - when(() => mockDatabase.pinnedMessageDao.deleteMessagesByUser( - userId: userId, - hardDelete: true, - deletedAt: any(named: 'deletedAt'), - )).thenAnswer((_) async => 1); + when( + () => mockDatabase.messageDao.deleteMessagesByUser( + userId: userId, + hardDelete: true, + deletedAt: any(named: 'deletedAt'), + ), + ).thenAnswer((_) async => 1); + + when( + () => mockDatabase.pinnedMessageDao.deleteMessagesByUser( + userId: userId, + hardDelete: true, + deletedAt: any(named: 'deletedAt'), + ), + ).thenAnswer((_) async => 1); await client.deleteMessagesFromUser( userId: userId, hardDelete: true, ); - verify(() => mockDatabase.messageDao.deleteMessagesByUser( - userId: userId, - hardDelete: true, - deletedAt: any(named: 'deletedAt'), - )).called(1); - - verify(() => mockDatabase.pinnedMessageDao.deleteMessagesByUser( - userId: userId, - hardDelete: true, - deletedAt: any(named: 'deletedAt'), - )).called(1); + verify( + () => mockDatabase.messageDao.deleteMessagesByUser( + userId: userId, + hardDelete: true, + deletedAt: any(named: 'deletedAt'), + ), + ).called(1); + + verify( + () => mockDatabase.pinnedMessageDao.deleteMessagesByUser( + userId: userId, + hardDelete: true, + deletedAt: any(named: 'deletedAt'), + ), + ).called(1); }); }); diff --git a/pubspec.lock b/pubspec.lock index 9eed33197b..b4283bfbc5 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,18 +5,18 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: e55636ed79578b9abca5fecf9437947798f5ef7456308b5cb85720b793eac92f + sha256: "8d7ff3948166b8ec5da0fbb5962000926b8e02f2ed9b3e51d1738905fbd4c98d" url: "https://pub.dev" source: hosted - version: "82.0.0" + version: "93.0.0" analyzer: dependency: transitive description: name: analyzer - sha256: "13c1e6c6fd460522ea840abec3f677cc226f5fec7872c04ad7b425517ccf54f7" + sha256: de7148ed2fcec579b19f122c1800933dfa028f6d9fd38a152b04b1516cec120b url: "https://pub.dev" source: hosted - version: "7.4.4" + version: "10.0.1" ansi_styles: dependency: transitive description: @@ -61,10 +61,10 @@ packages: dependency: transitive description: name: built_value - sha256: ea90e81dc4a25a043d9bee692d20ed6d1c4a1662a28c03a96417446c093ed6b4 + sha256: "7931c90b84bc573fef103548e354258ae4c9d28d140e41961df6843c5d60d4d8" url: "https://pub.dev" source: hosted - version: "8.9.5" + version: "8.12.3" charcode: dependency: transitive description: @@ -77,18 +77,18 @@ packages: dependency: transitive description: name: checked_yaml - sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff + sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" url: "https://pub.dev" source: hosted - version: "2.0.3" + version: "2.0.4" cli_launcher: dependency: transitive description: name: cli_launcher - sha256: "5e7e0282b79e8642edd6510ee468ae2976d847a0a29b3916e85f5fa1bfe24005" + sha256: "17d2744fb9a254c49ec8eda582536abe714ea0131533e24389843a4256f82eac" url: "https://pub.dev" source: hosted - version: "0.3.1" + version: "0.3.2+1" cli_util: dependency: transitive description: @@ -97,22 +97,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.4.2" - clock: - dependency: transitive - description: - name: clock - sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b - url: "https://pub.dev" - source: hosted - version: "1.1.2" code_builder: dependency: "direct dev" description: name: code_builder - sha256: "0ec10bf4a89e4c613960bf1e8b42c64127021740fb21640c29c909826a5eea3e" + sha256: "6a6cab2ba4680d6423f34a9b972a4c9a94ebe1b62ecec4e1a1f2cba91fd1319d" url: "https://pub.dev" source: hosted - version: "4.10.1" + version: "4.11.1" collection: dependency: transitive description: @@ -125,10 +117,10 @@ packages: dependency: transitive description: name: conventional_commit - sha256: fad254feb6fb8eace2be18855176b0a4b97e0d50e416ff0fe590d5ba83735d34 + sha256: c40b1b449ce2a63fa2ce852f35e3890b1e182f5951819934c0e4a66254bc0dc3 url: "https://pub.dev" source: hosted - version: "0.6.1" + version: "0.6.1+1" convert: dependency: transitive description: @@ -141,18 +133,18 @@ packages: dependency: transitive description: name: crypto - sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf url: "https://pub.dev" source: hosted - version: "3.0.6" + version: "3.0.7" dart_style: dependency: "direct dev" description: name: dart_style - sha256: "27eb0ae77836989a3bc541ce55595e8ceee0992807f14511552a898ddd0d88ac" + sha256: "15a7db352c8fc6a4d2bc475ba901c25b39fe7157541da4c16eacce6f8be83e49" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.1.5" file: dependency: transitive description: @@ -189,10 +181,10 @@ packages: dependency: transitive description: name: http - sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b" + sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.6.0" http_parser: dependency: transitive description: @@ -201,14 +193,6 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.2" - intl: - dependency: transitive - description: - name: intl - sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf - url: "https://pub.dev" - source: hosted - version: "0.19.0" io: dependency: transitive description: @@ -221,42 +205,42 @@ packages: dependency: transitive description: name: json_annotation - sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" + sha256: "805fa86df56383000f640384b282ce0cb8431f1a7a2396de92fb66186d8c57df" url: "https://pub.dev" source: hosted - version: "4.9.0" + version: "4.10.0" matcher: dependency: transitive description: name: matcher - sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6" url: "https://pub.dev" source: hosted - version: "0.12.17" + version: "0.12.18" melos: dependency: "direct dev" description: name: melos - sha256: "3f3ab3f902843d1e5a1b1a4dd39a4aca8ba1056f2d32fd8995210fa2843f646f" + sha256: "4280dc46bd5b741887cce1e67e5c1a6aaf3c22310035cf5bd33dceeeda62ed22" url: "https://pub.dev" source: hosted - version: "6.3.2" + version: "6.3.3" meta: dependency: transitive description: name: meta - sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + sha256: "9f29b9bcc8ee287b1a31e0d01be0eae99a930dbffdaecf04b3f3d82a969f296f" url: "https://pub.dev" source: hosted - version: "1.16.0" + version: "1.18.1" mustache_template: dependency: transitive description: name: mustache_template - sha256: a46e26f91445bfb0b60519be280555b06792460b27b19e2b19ad5b9740df5d1c + sha256: "4326d0002ff58c74b9486990ccbdab08157fca3c996fe9e197aff9d61badf307" url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "2.0.3" package_config: dependency: transitive description: @@ -285,18 +269,18 @@ packages: dependency: transitive description: name: pool - sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" + sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d" url: "https://pub.dev" source: hosted - version: "1.5.1" + version: "1.5.2" process: dependency: transitive description: name: process - sha256: "107d8be718f120bbba9dcd1e95e3bd325b1b4a4f07db64154635ba03f2567a0d" + sha256: c6248e4526673988586e8c00bb22a49210c258dc91df5227d5da9748ecf79744 url: "https://pub.dev" source: hosted - version: "5.0.3" + version: "5.0.5" prompts: dependency: transitive description: @@ -317,10 +301,10 @@ packages: dependency: transitive description: name: pub_updater - sha256: "54e8dc865349059ebe7f163d6acce7c89eb958b8047e6d6e80ce93b13d7c9e60" + sha256: "739a0161d73a6974c0675b864fb0cf5147305f7b077b7f03a58fa7a9ab3e7e7d" url: "https://pub.dev" source: hosted - version: "0.4.0" + version: "0.5.0" pubspec_parse: dependency: transitive description: @@ -381,10 +365,10 @@ packages: dependency: transitive description: name: test_api - sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636" url: "https://pub.dev" source: hosted - version: "0.7.4" + version: "0.7.9" typed_data: dependency: transitive description: @@ -397,10 +381,10 @@ packages: dependency: transitive description: name: watcher - sha256: "69da27e49efa56a15f8afe8f4438c4ec02eff0a117df1b22ea4aad194fe1c104" + sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.2.1" web: dependency: transitive description: @@ -421,9 +405,9 @@ packages: dependency: transitive description: name: yaml_edit - sha256: fb38626579fb345ad00e674e2af3a5c9b0cc4b9bfb8fd7f7ff322c7c9e62aef5 + sha256: ec709065bb2c911b336853b67f3732dd13e0336bd065cc2f1061d7610ddf45e3 url: "https://pub.dev" source: hosted - version: "2.2.2" + version: "2.2.3" sdks: - dart: ">=3.6.2 <4.0.0" + dart: ">=3.10.0 <4.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index a65d06f2b8..2ac3430d00 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_flutter_workspace environment: - sdk: ^3.6.2 + sdk: ^3.10.0 dev_dependencies: code_builder: ^4.10.1 diff --git a/sample_app/android/app/src/main/AndroidManifest.xml b/sample_app/android/app/src/main/AndroidManifest.xml index 6fb866b633..28a3985094 100644 --- a/sample_app/android/app/src/main/AndroidManifest.xml +++ b/sample_app/android/app/src/main/AndroidManifest.xml @@ -7,6 +7,7 @@ + @@ -14,6 +15,10 @@ + + + + ???? CFBundleVersion 1.0 - MinimumOSVersion - 12.0 diff --git a/sample_app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/sample_app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index c53e2b314e..9c12df59c6 100644 --- a/sample_app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/sample_app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -26,6 +26,7 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit" shouldUseLaunchSchemeArgsEnv = "YES"> _showAndroidNotification({ await flutterLocalNotificationsPlugin.initialize( initializationSettings, onDidReceiveNotificationResponse: (NotificationResponse response) async { - debugPrint( - '[onBackgroundMessage] #firebase; notification clicked: ${response.payload}'); + debugPrint('[onBackgroundMessage] #firebase; notification clicked: ${response.payload}'); // The payload contains the channel information (channelType:channelId) // This will be handled when the app is opened }, @@ -199,10 +199,10 @@ Future _showAndroidNotification({ ); debugPrint( - '[onBackgroundMessage] #firebase; android notification shown successfully: ID=$notificationId, Title="$title"'); + '[onBackgroundMessage] #firebase; android notification shown successfully: ID=$notificationId, Title="$title"', + ); } catch (e) { - debugPrint( - '[onBackgroundMessage] #firebase; failed to show notification: $e'); + debugPrint('[onBackgroundMessage] #firebase; failed to show notification: $e'); } } @@ -220,21 +220,17 @@ Future _createNotificationChannel() async { ); // Create the channel - final androidPlugin = - flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation< - AndroidFlutterLocalNotificationsPlugin>(); + final androidPlugin = flutterLocalNotificationsPlugin + .resolvePlatformSpecificImplementation(); if (androidPlugin != null) { await androidPlugin.createNotificationChannel(channel); - debugPrint( - '[onBackgroundMessage] #firebase; notification channel created'); + debugPrint('[onBackgroundMessage] #firebase; notification channel created'); } else { - debugPrint( - '[onBackgroundMessage] #firebase; failed to resolve Android plugin'); + debugPrint('[onBackgroundMessage] #firebase; failed to resolve Android plugin'); } } catch (e) { - debugPrint( - '[onBackgroundMessage] #firebase; notification channel failed: $e'); + debugPrint('[onBackgroundMessage] #firebase; notification channel failed: $e'); } } @@ -315,10 +311,7 @@ class _StreamChatSampleAppState extends State Future _initFirebaseMessaging(StreamChatClient client) async { userIdSubscription?.cancel(); - userIdSubscription = client.state.currentUserStream - .map((it) => it?.id) - .distinct() - .listen((userId) async { + userIdSubscription = client.state.currentUserStream.map((it) => it?.id).distinct().listen((userId) async { // User logged in if (userId != null) { // Requests notification permission. @@ -326,23 +319,24 @@ class _StreamChatSampleAppState extends State // Sets callback for background messages. FirebaseMessaging.onBackgroundMessage(_onFirebaseBackgroundMessage); // Sets callback for the notification click event. - firebaseSubscriptions.add(FirebaseMessaging.onMessageOpenedApp - .listen(_onFirebaseMessageOpenedApp(client))); + firebaseSubscriptions.add(FirebaseMessaging.onMessageOpenedApp.listen(_onFirebaseMessageOpenedApp(client))); // Sets callback for foreground messages - firebaseSubscriptions.add(FirebaseMessaging.onMessage - .listen(_onFirebaseForegroundMessage(client))); + firebaseSubscriptions.add(FirebaseMessaging.onMessage.listen(_onFirebaseForegroundMessage(client))); // Sets callback for the token refresh event. - firebaseSubscriptions.add(FirebaseMessaging.instance.onTokenRefresh - .listen(_onFirebaseTokenRefresh(client))); - - final token = await FirebaseMessaging.instance.getToken(); - debugPrint('[onTokenInit] #firebase; token: $token'); - if (token != null) { - // replace with your push provider, e.g., 'PushProvider.xiaomi' - const pushProvider = PushProvider.firebase; - - // add Token to Stream - await client.addDevice(token, pushProvider); + firebaseSubscriptions.add(FirebaseMessaging.instance.onTokenRefresh.listen(_onFirebaseTokenRefresh(client))); + + try { + final token = await FirebaseMessaging.instance.getToken(); + debugPrint('[onTokenInit] #firebase; token: $token'); + if (token != null) { + // replace with your push provider, e.g., 'PushProvider.xiaomi' + const pushProvider = PushProvider.firebase; + + // add Token to Stream + await client.addDevice(token, pushProvider); + } + } catch (e) { + debugPrint('[onTokenInit] #firebase; failed to get token: $e'); } } // User logged out @@ -386,8 +380,7 @@ class _StreamChatSampleAppState extends State /// Constructs callback for foreground notification handling. OnRemoteMessage _onFirebaseForegroundMessage(StreamChatClient client) { return (message) async { - debugPrint( - '[onForegroundMessage] #firebase; message: ${message.toMap()}'); + debugPrint('[onForegroundMessage] #firebase; message: ${message.toMap()}'); }; } @@ -449,8 +442,7 @@ class _StreamChatSampleAppState extends State if (localNotificationObserver != null) { localNotificationObserver!.dispose(); } - localNotificationObserver = LocalNotificationObserver( - _initNotifier.initData!.client, _navigatorKey); + localNotificationObserver = LocalNotificationObserver(_initNotifier.initData!.client, _navigatorKey); return router ??= GoRouter( refreshListenable: _initNotifier, @@ -458,10 +450,9 @@ class _StreamChatSampleAppState extends State navigatorKey: _navigatorKey, observers: [localNotificationObserver!], redirect: (context, state) { - final loggedIn = - _initNotifier.initData?.client.state.currentUser != null; - final loggingIn = state.matchedLocation == Routes.CHOOSE_USER.path || - state.matchedLocation == Routes.ADVANCED_OPTIONS.path; + final loggedIn = _initNotifier.initData?.client.state.currentUser != null; + final loggingIn = + state.matchedLocation == Routes.CHOOSE_USER.path || state.matchedLocation == Routes.ADVANCED_OPTIONS.path; if (!loggedIn) { return loggingIn ? null : Routes.CHOOSE_USER.path; @@ -496,8 +487,14 @@ class _StreamChatSampleAppState extends State defaultValue: 0, ), builder: (context, snapshot) => MaterialApp.router( - theme: ThemeData.light(), - darkTheme: ThemeData.dark(), + theme: ThemeData( + brightness: .light, + extensions: [StreamTheme.light()], + ), + darkTheme: ThemeData( + brightness: .dark, + extensions: [StreamTheme.dark()], + ), themeMode: const { -1: ThemeMode.dark, 0: ThemeMode.system, @@ -515,9 +512,22 @@ class _StreamChatSampleAppState extends State ], builder: (context, child) => StreamChat( client: _initNotifier.initData!.client, + componentBuilders: StreamComponentBuilders( + extensions: streamChatComponentBuilders( + messageWidget: customMessageWidgetBuilder, + ), + ), streamChatConfigData: StreamChatConfigurationData( - draftMessagesEnabled: true, + draftMessagesEnabled: false, + attachmentBuilders: [ + LocationAttachmentBuilder( + onAttachmentTap: (context, location) { + showLocationDetailDialog(context: context, location: location); + }, + ), + ], ), + child: child, ), routerConfig: _setupRouter(), diff --git a/sample_app/lib/firebase_options.dart b/sample_app/lib/firebase_options.dart index 683caab1ce..9fd43a439e 100644 --- a/sample_app/lib/firebase_options.dart +++ b/sample_app/lib/firebase_options.dart @@ -1,8 +1,7 @@ // File generated by FlutterFire CLI. // ignore_for_file: lines_longer_than_80_chars, avoid_classes_with_only_static_members import 'package:firebase_core/firebase_core.dart' show FirebaseOptions; -import 'package:flutter/foundation.dart' - show defaultTargetPlatform, kIsWeb, TargetPlatform; +import 'package:flutter/foundation.dart' show defaultTargetPlatform, kIsWeb, TargetPlatform; /// Default [FirebaseOptions] for use with your Firebase apps. /// @@ -70,10 +69,8 @@ class DefaultFirebaseOptions { projectId: 'stream-chat-internal', databaseURL: 'https://stream-chat-internal.firebaseio.com', storageBucket: 'stream-chat-internal.appspot.com', - androidClientId: - '674907137625-0aa50j6b2i35ef9c52lsbk1v16otl492.apps.googleusercontent.com', - iosClientId: - '674907137625-flarfn9cefu4lermgpbc4b8rm8l15ian.apps.googleusercontent.com', + androidClientId: '674907137625-0aa50j6b2i35ef9c52lsbk1v16otl492.apps.googleusercontent.com', + iosClientId: '674907137625-flarfn9cefu4lermgpbc4b8rm8l15ian.apps.googleusercontent.com', iosBundleId: 'io.getstream.flutter', ); @@ -84,10 +81,8 @@ class DefaultFirebaseOptions { projectId: 'stream-chat-internal', databaseURL: 'https://stream-chat-internal.firebaseio.com', storageBucket: 'stream-chat-internal.appspot.com', - androidClientId: - '674907137625-0aa50j6b2i35ef9c52lsbk1v16otl492.apps.googleusercontent.com', - iosClientId: - '674907137625-p3msks3snq0h22l7ekpqcf0frr0vt8mg.apps.googleusercontent.com', + androidClientId: '674907137625-0aa50j6b2i35ef9c52lsbk1v16otl492.apps.googleusercontent.com', + iosClientId: '674907137625-p3msks3snq0h22l7ekpqcf0frr0vt8mg.apps.googleusercontent.com', iosBundleId: 'io.getstream.streamChatV1', ); } diff --git a/sample_app/lib/pages/advanced_options_page.dart b/sample_app/lib/pages/advanced_options_page.dart index 81ab732c02..60ef370698 100644 --- a/sample_app/lib/pages/advanced_options_page.dart +++ b/sample_app/lib/pages/advanced_options_page.dart @@ -132,11 +132,12 @@ class _AdvancedOptionsPageState extends State { centerTitle: true, title: Text( AppLocalizations.of(context).advancedOptions, - style: StreamChatTheme.of(context).textTheme.headlineBold.copyWith( - color: StreamChatTheme.of(context).colorTheme.textHighEmphasis), + style: StreamChatTheme.of( + context, + ).textTheme.headlineBold.copyWith(color: StreamChatTheme.of(context).colorTheme.textHighEmphasis), ), leading: IconButton( - icon: const StreamSvgIcon(icon: StreamSvgIcons.left), + icon: Icon(context.streamIcons.chevronLeft), color: StreamChatTheme.of(context).colorTheme.textHighEmphasis, onPressed: () { Navigator.pop(context); @@ -164,9 +165,7 @@ class _AdvancedOptionsPageState extends State { validator: (value) { if (value!.isEmpty) { setState(() { - _apiKeyError = AppLocalizations.of(context) - .apiKeyError - .toUpperCase(); + _apiKeyError = AppLocalizations.of(context).apiKeyError.toUpperCase(); }); return _apiKeyError; } @@ -174,9 +173,7 @@ class _AdvancedOptionsPageState extends State { }, style: TextStyle( fontSize: 14, - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis, + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis, ), decoration: InputDecoration( errorStyle: const TextStyle(height: 0, fontSize: 0), @@ -185,9 +182,7 @@ class _AdvancedOptionsPageState extends State { fontWeight: FontWeight.bold, color: _apiKeyError != null ? StreamChatTheme.of(context).colorTheme.accentError - : StreamChatTheme.of(context) - .colorTheme - .textLowEmphasis, + : StreamChatTheme.of(context).colorTheme.textLowEmphasis, ), border: UnderlineInputBorder( borderRadius: BorderRadius.circular(8), @@ -214,9 +209,7 @@ class _AdvancedOptionsPageState extends State { validator: (value) { if (value!.isEmpty) { setState(() { - _userIdError = AppLocalizations.of(context) - .userIdError - .toUpperCase(); + _userIdError = AppLocalizations.of(context).userIdError.toUpperCase(); }); return _userIdError; } @@ -224,9 +217,7 @@ class _AdvancedOptionsPageState extends State { }, style: TextStyle( fontSize: 14, - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis, + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis, ), textInputAction: TextInputAction.next, decoration: InputDecoration( @@ -236,9 +227,7 @@ class _AdvancedOptionsPageState extends State { fontSize: 14, color: _userIdError != null ? StreamChatTheme.of(context).colorTheme.accentError - : StreamChatTheme.of(context) - .colorTheme - .textLowEmphasis, + : StreamChatTheme.of(context).colorTheme.textLowEmphasis, ), border: UnderlineInputBorder( borderRadius: BorderRadius.circular(8), @@ -264,9 +253,7 @@ class _AdvancedOptionsPageState extends State { validator: (value) { if (value!.isEmpty) { setState(() { - _userTokenError = AppLocalizations.of(context) - .userTokenError - .toUpperCase(); + _userTokenError = AppLocalizations.of(context).userTokenError.toUpperCase(); }); return _userTokenError; } @@ -274,9 +261,7 @@ class _AdvancedOptionsPageState extends State { }, style: TextStyle( fontSize: 14, - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis, + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis, ), textInputAction: TextInputAction.next, decoration: InputDecoration( @@ -286,9 +271,7 @@ class _AdvancedOptionsPageState extends State { fontSize: 14, color: _userTokenError != null ? StreamChatTheme.of(context).colorTheme.accentError - : StreamChatTheme.of(context) - .colorTheme - .textLowEmphasis, + : StreamChatTheme.of(context).colorTheme.textLowEmphasis, ), border: UnderlineInputBorder( borderRadius: BorderRadius.circular(8), @@ -309,9 +292,7 @@ class _AdvancedOptionsPageState extends State { labelStyle: TextStyle( fontSize: 14, fontWeight: FontWeight.bold, - color: StreamChatTheme.of(context) - .colorTheme - .textLowEmphasis, + color: StreamChatTheme.of(context).colorTheme.textLowEmphasis, ), border: UnderlineInputBorder( borderRadius: BorderRadius.circular(8), @@ -326,14 +307,12 @@ class _AdvancedOptionsPageState extends State { ElevatedButton( style: ButtonStyle( backgroundColor: WidgetStateProperty.all( - Theme.of(context).brightness == Brightness.light - ? StreamChatTheme.of(context) - .colorTheme - .accentPrimary - : Colors.white), + Theme.of(context).brightness == Brightness.light + ? StreamChatTheme.of(context).colorTheme.accentPrimary + : Colors.white, + ), elevation: WidgetStateProperty.all(0), - padding: WidgetStateProperty.all( - const EdgeInsets.symmetric(vertical: 16)), + padding: WidgetStateProperty.all(const EdgeInsets.symmetric(vertical: 16)), shape: WidgetStateProperty.all( RoundedRectangleBorder( borderRadius: BorderRadius.circular(26), @@ -346,9 +325,7 @@ class _AdvancedOptionsPageState extends State { style: TextStyle( fontSize: 16, color: Theme.of(context).brightness != Brightness.light - ? StreamChatTheme.of(context) - .colorTheme - .accentPrimary + ? StreamChatTheme.of(context).colorTheme.accentPrimary : Colors.white, ), ), diff --git a/sample_app/lib/pages/channel_file_display_screen.dart b/sample_app/lib/pages/channel_file_display_screen.dart index a3228ca68f..e543dc1aed 100644 --- a/sample_app/lib/pages/channel_file_display_screen.dart +++ b/sample_app/lib/pages/channel_file_display_screen.dart @@ -13,8 +13,7 @@ class ChannelFileDisplayScreen extends StatefulWidget { final StreamMessageThemeData messageTheme; @override - State createState() => - _ChannelFileDisplayScreenState(); + State createState() => _ChannelFileDisplayScreenState(); } class _ChannelFileDisplayScreenState extends State { @@ -55,88 +54,82 @@ class _ChannelFileDisplayScreenState extends State { ), body: ValueListenableBuilder( valueListenable: controller, - builder: ( - BuildContext context, - PagedValue value, - Widget? child, - ) { - return value.when( - (items, nextPageKey, error) { - if (items.isEmpty) { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - StreamSvgIcon( - icon: StreamSvgIcons.files, - size: 136, - color: StreamChatTheme.of(context).colorTheme.disabled, + builder: + ( + BuildContext context, + PagedValue value, + Widget? child, + ) { + return value.when( + (items, nextPageKey, error) { + if (items.isEmpty) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + context.streamIcons.fileBend, + size: 136, + color: StreamChatTheme.of(context).colorTheme.disabled, + ), + const SizedBox(height: 16), + Text( + AppLocalizations.of(context).noFiles, + style: TextStyle( + fontSize: 14, + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis, + ), + ), + const SizedBox(height: 8), + Text( + AppLocalizations.of(context).filesAppearHere, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 14, + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(0.5), + ), + ), + ], ), - const SizedBox(height: 16), - Text( - AppLocalizations.of(context).noFiles, - style: TextStyle( - fontSize: 14, - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis, - ), - ), - const SizedBox(height: 8), - Text( - AppLocalizations.of(context).filesAppearHere, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 14, - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(0.5), - ), - ), - ], - ), - ); - } - final media = {}; - - for (final item in items) { - item.message.attachments - .where((e) => e.type == 'file') - .forEach((e) { - media[e] = item.message; - }); - } + ); + } + final media = {}; - return LazyLoadScrollView( - onEndOfPage: () async { - if (nextPageKey != null) { - controller.loadMore(nextPageKey); + for (final item in items) { + item.message.attachments.where((e) => e.type == 'file').forEach((e) { + media[e] = item.message; + }); } + + return LazyLoadScrollView( + onEndOfPage: () async { + if (nextPageKey != null) { + controller.loadMore(nextPageKey); + } + }, + child: ListView.builder( + itemBuilder: (context, position) { + return Padding( + padding: const EdgeInsets.all(1), + child: Padding( + padding: const EdgeInsets.all(8), + child: StreamFileAttachment( + message: media.values.toList()[position], + file: media.keys.toList()[position], + ), + ), + ); + }, + itemCount: media.length, + ), + ); }, - child: ListView.builder( - itemBuilder: (context, position) { - return Padding( - padding: const EdgeInsets.all(1), - child: Padding( - padding: const EdgeInsets.all(8), - child: StreamFileAttachment( - message: media.values.toList()[position], - file: media.keys.toList()[position], - ), - ), - ); - }, - itemCount: media.length, + loading: () => const Center( + child: CircularProgressIndicator(), ), + error: (_) => const Offstage(), ); }, - loading: () => const Center( - child: CircularProgressIndicator(), - ), - error: (_) => const Offstage(), - ); - }, ), ); } diff --git a/sample_app/lib/pages/channel_list_page.dart b/sample_app/lib/pages/channel_list_page.dart index 8355dd0413..d02dd41bbc 100644 --- a/sample_app/lib/pages/channel_list_page.dart +++ b/sample_app/lib/pages/channel_list_page.dart @@ -33,16 +33,16 @@ class _ChannelListPageState extends State { bool _isSelected(int index) => _currentIndex == index; List get _navBarItems { + final icons = context.streamIcons; + return [ BottomNavigationBarItem( icon: Stack( clipBehavior: Clip.none, children: [ - StreamSvgIcon( - icon: StreamSvgIcons.message, - color: _isSelected(0) - ? StreamChatTheme.of(context).colorTheme.textHighEmphasis - : Colors.grey, + Icon( + _isSelected(0) ? icons.bubble3Solid : icons.bubble3ChatMessage, + color: _isSelected(0) ? StreamChatTheme.of(context).colorTheme.textHighEmphasis : Colors.grey, ), const PositionedDirectional( top: -4, @@ -54,11 +54,9 @@ class _ChannelListPageState extends State { label: AppLocalizations.of(context).chats, ), BottomNavigationBarItem( - icon: StreamSvgIcon( - icon: StreamSvgIcons.mentions, - color: _isSelected(1) - ? StreamChatTheme.of(context).colorTheme.textHighEmphasis - : Colors.grey, + icon: Icon( + _isSelected(1) ? icons.atSolid : icons.at, + color: _isSelected(1) ? StreamChatTheme.of(context).colorTheme.textHighEmphasis : Colors.grey, ), label: AppLocalizations.of(context).mentions, ), @@ -67,10 +65,8 @@ class _ChannelListPageState extends State { clipBehavior: Clip.none, children: [ Icon( - Icons.message_outlined, - color: _isSelected(2) - ? StreamChatTheme.of(context).colorTheme.textHighEmphasis - : Colors.grey, + _isSelected(2) ? icons.bubbleText6Solid : icons.bubbleText6ChatMessage, + color: _isSelected(2) ? StreamChatTheme.of(context).colorTheme.textHighEmphasis : Colors.grey, ), PositionedDirectional( top: -4, @@ -83,19 +79,15 @@ class _ChannelListPageState extends State { ), BottomNavigationBarItem( icon: Icon( - Icons.edit_note_rounded, - color: _isSelected(3) - ? StreamChatTheme.of(context).colorTheme.textHighEmphasis - : Colors.grey, + _isSelected(3) ? icons.editBigSolid : icons.editBig, + color: _isSelected(3) ? StreamChatTheme.of(context).colorTheme.textHighEmphasis : Colors.grey, ), label: 'Drafts', ), BottomNavigationBarItem( icon: Icon( - Icons.bookmark_border_rounded, - color: _isSelected(4) - ? StreamChatTheme.of(context).colorTheme.textHighEmphasis - : Colors.grey, + icons.bookmark, + color: _isSelected(4) ? StreamChatTheme.of(context).colorTheme.textHighEmphasis : Colors.grey, ), label: 'Reminders', ), @@ -118,8 +110,7 @@ class _ChannelListPageState extends State { onNewChatButtonTap: () { GoRouter.of(context).pushNamed(Routes.NEW_CHAT.name); }, - preNavigationCallback: () => - FocusScope.of(context).requestFocus(FocusNode()), + preNavigationCallback: () => FocusScope.of(context).requestFocus(FocusNode()), ), drawer: LeftDrawer( user: user, @@ -130,11 +121,9 @@ class _ChannelListPageState extends State { currentIndex: _currentIndex, items: _navBarItems, selectedLabelStyle: StreamChatTheme.of(context).textTheme.footnoteBold, - unselectedLabelStyle: - StreamChatTheme.of(context).textTheme.footnoteBold, + unselectedLabelStyle: StreamChatTheme.of(context).textTheme.footnoteBold, type: BottomNavigationBarType.fixed, - selectedItemColor: - StreamChatTheme.of(context).colorTheme.textHighEmphasis, + selectedItemColor: StreamChatTheme.of(context).colorTheme.textHighEmphasis, unselectedItemColor: Colors.grey, onTap: (index) { setState(() => _currentIndex = index); @@ -159,11 +148,7 @@ class _ChannelListPageState extends State { void initState() { super.initState(); if (!kIsWeb) { - badgeListener = StreamChat.of(context) - .client - .state - .totalUnreadCountStream - .listen((count) { + badgeListener = StreamChat.of(context).client.state.totalUnreadCountStream.listen((count) { if (count > 0) { FlutterAppBadger.updateBadgeCount(count); } else { @@ -211,10 +196,9 @@ class LeftDrawer extends StatelessWidget { child: Row( children: [ StreamUserAvatar( + size: .lg, user: user, - showOnlineStatus: false, - constraints: - BoxConstraints.tight(const Size.fromRadius(20)), + showOnlineIndicator: false, ), Padding( padding: const EdgeInsets.only(left: 16), @@ -230,12 +214,9 @@ class LeftDrawer extends StatelessWidget { ), ), ListTile( - leading: StreamSvgIcon( - icon: StreamSvgIcons.penWrite, - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(.5), + leading: Icon( + context.streamIcons.pencil, + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(.5), ), onTap: () { Navigator.of(context).pop(); @@ -249,12 +230,9 @@ class LeftDrawer extends StatelessWidget { ), ), ListTile( - leading: StreamSvgIcon( - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(.5), - icon: StreamSvgIcons.contacts, + leading: Icon( + context.streamIcons.users, + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(.5), ), onTap: () { Navigator.of(context).pop(); @@ -284,12 +262,9 @@ class LeftDrawer extends StatelessWidget { router.goNamed(Routes.CHOOSE_USER.name); }, - leading: StreamSvgIcon( - icon: StreamSvgIcons.user, - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(.5), + leading: Icon( + context.streamIcons.people, + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(.5), ), title: Text( AppLocalizations.of(context).signOut, @@ -300,9 +275,7 @@ class LeftDrawer extends StatelessWidget { trailing: IconButton( iconSize: 24, icon: const StreamSvgIcon(icon: StreamSvgIcons.moon), - color: StreamChatTheme.of(context) - .colorTheme - .textLowEmphasis, + color: StreamChatTheme.of(context).colorTheme.textLowEmphasis, onPressed: () async { final theme = Theme.of(context); final sp = await StreamingSharedPreferences.instance; diff --git a/sample_app/lib/pages/channel_media_display_screen.dart b/sample_app/lib/pages/channel_media_display_screen.dart index 2504e3ae00..1725c44e8e 100644 --- a/sample_app/lib/pages/channel_media_display_screen.dart +++ b/sample_app/lib/pages/channel_media_display_screen.dart @@ -15,8 +15,7 @@ class ChannelMediaDisplayScreen extends StatefulWidget { final StreamMessageThemeData messageTheme; @override - State createState() => - _ChannelMediaDisplayScreenState(); + State createState() => _ChannelMediaDisplayScreenState(); } class _ChannelMediaDisplayScreenState extends State { @@ -57,8 +56,7 @@ class _ChannelMediaDisplayScreenState extends State { ), body: ValueListenableBuilder( valueListenable: controller, - builder: (BuildContext context, - PagedValue value, Widget? child) { + builder: (BuildContext context, PagedValue value, Widget? child) { return value.when( (items, nextPageKey, error) { if (items.isEmpty) { @@ -66,8 +64,8 @@ class _ChannelMediaDisplayScreenState extends State { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - StreamSvgIcon( - icon: StreamSvgIcons.pictures, + Icon( + context.streamIcons.images1Alt, size: 136, color: StreamChatTheme.of(context).colorTheme.disabled, ), @@ -76,22 +74,16 @@ class _ChannelMediaDisplayScreenState extends State { AppLocalizations.of(context).noMedia, style: TextStyle( fontSize: 14, - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis, + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis, ), ), const SizedBox(height: 8), Text( - AppLocalizations.of(context) - .photosOrVideosWillAppearHere, + AppLocalizations.of(context).photosOrVideosWillAppearHere, textAlign: TextAlign.center, style: TextStyle( fontSize: 14, - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(0.5), + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(0.5), ), ), ], @@ -102,25 +94,23 @@ class _ChannelMediaDisplayScreenState extends State { for (final item in value.asSuccess.items) { item.message.attachments - .where((e) => - (e.type == 'image' || e.type == 'video') && - e.ogScrapeUrl == null) + .where((e) => (e.type == 'image' || e.type == 'video') && e.ogScrapeUrl == null) .forEach((e) { - VideoPlayerController? controller; - if (e.type == 'video') { - final cachedController = controllerCache[e.assetUrl]; + VideoPlayerController? controller; + if (e.type == 'video') { + final cachedController = controllerCache[e.assetUrl]; - if (cachedController == null) { - final url = Uri.parse(e.assetUrl!); - controller = VideoPlayerController.networkUrl(url); - controller.initialize(); - controllerCache[e.assetUrl] = controller; - } else { - controller = cachedController; - } - } - media.add(_AssetPackage(e, item.message, controller)); - }); + if (cachedController == null) { + final url = Uri.parse(e.assetUrl!); + controller = VideoPlayerController.networkUrl(url); + controller.initialize(); + controllerCache[e.assetUrl] = controller; + } else { + controller = cachedController; + } + } + media.add(_AssetPackage(e, item.message, controller)); + }); } return LazyLoadScrollView( @@ -130,8 +120,7 @@ class _ChannelMediaDisplayScreenState extends State { } }, child: GridView.builder( - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 3), + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3), itemBuilder: (context, position) { final channel = StreamChannel.of(context).channel; return Padding( @@ -161,10 +150,8 @@ class _ChannelMediaDisplayScreenState extends State { } router.pushNamed( Routes.CHANNEL_PAGE.name, - pathParameters: - Routes.CHANNEL_PAGE.params(channel), - queryParameters: - Routes.CHANNEL_PAGE.queryParams(m), + pathParameters: Routes.CHANNEL_PAGE.params(channel), + queryParameters: Routes.CHANNEL_PAGE.queryParams(m), ); }, ), diff --git a/sample_app/lib/pages/channel_page.dart b/sample_app/lib/pages/channel_page.dart index 18d831528b..54f2896c7b 100644 --- a/sample_app/lib/pages/channel_page.dart +++ b/sample_app/lib/pages/channel_page.dart @@ -1,16 +1,15 @@ // ignore_for_file: deprecated_member_use, avoid_redundant_argument_values +import 'dart:math' as math; +import 'dart:ui'; + import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:sample_app/pages/thread_page.dart'; import 'package:sample_app/routes/routes.dart'; -import 'package:sample_app/widgets/location/location_attachment.dart'; -import 'package:sample_app/widgets/location/location_detail_dialog.dart'; import 'package:sample_app/widgets/location/location_picker_dialog.dart'; import 'package:sample_app/widgets/location/location_picker_option.dart'; -import 'package:sample_app/widgets/message_info_sheet.dart'; -import 'package:sample_app/widgets/reminder_dialog.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; class ChannelPage extends StatefulWidget { @@ -51,6 +50,13 @@ class _ChannelPageState extends State { }); } + void _editMessage(Message message) { + _messageInputController.editMessage(message); + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + _focusNode!.requestFocus(); + }); + } + @override Widget build(BuildContext context) { final theme = StreamChatTheme.of(context); @@ -98,9 +104,10 @@ class _ChannelPageState extends State { initialScrollIndex: widget.initialScrollIndex, initialAlignment: widget.initialAlignment, highlightInitialMessage: widget.highlightInitialMessage, - //onMessageSwiped: _reply, + onEditMessageTap: _editMessage, + onReplyTap: _reply, messageFilter: defaultFilter, - messageBuilder: customMessageBuilder, + messageBuilder: _messageBuilder, threadBuilder: (_, parentMessage) { return ThreadPage(parent: parentMessage!); }, @@ -133,8 +140,7 @@ class _ChannelPageState extends State { enableVoiceRecording: true, allowedAttachmentPickerTypes: [ ...AttachmentPickerType.values, - if (config?.sharedLocations == true && channel.canShareLocation) - const LocationPickerType(), + if (config?.sharedLocations == true && channel.canShareLocation) const LocationPickerType(), ], onAttachmentPickerResult: (result) { return _onCustomAttachmentPickerResult(channel, result); @@ -143,7 +149,7 @@ class _ChannelPageState extends State { ...defaultOptions, TabbedAttachmentPickerOption( key: 'location-picker', - icon: const Icon(Icons.near_me_rounded), + icon: Icons.near_me_rounded, supportedTypes: [const LocationPickerType()], isEnabled: (value) { // Enable if nothing has been selected yet. @@ -199,210 +205,59 @@ class _ChannelPageState extends State { return channel.sendStaticLocation(location: result.coordinates); } - Widget customMessageBuilder( + Widget _messageBuilder( BuildContext context, - MessageDetails details, - List messages, - StreamMessageWidget defaultMessageWidget, + Message message, + StreamMessageWidgetProps defaultProps, ) { - final theme = StreamChatTheme.of(context); - final textTheme = theme.textTheme; - final colorTheme = theme.colorTheme; - - final message = details.message; - final reminder = message.reminder; - final channel = StreamChannel.of(context).channel; - final channelConfig = channel.config; - - final currentUser = StreamChat.of(context).currentUser; - final isSentByCurrentUser = message.user?.id == currentUser?.id; - final canDeleteOwnMessage = channel.canDeleteOwnMessage; - - final customOptions = [ - if (isSentByCurrentUser && canDeleteOwnMessage) - StreamMessageAction( - isDestructive: true, - title: const Text('Delete Message for Me'), - action: DeleteMessageForMe(message: message), - leading: const StreamSvgIcon(icon: StreamSvgIcons.delete), - ), - if (channelConfig?.userMessageReminders == true) ...[ - if (reminder != null) ...[ - StreamMessageAction( - title: const Text('Edit Reminder'), - leading: const StreamSvgIcon(icon: StreamSvgIcons.time), - action: EditReminder(message: message, reminder: reminder), - ), - StreamMessageAction( - title: const Text('Remove from later'), - leading: const StreamSvgIcon(icon: StreamSvgIcons.checkAll), - action: RemoveReminder(message: message, reminder: reminder), - ), - ] else ...[ - StreamMessageAction( - title: const Text('Remind me'), - leading: const StreamSvgIcon(icon: StreamSvgIcons.time), - action: CreateReminder(message: message), - ), - StreamMessageAction( - title: const Text('Save for later'), - leading: const Icon(Icons.bookmark_border), - action: CreateBookmark(message: message), - ), - ], - ], - if (channelConfig?.deliveryEvents == true) - StreamMessageAction( - title: const Text('Message Info'), - leading: const Icon(Icons.info_outline_rounded), - action: ShowMessageInfo(message: message), - ), - ]; - - final locationAttachmentBuilder = LocationAttachmentBuilder( - onAttachmentTap: (location) => showLocationDetailDialog( - context: context, - location: location, - ), - ); - - return Container( - color: reminder != null ? colorTheme.accentPrimary.withOpacity(.1) : null, - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - if (reminder != null) - Align( - alignment: switch (defaultMessageWidget.reverse) { - true => AlignmentDirectional.centerEnd, - false => AlignmentDirectional.centerStart, - }, - child: Padding( - padding: const EdgeInsetsDirectional.fromSTEB(16, 4, 16, 8), - child: Row( - spacing: 4, - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - size: 16, - Icons.bookmark_rounded, - color: colorTheme.accentPrimary, - ), - Text( - 'Saved for later', - style: textTheme.footnote.copyWith( - color: colorTheme.accentPrimary, - ), + final defaultWidget = StreamMessageWidget.fromProps(props: defaultProps); + + if (message.isDeleted || message.state.isFailed) return defaultWidget; + + final alignment = StreamMessagePlacement.alignmentDirectionalOf(context); + final isEnd = alignment == AlignmentDirectional.centerEnd; + + const threshold = 0.2; + + return Swipeable( + key: ValueKey(message.id), + direction: isEnd ? SwipeDirection.endToStart : SwipeDirection.startToEnd, + swipeThreshold: threshold, + onSwiped: (_) => _reply(message), + backgroundBuilder: (context, details) { + final progress = math.min(details.progress, threshold) / threshold; + + var offset = Offset.lerp(const Offset(-24, 0), const Offset(12, 0), progress)!; + if (isEnd) offset = Offset(-offset.dx, -offset.dy); + + return Align( + alignment: alignment, + child: Transform.translate( + offset: offset, + child: Opacity( + opacity: progress, + child: SizedBox.square( + dimension: 30, + child: CustomPaint( + painter: AnimatedCircleBorderPainter( + progress: progress, + color: context.streamColorScheme.borderDefault, + ), + child: Center( + child: Icon( + context.streamIcons.arrowShareLeft, + size: lerpDouble(0, 18, progress), + color: context.streamColorScheme.accentPrimary, ), - ], + ), ), ), ), - defaultMessageWidget.copyWith( - onReplyTap: _reply, - customActions: customOptions, - showEditMessage: message.sharedLocation == null, - onCustomActionTap: (it) async => await switch (it) { - CreateReminder() => _createReminder(it.message), - CreateBookmark() => _createBookmark(it.message), - EditReminder() => _editReminder(it.message, it.reminder), - RemoveReminder() => _removeReminder(it.message, it.reminder), - DeleteMessageForMe() => _deleteMessageForMe(it.message), - ShowMessageInfo() => _showMessageInfo(it.message), - _ => null, - }, - attachmentBuilders: [locationAttachmentBuilder], - onShowMessage: (message, channel) => GoRouter.of(context).goNamed( - Routes.CHANNEL_PAGE.name, - pathParameters: Routes.CHANNEL_PAGE.params(channel), - queryParameters: Routes.CHANNEL_PAGE.queryParams(message), - ), - bottomRowBuilderWithDefaultWidget: (_, __, defaultWidget) { - return defaultWidget.copyWith( - deletedBottomRowBuilder: (context, message) { - return const StreamVisibleFootnote(); - }, - ); - }, ), - // If the message has a reminder, add some space below it. - if (reminder != null) const SizedBox(height: 4), - ], - ), - ); - } - - Future _editReminder( - Message message, - MessageReminder reminder, - ) async { - final option = await showDialog( - context: context, - builder: (_) => EditReminderDialog( - isBookmarkReminder: reminder.remindAt == null, - ), - ); - - if (option == null) return; - final client = StreamChat.of(context).client; - final messageId = message.id; - final remindAt = option.remindAt; - - return client.updateReminder(messageId, remindAt: remindAt).ignore(); - } - - Future _removeReminder( - Message message, - MessageReminder reminder, - ) async { - final client = StreamChat.of(context).client; - final messageId = message.id; - - return client.deleteReminder(messageId).ignore(); - } - - Future _createReminder(Message message) async { - final reminder = await showDialog( - context: context, - builder: (_) => const CreateReminderDialog(), - ); - - if (reminder == null) return; - final client = StreamChat.of(context).client; - final messageId = message.id; - final remindAt = reminder.remindAt; - - return client.createReminder(messageId, remindAt: remindAt).ignore(); - } - - Future _createBookmark(Message message) async { - final client = StreamChat.of(context).client; - final messageId = message.id; - - return client.createReminder(messageId).ignore(); - } - - Future _deleteMessageForMe(Message message) async { - final confirmDelete = await showStreamDialog( - context: context, - builder: (context) => const StreamMessageActionConfirmationModal( - isDestructiveAction: true, - title: Text('Delete for me'), - content: Text('Are you sure you want to delete this message for you?'), - cancelActionTitle: Text('Cancel'), - confirmActionTitle: Text('Delete'), - ), + ); + }, + child: defaultWidget, ); - - if (confirmDelete != true) return; - - final channel = StreamChannel.of(context).channel; - return channel.deleteMessageForMe(message).ignore(); - } - - Future _showMessageInfo(Message message) async { - return MessageInfoSheet.show(context: context, message: message); } bool defaultFilter(Message m) { @@ -413,48 +268,3 @@ class _ChannelPageState extends State { return true; } } - -class ReminderMessageAction extends CustomMessageAction { - const ReminderMessageAction({ - required super.message, - this.reminder, - }); - - final MessageReminder? reminder; -} - -final class CreateReminder extends ReminderMessageAction { - const CreateReminder({required super.message}); -} - -final class CreateBookmark extends ReminderMessageAction { - const CreateBookmark({required super.message}); -} - -final class EditReminder extends ReminderMessageAction { - const EditReminder({ - required super.message, - required this.reminder, - }) : super(reminder: reminder); - - @override - final MessageReminder reminder; -} - -final class RemoveReminder extends ReminderMessageAction { - const RemoveReminder({ - required super.message, - required this.reminder, - }) : super(reminder: reminder); - - @override - final MessageReminder reminder; -} - -final class DeleteMessageForMe extends CustomMessageAction { - const DeleteMessageForMe({required super.message}); -} - -final class ShowMessageInfo extends CustomMessageAction { - const ShowMessageInfo({required super.message}); -} diff --git a/sample_app/lib/pages/chat_info_screen.dart b/sample_app/lib/pages/chat_info_screen.dart index 81ac6dbfe5..5c7accdad0 100644 --- a/sample_app/lib/pages/chat_info_screen.dart +++ b/sample_app/lib/pages/chat_info_screen.dart @@ -68,19 +68,14 @@ class _ChatInfoScreenState extends State { Padding( padding: const EdgeInsets.all(16), child: StreamUserAvatar( + size: .xl, user: widget.user!, - constraints: const BoxConstraints.tightFor( - width: 72, - height: 72, - ), - borderRadius: BorderRadius.circular(36), - showOnlineStatus: false, + showOnlineIndicator: false, ), ), Text( widget.user!.name, - style: const TextStyle( - fontSize: 16, fontWeight: FontWeight.bold), + style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), const SizedBox(height: 7), _buildConnectedTitleState(), @@ -93,11 +88,9 @@ class _ChatInfoScreenState extends State { child: Text( widget.user!.name, style: TextStyle( - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(0.5), - fontSize: 16), + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(0.5), + fontSize: 16, + ), ), ), onTap: () {}, @@ -122,63 +115,59 @@ class _ChatInfoScreenState extends State { return Column( children: [ StreamBuilder( - stream: StreamChannel.of(context).channel.isMutedStream, - builder: (context, snapshot) { - mutedBool.value = snapshot.data; - - return StreamOptionListTile( - tileColor: StreamChatTheme.of(context).colorTheme.appBg, - title: AppLocalizations.of(context).muteUser, - titleTextStyle: StreamChatTheme.of(context).textTheme.body, - leading: Padding( - padding: const EdgeInsets.symmetric(horizontal: 22), - child: StreamSvgIcon( - icon: StreamSvgIcons.mute, - size: 24, - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(0.5), - ), + stream: StreamChannel.of(context).channel.isMutedStream, + builder: (context, snapshot) { + mutedBool.value = snapshot.data; + + return StreamOptionListTile( + tileColor: StreamChatTheme.of(context).colorTheme.appBg, + title: AppLocalizations.of(context).muteUser, + titleTextStyle: StreamChatTheme.of(context).textTheme.body, + leading: Padding( + padding: const EdgeInsets.symmetric(horizontal: 22), + child: Icon( + context.streamIcons.mute, + size: 24, + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(0.5), ), - trailing: snapshot.data == null - ? const CircularProgressIndicator() - : ValueListenableBuilder( - valueListenable: mutedBool, - builder: (context, value, _) { - return CupertinoSwitch( - value: value!, - onChanged: (val) { - mutedBool.value = val; - - if (snapshot.data!) { - channel.channel.unmute(); - } else { - channel.channel.mute(); - } - }, - ); - }), - onTap: () {}, - ); - }), + ), + trailing: snapshot.data == null + ? const CircularProgressIndicator() + : ValueListenableBuilder( + valueListenable: mutedBool, + builder: (context, value, _) { + return CupertinoSwitch( + value: value!, + onChanged: (val) { + mutedBool.value = val; + + if (snapshot.data!) { + channel.channel.unmute(); + } else { + channel.channel.mute(); + } + }, + ); + }, + ), + onTap: () {}, + ); + }, + ), StreamOptionListTile( title: AppLocalizations.of(context).pinnedMessages, tileColor: StreamChatTheme.of(context).colorTheme.appBg, titleTextStyle: StreamChatTheme.of(context).textTheme.body, leading: Padding( padding: const EdgeInsets.symmetric(horizontal: 22), - child: StreamSvgIcon( - icon: StreamSvgIcons.pin, + child: Icon( + context.streamIcons.pin, size: 24, - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(0.5), + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(0.5), ), ), - trailing: StreamSvgIcon( - icon: StreamSvgIcons.right, + trailing: Icon( + context.streamIcons.chevronRight, color: StreamChatTheme.of(context).colorTheme.textLowEmphasis, ), onTap: () { @@ -201,17 +190,14 @@ class _ChatInfoScreenState extends State { titleTextStyle: StreamChatTheme.of(context).textTheme.body, leading: Padding( padding: const EdgeInsets.symmetric(horizontal: 16), - child: StreamSvgIcon( - icon: StreamSvgIcons.pictures, + child: Icon( + context.streamIcons.images1Alt, size: 36, - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(0.5), + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(0.5), ), ), - trailing: StreamSvgIcon( - icon: StreamSvgIcons.right, + trailing: Icon( + context.streamIcons.chevronRight, color: StreamChatTheme.of(context).colorTheme.textLowEmphasis, ), onTap: () { @@ -236,17 +222,14 @@ class _ChatInfoScreenState extends State { titleTextStyle: StreamChatTheme.of(context).textTheme.body, leading: Padding( padding: const EdgeInsets.symmetric(horizontal: 18), - child: StreamSvgIcon( - icon: StreamSvgIcons.files, + child: Icon( + context.streamIcons.fileBend, size: 32, - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(0.5), + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(0.5), ), ), - trailing: StreamSvgIcon( - icon: StreamSvgIcons.right, + trailing: Icon( + context.streamIcons.chevronRight, color: StreamChatTheme.of(context).colorTheme.textLowEmphasis, ), onTap: () { @@ -271,25 +254,23 @@ class _ChatInfoScreenState extends State { titleTextStyle: StreamChatTheme.of(context).textTheme.body, leading: Padding( padding: const EdgeInsets.symmetric(horizontal: 22), - child: StreamSvgIcon( - icon: StreamSvgIcons.group, + child: Icon( + context.streamIcons.users, size: 24, - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(0.5), + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(0.5), ), ), - trailing: StreamSvgIcon( - icon: StreamSvgIcons.right, + trailing: Icon( + context.streamIcons.chevronRight, color: StreamChatTheme.of(context).colorTheme.textLowEmphasis, ), onTap: () { Navigator.push( - context, - MaterialPageRoute( - builder: (context) => _SharedGroupsScreen( - StreamChat.of(context).currentUser, widget.user))); + context, + MaterialPageRoute( + builder: (context) => _SharedGroupsScreen(StreamChat.of(context).currentUser, widget.user), + ), + ); }, ), ], @@ -301,12 +282,12 @@ class _ChatInfoScreenState extends State { title: 'Delete Conversation', tileColor: StreamChatTheme.of(context).colorTheme.appBg, titleTextStyle: StreamChatTheme.of(context).textTheme.body.copyWith( - color: StreamChatTheme.of(context).colorTheme.accentError, - ), + color: StreamChatTheme.of(context).colorTheme.accentError, + ), leading: Padding( padding: const EdgeInsets.symmetric(horizontal: 22), - child: StreamSvgIcon( - icon: StreamSvgIcons.delete, + child: Icon( + context.streamIcons.trashBin, size: 24, color: StreamChatTheme.of(context).colorTheme.accentError, ), @@ -324,8 +305,8 @@ class _ChatInfoScreenState extends State { okText: AppLocalizations.of(context).delete.toUpperCase(), question: AppLocalizations.of(context).deleteConversationAreYouSure, cancelText: AppLocalizations.of(context).cancel.toUpperCase(), - icon: StreamSvgIcon( - icon: StreamSvgIcons.delete, + icon: Icon( + context.streamIcons.trashBin, color: StreamChatTheme.of(context).colorTheme.accentError, ), ); @@ -347,20 +328,12 @@ class _ChatInfoScreenState extends State { if (otherMember.online) { alternativeWidget = Text( AppLocalizations.of(context).online, - style: TextStyle( - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(0.5)), + style: TextStyle(color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(0.5)), ); } else { alternativeWidget = Text( '${AppLocalizations.of(context).lastSeen} ${Jiffy.parseFromDateTime(otherMember.lastActive!).fromNow()}', - style: TextStyle( - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(0.5)), + style: TextStyle(color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(0.5)), ); } } @@ -415,9 +388,7 @@ class __SharedGroupsScreenState extends State<_SharedGroupsScreen> { centerTitle: true, title: Text( AppLocalizations.of(context).sharedGroups, - style: TextStyle( - color: StreamChatTheme.of(context).colorTheme.textHighEmphasis, - fontSize: 16), + style: TextStyle(color: StreamChatTheme.of(context).colorTheme.textHighEmphasis, fontSize: 16), ), leading: const StreamBackButton(), backgroundColor: StreamChatTheme.of(context).colorTheme.barsBg, @@ -441,8 +412,8 @@ class __SharedGroupsScreenState extends State<_SharedGroupsScreen> { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - StreamSvgIcon( - icon: StreamSvgIcons.message, + Icon( + context.streamIcons.bubble3ChatMessage, size: 136, color: StreamChatTheme.of(context).colorTheme.disabled, ), @@ -451,9 +422,7 @@ class __SharedGroupsScreenState extends State<_SharedGroupsScreen> { AppLocalizations.of(context).noSharedGroups, style: TextStyle( fontSize: 14, - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis, + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis, ), ), const SizedBox(height: 8), @@ -462,10 +431,7 @@ class __SharedGroupsScreenState extends State<_SharedGroupsScreen> { textAlign: TextAlign.center, style: TextStyle( fontSize: 14, - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(0.5), + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(0.5), ), ), ], @@ -474,11 +440,11 @@ class __SharedGroupsScreenState extends State<_SharedGroupsScreen> { } final channels = snapshot.data! - .where((c) => - c.state!.members.any((m) => - m.userId != widget.mainUser!.id && - m.userId != widget.otherUser!.id) || - !c.isDistinct) + .where( + (c) => + c.state!.members.any((m) => m.userId != widget.mainUser!.id && m.userId != widget.otherUser!.id) || + !c.isDistinct, + ) .toList(); return ListView.builder( @@ -507,8 +473,7 @@ class __SharedGroupsScreenState extends State<_SharedGroupsScreen> { builder: (context, constraints) { String? title; if (extraData['name'] == null) { - final otherMembers = members.where((member) => - member.userId != StreamChat.of(context).currentUser!.id); + final otherMembers = members.where((member) => member.userId != StreamChat.of(context).currentUser!.id); if (otherMembers.isNotEmpty) { final maxWidth = constraints.maxWidth; final maxChars = maxWidth / textStyle.fontSize!; @@ -522,8 +487,7 @@ class __SharedGroupsScreenState extends State<_SharedGroupsScreen> { } } - final exceedingMembers = - otherMembers.length - currentMembers.length; + final exceedingMembers = otherMembers.length - currentMembers.length; title = '${currentMembers.map((e) => e.user!.name).join(', ')} ${exceedingMembers > 0 ? '+ $exceedingMembers' : ''}'; } else { @@ -541,36 +505,31 @@ class __SharedGroupsScreenState extends State<_SharedGroupsScreen> { Padding( padding: const EdgeInsets.all(8), child: StreamChannelAvatar( + size: .lg, channel: channel, - constraints: - const BoxConstraints(maxWidth: 40, maxHeight: 40), ), ), Expanded( - child: Text( - title, - style: textStyle, - )), + child: Text( + title, + style: textStyle, + ), + ), Padding( padding: const EdgeInsets.all(8), child: Text( '${channel.memberCount} ${AppLocalizations.of(context).members.toLowerCase()}', style: TextStyle( - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(0.5)), + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(0.5), + ), ), - ) + ), ], ), ), Container( height: 1, - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(.08), + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(.08), ), ], ); diff --git a/sample_app/lib/pages/choose_user_page.dart b/sample_app/lib/pages/choose_user_page.dart index b2aa66391a..617c52c252 100644 --- a/sample_app/lib/pages/choose_user_page.dart +++ b/sample_app/lib/pages/choose_user_page.dart @@ -76,16 +76,12 @@ class ChooseUserPage extends StatelessWidget { showDialog( barrierDismissible: false, context: context, - barrierColor: StreamChatTheme.of(context) - .colorTheme - .overlay, + barrierColor: StreamChatTheme.of(context).colorTheme.overlay, builder: (context) => Center( child: Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(16), - color: StreamChatTheme.of(context) - .colorTheme - .barsBg, + color: StreamChatTheme.of(context).colorTheme.barsBg, ), height: 100, width: 100, @@ -96,8 +92,7 @@ class ChooseUserPage extends StatelessWidget { ), ); - final client = - context.read().initData!.client; + final client = context.read().initData!.client; final router = GoRouter.of(context); @@ -127,46 +122,32 @@ class ChooseUserPage extends StatelessWidget { router.replaceNamed(Routes.CHANNEL_LIST_PAGE.name); }, leading: StreamUserAvatar( + size: .lg, user: user, - constraints: BoxConstraints.tight( - const Size.fromRadius(20), - ), ), title: Text( user.name, - style: - StreamChatTheme.of(context).textTheme.bodyBold, + style: StreamChatTheme.of(context).textTheme.bodyBold, ), subtitle: Text( AppLocalizations.of(context).streamTestAccount, - style: StreamChatTheme.of(context) - .textTheme - .footnote - .copyWith( - color: StreamChatTheme.of(context) - .colorTheme - .textLowEmphasis, - ), + style: StreamChatTheme.of(context).textTheme.footnote.copyWith( + color: StreamChatTheme.of(context).colorTheme.textLowEmphasis, + ), ), - trailing: StreamSvgIcon( - icon: StreamSvgIcons.arrowRight, - color: StreamChatTheme.of(context) - .colorTheme - .accentPrimary, + trailing: Icon( + context.streamIcons.arrowRight, + color: StreamChatTheme.of(context).colorTheme.accentPrimary, ), ); }), ListTile( - onTap: () => GoRouter.of(context) - .pushNamed(Routes.ADVANCED_OPTIONS.name), + onTap: () => GoRouter.of(context).pushNamed(Routes.ADVANCED_OPTIONS.name), leading: CircleAvatar( - backgroundColor: - StreamChatTheme.of(context).colorTheme.borders, - child: StreamSvgIcon( - icon: StreamSvgIcons.settings, - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis, + backgroundColor: StreamChatTheme.of(context).colorTheme.borders, + child: Icon( + context.streamIcons.settingsGear2, + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis, ), ), title: Text( @@ -175,14 +156,9 @@ class ChooseUserPage extends StatelessWidget { ), subtitle: Text( AppLocalizations.of(context).customSettings, - style: StreamChatTheme.of(context) - .textTheme - .footnote - .copyWith( - color: StreamChatTheme.of(context) - .colorTheme - .textLowEmphasis, - ), + style: StreamChatTheme.of(context).textTheme.footnote.copyWith( + color: StreamChatTheme.of(context).colorTheme.textLowEmphasis, + ), ), trailing: SvgPicture.asset( 'assets/icon_arrow_right.svg', diff --git a/sample_app/lib/pages/draft_list_page.dart b/sample_app/lib/pages/draft_list_page.dart index ffdda5fec7..00d7e56f87 100644 --- a/sample_app/lib/pages/draft_list_page.dart +++ b/sample_app/lib/pages/draft_list_page.dart @@ -39,9 +39,9 @@ class _DraftListPageState extends State { children: [ CustomSlidableAction( backgroundColor: Colors.red, - child: const StreamSvgIcon( + child: Icon( + context.streamIcons.trashBin, size: 24, - icon: StreamSvgIcons.delete, color: Colors.white, ), onPressed: (context) { @@ -71,8 +71,8 @@ class _DraftListPageState extends State { initialMessageId: draft.parentId, child: switch (draft.parentMessage) { final parent? => ThreadPage( - parent: parent.copyWith(draft: draft), - ), + parent: parent.copyWith(draft: draft), + ), _ => const ChannelPage(), }, ); diff --git a/sample_app/lib/pages/group_chat_details_screen.dart b/sample_app/lib/pages/group_chat_details_screen.dart index 5df7ae5075..0873313e26 100644 --- a/sample_app/lib/pages/group_chat_details_screen.dart +++ b/sample_app/lib/pages/group_chat_details_screen.dart @@ -19,8 +19,7 @@ class GroupChatDetailsScreen extends StatefulWidget { } class _GroupChatDetailsScreenState extends State { - late final TextEditingController _groupNameController = - TextEditingController()..addListener(_groupNameListener); + late final TextEditingController _groupNameController = TextEditingController()..addListener(_groupNameListener); bool _isGroupNameEmpty = true; @@ -74,9 +73,7 @@ class _GroupChatDetailsScreenState extends State { AppLocalizations.of(context).name.toUpperCase(), style: TextStyle( fontSize: 12, - color: StreamChatTheme.of(context) - .colorTheme - .textLowEmphasis, + color: StreamChatTheme.of(context).colorTheme.textLowEmphasis, ), ), const SizedBox(width: 16), @@ -91,13 +88,10 @@ class _GroupChatDetailsScreenState extends State { errorBorder: InputBorder.none, disabledBorder: InputBorder.none, contentPadding: EdgeInsets.zero, - hintText: - AppLocalizations.of(context).chooseAGroupChatName, + hintText: AppLocalizations.of(context).chooseAGroupChatName, hintStyle: TextStyle( fontSize: 14, - color: StreamChatTheme.of(context) - .colorTheme - .textLowEmphasis, + color: StreamChatTheme.of(context).colorTheme.textLowEmphasis, ), ), ), @@ -114,7 +108,7 @@ class _GroupChatDetailsScreenState extends State { color: _isGroupNameEmpty ? StreamChatTheme.of(context).colorTheme.textLowEmphasis : StreamChatTheme.of(context).colorTheme.accentPrimary, - icon: const StreamSvgIcon(icon: StreamSvgIcons.check), + icon: Icon(context.streamIcons.checkmark2), onPressed: _isGroupNameEmpty ? null : () async { @@ -122,16 +116,17 @@ class _GroupChatDetailsScreenState extends State { final groupName = _groupNameController.text; final client = StreamChat.of(context).client; final router = GoRouter.of(context); - final channel = client.channel('messaging', - id: const Uuid().v4(), - extraData: { - 'members': [ - client.state.currentUser!.id, - ...widget.groupChatState.users - .map((e) => e.id), - ], - 'name': groupName, - }); + final channel = client.channel( + 'messaging', + id: const Uuid().v4(), + extraData: { + 'members': [ + client.state.currentUser!.id, + ...widget.groupChatState.users.map((e) => e.id), + ], + 'name': groupName, + }, + ); await channel.watch(); router.goNamed( Routes.CHANNEL_PAGE.name, @@ -172,8 +167,7 @@ class _GroupChatDetailsScreenState extends State { Container( width: double.maxFinite, decoration: BoxDecoration( - gradient: - StreamChatTheme.of(context).colorTheme.bgGradient, + gradient: StreamChatTheme.of(context).colorTheme.bgGradient, ), child: Padding( padding: const EdgeInsets.symmetric( @@ -183,80 +177,67 @@ class _GroupChatDetailsScreenState extends State { child: Text( '$_totalUsers ${_totalUsers > 1 ? AppLocalizations.of(context).members : AppLocalizations.of(context).member}', style: TextStyle( - color: StreamChatTheme.of(context) - .colorTheme - .textLowEmphasis, + color: StreamChatTheme.of(context).colorTheme.textLowEmphasis, ), ), ), ), AnimatedBuilder( - animation: widget.groupChatState, - builder: (context, child) { - return Expanded( - child: GestureDetector( - behavior: HitTestBehavior.opaque, - onPanDown: (_) => FocusScope.of(context).unfocus(), - child: ListView.separated( - itemCount: widget.groupChatState.users.length + 1, - separatorBuilder: (_, __) => Container( - height: 1, - color: StreamChatTheme.of(context) - .colorTheme - .borders, - ), - itemBuilder: (_, index) { - if (index == - widget.groupChatState.users.length) { - return Container( - height: 1, - color: StreamChatTheme.of(context) - .colorTheme - .borders, - ); - } - final user = widget.groupChatState.users - .elementAt(index); - return ListTile( - key: ObjectKey(user), - leading: StreamUserAvatar( - user: user, - constraints: const BoxConstraints.tightFor( - width: 40, - height: 40, - ), - ), - title: Text( - user.name, - style: const TextStyle( - fontWeight: FontWeight.bold), - ), - contentPadding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 8, - ), - trailing: IconButton( - icon: Icon( - Icons.clear_rounded, - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis, - ), - padding: EdgeInsets.zero, - splashRadius: 24, - onPressed: () { - widget.groupChatState.removeUser(user); - if (widget.groupChatState.users.isEmpty) { - GoRouter.of(context).pop(); - } - }, - ), - ); - }, + animation: widget.groupChatState, + builder: (context, child) { + return Expanded( + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onPanDown: (_) => FocusScope.of(context).unfocus(), + child: ListView.separated( + itemCount: widget.groupChatState.users.length + 1, + separatorBuilder: (_, __) => Container( + height: 1, + color: StreamChatTheme.of(context).colorTheme.borders, ), + itemBuilder: (_, index) { + if (index == widget.groupChatState.users.length) { + return Container( + height: 1, + color: StreamChatTheme.of(context).colorTheme.borders, + ); + } + final user = widget.groupChatState.users.elementAt(index); + return ListTile( + key: ObjectKey(user), + leading: StreamUserAvatar( + size: .lg, + user: user, + ), + title: Text( + user.name, + style: const TextStyle(fontWeight: FontWeight.bold), + ), + contentPadding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 8, + ), + trailing: IconButton( + icon: Icon( + Icons.clear_rounded, + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis, + ), + padding: EdgeInsets.zero, + splashRadius: 24, + onPressed: () { + widget.groupChatState.removeUser(user); + if (widget.groupChatState.users.isEmpty) { + GoRouter.of(context).pop(); + } + }, + ), + ); + }, ), - ); - }), + ), + ); + }, + ), ], ), ); @@ -271,10 +252,11 @@ class _GroupChatDetailsScreenState extends State { backgroundColor: StreamChatTheme.of(context).colorTheme.barsBg, context: context, shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(16), - topRight: Radius.circular(16), - )), + borderRadius: BorderRadius.only( + topLeft: Radius.circular(16), + topRight: Radius.circular(16), + ), + ), builder: (context) { return Column( mainAxisSize: MainAxisSize.min, @@ -282,8 +264,8 @@ class _GroupChatDetailsScreenState extends State { const SizedBox( height: 26, ), - StreamSvgIcon( - icon: StreamSvgIcons.error, + Icon( + context.streamIcons.exclamationCircle1, color: StreamChatTheme.of(context).colorTheme.accentError, size: 24, ), @@ -302,10 +284,7 @@ class _GroupChatDetailsScreenState extends State { height: 36, ), Container( - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(.08), + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(.08), height: 1, ), Row( @@ -315,13 +294,9 @@ class _GroupChatDetailsScreenState extends State { onPressed: GoRouter.of(context).pop, child: Text( AppLocalizations.of(context).ok, - style: StreamChatTheme.of(context) - .textTheme - .bodyBold - .copyWith( - color: StreamChatTheme.of(context) - .colorTheme - .accentPrimary), + style: StreamChatTheme.of( + context, + ).textTheme.bodyBold.copyWith(color: StreamChatTheme.of(context).colorTheme.accentPrimary), ), ), ], diff --git a/sample_app/lib/pages/group_info_screen.dart b/sample_app/lib/pages/group_info_screen.dart index 1a5e6997ad..768bf8ee75 100644 --- a/sample_app/lib/pages/group_info_screen.dart +++ b/sample_app/lib/pages/group_info_screen.dart @@ -25,13 +25,11 @@ class GroupInfoScreen extends StatefulWidget { } class _GroupInfoScreenState extends State { - late final TextEditingController _nameController = - TextEditingController.fromValue( + late final TextEditingController _nameController = TextEditingController.fromValue( TextEditingValue(text: (channel.extraData['name'] as String?) ?? ''), ); - late final TextEditingController _searchController = TextEditingController() - ..addListener(_userNameListener); + late final TextEditingController _searchController = TextEditingController()..addListener(_userNameListener); String _userNameQuery = ''; @@ -62,13 +60,10 @@ class _GroupInfoScreenState extends State { _userNameQuery = _searchController.text; _userListController.filter = Filter.and( [ - if (_searchController.text.isNotEmpty) - Filter.autoComplete('name', _userNameQuery), + if (_searchController.text.isNotEmpty) Filter.autoComplete('name', _userNameQuery), Filter.notIn('id', [ StreamChat.of(context).currentUser!.id, - ...channel.state!.members - .map((e) => e.userId) - .whereType(), + ...channel.state!.members.map((e) => e.userId).whereType(), ]), ], ); @@ -94,13 +89,10 @@ class _GroupInfoScreenState extends State { limit: 25, filter: Filter.and( [ - if (_searchController.text.isNotEmpty) - Filter.autoComplete('name', _userNameQuery), + if (_searchController.text.isNotEmpty) Filter.autoComplete('name', _userNameQuery), Filter.notIn('id', [ StreamChat.of(context).currentUser!.id, - ...channel.state!.members - .map((e) => e.userId) - .whereType(), + ...channel.state!.members.map((e) => e.userId).whereType(), ]), ], ), @@ -122,106 +114,100 @@ class _GroupInfoScreenState extends State { @override Widget build(BuildContext context) { return StreamBuilder>( - stream: channel.state!.membersStream, - builder: (context, snapshot) { - if (!snapshot.hasData) { - return ColoredBox( - color: StreamChatTheme.of(context).colorTheme.disabled, - child: const Center(child: CircularProgressIndicator()), - ); - } + stream: channel.state!.membersStream, + builder: (context, snapshot) { + if (!snapshot.hasData) { + return ColoredBox( + color: StreamChatTheme.of(context).colorTheme.disabled, + child: const Center(child: CircularProgressIndicator()), + ); + } - return Scaffold( - backgroundColor: StreamChatTheme.of(context).colorTheme.appBg, - appBar: AppBar( - elevation: 1, - toolbarHeight: 56, - backgroundColor: StreamChatTheme.of(context).colorTheme.barsBg, - leading: const StreamBackButton(), - title: Column( - children: [ - StreamBuilder( - stream: channel.state?.channelStateStream, - builder: (context, state) { - if (!state.hasData) { - return Text( - AppLocalizations.of(context).loading, - style: TextStyle( - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis, - fontSize: 16, - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ); - } + return Scaffold( + backgroundColor: StreamChatTheme.of(context).colorTheme.appBg, + appBar: AppBar( + elevation: 1, + toolbarHeight: 56, + backgroundColor: StreamChatTheme.of(context).colorTheme.barsBg, + leading: const StreamBackButton(), + title: Column( + children: [ + StreamBuilder( + stream: channel.state?.channelStateStream, + builder: (context, state) { + if (!state.hasData) { + return Text( + AppLocalizations.of(context).loading, + style: TextStyle( + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis, + fontSize: 16, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ); + } - return Text( - _getChannelName( - 2 * MediaQuery.of(context).size.width / 3, - members: snapshot.data, - extraData: state.data!.channel!.extraData, - maxFontSize: 16, - )!, - style: TextStyle( - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis, - fontSize: 16, - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ); - }), - const SizedBox( - height: 3, - ), - Text( - '${channel.memberCount} ${AppLocalizations.of(context).members}, ${snapshot.data?.where((e) => e.user!.online).length ?? 0} ${AppLocalizations.of(context).online}', - style: TextStyle( - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(0.5), - fontSize: 12, - ), - ), - ], - ), - centerTitle: true, - actions: [ - if (channel.canUpdateChannelMembers) - StreamNeumorphicButton( - child: InkWell( - onTap: () { - _buildAddUserModal(context); - }, - child: Padding( - padding: const EdgeInsets.all(8), - child: StreamSvgIcon( - icon: StreamSvgIcons.userAdd, - color: StreamChatTheme.of(context) - .colorTheme - .accentPrimary), + return Text( + _getChannelName( + 2 * MediaQuery.of(context).size.width / 3, + members: snapshot.data, + extraData: state.data!.channel!.extraData, + maxFontSize: 16, + )!, + style: TextStyle( + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis, + fontSize: 16, ), - ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ); + }, + ), + const SizedBox( + height: 3, + ), + Text( + '${channel.memberCount} ${AppLocalizations.of(context).members}, ${snapshot.data?.where((e) => e.user!.online).length ?? 0} ${AppLocalizations.of(context).online}', + style: TextStyle( + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(0.5), + fontSize: 12, ), - ], - ), - body: ListView( - children: [ - _buildMembers(snapshot.data!), - Container( - height: 8, - color: StreamChatTheme.of(context).colorTheme.disabled, ), - if (channel.canUpdateChannel) _buildNameTile(), - _buildOptionListTiles(), ], ), - ); - }); + centerTitle: true, + actions: [ + if (channel.canUpdateChannelMembers) + StreamNeumorphicButton( + child: InkWell( + onTap: () { + _buildAddUserModal(context); + }, + child: Padding( + padding: const EdgeInsets.all(8), + child: Icon( + context.streamIcons.peopleAdd, + color: StreamChatTheme.of(context).colorTheme.accentPrimary, + ), + ), + ), + ), + ], + ), + body: ListView( + children: [ + _buildMembers(snapshot.data!), + Container( + height: 8, + color: StreamChatTheme.of(context).colorTheme.disabled, + ), + if (channel.canUpdateChannel) _buildNameTile(), + _buildOptionListTiles(), + ], + ), + ); + }, + ); } Widget _buildMembers(List members) { @@ -254,8 +240,7 @@ class _GroupInfoScreenState extends State { final userMember = groupMembers.firstWhereOrNull( (e) => e.user!.id == StreamChat.of(context).currentUser!.id, ); - _showUserInfoModal( - member.user, userMember?.userId == channel.createdBy?.id); + _showUserInfoModal(member.user, userMember?.userId == channel.createdBy?.id); }, child: SizedBox( height: 65, @@ -269,11 +254,8 @@ class _GroupInfoScreenState extends State { vertical: 12, ), child: StreamUserAvatar( + size: .lg, user: member.user!, - constraints: const BoxConstraints.tightFor( - height: 40, - width: 40, - ), ), ), Expanded( @@ -283,8 +265,7 @@ class _GroupInfoScreenState extends State { children: [ Text( member.user!.name, - style: const TextStyle( - fontWeight: FontWeight.bold), + style: const TextStyle(fontWeight: FontWeight.bold), ), const SizedBox( height: 1, @@ -292,10 +273,8 @@ class _GroupInfoScreenState extends State { Text( _getLastSeen(member.user!), style: TextStyle( - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(0.5)), + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(0.5), + ), ), ], ), @@ -303,14 +282,10 @@ class _GroupInfoScreenState extends State { Padding( padding: const EdgeInsets.all(8), child: Text( - member.userId == channel.createdBy?.id - ? AppLocalizations.of(context).owner - : '', + member.userId == channel.createdBy?.id ? AppLocalizations.of(context).owner : '', style: TextStyle( - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(0.5)), + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(0.5), + ), ), ), ], @@ -343,13 +318,10 @@ class _GroupInfoScreenState extends State { child: Row( children: [ Padding( - padding: const EdgeInsets.symmetric( - horizontal: 21, vertical: 12), - child: StreamSvgIcon( - icon: StreamSvgIcons.down, - color: StreamChatTheme.of(context) - .colorTheme - .textLowEmphasis, + padding: const EdgeInsets.symmetric(horizontal: 21, vertical: 12), + child: Icon( + context.streamIcons.chevronDown, + color: StreamChatTheme.of(context).colorTheme.textLowEmphasis, ), ), Expanded( @@ -359,10 +331,7 @@ class _GroupInfoScreenState extends State { children: [ Text( '${members.length - groupMembersLength} ${AppLocalizations.of(context).more}', - style: TextStyle( - color: StreamChatTheme.of(context) - .colorTheme - .textLowEmphasis), + style: TextStyle(color: StreamChatTheme.of(context).colorTheme.textLowEmphasis), ), ], ), @@ -398,10 +367,8 @@ class _GroupInfoScreenState extends State { child: Text( AppLocalizations.of(context).name.toUpperCase(), style: StreamChatTheme.of(context).textTheme.footnote.copyWith( - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(0.5)), + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(0.5), + ), ), ), const SizedBox( @@ -412,18 +379,13 @@ class _GroupInfoScreenState extends State { enabled: channel.canUpdateChannel, focusNode: _focusNode, controller: _nameController, - cursorColor: - StreamChatTheme.of(context).colorTheme.textHighEmphasis, + cursorColor: StreamChatTheme.of(context).colorTheme.textHighEmphasis, decoration: InputDecoration.collapsed( - hintText: AppLocalizations.of(context).addAGroupName, - hintStyle: StreamChatTheme.of(context) - .textTheme - .bodyBold - .copyWith( - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(0.5))), + hintText: AppLocalizations.of(context).addAGroupName, + hintStyle: StreamChatTheme.of(context).textTheme.bodyBold.copyWith( + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(0.5), + ), + ), style: const TextStyle( fontWeight: FontWeight.bold, height: 0.82, @@ -435,7 +397,7 @@ class _GroupInfoScreenState extends State { mainAxisSize: MainAxisSize.min, children: [ IconButton( - icon: const StreamSvgIcon(icon: StreamSvgIcons.closeSmall), + icon: Icon(context.streamIcons.crossSmall), onPressed: () { setState(() { _nameController.text = _getChannelName( @@ -450,7 +412,7 @@ class _GroupInfoScreenState extends State { ), IconButton( color: StreamChatTheme.of(context).colorTheme.accentPrimary, - icon: const StreamSvgIcon(icon: StreamSvgIcons.check), + icon: Icon(context.streamIcons.checkmark2), onPressed: () { try { channel.update({ @@ -478,7 +440,7 @@ class _GroupInfoScreenState extends State { if (channel.canMuteChannel) _GroupInfoToggle( title: AppLocalizations.of(context).muteGroup, - icon: StreamSvgIcons.mute, + icon: context.streamIcons.mute, channelStream: channel.isMutedStream, localNotifier: mutedBool, onTurnOff: channel.unmute, @@ -486,7 +448,7 @@ class _GroupInfoScreenState extends State { ), _GroupInfoToggle( title: AppLocalizations.of(context).pinGroup, - icon: StreamSvgIcons.pin, + icon: context.streamIcons.pin, channelStream: channel.isPinnedStream, localNotifier: isPinned, onTurnOff: channel.unpin, @@ -494,7 +456,7 @@ class _GroupInfoScreenState extends State { ), _GroupInfoToggle( title: AppLocalizations.of(context).archiveGroup, - icon: StreamSvgIcons.save, + icon: context.streamIcons.bookmark, channelStream: channel.isArchivedStream, localNotifier: isArchived, onTurnOff: channel.unarchive, @@ -502,7 +464,7 @@ class _GroupInfoScreenState extends State { ), _GroupInfoListTile( title: AppLocalizations.of(context).pinnedMessages, - icon: StreamSvgIcons.pin, + icon: context.streamIcons.pin, iconSize: 24, iconPadding: 16, onTap: () { @@ -521,7 +483,7 @@ class _GroupInfoScreenState extends State { ), _GroupInfoListTile( title: AppLocalizations.of(context).photosAndVideos, - icon: StreamSvgIcons.pictures, + icon: context.streamIcons.images1Alt, iconSize: 32, iconPadding: 12, onTap: () { @@ -542,7 +504,7 @@ class _GroupInfoScreenState extends State { ), _GroupInfoListTile( title: AppLocalizations.of(context).files, - icon: StreamSvgIcons.files, + icon: context.streamIcons.fileBend, iconSize: 32, iconPadding: 12, onTap: () { @@ -569,13 +531,10 @@ class _GroupInfoScreenState extends State { titleTextStyle: StreamChatTheme.of(context).textTheme.body, leading: Padding( padding: const EdgeInsets.symmetric(horizontal: 16), - child: StreamSvgIcon( - icon: StreamSvgIcons.userRemove, + child: Icon( + context.streamIcons.peopleRemove, size: 24, - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(0.5), + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(0.5), ), ), trailing: const SizedBox( @@ -590,11 +549,10 @@ class _GroupInfoScreenState extends State { context, title: AppLocalizations.of(context).leaveConversation, okText: AppLocalizations.of(context).leave.toUpperCase(), - question: - AppLocalizations.of(context).leaveConversationAreYouSure, + question: AppLocalizations.of(context).leaveConversationAreYouSure, cancelText: AppLocalizations.of(context).cancel.toUpperCase(), - icon: StreamSvgIcon( - icon: StreamSvgIcons.userRemove, + icon: Icon( + context.streamIcons.peopleRemove, color: StreamChatTheme.of(context).colorTheme.accentError, ), ); @@ -655,16 +613,13 @@ class _GroupInfoScreenState extends State { children: [ Padding( padding: const EdgeInsets.all(24), - child: StreamSvgIcon( - icon: StreamSvgIcons.search, + child: Icon( + context.streamIcons.magnifyingGlassSearch, size: 96, - color: StreamChatTheme.of(context) - .colorTheme - .textLowEmphasis, + color: StreamChatTheme.of(context).colorTheme.textLowEmphasis, ), ), - Text(AppLocalizations.of(context) - .noUserMatchesTheseKeywords), + Text(AppLocalizations.of(context).noUserMatchesTheseKeywords), ], ), ), @@ -704,8 +659,8 @@ class _GroupInfoScreenState extends State { color: theme.colorTheme.textLowEmphasis, ), prefixIconConstraints: BoxConstraints.tight(const Size(40, 24)), - prefixIcon: StreamSvgIcon( - icon: StreamSvgIcons.search, + prefixIcon: Icon( + context.streamIcons.magnifyingGlassSearch, color: theme.colorTheme.textHighEmphasis, size: 24, ), @@ -716,20 +671,21 @@ class _GroupInfoScreenState extends State { ), ), focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(24), - borderSide: BorderSide( - color: theme.colorTheme.borders, - )), + borderRadius: BorderRadius.circular(24), + borderSide: BorderSide( + color: theme.colorTheme.borders, + ), + ), contentPadding: EdgeInsets.zero, ), ), ), ), IconButton( - icon: const StreamSvgIcon(icon: StreamSvgIcons.closeSmall), + icon: Icon(context.streamIcons.crossSmall), color: theme.colorTheme.textHighEmphasis, onPressed: () => Navigator.pop(context), - ) + ), ], ); } @@ -771,36 +727,33 @@ class _GroupInfoScreenState extends State { child: Padding( padding: const EdgeInsets.all(16), child: StreamUserAvatar( + size: .xl, user: user, - constraints: const BoxConstraints.tightFor( - height: 64, - width: 64, - ), - borderRadius: BorderRadius.circular(32), ), ), ), if (StreamChat.of(context).currentUser!.id != user.id) _buildModalListTile( context, - StreamSvgIcon( - color: StreamChatTheme.of(context) - .colorTheme - .textLowEmphasis, + Icon( + context.streamIcons.people, + color: StreamChatTheme.of(context).colorTheme.textLowEmphasis, size: 24, - icon: StreamSvgIcons.user, ), AppLocalizations.of(context).viewInfo, () async { final client = StreamChat.of(context).client; final router = GoRouter.of(context); - final c = client.channel('messaging', extraData: { - 'members': [ - user.id, - StreamChat.of(context).currentUser!.id, - ], - }); + final c = client.channel( + 'messaging', + extraData: { + 'members': [ + user.id, + StreamChat.of(context).currentUser!.id, + ], + }, + ); await c.watch(); @@ -814,24 +767,25 @@ class _GroupInfoScreenState extends State { if (StreamChat.of(context).currentUser!.id != user.id) _buildModalListTile( context, - StreamSvgIcon( - color: StreamChatTheme.of(context) - .colorTheme - .textLowEmphasis, + Icon( + context.streamIcons.bubble3ChatMessage, + color: StreamChatTheme.of(context).colorTheme.textLowEmphasis, size: 24, - icon: StreamSvgIcons.message, ), AppLocalizations.of(context).message, () async { final client = StreamChat.of(context).client; final router = GoRouter.of(context); - final c = client.channel('messaging', extraData: { - 'members': [ - user.id, - StreamChat.of(context).currentUser!.id, - ], - }); + final c = client.channel( + 'messaging', + extraData: { + 'members': [ + user.id, + StreamChat.of(context).currentUser!.id, + ], + }, + ); await c.watch(); @@ -841,46 +795,38 @@ class _GroupInfoScreenState extends State { ); }, ), - if (!channel.isDistinct && - StreamChat.of(context).currentUser!.id != user.id && - isUserAdmin) + if (!channel.isDistinct && StreamChat.of(context).currentUser!.id != user.id && isUserAdmin) _buildModalListTile( - context, - StreamSvgIcon( - color: StreamChatTheme.of(context) - .colorTheme - .accentError, - size: 24, - icon: StreamSvgIcons.userRemove, - ), - AppLocalizations.of(context).removeFromGroup, () async { - final router = GoRouter.of(context); - final res = await showConfirmationBottomSheet( - context, - title: AppLocalizations.of(context).removeMember, - okText: - AppLocalizations.of(context).remove.toUpperCase(), - question: - AppLocalizations.of(context).removeMemberAreYouSure, - cancelText: - AppLocalizations.of(context).cancel.toUpperCase(), - ); + context, + Icon( + context.streamIcons.peopleRemove, + color: StreamChatTheme.of(context).colorTheme.accentError, + size: 24, + ), + AppLocalizations.of(context).removeFromGroup, + () async { + final router = GoRouter.of(context); + final res = await showConfirmationBottomSheet( + context, + title: AppLocalizations.of(context).removeMember, + okText: AppLocalizations.of(context).remove.toUpperCase(), + question: AppLocalizations.of(context).removeMemberAreYouSure, + cancelText: AppLocalizations.of(context).cancel.toUpperCase(), + ); - if (res == true) { - await channel.removeMembers([user.id]); - } - router.pop(); - }, - color: - StreamChatTheme.of(context).colorTheme.accentError), + if (res == true) { + await channel.removeMembers([user.id]); + } + router.pop(); + }, + color: StreamChatTheme.of(context).colorTheme.accentError, + ), _buildModalListTile( context, - StreamSvgIcon( - color: StreamChatTheme.of(context) - .colorTheme - .textLowEmphasis, + Icon( + context.streamIcons.crossSmall, + color: StreamChatTheme.of(context).colorTheme.textLowEmphasis, size: 24, - icon: StreamSvgIcons.closeSmall, ), AppLocalizations.of(context).cancel, () { @@ -911,20 +857,12 @@ class _GroupInfoScreenState extends State { if (otherMember.online) { alternativeWidget = Text( AppLocalizations.of(context).online, - style: TextStyle( - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(0.5)), + style: TextStyle(color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(0.5)), ); } else { alternativeWidget = Text( '${AppLocalizations.of(context).lastSeen} ${Jiffy.parseFromDateTime(otherMember.lastActive!).fromNow()}', - style: TextStyle( - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(0.5)), + style: TextStyle(color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(0.5)), ); } } @@ -932,9 +870,7 @@ class _GroupInfoScreenState extends State { return alternativeWidget; } - Widget _buildModalListTile( - BuildContext context, Widget leading, String title, VoidCallback onTap, - {Color? color}) { + Widget _buildModalListTile(BuildContext context, Widget leading, String title, VoidCallback onTap, {Color? color}) { color ??= StreamChatTheme.of(context).colorTheme.textHighEmphasis; return Material( @@ -958,10 +894,9 @@ class _GroupInfoScreenState extends State { Expanded( child: Text( title, - style: - TextStyle(color: color, fontWeight: FontWeight.bold), + style: TextStyle(color: color, fontWeight: FontWeight.bold), ), - ) + ), ], ), ), @@ -980,8 +915,7 @@ class _GroupInfoScreenState extends State { String? title; final client = StreamChat.of(context); if (extraData['name'] == null) { - final otherMembers = - members!.where((member) => member.user!.id != client.currentUser!.id); + final otherMembers = members!.where((member) => member.user!.id != client.currentUser!.id); if (otherMembers.isNotEmpty) { final maxWidth = width; final maxChars = maxWidth / maxFontSize!; @@ -1031,7 +965,7 @@ class _GroupInfoToggle extends StatelessWidget { }); final String title; - final StreamSvgIconData icon; + final IconData icon; final Stream channelStream; final ValueNotifier localNotifier; final VoidCallback onTurnOff; @@ -1040,46 +974,45 @@ class _GroupInfoToggle extends StatelessWidget { @override Widget build(BuildContext context) { return StreamBuilder( - stream: channelStream, - builder: (context, snapshot) { - localNotifier.value = snapshot.data; + stream: channelStream, + builder: (context, snapshot) { + localNotifier.value = snapshot.data; - return StreamOptionListTile( - tileColor: StreamChatTheme.of(context).colorTheme.appBg, - separatorColor: StreamChatTheme.of(context).colorTheme.disabled, - title: title, - titleTextStyle: StreamChatTheme.of(context).textTheme.body, - leading: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: StreamSvgIcon( - icon: icon, - size: 24, - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(0.5), - ), + return StreamOptionListTile( + tileColor: StreamChatTheme.of(context).colorTheme.appBg, + separatorColor: StreamChatTheme.of(context).colorTheme.disabled, + title: title, + titleTextStyle: StreamChatTheme.of(context).textTheme.body, + leading: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Icon( + icon, + size: 24, + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(0.5), ), - trailing: snapshot.data == null - ? const CircularProgressIndicator() - : ValueListenableBuilder( - valueListenable: localNotifier, - builder: (context, value, _) { - return CupertinoSwitch( - value: value!, - onChanged: (val) { - localNotifier.value = val; - if (snapshot.data!) { - onTurnOff(); - } else { - onTurnOn(); - } - }, - ); - }), - onTap: () {}, - ); - }); + ), + trailing: snapshot.data == null + ? const CircularProgressIndicator() + : ValueListenableBuilder( + valueListenable: localNotifier, + builder: (context, value, _) { + return CupertinoSwitch( + value: value!, + onChanged: (val) { + localNotifier.value = val; + if (snapshot.data!) { + onTurnOff(); + } else { + onTurnOn(); + } + }, + ); + }, + ), + onTap: () {}, + ); + }, + ); } } @@ -1093,7 +1026,7 @@ class _GroupInfoListTile extends StatelessWidget { }); final String title; - final StreamSvgIconData icon; + final IconData icon; final double iconSize; final double iconPadding; final VoidCallback onTap; @@ -1106,17 +1039,14 @@ class _GroupInfoListTile extends StatelessWidget { titleTextStyle: StreamChatTheme.of(context).textTheme.body, leading: Padding( padding: EdgeInsets.symmetric(horizontal: iconPadding), - child: StreamSvgIcon( - icon: icon, + child: Icon( + icon, size: iconSize, - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(0.5), + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(0.5), ), ), - trailing: StreamSvgIcon( - icon: StreamSvgIcons.right, + trailing: Icon( + context.streamIcons.chevronRight, color: StreamChatTheme.of(context).colorTheme.textLowEmphasis, ), onTap: onTap, diff --git a/sample_app/lib/pages/new_chat_screen.dart b/sample_app/lib/pages/new_chat_screen.dart index fa162f28aa..693c981833 100644 --- a/sample_app/lib/pages/new_chat_screen.dart +++ b/sample_app/lib/pages/new_chat_screen.dart @@ -17,8 +17,7 @@ class NewChatScreen extends StatefulWidget { } class _NewChatScreenState extends State { - final _chipInputTextFieldStateKey = - GlobalKey>(); + final _chipInputTextFieldStateKey = GlobalKey>(); late TextEditingController _controller; @@ -33,8 +32,7 @@ class _NewChatScreenState extends State { ], ); - ChipInputTextFieldState? get _chipInputTextFieldState => - _chipInputTextFieldStateKey.currentState; + ChipInputTextFieldState? get _chipInputTextFieldState => _chipInputTextFieldStateKey.currentState; String _userNameQuery = ''; @@ -61,8 +59,7 @@ class _NewChatScreenState extends State { }); userListController.filter = Filter.and([ - if (_userNameQuery.isNotEmpty) - Filter.autoComplete('name', _userNameQuery), + if (_userNameQuery.isNotEmpty) Filter.autoComplete('name', _userNameQuery), Filter.notEqual('id', StreamChat.of(context).currentUser!.id), ]); userListController.doInitialLoad(); @@ -91,13 +88,15 @@ class _NewChatScreenState extends State { final res = await chatState.client.queryChannelsOnline( state: false, watch: false, - filter: Filter.raw(value: { - 'members': [ - ..._selectedUsers.map((e) => e.id), - chatState.currentUser!.id, - ], - 'distinct': true, - }), + filter: Filter.raw( + value: { + 'members': [ + ..._selectedUsers.map((e) => e.id), + chatState.currentUser!.id, + ], + 'distinct': true, + }, + ), messageLimit: 0, paginationParams: const PaginationParams( limit: 1, @@ -148,8 +147,9 @@ class _NewChatScreenState extends State { leading: const StreamBackButton(), title: Text( AppLocalizations.of(context).newChat, - style: StreamChatTheme.of(context).textTheme.headlineBold.copyWith( - color: StreamChatTheme.of(context).colorTheme.textHighEmphasis), + style: StreamChatTheme.of( + context, + ).textTheme.headlineBold.copyWith(color: StreamChatTheme.of(context).colorTheme.textHighEmphasis), ), centerTitle: true, ), @@ -197,9 +197,7 @@ class _NewChatScreenState extends State { children: [ Container( decoration: BoxDecoration( - color: StreamChatTheme.of(context) - .colorTheme - .disabled, + color: StreamChatTheme.of(context).colorTheme.disabled, borderRadius: BorderRadius.circular(12), ), padding: const EdgeInsets.only(left: 24), @@ -209,30 +207,23 @@ class _NewChatScreenState extends State { user.name, maxLines: 1, style: TextStyle( - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis, + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis, ), ), ), ), Container( foregroundDecoration: BoxDecoration( - color: StreamChatTheme.of(context) - .colorTheme - .overlay, + color: StreamChatTheme.of(context).colorTheme.overlay, shape: BoxShape.circle, ), child: StreamUserAvatar( - showOnlineStatus: false, + size: .sm, user: user, - constraints: const BoxConstraints.tightFor( - height: 24, - width: 24, - ), + showOnlineIndicator: false, ), ), - const StreamSvgIcon(icon: StreamSvgIcons.close), + Icon(context.streamIcons.crossMedium), ], ), ); @@ -257,21 +248,17 @@ class _NewChatScreenState extends State { children: [ StreamNeumorphicButton( child: Center( - child: StreamSvgIcon( - color: StreamChatTheme.of(context) - .colorTheme - .accentPrimary, + child: Icon( + context.streamIcons.users, + color: StreamChatTheme.of(context).colorTheme.accentPrimary, size: 24, - icon: StreamSvgIcons.contacts, ), ), ), const SizedBox(width: 8), Text( AppLocalizations.of(context).createAGroup, - style: StreamChatTheme.of(context) - .textTheme - .bodyBold, + style: StreamChatTheme.of(context).textTheme.bodyBold, ), ], ), @@ -281,8 +268,7 @@ class _NewChatScreenState extends State { Container( width: double.maxFinite, decoration: BoxDecoration( - gradient: - StreamChatTheme.of(context).colorTheme.bgGradient, + gradient: StreamChatTheme.of(context).colorTheme.bgGradient, ), child: Padding( padding: const EdgeInsets.symmetric( @@ -290,17 +276,13 @@ class _NewChatScreenState extends State { horizontal: 8, ), child: Text( - _isSearchActive - ? '${AppLocalizations.of(context).matchesFor} "$_userNameQuery"' - : AppLocalizations.of(context).onThePlatorm, - style: StreamChatTheme.of(context) - .textTheme - .footnote - .copyWith( - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(.5))), + _isSearchActive + ? '${AppLocalizations.of(context).matchesFor} "$_userNameQuery"' + : AppLocalizations.of(context).onThePlatorm, + style: StreamChatTheme.of(context).textTheme.footnote.copyWith( + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(.5), + ), + ), ), ), Expanded( @@ -320,52 +302,44 @@ class _NewChatScreenState extends State { _chipInputTextFieldState!.removeItem(user); } }, - itemBuilder: ( - context, - users, - index, - defaultWidget, - ) { - return defaultWidget.copyWith( - selected: - _selectedUsers.contains(users[index]), - ); - }, + itemBuilder: + ( + context, + users, + index, + defaultWidget, + ) { + return defaultWidget.copyWith( + selected: _selectedUsers.contains(users[index]), + ); + }, emptyBuilder: (_) { return LayoutBuilder( builder: (context, viewportConstraints) { return SingleChildScrollView( - physics: - const AlwaysScrollableScrollPhysics(), + physics: const AlwaysScrollableScrollPhysics(), child: ConstrainedBox( constraints: BoxConstraints( - minHeight: - viewportConstraints.maxHeight, + minHeight: viewportConstraints.maxHeight, ), child: Center( child: Column( children: [ - const Padding( - padding: EdgeInsets.all(24), - child: StreamSvgIcon( - icon: StreamSvgIcons.search, + Padding( + padding: const EdgeInsets.all(24), + child: Icon( + context.streamIcons.magnifyingGlassSearch, size: 96, color: Colors.grey, ), ), Text( - AppLocalizations.of(context) - .noUserMatchesTheseKeywords, - style: StreamChatTheme.of( - context) - .textTheme - .footnote - .copyWith( - color: StreamChatTheme - .of(context) - .colorTheme - .textHighEmphasis - .withOpacity(.5)), + AppLocalizations.of(context).noUserMatchesTheseKeywords, + style: StreamChatTheme.of(context).textTheme.footnote.copyWith( + color: StreamChatTheme.of( + context, + ).colorTheme.textHighEmphasis.withOpacity(.5), + ), ), ], ), @@ -389,10 +363,7 @@ class _NewChatScreenState extends State { AppLocalizations.of(context).noChatsHereYet, style: TextStyle( fontSize: 12, - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(.5), + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(.5), ), ), ); diff --git a/sample_app/lib/pages/new_group_chat_screen.dart b/sample_app/lib/pages/new_group_chat_screen.dart index d9fd969568..fa4e9b844a 100644 --- a/sample_app/lib/pages/new_group_chat_screen.dart +++ b/sample_app/lib/pages/new_group_chat_screen.dart @@ -16,8 +16,7 @@ class NewGroupChatScreen extends StatefulWidget { } class _NewGroupChatScreenState extends State { - late final TextEditingController _controller = TextEditingController() - ..addListener(_userNameListener); + late final TextEditingController _controller = TextEditingController()..addListener(_userNameListener); String _userNameQuery = ''; @@ -45,8 +44,7 @@ class _NewGroupChatScreenState extends State { _isSearchActive = _userNameQuery.isNotEmpty; }); userListController.filter = Filter.and([ - if (_userNameQuery.isNotEmpty) - Filter.autoComplete('name', _userNameQuery), + if (_userNameQuery.isNotEmpty) Filter.autoComplete('name', _userNameQuery), Filter.notEqual('id', StreamChat.of(context).currentUser!.id), ]); userListController.doInitialLoad(); @@ -87,14 +85,14 @@ class _NewGroupChatScreenState extends State { if (state.users.isNotEmpty) IconButton( color: StreamChatTheme.of(context).colorTheme.accentPrimary, - icon: const StreamSvgIcon(icon: StreamSvgIcons.arrowRight), + icon: Icon(context.streamIcons.arrowRight), onPressed: () async { GoRouter.of(context).pushNamed( Routes.NEW_GROUP_CHAT_DETAILS.name, extra: state, ); }, - ) + ), ], ), body: StreamConnectionStatusBuilder( @@ -121,8 +119,7 @@ class _NewGroupChatScreenState extends State { message: statusString, child: NestedScrollView( floatHeaderSlivers: true, - headerSliverBuilder: - (BuildContext context, bool innerBoxIsScrolled) { + headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) { return [ SliverToBoxAdapter( child: SearchTextField( @@ -138,8 +135,7 @@ class _NewGroupChatScreenState extends State { scrollDirection: Axis.horizontal, itemCount: state.users.length, padding: const EdgeInsets.all(8), - separatorBuilder: (_, __) => - const SizedBox(width: 16), + separatorBuilder: (_, __) => const SizedBox(width: 16), itemBuilder: (_, index) { final user = state.users.elementAt(index); return Column( @@ -147,16 +143,8 @@ class _NewGroupChatScreenState extends State { Stack( children: [ StreamUserAvatar( - onlineIndicatorAlignment: - const Alignment(0.9, 0.9), + size: .xl, user: user, - borderRadius: - BorderRadius.circular(32), - constraints: - const BoxConstraints.tightFor( - height: 64, - width: 64, - ), ), Positioned( top: -4, @@ -167,29 +155,20 @@ class _NewGroupChatScreenState extends State { }, child: DecoratedBox( decoration: BoxDecoration( - color: - StreamChatTheme.of(context) - .colorTheme - .appBg, + color: StreamChatTheme.of(context).colorTheme.appBg, shape: BoxShape.circle, border: Border.all( - color: StreamChatTheme.of( - context) - .colorTheme - .appBg, + color: StreamChatTheme.of(context).colorTheme.appBg, ), ), - child: StreamSvgIcon( - color: - StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis, + child: Icon( + context.streamIcons.crossMedium, + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis, size: 24, - icon: StreamSvgIcons.close, ), ), ), - ) + ), ], ), const SizedBox(height: 4), @@ -213,9 +192,7 @@ class _NewGroupChatScreenState extends State { child: Container( width: double.maxFinite, decoration: BoxDecoration( - gradient: StreamChatTheme.of(context) - .colorTheme - .bgGradient, + gradient: StreamChatTheme.of(context).colorTheme.bgGradient, ), child: Padding( padding: const EdgeInsets.symmetric( @@ -227,9 +204,7 @@ class _NewGroupChatScreenState extends State { ? '${AppLocalizations.of(context).matchesFor} "$_userNameQuery"' : AppLocalizations.of(context).onThePlatorm, style: TextStyle( - color: StreamChatTheme.of(context) - .colorTheme - .textLowEmphasis, + color: StreamChatTheme.of(context).colorTheme.textLowEmphasis, ), ), ), @@ -263,25 +238,17 @@ class _NewGroupChatScreenState extends State { children: [ Padding( padding: const EdgeInsets.all(24), - child: StreamSvgIcon( - icon: StreamSvgIcons.search, + child: Icon( + context.streamIcons.magnifyingGlassSearch, size: 96, - color: StreamChatTheme.of(context) - .colorTheme - .textLowEmphasis, + color: StreamChatTheme.of(context).colorTheme.textLowEmphasis, ), ), Text( - AppLocalizations.of(context) - .noUserMatchesTheseKeywords, - style: StreamChatTheme.of(context) - .textTheme - .footnote - .copyWith( - color: StreamChatTheme.of(context) - .colorTheme - .textLowEmphasis, - ), + AppLocalizations.of(context).noUserMatchesTheseKeywords, + style: StreamChatTheme.of(context).textTheme.footnote.copyWith( + color: StreamChatTheme.of(context).colorTheme.textLowEmphasis, + ), ), ], ), @@ -312,8 +279,7 @@ class _HeaderDelegate extends SliverPersistentHeaderDelegate { final double height; @override - Widget build( - BuildContext context, double shrinkOffset, bool overlapsContent) { + Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) { return ColoredBox( color: StreamChatTheme.of(context).colorTheme.barsBg, child: child, diff --git a/sample_app/lib/pages/pinned_messages_screen.dart b/sample_app/lib/pages/pinned_messages_screen.dart index 01c9ac5075..af299b826b 100644 --- a/sample_app/lib/pages/pinned_messages_screen.dart +++ b/sample_app/lib/pages/pinned_messages_screen.dart @@ -54,8 +54,8 @@ class _PinnedMessagesScreenState extends State { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - StreamSvgIcon( - icon: StreamSvgIcons.pin, + Icon( + context.streamIcons.pin, size: 136, color: StreamChatTheme.of(context).colorTheme.disabled, ), @@ -64,37 +64,32 @@ class _PinnedMessagesScreenState extends State { AppLocalizations.of(context).noPinnedItems, style: TextStyle( fontSize: 17, - color: - StreamChatTheme.of(context).colorTheme.textHighEmphasis, + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 8), RichText( textAlign: TextAlign.center, - text: TextSpan(children: [ - TextSpan( - text: '${AppLocalizations.of(context).longPressMessage} ', - style: TextStyle( - fontSize: 14, - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(0.5), + text: TextSpan( + children: [ + TextSpan( + text: '${AppLocalizations.of(context).longPressMessage} ', + style: TextStyle( + fontSize: 14, + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(0.5), + ), ), - ), - TextSpan( - text: AppLocalizations.of(context).pinToConversation, - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.bold, - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(0.5), + TextSpan( + text: AppLocalizations.of(context).pinToConversation, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(0.5), + ), ), - ), - ]), + ], + ), ), ], ), diff --git a/sample_app/lib/pages/reminders_page.dart b/sample_app/lib/pages/reminders_page.dart index 18507e9e83..99426f30da 100644 --- a/sample_app/lib/pages/reminders_page.dart +++ b/sample_app/lib/pages/reminders_page.dart @@ -15,20 +15,21 @@ class RemindersPage extends StatefulWidget { } class _RemindersPageState extends State { - late final controller = StreamMessageReminderListController( - client: StreamChat.of(context).client, - )..eventListener = (event) { - if (event.type == EventType.connectionRecovered || - event.type == EventType.notificationReminderDue) { - // This will create the query filter with the updated current date - // and time, so that the reminders list is updated with the new - // reminders that are due. - onFilterChanged(_currentFilter); - } - - // Returning false as we also want the controller to handle the event. - return false; - }; + late final controller = + StreamMessageReminderListController( + client: StreamChat.of(context).client, + ) + ..eventListener = (event) { + if (event.type == EventType.connectionRecovered || event.type == EventType.notificationReminderDue) { + // This will create the query filter with the updated current date + // and time, so that the reminders list is updated with the new + // reminders that are due. + onFilterChanged(_currentFilter); + } + + // Returning false as we also want the controller to handle the event. + return false; + }; @override void dispose() { @@ -80,9 +81,9 @@ class _RemindersPageState extends State { children: [ CustomSlidableAction( backgroundColor: theme.colorTheme.inputBg, - child: StreamSvgIcon( + child: Icon( + context.streamIcons.editBig, size: 24, - icon: StreamSvgIcons.edit, color: theme.colorTheme.accentPrimary, ), onPressed: (_) async { @@ -104,9 +105,9 @@ class _RemindersPageState extends State { ), CustomSlidableAction( backgroundColor: theme.colorTheme.inputBg, - child: StreamSvgIcon( + child: Icon( + context.streamIcons.trashBin, size: 24, - icon: StreamSvgIcons.delete, color: theme.colorTheme.accentError, ), onPressed: (context) { @@ -209,7 +210,8 @@ enum MessageRemindersFilter { overdue('Overdue'), upcoming('Upcoming'), scheduled('Scheduled'), - savedForLater('Saved for later'); + savedForLater('Saved for later') + ; const MessageRemindersFilter(this.label); final String label; @@ -238,12 +240,10 @@ class MessageRemindersFilterSelection extends StatefulWidget { final ValueSetter onSelected; @override - State createState() => - _MessageRemindersFilterSelectionState(); + State createState() => _MessageRemindersFilterSelectionState(); } -class _MessageRemindersFilterSelectionState - extends State { +class _MessageRemindersFilterSelectionState extends State { final _filterKeys = {}; @override diff --git a/sample_app/lib/pages/splash_screen.dart b/sample_app/lib/pages/splash_screen.dart index 0bbdcd9ebe..a7fb8c9269 100644 --- a/sample_app/lib/pages/splash_screen.dart +++ b/sample_app/lib/pages/splash_screen.dart @@ -3,8 +3,7 @@ import 'package:flutter/material.dart'; import 'package:lottie/lottie.dart'; -mixin SplashScreenStateMixin on State - implements TickerProvider { +mixin SplashScreenStateMixin on State implements TickerProvider { late final _animationController = AnimationController( vsync: this, duration: const Duration( @@ -20,29 +19,38 @@ mixin SplashScreenStateMixin on State ), ); - late final _circleAnimation = Tween( - begin: 0, - end: 1000, - ).animate(CurvedAnimation( - parent: _animationController, - curve: Curves.easeInOut, - )); + late final _circleAnimation = + Tween( + begin: 0, + end: 1000, + ).animate( + CurvedAnimation( + parent: _animationController, + curve: Curves.easeInOut, + ), + ); - late final _colorAnimation = ColorTween( - begin: const Color(0xff005FFF), - end: Colors.transparent, - ).animate(CurvedAnimation( - parent: _animationController, - curve: Curves.easeInOut, - )); + late final _colorAnimation = + ColorTween( + begin: const Color(0xff005FFF), + end: Colors.transparent, + ).animate( + CurvedAnimation( + parent: _animationController, + curve: Curves.easeInOut, + ), + ); - late final _scaleAnimation = Tween( - begin: 1, - end: 1.5, - ).animate(CurvedAnimation( - parent: _scaleAnimationController, - curve: Curves.easeInOutCubic, - )); + late final _scaleAnimation = + Tween( + begin: 1, + end: 1.5, + ).animate( + CurvedAnimation( + parent: _scaleAnimationController, + curve: Curves.easeInOutCubic, + ), + ); bool animationCompleted = false; @@ -75,50 +83,46 @@ mixin SplashScreenStateMixin on State } Widget buildAnimation() => Stack( - clipBehavior: Clip.none, - alignment: Alignment.center, - children: [ - AnimatedBuilder( - animation: _scaleAnimation, - builder: (context, child) => - Transform.scale(scale: _scaleAnimation.value, child: child), - child: AnimatedBuilder( - animation: _colorAnimation, - builder: (context, child) { - return DecoratedBox( - decoration: BoxDecoration(color: _colorAnimation.value), - child: Center( - child: !_animationController.isAnimating - ? child - : const SizedBox(), - ), - ); - }, - child: RepaintBoundary( - child: Lottie.asset( - 'assets/floating_boat.json', - alignment: Alignment.center, - ), + clipBehavior: Clip.none, + alignment: Alignment.center, + children: [ + AnimatedBuilder( + animation: _scaleAnimation, + builder: (context, child) => Transform.scale(scale: _scaleAnimation.value, child: child), + child: AnimatedBuilder( + animation: _colorAnimation, + builder: (context, child) { + return DecoratedBox( + decoration: BoxDecoration(color: _colorAnimation.value), + child: Center( + child: !_animationController.isAnimating ? child : const SizedBox(), ), + ); + }, + child: RepaintBoundary( + child: Lottie.asset( + 'assets/floating_boat.json', + alignment: Alignment.center, ), ), - AnimatedBuilder( - animation: _circleAnimation, - builder: (context, snapshot) { - return Transform.scale( - scale: _circleAnimation.value, - child: Container( - width: 1, - height: 1, - decoration: BoxDecoration( - color: Colors.white - .withOpacity(1 - _animationController.value), - shape: BoxShape.circle, - ), - ), - ); - }, - ), - ], - ); + ), + ), + AnimatedBuilder( + animation: _circleAnimation, + builder: (context, snapshot) { + return Transform.scale( + scale: _circleAnimation.value, + child: Container( + width: 1, + height: 1, + decoration: BoxDecoration( + color: Colors.white.withOpacity(1 - _animationController.value), + shape: BoxShape.circle, + ), + ), + ); + }, + ), + ], + ); } diff --git a/sample_app/lib/pages/thread_list_page.dart b/sample_app/lib/pages/thread_list_page.dart index 8dc69ef3df..f730dde1f3 100644 --- a/sample_app/lib/pages/thread_list_page.dart +++ b/sample_app/lib/pages/thread_list_page.dart @@ -29,9 +29,7 @@ class _ThreadListPageState extends State { valueListenable: controller.unseenThreadIds, builder: (_, unreadThreads, __) => StreamUnreadThreadsBanner( unreadThreads: unreadThreads, - onTap: () => controller - .refresh(resetValue: false) - .then((_) => controller.clearUnseenThreadIds()), + onTap: () => controller.refresh(resetValue: false).then((_) => controller.clearUnseenThreadIds()), ), ), Expanded( @@ -41,9 +39,9 @@ class _ThreadListPageState extends State { final channelCid = thread.channelCid; final channel = StreamChat.of(context).client.channel( - channelCid.split(':')[0], - id: channelCid.split(':')[1], - ); + channelCid.split(':')[0], + id: channelCid.split(':')[1], + ); Navigator.of(context).push( MaterialPageRoute( diff --git a/sample_app/lib/pages/thread_page.dart b/sample_app/lib/pages/thread_page.dart index 3e8376eb44..197fb72152 100644 --- a/sample_app/lib/pages/thread_page.dart +++ b/sample_app/lib/pages/thread_page.dart @@ -1,6 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:sample_app/widgets/location/location_attachment.dart'; -import 'package:sample_app/widgets/location/location_detail_dialog.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; class ThreadPage extends StatefulWidget { @@ -45,18 +43,9 @@ class _ThreadPageState extends State { @override Widget build(BuildContext context) { - final locationAttachmentBuilder = LocationAttachmentBuilder( - onAttachmentTap: (location) => showLocationDetailDialog( - context: context, - location: location, - ), - ); - return Scaffold( backgroundColor: StreamChatTheme.of(context).colorTheme.appBg, - appBar: StreamThreadHeader( - parent: widget.parent, - ), + appBar: StreamThreadHeader(parent: widget.parent), body: Column( children: [ Expanded( @@ -64,35 +53,10 @@ class _ThreadPageState extends State { parentMessage: widget.parent, initialScrollIndex: widget.initialScrollIndex, initialAlignment: widget.initialAlignment, - //onMessageSwiped: _reply, + onReplyTap: _reply, messageFilter: defaultFilter, showScrollToBottom: false, highlightInitialMessage: true, - parentMessageBuilder: (context, message, defaultMessage) { - return defaultMessage.copyWith( - attachmentBuilders: [locationAttachmentBuilder], - ); - }, - messageBuilder: (context, details, messages, defaultMessage) { - final message = details.message; - - return defaultMessage.copyWith( - onReplyTap: _reply, - showEditMessage: message.sharedLocation == null, - attachmentBuilders: [locationAttachmentBuilder], - bottomRowBuilderWithDefaultWidget: ( - context, - message, - defaultWidget, - ) { - return defaultWidget.copyWith( - deletedBottomRowBuilder: (context, message) { - return const StreamVisibleFootnote(); - }, - ); - }, - ); - }, ), ), if (widget.parent.type != 'deleted') diff --git a/sample_app/lib/pages/user_mentions_page.dart b/sample_app/lib/pages/user_mentions_page.dart index d4bb19e273..f7eac3310c 100644 --- a/sample_app/lib/pages/user_mentions_page.dart +++ b/sample_app/lib/pages/user_mentions_page.dart @@ -41,21 +41,17 @@ class _UserMentionsPageState extends State { children: [ Padding( padding: const EdgeInsets.all(24), - child: StreamSvgIcon( - icon: StreamSvgIcons.mentions, + child: Icon( + context.streamIcons.at, size: 96, - color: - StreamChatTheme.of(context).colorTheme.disabled, + color: StreamChatTheme.of(context).colorTheme.disabled, ), ), Text( AppLocalizations.of(context).noMentionsExistYet, - style: - StreamChatTheme.of(context).textTheme.body.copyWith( - color: StreamChatTheme.of(context) - .colorTheme - .textLowEmphasis, - ), + style: StreamChatTheme.of(context).textTheme.body.copyWith( + color: StreamChatTheme.of(context).colorTheme.textLowEmphasis, + ), ), ], ), diff --git a/sample_app/lib/routes/app_routes.dart b/sample_app/lib/routes/app_routes.dart index 094060eb44..ef0f41dce5 100644 --- a/sample_app/lib/routes/app_routes.dart +++ b/sample_app/lib/routes/app_routes.dart @@ -19,24 +19,19 @@ final appRoutes = [ GoRoute( name: Routes.CHANNEL_LIST_PAGE.name, path: Routes.CHANNEL_LIST_PAGE.path, - builder: (BuildContext context, GoRouterState state) => - const ChannelListPage(), + builder: (BuildContext context, GoRouterState state) => const ChannelListPage(), routes: [ GoRoute( name: Routes.CHANNEL_PAGE.name, path: Routes.CHANNEL_PAGE.path, builder: (context, state) { - final channel = StreamChat.of(context) - .client - .state - .channels[state.pathParameters['cid']]; + final channel = StreamChat.of(context).client.state.channels[state.pathParameters['cid']]; final messageId = state.uri.queryParameters['mid']; final parentId = state.uri.queryParameters['pid']; Message? parentMessage; if (parentId != null) { - parentMessage = channel?.state!.messages - .firstWhereOrNull((it) => it.id == parentId); + parentMessage = channel?.state!.messages.firstWhereOrNull((it) => it.id == parentId); } return StreamChannel( @@ -58,10 +53,7 @@ final appRoutes = [ name: Routes.CHAT_INFO_SCREEN.name, path: Routes.CHAT_INFO_SCREEN.path, builder: (BuildContext context, GoRouterState state) { - final channel = StreamChat.of(context) - .client - .state - .channels[state.pathParameters['cid']]; + final channel = StreamChat.of(context).client.state.channels[state.pathParameters['cid']]; return StreamChannel( channel: channel!, child: ChatInfoScreen( @@ -75,10 +67,7 @@ final appRoutes = [ name: Routes.GROUP_INFO_SCREEN.name, path: Routes.GROUP_INFO_SCREEN.path, builder: (BuildContext context, GoRouterState state) { - final channel = StreamChat.of(context) - .client - .state - .channels[state.pathParameters['cid']]; + final channel = StreamChat.of(context).client.state.channels[state.pathParameters['cid']]; return StreamChannel( channel: channel!, child: GroupInfoScreen( @@ -116,13 +105,11 @@ final appRoutes = [ GoRoute( name: Routes.CHOOSE_USER.name, path: Routes.CHOOSE_USER.path, - builder: (BuildContext context, GoRouterState state) => - const ChooseUserPage(), + builder: (BuildContext context, GoRouterState state) => const ChooseUserPage(), ), GoRoute( name: Routes.ADVANCED_OPTIONS.name, path: Routes.ADVANCED_OPTIONS.path, - builder: (BuildContext context, GoRouterState state) => - const AdvancedOptionsPage(), + builder: (BuildContext context, GoRouterState state) => const AdvancedOptionsPage(), ), ]; diff --git a/sample_app/lib/routes/routes.dart b/sample_app/lib/routes/routes.dart index d60383edd2..ac62189d37 100644 --- a/sample_app/lib/routes/routes.dart +++ b/sample_app/lib/routes/routes.dart @@ -4,24 +4,24 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// Application routes abstract class Routes { - static const RouteConfig CHOOSE_USER = - RouteConfig(name: 'choose_user', path: '/users'); - static const RouteConfig ADVANCED_OPTIONS = - RouteConfig(name: 'advanced_options', path: '/options'); - static const ChannelRouteConfig CHANNEL_PAGE = - ChannelRouteConfig(name: 'channel_page', path: 'channel/:cid'); - static const RouteConfig NEW_CHAT = - RouteConfig(name: 'new_chat', path: '/new_chat'); - static const RouteConfig NEW_GROUP_CHAT = - RouteConfig(name: 'new_group_chat', path: '/new_group_chat'); + static const RouteConfig CHOOSE_USER = RouteConfig(name: 'choose_user', path: '/users'); + static const RouteConfig ADVANCED_OPTIONS = RouteConfig(name: 'advanced_options', path: '/options'); + static const ChannelRouteConfig CHANNEL_PAGE = ChannelRouteConfig(name: 'channel_page', path: 'channel/:cid'); + static const RouteConfig NEW_CHAT = RouteConfig(name: 'new_chat', path: '/new_chat'); + static const RouteConfig NEW_GROUP_CHAT = RouteConfig(name: 'new_group_chat', path: '/new_group_chat'); static const RouteConfig NEW_GROUP_CHAT_DETAILS = RouteConfig( - name: 'new_group_chat_details', path: '/new_group_chat_details'); - static const ChannelRouteConfig CHAT_INFO_SCREEN = - ChannelRouteConfig(name: 'chat_info_screen', path: 'chat_info_screen'); - static const ChannelRouteConfig GROUP_INFO_SCREEN = - ChannelRouteConfig(name: 'group_info_screen', path: 'group_info_screen'); - static const RouteConfig CHANNEL_LIST_PAGE = - RouteConfig(name: 'channel_list_page', path: '/channels'); + name: 'new_group_chat_details', + path: '/new_group_chat_details', + ); + static const ChannelRouteConfig CHAT_INFO_SCREEN = ChannelRouteConfig( + name: 'chat_info_screen', + path: 'chat_info_screen', + ); + static const ChannelRouteConfig GROUP_INFO_SCREEN = ChannelRouteConfig( + name: 'group_info_screen', + path: 'group_info_screen', + ); + static const RouteConfig CHANNEL_LIST_PAGE = RouteConfig(name: 'channel_list_page', path: '/channels'); } class RouteConfig { @@ -36,7 +36,7 @@ class ChannelRouteConfig extends RouteConfig { Map params(Channel channel) => {'cid': channel.cid!}; Map queryParams(Message message) => { - 'mid': message.id, - if (message.parentId != null) 'pid': message.parentId! - }; + 'mid': message.id, + if (message.parentId != null) 'pid': message.parentId!, + }; } diff --git a/sample_app/lib/state/init_data.dart b/sample_app/lib/state/init_data.dart index 7f23896afd..3bb7fc4f1b 100644 --- a/sample_app/lib/state/init_data.dart +++ b/sample_app/lib/state/init_data.dart @@ -31,6 +31,5 @@ class InitData { final StreamChatClient client; final StreamingSharedPreferences preferences; - InitData copyWith({required StreamChatClient client}) => - InitData(client, preferences); + InitData copyWith({required StreamChatClient client}) => InitData(client, preferences); } diff --git a/sample_app/lib/utils/app_config.dart b/sample_app/lib/utils/app_config.dart index e006cddfee..d6443747e6 100644 --- a/sample_app/lib/utils/app_config.dart +++ b/sample_app/lib/utils/app_config.dart @@ -1,81 +1,68 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -const sentryDsn = - 'https://6381ef88de4140db8f5e25ab37e0f08c@o1213503.ingest.sentry.io/6352870'; +const sentryDsn = 'https://6381ef88de4140db8f5e25ab37e0f08c@o1213503.ingest.sentry.io/6352870'; const kDefaultStreamApiKey = 'kv7mcsxr24p8'; final defaultUsers = { 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoic2FsdmF0b3JlIn0.pgiJz7sIc7iP29BHKFwe3nLm5-OaR_1l2P-SlgiC9a8': User( - id: 'salvatore', - extraData: const { - 'name': 'Salvatore Giordano', - 'image': - 'https://avatars.githubusercontent.com/u/20601437?s=460&u=3f66c22a7483980624804054ae7f357cf102c784&v=4', - }, - ), - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoic2FoaWwifQ.WnIUoB5gR2kcAsFhiDvkiD6zdHXZ-VSU2aQWWkhsvfo': - User( + id: 'salvatore', + extraData: const { + 'name': 'Salvatore Giordano', + 'image': + 'https://avatars.githubusercontent.com/u/20601437?s=460&u=3f66c22a7483980624804054ae7f357cf102c784&v=4', + }, + ), + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoic2FoaWwifQ.WnIUoB5gR2kcAsFhiDvkiD6zdHXZ-VSU2aQWWkhsvfo': User( id: 'sahil', extraData: const { 'name': 'Sahil Kumar', - 'image': - 'https://avatars.githubusercontent.com/u/25670178?s=400&u=30ded3784d8d2310c5748f263fd5e6433c119aa1&v=4', + 'image': 'https://avatars.githubusercontent.com/u/25670178?s=400&u=30ded3784d8d2310c5748f263fd5e6433c119aa1&v=4', }, ), - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiYmVuIn0.nAz2sNFGQwY7rl2Og2z3TGHUsdpnN53tOsUglJFvLmg': - User( + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiYmVuIn0.nAz2sNFGQwY7rl2Og2z3TGHUsdpnN53tOsUglJFvLmg': User( id: 'ben', extraData: const { 'name': 'Ben Golden', 'image': 'https://avatars.githubusercontent.com/u/1581974?s=400&v=4', }, ), - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoidGhpZXJyeSJ9.lEq6TrZtHzjoNtf7HHRufUPyGo_pa8vg4_XhEBp4ckY': - User( + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoidGhpZXJyeSJ9.lEq6TrZtHzjoNtf7HHRufUPyGo_pa8vg4_XhEBp4ckY': User( id: 'thierry', extraData: const { 'name': 'Thierry Schellenbach', - 'image': - 'https://avatars.githubusercontent.com/u/265409?s=400&u=2d0e3bb1820db992066196bff7b004f0eee8e28d&v=4', + 'image': 'https://avatars.githubusercontent.com/u/265409?s=400&u=2d0e3bb1820db992066196bff7b004f0eee8e28d&v=4', }, ), - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoidG9tbWFzbyJ9.GLSI0ESshERMo2WjUpysD709NEtn1zmGimUN2an7g9o': - User( + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoidG9tbWFzbyJ9.GLSI0ESshERMo2WjUpysD709NEtn1zmGimUN2an7g9o': User( id: 'tommaso', extraData: const { 'name': 'Tommaso Barbugli', 'image': 'https://avatars.githubusercontent.com/u/88735?s=400&v=4', }, ), - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiZGV2ZW4ifQ.z3zI4PqJnNhc-1o-VKcmb6BnnQ0oxFNCRHwEulHqcWc': - User( + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiZGV2ZW4ifQ.z3zI4PqJnNhc-1o-VKcmb6BnnQ0oxFNCRHwEulHqcWc': User( id: 'deven', extraData: const { 'name': 'Deven Joshi', - 'image': - 'https://avatars.githubusercontent.com/u/26357843?s=400&u=0c61d890866e67bf69f58878be58915e9bfd39ee&v=4', + 'image': 'https://avatars.githubusercontent.com/u/26357843?s=400&u=0c61d890866e67bf69f58878be58915e9bfd39ee&v=4', }, ), - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoibmVldmFzaCJ9.3EdHegTxibrz3A9cTiKmpEyawwcCVB8FXnoFzr4eKvw': - User( + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoibmVldmFzaCJ9.3EdHegTxibrz3A9cTiKmpEyawwcCVB8FXnoFzr4eKvw': User( id: 'neevash', extraData: const { 'name': 'Neevash Ramdial', - 'image': - 'https://avatars.githubusercontent.com/u/25674767?s=400&u=1d7333baf7dd9d143db8bfcdb31a838b89cfff9c&v=4', + 'image': 'https://avatars.githubusercontent.com/u/25674767?s=400&u=1d7333baf7dd9d143db8bfcdb31a838b89cfff9c&v=4', }, ), - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoicWF0ZXN0MSJ9.fnelU7HcP7QoEEsCGteNlF1fppofzNlrnpDQuIgeKCU': - User( + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoicWF0ZXN0MSJ9.fnelU7HcP7QoEEsCGteNlF1fppofzNlrnpDQuIgeKCU': User( id: 'qatest1', extraData: const { 'name': 'QA test 1', }, ), - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoicWF0ZXN0MiJ9.vSCqAEbs2WVmMWsOsa7065Fsjq-rsTih6qsHPynl7XM': - User( + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoicWF0ZXN0MiJ9.vSCqAEbs2WVmMWsOsa7065Fsjq-rsTih6qsHPynl7XM': User( id: 'qatest2', extraData: const { 'name': 'QA test 2', diff --git a/sample_app/lib/utils/local_notification_observer.dart b/sample_app/lib/utils/local_notification_observer.dart index 4b2fc12a69..7be9674c63 100644 --- a/sample_app/lib/utils/local_notification_observer.dart +++ b/sample_app/lib/utils/local_notification_observer.dart @@ -12,18 +12,17 @@ class LocalNotificationObserver extends NavigatorObserver { ) { _subscription = client .on( - EventType.messageNew, - EventType.notificationMessageNew, - ) + EventType.messageNew, + EventType.notificationMessageNew, + ) .listen((event) { - _handleEvent(event, client, navigatorKey); - }); + _handleEvent(event, client, navigatorKey); + }); } Route? currentRoute; late final StreamSubscription _subscription; - void _handleEvent(Event event, StreamChatClient client, - GlobalKey navigatorKey) { + void _handleEvent(Event event, StreamChatClient client, GlobalKey navigatorKey) { if (event.message?.user?.id == client.state.currentUser?.id) { return; } diff --git a/sample_app/lib/utils/localizations.dart b/sample_app/lib/utils/localizations.dart index 7ce1005291..2d761d8501 100644 --- a/sample_app/lib/utils/localizations.dart +++ b/sample_app/lib/utils/localizations.dart @@ -20,20 +20,17 @@ class AppLocalizations { 'create_a_group': 'Create a Group', 'custom_settings': 'Custom settings', 'delete': 'Delete', - 'delete_conversation_are_you_sure': - 'Are you sure you want to delete this conversation?', + 'delete_conversation_are_you_sure': 'Are you sure you want to delete this conversation?', 'delete_conversation_title': 'Delete Conversation', 'disconnected': 'Disconnected', 'error_connecting': 'Error connecting, retry', 'files': 'Files', 'files_appear_here': 'Files sent in this chat will appear here', - 'group_shared_with_user_appear_here': - 'Group shared with User will appear here.', + 'group_shared_with_user_appear_here': 'Group shared with User will appear here.', 'last_seen': 'Last seen', 'leave': 'Leave', 'leave_conversation': 'Leave conversation', - 'leave_conversation_are_you_sure': - 'Are you sure you want to leave this conversation?', + 'leave_conversation_are_you_sure': 'Are you sure you want to leave this conversation?', 'leave_group': 'Leave Group', 'loading': 'Loading...', 'login': 'Login', @@ -65,21 +62,18 @@ class AppLocalizations { 'ok': 'OK', 'online': 'Online', 'on_the_platform': 'On the platform', - 'operation_could_not_be_completed': - "The operation couldn't be completed.", + 'operation_could_not_be_completed': "The operation couldn't be completed.", 'owner': 'Owner', 'pin_group': 'Pin group', 'photos_and_videos': 'Photos & Videos', - 'photos_or_videos_will_appear_here': - 'Photos or videos sent in this chat will \nappear here', + 'photos_or_videos_will_appear_here': 'Photos or videos sent in this chat will \nappear here', 'pinned_messages': 'Pinned Messages', 'pin_to_conversation': 'Pin to conversation', 'reconnecting': 'Reconnecting...', 'remove': 'Remove', 'remove_from_group': 'Remove From Group', 'remove_member': 'Remove member', - 'remove_member_are_you_sure': - 'Are you sure you want to remove this member?', + 'remove_member_are_you_sure': 'Are you sure you want to remove this member?', 'search': 'Search', 'select_user_to_try_flutter_sdk': 'Select a user to try the Flutter SDK', 'shared_groups': 'Shared Groups', @@ -113,20 +107,17 @@ class AppLocalizations { 'create_a_group': 'Crea un Gruppo', 'custom_settings': 'Opzioni Personalizzate', 'delete': 'Cancella', - 'delete_conversation_are_you_sure': - 'Sei sicuro di voler eliminare la conversazione?', + 'delete_conversation_are_you_sure': 'Sei sicuro di voler eliminare la conversazione?', 'delete_conversation_title': 'Elimina Conversazione', 'disconnected': 'Disconnesso', 'error_connecting': 'Errore durante la connessione, riprova', 'files': 'File', 'files_appear_here': 'I file inviati in questa chat compariranno qui', - 'group_shared_with_user_appear_here': - "I gruppi in comune con quest'utente compariranno qui", + 'group_shared_with_user_appear_here': "I gruppi in comune con quest'utente compariranno qui", 'last_seen': 'Ultimo accesso', 'leave': 'Lascia', 'leave_conversation': 'Lascia conversazione', - 'leave_conversation_are_you_sure': - 'Sei sicuro di voler lasciare questa conversazione?', + 'leave_conversation_are_you_sure': 'Sei sicuro di voler lasciare questa conversazione?', 'leave_group': 'Lascia Gruppo', 'loading': 'Caricamento...', 'login': 'Login', @@ -158,12 +149,10 @@ class AppLocalizations { 'ok': 'OK', 'online': 'Online', 'on_the_platform': 'Sulla piattaforma', - 'operation_could_not_be_completed': - "Non é stato possibile completare l'operazione.", + 'operation_could_not_be_completed': "Non é stato possibile completare l'operazione.", 'owner': 'Proprietario', 'photos_and_videos': 'Foto & Video', - 'photos_or_videos_will_appear_here': - 'Foto or video inviati in questa chat \ncompariranno qui', + 'photos_or_videos_will_appear_here': 'Foto or video inviati in questa chat \ncompariranno qui', 'pinned_messages': 'Messaggi in evidenza', 'pin_group': 'Gruppo di evidenziazione', 'pin_to_conversation': 'Metti in evidenza', @@ -171,11 +160,9 @@ class AppLocalizations { 'remove': 'Rimuovi', 'remove_from_group': 'Rimuovi Dal Gruppo', 'remove_member': 'Rimuovi membro', - 'remove_member_are_you_sure': - 'Sei sicuro di voler rimuovere questo membro?', + 'remove_member_are_you_sure': 'Sei sicuro di voler rimuovere questo membro?', 'search': 'Cerca', - 'select_user_to_try_flutter_sdk': - "Seleziona un utente per provare l'SDK Flutter", + 'select_user_to_try_flutter_sdk': "Seleziona un utente per provare l'SDK Flutter", 'shared_groups': 'Gruppi in comune', 'sign_out': 'Sign out', 'something_went_wrong_error_message': 'Qualcosa é andato storto', @@ -256,8 +243,7 @@ class AppLocalizations { } String get deleteConversationAreYouSure { - return _localizedValues[locale.languageCode]![ - 'delete_conversation_are_you_sure']!; + return _localizedValues[locale.languageCode]!['delete_conversation_are_you_sure']!; } String get deleteConversationTitle { @@ -281,8 +267,7 @@ class AppLocalizations { } String get groupSharedWithUserAppearHere { - return _localizedValues[locale.languageCode]![ - 'group_shared_with_user_appear_here']!; + return _localizedValues[locale.languageCode]!['group_shared_with_user_appear_here']!; } String get lastSeen { @@ -298,8 +283,7 @@ class AppLocalizations { } String get leaveConversationAreYouSure { - return _localizedValues[locale.languageCode]![ - 'leave_conversation_are_you_sure']!; + return _localizedValues[locale.languageCode]!['leave_conversation_are_you_sure']!; } String get leaveGroup { @@ -339,8 +323,7 @@ class AppLocalizations { } String get messageChannelDescription { - return _localizedValues[locale.languageCode]![ - 'message_channel_description']!; + return _localizedValues[locale.languageCode]!['message_channel_description']!; } String get messageChannelName { @@ -412,8 +395,7 @@ class AppLocalizations { } String get noUserMatchesTheseKeywords { - return _localizedValues[locale.languageCode]![ - 'no_user_matches_these_keywords']!; + return _localizedValues[locale.languageCode]!['no_user_matches_these_keywords']!; } String get ok { @@ -429,8 +411,7 @@ class AppLocalizations { } String get operationCouldNotBeCompleted { - return _localizedValues[locale.languageCode]![ - 'operation_could_not_be_completed']!; + return _localizedValues[locale.languageCode]!['operation_could_not_be_completed']!; } String get owner { @@ -442,8 +423,7 @@ class AppLocalizations { } String get photosOrVideosWillAppearHere { - return _localizedValues[locale.languageCode]![ - 'photos_or_videos_will_appear_here']!; + return _localizedValues[locale.languageCode]!['photos_or_videos_will_appear_here']!; } String get pinGroup { @@ -475,8 +455,7 @@ class AppLocalizations { } String get removeMemberAreYouSure { - return _localizedValues[locale.languageCode]![ - 'remove_member_are_you_sure']!; + return _localizedValues[locale.languageCode]!['remove_member_are_you_sure']!; } String get search { @@ -484,8 +463,7 @@ class AppLocalizations { } String get selectUserToTryFlutterSDK { - return _localizedValues[locale.languageCode]![ - 'select_user_to_try_flutter_sdk']!; + return _localizedValues[locale.languageCode]!['select_user_to_try_flutter_sdk']!; } String get sharedGroups { @@ -497,8 +475,7 @@ class AppLocalizations { } String get somethingWentWrongErrorMessage { - return _localizedValues[locale.languageCode]![ - 'something_went_wrong_error_message']!; + return _localizedValues[locale.languageCode]!['something_went_wrong_error_message']!; } String get streamSDK { @@ -556,8 +533,7 @@ class AppLocalizationsDelegate extends LocalizationsDelegate { const AppLocalizationsDelegate(); @override - bool isSupported(Locale locale) => - AppLocalizations.languages().contains(locale.languageCode); + bool isSupported(Locale locale) => AppLocalizations.languages().contains(locale.languageCode); @override Future load(Locale locale) { diff --git a/sample_app/lib/utils/location_provider.dart b/sample_app/lib/utils/location_provider.dart index fbf7168028..67215d2efb 100644 --- a/sample_app/lib/utils/location_provider.dart +++ b/sample_app/lib/utils/location_provider.dart @@ -44,35 +44,36 @@ class LocationProvider { final settings = switch (CurrentPlatform.type) { PlatformType.android => AndroidSettings( - accuracy: accuracy, - distanceFilter: distanceFilter, - foregroundNotificationConfig: const ForegroundNotificationConfig( - setOngoing: true, - notificationText: notificationText, - notificationTitle: notificationTitle, - notificationIcon: AndroidResource(name: 'ic_notification'), - ), + accuracy: accuracy, + distanceFilter: distanceFilter, + foregroundNotificationConfig: const ForegroundNotificationConfig( + setOngoing: true, + notificationText: notificationText, + notificationTitle: notificationTitle, + notificationIcon: AndroidResource(name: 'ic_notification'), ), + ), PlatformType.ios || PlatformType.macOS => AppleSettings( - accuracy: accuracy, - activityType: activityType, - distanceFilter: distanceFilter, - showBackgroundLocationIndicator: true, - pauseLocationUpdatesAutomatically: true, - ), + accuracy: accuracy, + activityType: activityType, + distanceFilter: distanceFilter, + showBackgroundLocationIndicator: true, + pauseLocationUpdatesAutomatically: true, + ), _ => LocationSettings( - accuracy: accuracy, - distanceFilter: distanceFilter, - ) + accuracy: accuracy, + distanceFilter: distanceFilter, + ), }; _positionSubscription?.cancel(); // avoid duplicate subscriptions - _positionSubscription = Geolocator.getPositionStream( - locationSettings: settings, - ).listen( - _positionStreamController.safeAdd, - onError: _positionStreamController.safeAddError, - ); + _positionSubscription = + Geolocator.getPositionStream( + locationSettings: settings, + ).listen( + _positionStreamController.safeAdd, + onError: _positionStreamController.safeAddError, + ); } /// Stop live tracking diff --git a/sample_app/lib/utils/notifications_service.dart b/sample_app/lib/utils/notifications_service.dart index 9c34099c4f..6a8b2e809b 100644 --- a/sample_app/lib/utils/notifications_service.dart +++ b/sample_app/lib/utils/notifications_service.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:flutter_local_notifications/flutter_local_notifications.dart' - hide Message; +import 'package:flutter_local_notifications/flutter_local_notifications.dart' hide Message; import 'package:go_router/go_router.dart'; import 'package:sample_app/routes/routes.dart'; import 'package:sample_app/utils/localizations.dart'; diff --git a/sample_app/lib/utils/shared_location_service.dart b/sample_app/lib/utils/shared_location_service.dart index 1d7a7128c6..27e04868dd 100644 --- a/sample_app/lib/utils/shared_location_service.dart +++ b/sample_app/lib/utils/shared_location_service.dart @@ -9,8 +9,8 @@ class SharedLocationService { SharedLocationService({ required StreamChatClient client, LocationProvider? locationProvider, - }) : _client = client, - _locationProvider = locationProvider ?? LocationProvider(); + }) : _client = client, + _locationProvider = locationProvider ?? LocationProvider(); final StreamChatClient _client; final LocationProvider _locationProvider; @@ -23,12 +23,12 @@ class SharedLocationService { _activeLiveLocationsSubscription = _client.state.activeLiveLocationsStream .distinct((prev, curr) => prev.length == curr.length) .listen((locations) async { - // If there are no more active locations to update, stop tracking. - if (locations.isEmpty) return _stopTrackingLocation(); + // If there are no more active locations to update, stop tracking. + if (locations.isEmpty) return _stopTrackingLocation(); - // Otherwise, start tracking the user's location. - return _startTrackingLocation(); - }); + // Otherwise, start tracking the user's location. + return _startTrackingLocation(); + }); return _client.getActiveLiveLocations().ignore(); } diff --git a/sample_app/lib/widgets/channel_list.dart b/sample_app/lib/widgets/channel_list.dart index a87e51b012..f1511eeedf 100644 --- a/sample_app/lib/widgets/channel_list.dart +++ b/sample_app/lib/widgets/channel_list.dart @@ -21,8 +21,7 @@ class ChannelList extends StatefulWidget { class _ChannelList extends State { final ScrollController _scrollController = ScrollController(); - late final StreamMessageSearchListController _messageSearchListController = - StreamMessageSearchListController( + late final StreamMessageSearchListController _messageSearchListController = StreamMessageSearchListController( client: StreamChat.of(context).client, filter: Filter.in_('members', [StreamChat.of(context).currentUser!.id]), limit: 5, @@ -33,8 +32,7 @@ class _ChannelList extends State { ], ); - late final TextEditingController _controller = TextEditingController() - ..addListener(_channelQueryListener); + late final TextEditingController _controller = TextEditingController()..addListener(_channelQueryListener); bool _isSearchActive = false; @@ -86,8 +84,7 @@ class _ChannelList extends State { }, child: NotificationListener( onNotification: (ScrollNotification scrollInfo) { - if (_scrollController.position.userScrollDirection == - ScrollDirection.reverse) { + if (_scrollController.position.userScrollDirection == ScrollDirection.reverse) { FocusScope.of(context).unfocus(); } return true; @@ -119,13 +116,13 @@ class _ChannelListDefault extends StatelessWidget { @override Widget build(BuildContext context) { + final chatTheme = StreamChatTheme.of(context); return SlidableAutoCloseBehavior( child: RefreshIndicator( onRefresh: channelListController.refresh, child: StreamChannelListView( controller: channelListController, itemBuilder: (context, channels, index, defaultWidget) { - final chatTheme = StreamChatTheme.of(context); final channel = channels[index]; final backgroundColor = chatTheme.colorTheme.inputBg; final canDeleteChannel = channel.canDeleteChannel; @@ -147,25 +144,19 @@ class _ChannelListDefault extends StatelessWidget { context, MaterialPageRoute( builder: (context) { - final isOneToOne = channel.memberCount == 2 && - channel.isDistinct; + final isOneToOne = channel.memberCount == 2 && channel.isDistinct; return StreamChannel( channel: channel, child: isOneToOne ? ChatInfoScreen( - messageTheme: - chatTheme.ownMessageTheme, + messageTheme: chatTheme.ownMessageTheme, user: channel.state!.members - .where((m) => - m.userId != - channel.client.state - .currentUser!.id) + .where((m) => m.userId != channel.client.state.currentUser!.id) .first .user, ) : GroupInfoScreen( - messageTheme: - chatTheme.ownMessageTheme, + messageTheme: chatTheme.ownMessageTheme, ), ); }, @@ -179,20 +170,19 @@ class _ChannelListDefault extends StatelessWidget { if (canDeleteChannel) CustomSlidableAction( backgroundColor: backgroundColor, - child: StreamSvgIcon( - icon: StreamSvgIcons.delete, + child: Icon( + context.streamIcons.trashBin, color: chatTheme.colorTheme.accentError, ), onPressed: (_) async { final res = await showConfirmationBottomSheet( context, title: 'Delete Conversation', - question: - 'Are you sure you want to delete this conversation?', + question: 'Are you sure you want to delete this conversation?', okText: 'Delete', cancelText: 'Cancel', - icon: StreamSvgIcon( - icon: StreamSvgIcons.delete, + icon: Icon( + context.streamIcons.trashBin, color: chatTheme.colorTheme.accentError, ), ); @@ -204,9 +194,7 @@ class _ChannelListDefault extends StatelessWidget { ], ), child: ColoredBox( - color: channel.isPinned - ? chatTheme.colorTheme.highlight - : Colors.transparent, + color: channel.isPinned ? chatTheme.colorTheme.highlight : Colors.transparent, child: defaultWidget, ), ); @@ -222,8 +210,8 @@ class _ChannelListDefault extends StatelessWidget { child: Padding( padding: const EdgeInsets.all(8), child: StreamScrollViewEmptyWidget( - emptyIcon: StreamSvgIcon( - icon: StreamSvgIcons.message, + emptyIcon: Icon( + context.streamIcons.bubble3ChatMessage, size: 148, color: StreamChatTheme.of(context).colorTheme.disabled, ), @@ -233,14 +221,9 @@ class _ChannelListDefault extends StatelessWidget { }, child: Text( 'Start a chat', - style: StreamChatTheme.of(context) - .textTheme - .bodyBold - .copyWith( - color: StreamChatTheme.of(context) - .colorTheme - .accentPrimary, - ), + style: StreamChatTheme.of(context).textTheme.bodyBold.copyWith( + color: StreamChatTheme.of(context).colorTheme.accentPrimary, + ), ), ), ), @@ -274,10 +257,10 @@ class _ChannelListSearch extends StatelessWidget { child: Center( child: Column( children: [ - const Padding( - padding: EdgeInsets.all(24), - child: StreamSvgIcon( - icon: StreamSvgIcons.search, + Padding( + padding: const EdgeInsets.all(24), + child: Icon( + context.streamIcons.magnifyingGlassSearch, size: 96, color: Colors.grey, ), @@ -293,35 +276,36 @@ class _ChannelListSearch extends StatelessWidget { }, ); }, - itemBuilder: ( - context, - messageResponses, - index, - defaultWidget, - ) { - final messageResponse = messageResponses[index]; + itemBuilder: + ( + context, + messageResponses, + index, + defaultWidget, + ) { + final messageResponse = messageResponses[index]; - return defaultWidget.copyWith( - onTap: () async { - FocusScope.of(context).requestFocus(FocusNode()); - final client = StreamChat.of(context).client; - final router = GoRouter.of(context); - final message = messageResponse.message; - final channel = client.channel( - messageResponse.channel!.type, - id: messageResponse.channel!.id, - ); - if (channel.state == null) { - await channel.watch(); - } - router.pushNamed( - Routes.CHANNEL_PAGE.name, - pathParameters: Routes.CHANNEL_PAGE.params(channel), - queryParameters: Routes.CHANNEL_PAGE.queryParams(message), + return defaultWidget.copyWith( + onTap: () async { + FocusScope.of(context).requestFocus(FocusNode()); + final client = StreamChat.of(context).client; + final router = GoRouter.of(context); + final message = messageResponse.message; + final channel = client.channel( + messageResponse.channel!.type, + id: messageResponse.channel!.id, + ); + if (channel.state == null) { + await channel.watch(); + } + router.pushNamed( + Routes.CHANNEL_PAGE.name, + pathParameters: Routes.CHANNEL_PAGE.params(channel), + queryParameters: Routes.CHANNEL_PAGE.queryParams(message), + ); + }, ); }, - ); - }, ); } } diff --git a/sample_app/lib/widgets/chips_input_text_field.dart b/sample_app/lib/widgets/chips_input_text_field.dart index 45368e0939..e4a4ca4e41 100644 --- a/sample_app/lib/widgets/chips_input_text_field.dart +++ b/sample_app/lib/widgets/chips_input_text_field.dart @@ -77,14 +77,9 @@ class ChipInputTextFieldState extends State> { padding: const EdgeInsets.symmetric(vertical: 4), child: Text( '${AppLocalizations.of(context).to.toUpperCase()}:', - style: StreamChatTheme.of(context) - .textTheme - .footnote - .copyWith( - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(.5)), + style: StreamChatTheme.of(context).textTheme.footnote.copyWith( + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(.5), + ), ), ), const SizedBox(width: 12), @@ -114,14 +109,9 @@ class ChipInputTextFieldState extends State> { disabledBorder: InputBorder.none, contentPadding: const EdgeInsets.only(top: 4), hintText: widget.hint, - hintStyle: StreamChatTheme.of(context) - .textTheme - .body - .copyWith( - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(.5)), + hintStyle: StreamChatTheme.of(context).textTheme.body.copyWith( + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(.5), + ), ), ), ], @@ -132,20 +122,14 @@ class ChipInputTextFieldState extends State> { alignment: Alignment.bottomCenter, child: IconButton( icon: _chips.isEmpty - ? StreamSvgIcon( - icon: StreamSvgIcons.user, - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(0.5), + ? Icon( + context.streamIcons.people, + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(0.5), size: 24, ) - : StreamSvgIcon( - icon: StreamSvgIcons.userAdd, - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(0.5), + : Icon( + context.streamIcons.peopleAdd, + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis.withOpacity(0.5), size: 24, ), onPressed: resumeItemAddition, diff --git a/sample_app/lib/widgets/custom_message_actions.dart b/sample_app/lib/widgets/custom_message_actions.dart new file mode 100644 index 0000000000..44197a3ec1 --- /dev/null +++ b/sample_app/lib/widgets/custom_message_actions.dart @@ -0,0 +1,196 @@ +import 'package:flutter/material.dart'; +import 'package:sample_app/widgets/message_info_sheet.dart'; +import 'package:sample_app/widgets/reminder_dialog.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +/// Custom [StreamComponentBuilder] for [StreamMessageWidgetProps] that +/// composes app-specific message action customizations via a delegation +/// chain. +/// +/// Delegation chain: +/// ``` +/// customMessageWidgetBuilder +/// → _ReminderActions (remind me, save for later, edit/remove reminder) +/// → _DeleteForMeAction (delete message for current user only) +/// → _MessageInfoAction (show message delivery info sheet) +/// ``` +Widget customMessageWidgetBuilder( + BuildContext context, + StreamMessageWidgetProps props, +) { + return DefaultStreamMessage( + props: props.copyWith( + actionsBuilder: (context, defaultActions) { + final message = props.message; + return StreamContextMenuAction.partitioned( + items: [ + ...defaultActions, + ..._ReminderActions.build(context, message), + ..._DeleteForMeAction.build(context, message), + ..._MessageInfoAction.build(context, message), + ], + ); + }, + ), + ); +} + +// --------------------------------------------------------------------------- +// Reminder actions +// --------------------------------------------------------------------------- + +abstract final class _ReminderActions { + static List build( + BuildContext context, + Message message, + ) { + final icons = context.streamIcons; + final channel = StreamChannel.of(context).channel; + final channelConfig = channel.config; + if (channelConfig?.userMessageReminders != true) return const []; + + final reminder = message.reminder; + if (reminder != null) { + return [ + StreamContextMenuAction( + label: const Text('Edit Reminder'), + leading: Icon(icons.clock), + onTap: () => _editReminder(context, message, reminder), + ), + StreamContextMenuAction( + label: const Text('Remove from later'), + leading: Icon(icons.checkmark2), + onTap: () => _removeReminder(context, message), + ), + ]; + } + + return [ + StreamContextMenuAction( + label: const Text('Remind me'), + leading: Icon(icons.bellNotification), + onTap: () => _createReminder(context, message), + ), + StreamContextMenuAction( + label: const Text('Save for later'), + leading: Icon(icons.fileBend), + onTap: () => _createBookmark(context, message), + ), + ]; + } + + static Future _editReminder( + BuildContext context, + Message message, + MessageReminder reminder, + ) async { + final option = await showDialog( + context: context, + builder: (_) => EditReminderDialog( + isBookmarkReminder: reminder.remindAt == null, + ), + ); + + if (option == null) return; + final client = StreamChat.of(context).client; + return client.updateReminder(message.id, remindAt: option.remindAt).ignore(); + } + + static Future _removeReminder( + BuildContext context, + Message message, + ) async { + final client = StreamChat.of(context).client; + return client.deleteReminder(message.id).ignore(); + } + + static Future _createReminder( + BuildContext context, + Message message, + ) async { + final reminder = await showDialog( + context: context, + builder: (_) => const CreateReminderDialog(), + ); + + if (reminder == null) return; + final client = StreamChat.of(context).client; + return client.createReminder(message.id, remindAt: reminder.remindAt).ignore(); + } + + static Future _createBookmark( + BuildContext context, + Message message, + ) async { + final client = StreamChat.of(context).client; + return client.createReminder(message.id).ignore(); + } +} + +// --------------------------------------------------------------------------- +// Delete-for-me action +// --------------------------------------------------------------------------- + +abstract final class _DeleteForMeAction { + static List build( + BuildContext context, + Message message, + ) { + final icons = context.streamIcons; + final channel = StreamChannel.of(context).channel; + final currentUser = StreamChat.of(context).currentUser; + final isSentByCurrentUser = message.user?.id == currentUser?.id; + if (!isSentByCurrentUser || !channel.canDeleteOwnMessage) return const []; + + return [ + StreamContextMenuAction.destructive( + label: const Text('Delete Message for Me'), + leading: Icon(icons.trashBin), + onTap: () => _confirmAndDelete(context, message), + ), + ]; + } + + static Future _confirmAndDelete( + BuildContext context, + Message message, + ) async { + final confirmed = await showStreamDialog( + context: context, + builder: (context) => const StreamMessageActionConfirmationModal( + isDestructiveAction: true, + title: Text('Delete for me'), + content: Text('Are you sure you want to delete this message for you?'), + cancelActionTitle: Text('Cancel'), + confirmActionTitle: Text('Delete'), + ), + ); + + if (confirmed != true) return; + final channel = StreamChannel.of(context).channel; + return channel.deleteMessageForMe(message).ignore(); + } +} + +// --------------------------------------------------------------------------- +// Message info action +// --------------------------------------------------------------------------- + +abstract final class _MessageInfoAction { + static List build( + BuildContext context, + Message message, + ) { + final icons = context.streamIcons; + final channel = StreamChannel.of(context).channel; + if (channel.config?.deliveryEvents != true) return const []; + + return [ + StreamContextMenuAction( + label: const Text('Message Info'), + leading: Icon(icons.circleInfoTooltip), + onTap: () => MessageInfoSheet.show(context: context, message: message), + ), + ]; + } +} diff --git a/sample_app/lib/widgets/location/location_attachment.dart b/sample_app/lib/widgets/location/location_attachment.dart index 405f97b7df..979e878aa2 100644 --- a/sample_app/lib/widgets/location/location_attachment.dart +++ b/sample_app/lib/widgets/location/location_attachment.dart @@ -15,7 +15,7 @@ class LocationAttachmentBuilder extends StreamAttachmentWidgetBuilder { /// {@macro locationAttachmentBuilder} const LocationAttachmentBuilder({ this.constraints = _defaultLocationConstraints, - this.padding = const EdgeInsets.all(4), + this.padding = const .symmetric(horizontal: 8), this.onAttachmentTap, }); @@ -26,14 +26,22 @@ class LocationAttachmentBuilder extends StreamAttachmentWidgetBuilder { final EdgeInsetsGeometry padding; /// Optional callback to handle tap events on the attachment. - final ValueSetter? onAttachmentTap; + /// + /// Receives the [BuildContext] from the widget tree where the attachment + /// is rendered, along with the [Location] data. This allows showing + /// dialogs or navigating from the correct context. + final void Function(BuildContext context, Location location)? onAttachmentTap; @override bool canHandle(Message message, _) => message.sharedLocation != null; @override - Widget build(BuildContext context, Message message, _) { - assert(debugAssertCanHandle(message, _), ''); + Widget build( + BuildContext context, + Message message, + Map> attachments, + ) { + assert(debugAssertCanHandle(message, attachments), ''); final user = message.user; final location = message.sharedLocation!; @@ -43,7 +51,7 @@ class LocationAttachmentBuilder extends StreamAttachmentWidgetBuilder { constraints: constraints, padding: padding, onLocationTap: switch (onAttachmentTap) { - final onTap? => () => onTap(location), + final onTap? => () => onTap(context, location), _ => null, }, ); @@ -58,7 +66,7 @@ class LocationAttachment extends StatelessWidget { required this.user, required this.sharedLocation, this.constraints = _defaultLocationConstraints, - this.padding = const EdgeInsets.all(2), + this.padding = const .symmetric(horizontal: 8), this.onLocationTap, }); @@ -99,12 +107,12 @@ class LocationAttachment extends StatelessWidget { onTap: onLocationTap, child: IgnorePointer( child: SimpleMapView( - markerSize: 40, + markerSize: MarkerSize.lg, showLocateMeButton: false, coordinates: sharedLocation.coordinates, markerBuilder: (_, __, size) => LocationUserMarker( user: user, - markerSize: size, + size: size, sharedLocation: sharedLocation, ), ), diff --git a/sample_app/lib/widgets/location/location_detail_dialog.dart b/sample_app/lib/widgets/location/location_detail_dialog.dart index c5082bf365..b6e39b48b9 100644 --- a/sample_app/lib/widgets/location/location_detail_dialog.dart +++ b/sample_app/lib/widgets/location/location_detail_dialog.dart @@ -72,11 +72,11 @@ class LocationDetailDialog extends StatelessWidget { children: [ SimpleMapView( cameraZoom: 16, - markerSize: 48, + markerSize: MarkerSize.xl, coordinates: sharedLocation.coordinates, markerBuilder: (_, __, size) => LocationUserMarker( user: message.user, - markerSize: size, + size: size, sharedLocation: sharedLocation, ), ), diff --git a/sample_app/lib/widgets/location/location_picker_dialog.dart b/sample_app/lib/widgets/location/location_picker_dialog.dart index 1abaaf9e1c..b352a6b528 100644 --- a/sample_app/lib/widgets/location/location_picker_dialog.dart +++ b/sample_app/lib/widgets/location/location_picker_dialog.dart @@ -2,6 +2,7 @@ import 'package:avatar_glow/avatar_glow.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:sample_app/utils/location_provider.dart'; +import 'package:sample_app/widgets/location/location_user_marker.dart'; import 'package:sample_app/widgets/simple_map_view.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; @@ -94,7 +95,7 @@ class _LocationPickerDialogState extends State { return SimpleMapView( cameraZoom: 18, - markerSize: 24, + markerSize: MarkerSize.sm, coordinates: coordinates, markerBuilder: (context, _, size) => AvatarGlow( glowColor: colorTheme.accentPrimary, @@ -107,7 +108,7 @@ class _LocationPickerDialogState extends State { ), ), child: CircleAvatar( - radius: size / 2, + radius: size.value / 2, backgroundColor: colorTheme.accentPrimary, ), ), @@ -305,9 +306,9 @@ class LocationPickerOptionItem extends StatelessWidget { ], ), ), - StreamSvgIcon( + Icon( + context.streamIcons.chevronRight, size: 24, - icon: StreamSvgIcons.right, color: colorTheme.textLowEmphasis, ), ], diff --git a/sample_app/lib/widgets/location/location_user_marker.dart b/sample_app/lib/widgets/location/location_user_marker.dart index 102e16876c..3c8994ab98 100644 --- a/sample_app/lib/widgets/location/location_user_marker.dart +++ b/sample_app/lib/widgets/location/location_user_marker.dart @@ -2,16 +2,30 @@ import 'package:avatar_glow/avatar_glow.dart'; import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +enum MarkerSize { + xs(20), + sm(24), + md(32), + lg(40), + xl(64) + ; + + const MarkerSize(this.value); + + final double value; +} + class LocationUserMarker extends StatelessWidget { const LocationUserMarker({ super.key, this.user, - this.markerSize = 40, + this.size = MarkerSize.lg, required this.sharedLocation, }); final User? user; - final double markerSize; + final MarkerSize size; + final Location sharedLocation; @override @@ -29,12 +43,9 @@ class LocationUserMarker extends StatelessWidget { child: Padding( padding: const EdgeInsets.all(borderWidth), child: StreamUserAvatar( + size: _avatarSizeForMarkerSize(size), user: user, - constraints: BoxConstraints.tightFor( - width: markerSize, - height: markerSize, - ), - showOnlineStatus: false, + showOnlineIndicator: false, ), ), ); @@ -48,9 +59,19 @@ class LocationUserMarker extends StatelessWidget { } return Icon( - size: markerSize, + size: size.value, Icons.person_pin, color: colorTheme.accentPrimary, ); } + + StreamAvatarSize _avatarSizeForMarkerSize( + MarkerSize size, + ) => switch (size) { + .xs => StreamAvatarSize.xs, + .sm => StreamAvatarSize.sm, + .md => StreamAvatarSize.md, + .lg => StreamAvatarSize.lg, + .xl => StreamAvatarSize.xl, + }; } diff --git a/sample_app/lib/widgets/message_info_sheet.dart b/sample_app/lib/widgets/message_info_sheet.dart index 6f0a8cec57..8c587b3610 100644 --- a/sample_app/lib/widgets/message_info_sheet.dart +++ b/sample_app/lib/widgets/message_info_sheet.dart @@ -158,7 +158,7 @@ class MessageInfoSheet extends StatelessWidget { ), IconButton( iconSize: 32, - icon: const StreamSvgIcon(icon: StreamSvgIcons.close), + icon: Icon(context.streamIcons.crossMedium), onPressed: Navigator.of(context).maybePop, color: colorTheme.textHighEmphasis, padding: const EdgeInsets.all(4), @@ -251,11 +251,8 @@ class _UserReadTile extends StatelessWidget { children: [ // User avatar StreamUserAvatar( + size: .lg, user: read.user, - constraints: const BoxConstraints.tightFor( - height: 40, - width: 40, - ), ), const SizedBox(width: 12), @@ -271,9 +268,9 @@ class _UserReadTile extends StatelessWidget { ), // Status icon - StreamSvgIcon( + Icon( + context.streamIcons.doupleCheckmark1Small, size: 18, - icon: StreamSvgIcons.checkAll, color: switch (isDelivered) { true => theme.colorTheme.textLowEmphasis, false => theme.colorTheme.accentPrimary, diff --git a/sample_app/lib/widgets/search_text_field.dart b/sample_app/lib/widgets/search_text_field.dart index ec3084e47e..a01b3edbb9 100644 --- a/sample_app/lib/widgets/search_text_field.dart +++ b/sample_app/lib/widgets/search_text_field.dart @@ -20,18 +20,24 @@ class SearchTextField extends StatelessWidget { @override Widget build(BuildContext context) { + final spacing = context.streamSpacing; + final colorScheme = context.streamColorScheme; + final textTheme = context.streamTextTheme; + return Container( - height: 36, + height: 44, decoration: BoxDecoration( - color: StreamChatTheme.of(context).colorTheme.barsBg, + color: Colors.transparent, border: Border.all( - color: StreamChatTheme.of(context).colorTheme.borders, + color: colorScheme.borderDefault, ), borderRadius: BorderRadius.circular(24), ), - margin: const EdgeInsets.symmetric( - vertical: 8, - horizontal: 8, + margin: EdgeInsets.only( + top: spacing.md, + bottom: spacing.xs, + left: spacing.md, + right: spacing.md, ), child: Row( children: [ @@ -41,27 +47,19 @@ class SearchTextField extends StatelessWidget { controller: controller, onChanged: onChanged, decoration: InputDecoration( - prefixText: ' ', - prefixIconConstraints: BoxConstraints.tight(const Size(40, 24)), + prefixIconConstraints: BoxConstraints.tight(const Size(36, 24)), prefixIcon: Padding( - padding: const EdgeInsets.only( - left: 8, - right: 8, - ), - child: StreamSvgIcon( - icon: StreamSvgIcons.search, - color: - StreamChatTheme.of(context).colorTheme.textHighEmphasis, - size: 24, + padding: EdgeInsets.only(left: spacing.md), + child: Icon( + context.streamIcons.magnifyingGlassSearch, + color: colorScheme.textTertiary, + size: 20, ), ), hintText: hintText, - hintStyle: StreamChatTheme.of(context).textTheme.body.copyWith( - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis - .withOpacity(.5)), - contentPadding: EdgeInsets.zero, + hintStyle: textTheme.bodyDefault.copyWith( + color: colorScheme.textTertiary, + ), border: OutlineInputBorder( borderSide: BorderSide.none, borderRadius: BorderRadius.circular(24), @@ -75,7 +73,7 @@ class SearchTextField extends StatelessWidget { child: IconButton( color: Colors.grey, padding: EdgeInsets.zero, - icon: const StreamSvgIcon(icon: StreamSvgIcons.closeSmall), + icon: Icon(context.streamIcons.crossSmall), splashRadius: 24, onPressed: () { if (controller!.text.isNotEmpty) { diff --git a/sample_app/lib/widgets/simple_map_view.dart b/sample_app/lib/widgets/simple_map_view.dart index 0faadc11f7..ab75abcff6 100644 --- a/sample_app/lib/widgets/simple_map_view.dart +++ b/sample_app/lib/widgets/simple_map_view.dart @@ -2,19 +2,21 @@ import 'package:flutter/material.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_map_animations/flutter_map_animations.dart'; import 'package:latlong2/latlong.dart'; +import 'package:sample_app/widgets/location/location_user_marker.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -typedef MarkerBuilder = Widget Function( - BuildContext context, - Animation animation, - double markerSize, -); +typedef MarkerBuilder = + Widget Function( + BuildContext context, + Animation animation, + MarkerSize markerSize, + ); class SimpleMapView extends StatefulWidget { const SimpleMapView({ super.key, this.cameraZoom = 15, - this.markerSize = 30, + this.markerSize = MarkerSize.lg, required this.coordinates, this.showLocateMeButton = true, this.markerBuilder = _defaultMarkerBuilder, @@ -22,25 +24,24 @@ class SimpleMapView extends StatefulWidget { final double cameraZoom; - final double markerSize; + final MarkerSize markerSize; final LocationCoordinates coordinates; final bool showLocateMeButton; final MarkerBuilder markerBuilder; - static Widget _defaultMarkerBuilder(BuildContext context, _, double size) { + static Widget _defaultMarkerBuilder(BuildContext context, _, MarkerSize size) { final theme = StreamChatTheme.of(context); final iconColor = theme.colorTheme.accentPrimary; - return Icon(size: size, Icons.person_pin, color: iconColor); + return Icon(size: size.value, Icons.person_pin, color: iconColor); } @override State createState() => _SimpleMapViewState(); } -class _SimpleMapViewState extends State - with TickerProviderStateMixin { +class _SimpleMapViewState extends State with TickerProviderStateMixin { late final _mapController = AnimatedMapController(vsync: this); late final _initialCenter = widget.coordinates.toLatLng(); @@ -87,8 +88,8 @@ class _SimpleMapViewState extends State AnimatedMarkerLayer( markers: [ AnimatedMarker( - height: widget.markerSize, - width: widget.markerSize, + height: widget.markerSize.value, + width: widget.markerSize.value, point: widget.coordinates.toLatLng(), builder: (context, animation) => widget.markerBuilder( context, diff --git a/sample_app/lib/widgets/stream_version.dart b/sample_app/lib/widgets/stream_version.dart index dfafee4cd3..416c9adabf 100644 --- a/sample_app/lib/widgets/stream_version.dart +++ b/sample_app/lib/widgets/stream_version.dart @@ -23,8 +23,7 @@ class StreamVersion extends StatelessWidget { final pubspec = snapshot.data!; final yaml = loadYaml(pubspec); - final streamChatDep = - yaml['packages']['stream_chat_flutter']['version']; + final streamChatDep = yaml['packages']['stream_chat_flutter']['version']; return Text( '${AppLocalizations.of(context).streamSDK} v $streamChatDep', diff --git a/sample_app/pubspec.yaml b/sample_app/pubspec.yaml index c9e10e5623..f43137f903 100644 --- a/sample_app/pubspec.yaml +++ b/sample_app/pubspec.yaml @@ -16,8 +16,8 @@ version: 2.2.0 # 2. Add it to the melos.yaml file for future updates. environment: - sdk: ^3.6.2 - flutter: ">=3.27.4" + sdk: ^3.10.0 + flutter: ">=3.38.1" dependencies: avatar_glow: ^3.0.0